1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 */
44
45 package org.eclipse.jgit.lib;
46
47 import java.io.IOException;
48 import java.text.MessageFormat;
49
50 import org.eclipse.jgit.errors.MissingObjectException;
51 import org.eclipse.jgit.internal.JGitText;
52 import org.eclipse.jgit.revwalk.RevCommit;
53 import org.eclipse.jgit.revwalk.RevObject;
54 import org.eclipse.jgit.revwalk.RevWalk;
55
56 /**
57 * Creates, updates or deletes any reference.
58 */
59 public abstract class RefUpdate {
60 /** Status of an update request. */
61 public static enum Result {
62 /** The ref update/delete has not been attempted by the caller. */
63 NOT_ATTEMPTED,
64
65 /**
66 * The ref could not be locked for update/delete.
67 * <p>
68 * This is generally a transient failure and is usually caused by
69 * another process trying to access the ref at the same time as this
70 * process was trying to update it. It is possible a future operation
71 * will be successful.
72 */
73 LOCK_FAILURE,
74
75 /**
76 * Same value already stored.
77 * <p>
78 * Both the old value and the new value are identical. No change was
79 * necessary for an update. For delete the branch is removed.
80 */
81 NO_CHANGE,
82
83 /**
84 * The ref was created locally for an update, but ignored for delete.
85 * <p>
86 * The ref did not exist when the update started, but it was created
87 * successfully with the new value.
88 */
89 NEW,
90
91 /**
92 * The ref had to be forcefully updated/deleted.
93 * <p>
94 * The ref already existed but its old value was not fully merged into
95 * the new value. The configuration permitted a forced update to take
96 * place, so ref now contains the new value. History associated with the
97 * objects not merged may no longer be reachable.
98 */
99 FORCED,
100
101 /**
102 * The ref was updated/deleted in a fast-forward way.
103 * <p>
104 * The tracking ref already existed and its old value was fully merged
105 * into the new value. No history was made unreachable.
106 */
107 FAST_FORWARD,
108
109 /**
110 * Not a fast-forward and not stored.
111 * <p>
112 * The tracking ref already existed but its old value was not fully
113 * merged into the new value. The configuration did not allow a forced
114 * update/delete to take place, so ref still contains the old value. No
115 * previous history was lost.
116 */
117 REJECTED,
118
119 /**
120 * Rejected because trying to delete the current branch.
121 * <p>
122 * Has no meaning for update.
123 */
124 REJECTED_CURRENT_BRANCH,
125
126 /**
127 * The ref was probably not updated/deleted because of I/O error.
128 * <p>
129 * Unexpected I/O error occurred when writing new ref. Such error may
130 * result in uncertain state, but most probably ref was not updated.
131 * <p>
132 * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
133 * different case.
134 */
135 IO_FAILURE,
136
137 /**
138 * The ref was renamed from another name
139 * <p>
140 */
141 RENAMED
142 }
143
144 /** New value the caller wants this ref to have. */
145 private ObjectId newValue;
146
147 /** Does this specification ask for forced updated (rewind/reset)? */
148 private boolean force;
149
150 /** Identity to record action as within the reflog. */
151 private PersonIdent refLogIdent;
152
153 /** Message the caller wants included in the reflog. */
154 private String refLogMessage;
155
156 /** Should the Result value be appended to {@link #refLogMessage}. */
157 private boolean refLogIncludeResult;
158
159 /** Old value of the ref, obtained after we lock it. */
160 private ObjectId oldValue;
161
162 /** If non-null, the value {@link #oldValue} must have to continue. */
163 private ObjectId expValue;
164
165 /** Result of the update operation. */
166 private Result result = Result.NOT_ATTEMPTED;
167
168 private final Ref ref;
169
170 /**
171 * Is this RefUpdate detaching a symbolic ref?
172 *
173 * We need this info since this.ref will normally be peeled of in case of
174 * detaching a symbolic ref (HEAD for example).
175 *
176 * Without this flag we cannot decide whether the ref has to be updated or
177 * not in case when it was a symbolic ref and the newValue == oldValue.
178 */
179 private boolean detachingSymbolicRef;
180
181 private boolean checkConflicting = true;
182
183 /**
184 * Construct a new update operation for the reference.
185 * <p>
186 * {@code ref.getObjectId()} will be used to seed {@link #getOldObjectId()},
187 * which callers can use as part of their own update logic.
188 *
189 * @param ref
190 * the reference that will be updated by this operation.
191 */
192 protected RefUpdate(final Ref ref) {
193 this.ref = ref;
194 oldValue = ref.getObjectId();
195 refLogMessage = ""; //$NON-NLS-1$
196 }
197
198 /** @return the reference database this update modifies. */
199 protected abstract RefDatabase getRefDatabase();
200
201 /** @return the repository storing the database's objects. */
202 protected abstract Repository getRepository();
203
204 /**
205 * Try to acquire the lock on the reference.
206 * <p>
207 * If the locking was successful the implementor must set the current
208 * identity value by calling {@link #setOldObjectId(ObjectId)}.
209 *
210 * @param deref
211 * true if the lock should be taken against the leaf level
212 * reference; false if it should be taken exactly against the
213 * current reference.
214 * @return true if the lock was acquired and the reference is likely
215 * protected from concurrent modification; false if it failed.
216 * @throws IOException
217 * the lock couldn't be taken due to an unexpected storage
218 * failure, and not because of a concurrent update.
219 */
220 protected abstract boolean tryLock(boolean deref) throws IOException;
221
222 /** Releases the lock taken by {@link #tryLock} if it succeeded. */
223 protected abstract void unlock();
224
225 /**
226 * @param desiredResult
227 * @return {@code result}
228 * @throws IOException
229 */
230 protected abstract Result doUpdate(Result desiredResult) throws IOException;
231
232 /**
233 * @param desiredResult
234 * @return {@code result}
235 * @throws IOException
236 */
237 protected abstract Result doDelete(Result desiredResult) throws IOException;
238
239 /**
240 * @param target
241 * @return {@link Result#NEW} on success.
242 * @throws IOException
243 */
244 protected abstract Result doLink(String target) throws IOException;
245
246 /**
247 * Get the name of the ref this update will operate on.
248 *
249 * @return name of underlying ref.
250 */
251 public String getName() {
252 return getRef().getName();
253 }
254
255 /** @return the reference this update will create or modify. */
256 public Ref getRef() {
257 return ref;
258 }
259
260 /**
261 * Get the new value the ref will be (or was) updated to.
262 *
263 * @return new value. Null if the caller has not configured it.
264 */
265 public ObjectId getNewObjectId() {
266 return newValue;
267 }
268
269 /**
270 * Tells this RefUpdate that it is actually detaching a symbolic ref.
271 */
272 public void setDetachingSymbolicRef() {
273 detachingSymbolicRef = true;
274 }
275
276 /**
277 * Set the new value the ref will update to.
278 *
279 * @param id
280 * the new value.
281 */
282 public void setNewObjectId(final AnyObjectId id) {
283 newValue = id.copy();
284 }
285
286 /**
287 * @return the expected value of the ref after the lock is taken, but before
288 * update occurs. Null to avoid the compare and swap test. Use
289 * {@link ObjectId#zeroId()} to indicate expectation of a
290 * non-existant ref.
291 */
292 public ObjectId getExpectedOldObjectId() {
293 return expValue;
294 }
295
296 /**
297 * @param id
298 * the expected value of the ref after the lock is taken, but
299 * before update occurs. Null to avoid the compare and swap test.
300 * Use {@link ObjectId#zeroId()} to indicate expectation of a
301 * non-existant ref.
302 */
303 public void setExpectedOldObjectId(final AnyObjectId id) {
304 expValue = id != null ? id.toObjectId() : null;
305 }
306
307 /**
308 * Check if this update wants to forcefully change the ref.
309 *
310 * @return true if this update should ignore merge tests.
311 */
312 public boolean isForceUpdate() {
313 return force;
314 }
315
316 /**
317 * Set if this update wants to forcefully change the ref.
318 *
319 * @param b
320 * true if this update should ignore merge tests.
321 */
322 public void setForceUpdate(final boolean b) {
323 force = b;
324 }
325
326 /** @return identity of the user making the change in the reflog. */
327 public PersonIdent getRefLogIdent() {
328 return refLogIdent;
329 }
330
331 /**
332 * Set the identity of the user appearing in the reflog.
333 * <p>
334 * The timestamp portion of the identity is ignored. A new identity with the
335 * current timestamp will be created automatically when the update occurs
336 * and the log record is written.
337 *
338 * @param pi
339 * identity of the user. If null the identity will be
340 * automatically determined based on the repository
341 * configuration.
342 */
343 public void setRefLogIdent(final PersonIdent pi) {
344 refLogIdent = pi;
345 }
346
347 /**
348 * Get the message to include in the reflog.
349 *
350 * @return message the caller wants to include in the reflog; null if the
351 * update should not be logged.
352 */
353 public String getRefLogMessage() {
354 return refLogMessage;
355 }
356
357 /** @return {@code true} if the ref log message should show the result. */
358 protected boolean isRefLogIncludingResult() {
359 return refLogIncludeResult;
360 }
361
362 /**
363 * Set the message to include in the reflog.
364 *
365 * @param msg
366 * the message to describe this change. It may be null if
367 * appendStatus is null in order not to append to the reflog
368 * @param appendStatus
369 * true if the status of the ref change (fast-forward or
370 * forced-update) should be appended to the user supplied
371 * message.
372 */
373 public void setRefLogMessage(final String msg, final boolean appendStatus) {
374 if (msg == null && !appendStatus)
375 disableRefLog();
376 else if (msg == null && appendStatus) {
377 refLogMessage = ""; //$NON-NLS-1$
378 refLogIncludeResult = true;
379 } else {
380 refLogMessage = msg;
381 refLogIncludeResult = appendStatus;
382 }
383 }
384
385 /** Don't record this update in the ref's associated reflog. */
386 public void disableRefLog() {
387 refLogMessage = null;
388 refLogIncludeResult = false;
389 }
390
391 /**
392 * The old value of the ref, prior to the update being attempted.
393 * <p>
394 * This value may differ before and after the update method. Initially it is
395 * populated with the value of the ref before the lock is taken, but the old
396 * value may change if someone else modified the ref between the time we
397 * last read it and when the ref was locked for update.
398 *
399 * @return the value of the ref prior to the update being attempted; null if
400 * the updated has not been attempted yet.
401 */
402 public ObjectId getOldObjectId() {
403 return oldValue;
404 }
405
406 /**
407 * Set the old value of the ref.
408 *
409 * @param old
410 * the old value.
411 */
412 protected void setOldObjectId(ObjectId old) {
413 oldValue = old;
414 }
415
416 /**
417 * Get the status of this update.
418 * <p>
419 * The same value that was previously returned from an update method.
420 *
421 * @return the status of the update.
422 */
423 public Result getResult() {
424 return result;
425 }
426
427 private void requireCanDoUpdate() {
428 if (newValue == null)
429 throw new IllegalStateException(JGitText.get().aNewObjectIdIsRequired);
430 }
431
432 /**
433 * Force the ref to take the new value.
434 * <p>
435 * This is just a convenient helper for setting the force flag, and as such
436 * the merge test is performed.
437 *
438 * @return the result status of the update.
439 * @throws IOException
440 * an unexpected IO error occurred while writing changes.
441 */
442 public Result forceUpdate() throws IOException {
443 force = true;
444 return update();
445 }
446
447 /**
448 * Gracefully update the ref to the new value.
449 * <p>
450 * Merge test will be performed according to {@link #isForceUpdate()}.
451 * <p>
452 * This is the same as:
453 *
454 * <pre>
455 * return update(new RevWalk(getRepository()));
456 * </pre>
457 *
458 * @return the result status of the update.
459 * @throws IOException
460 * an unexpected IO error occurred while writing changes.
461 */
462 public Result update() throws IOException {
463 try (RevWalk rw = new RevWalk(getRepository())) {
464 return update(rw);
465 }
466 }
467
468 /**
469 * Gracefully update the ref to the new value.
470 * <p>
471 * Merge test will be performed according to {@link #isForceUpdate()}.
472 *
473 * @param walk
474 * a RevWalk instance this update command can borrow to perform
475 * the merge test. The walk will be reset to perform the test.
476 * @return the result status of the update.
477 * @throws IOException
478 * an unexpected IO error occurred while writing changes.
479 */
480 public Result update(final RevWalk walk) throws IOException {
481 requireCanDoUpdate();
482 try {
483 return result = updateImpl(walk, new Store() {
484 @Override
485 Result execute(Result status) throws IOException {
486 if (status == Result.NO_CHANGE)
487 return status;
488 return doUpdate(status);
489 }
490 });
491 } catch (IOException x) {
492 result = Result.IO_FAILURE;
493 throw x;
494 }
495 }
496
497 /**
498 * Delete the ref.
499 * <p>
500 * This is the same as:
501 *
502 * <pre>
503 * return delete(new RevWalk(getRepository()));
504 * </pre>
505 *
506 * @return the result status of the delete.
507 * @throws IOException
508 */
509 public Result delete() throws IOException {
510 try (RevWalk rw = new RevWalk(getRepository())) {
511 return delete(rw);
512 }
513 }
514
515 /**
516 * Delete the ref.
517 *
518 * @param walk
519 * a RevWalk instance this delete command can borrow to perform
520 * the merge test. The walk will be reset to perform the test.
521 * @return the result status of the delete.
522 * @throws IOException
523 */
524 public Result delete(final RevWalk walk) throws IOException {
525 final String myName = getRef().getLeaf().getName();
526 if (myName.startsWith(Constants.R_HEADS)) {
527 Ref head = getRefDatabase().getRef(Constants.HEAD);
528 while (head != null && head.isSymbolic()) {
529 head = head.getTarget();
530 if (myName.equals(head.getName()))
531 return result = Result.REJECTED_CURRENT_BRANCH;
532 }
533 }
534
535 try {
536 return result = updateImpl(walk, new Store() {
537 @Override
538 Result execute(Result status) throws IOException {
539 return doDelete(status);
540 }
541 });
542 } catch (IOException x) {
543 result = Result.IO_FAILURE;
544 throw x;
545 }
546 }
547
548 /**
549 * Replace this reference with a symbolic reference to another reference.
550 * <p>
551 * This exact reference (not its traversed leaf) is replaced with a symbolic
552 * reference to the requested name.
553 *
554 * @param target
555 * name of the new target for this reference. The new target name
556 * must be absolute, so it must begin with {@code refs/}.
557 * @return {@link Result#NEW} or {@link Result#FORCED} on success.
558 * @throws IOException
559 */
560 public Result link(String target) throws IOException {
561 if (!target.startsWith(Constants.R_REFS))
562 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().illegalArgumentNotA, Constants.R_REFS));
563 if (checkConflicting && getRefDatabase().isNameConflicting(getName()))
564 return Result.LOCK_FAILURE;
565 try {
566 if (!tryLock(false))
567 return Result.LOCK_FAILURE;
568
569 final Ref old = getRefDatabase().getRef(getName());
570 if (old != null && old.isSymbolic()) {
571 final Ref dst = old.getTarget();
572 if (target.equals(dst.getName()))
573 return result = Result.NO_CHANGE;
574 }
575
576 if (old != null && old.getObjectId() != null)
577 setOldObjectId(old.getObjectId());
578
579 final Ref dst = getRefDatabase().getRef(target);
580 if (dst != null && dst.getObjectId() != null)
581 setNewObjectId(dst.getObjectId());
582
583 return result = doLink(target);
584 } catch (IOException x) {
585 result = Result.IO_FAILURE;
586 throw x;
587 } finally {
588 unlock();
589 }
590 }
591
592 private Result updateImpl(final RevWalk walk, final Store store)
593 throws IOException {
594 RevObject newObj;
595 RevObject oldObj;
596
597 // don't make expensive conflict check if this is an existing Ref
598 if (oldValue == null && checkConflicting && getRefDatabase().isNameConflicting(getName()))
599 return Result.LOCK_FAILURE;
600 try {
601 if (!tryLock(true))
602 return Result.LOCK_FAILURE;
603 if (expValue != null) {
604 final ObjectId o;
605 o = oldValue != null ? oldValue : ObjectId.zeroId();
606 if (!AnyObjectId.equals(expValue, o))
607 return Result.LOCK_FAILURE;
608 }
609 if (oldValue == null)
610 return store.execute(Result.NEW);
611
612 newObj = safeParse(walk, newValue);
613 oldObj = safeParse(walk, oldValue);
614 if (newObj == oldObj && !detachingSymbolicRef)
615 return store.execute(Result.NO_CHANGE);
616
617 if (isForceUpdate())
618 return store.execute(Result.FORCED);
619
620 if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
621 if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
622 return store.execute(Result.FAST_FORWARD);
623 }
624
625 return Result.REJECTED;
626 } finally {
627 unlock();
628 }
629 }
630
631 /**
632 * Enable/disable the check for conflicting ref names. By default conflicts
633 * are checked explicitly.
634 *
635 * @param check
636 * @since 3.0
637 */
638 public void setCheckConflicting(boolean check) {
639 checkConflicting = check;
640 }
641
642 private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
643 throws IOException {
644 try {
645 return id != null ? rw.parseAny(id) : null;
646 } catch (MissingObjectException e) {
647 // We can expect some objects to be missing, like if we are
648 // trying to force a deletion of a branch and the object it
649 // points to has been pruned from the database due to freak
650 // corruption accidents (it happens with 'git new-work-dir').
651 //
652 return null;
653 }
654 }
655
656 /**
657 * Handle the abstraction of storing a ref update. This is because both
658 * updating and deleting of a ref have merge testing in common.
659 */
660 private abstract class Store {
661 abstract Result execute(Result status) throws IOException;
662 }
663 }