1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4 *
5 * This program and the accompanying materials are made available under the
6 * terms of the Eclipse Distribution License v. 1.0 which is available at
7 * https://www.eclipse.org/org/documents/edl-v10.php.
8 *
9 * SPDX-License-Identifier: BSD-3-Clause
10 */
11
12 package org.eclipse.jgit.lib;
13
14 import java.io.IOException;
15 import java.text.MessageFormat;
16
17 import org.eclipse.jgit.errors.MissingObjectException;
18 import org.eclipse.jgit.internal.JGitText;
19 import org.eclipse.jgit.revwalk.RevCommit;
20 import org.eclipse.jgit.revwalk.RevObject;
21 import org.eclipse.jgit.revwalk.RevWalk;
22 import org.eclipse.jgit.transport.PushCertificate;
23 import org.eclipse.jgit.util.References;
24
25 /**
26 * Creates, updates or deletes any reference.
27 */
28 public abstract class RefUpdate {
29 /**
30 * Status of an update request.
31 * <p>
32 * New values may be added to this enum in the future. Callers may assume that
33 * unknown values are failures, and may generally treat them the same as
34 * {@link #REJECTED_OTHER_REASON}.
35 */
36 public enum Result {
37 /** The ref update/delete has not been attempted by the caller. */
38 NOT_ATTEMPTED,
39
40 /**
41 * The ref could not be locked for update/delete.
42 * <p>
43 * This is generally a transient failure and is usually caused by
44 * another process trying to access the ref at the same time as this
45 * process was trying to update it. It is possible a future operation
46 * will be successful.
47 */
48 LOCK_FAILURE,
49
50 /**
51 * Same value already stored.
52 * <p>
53 * Both the old value and the new value are identical. No change was
54 * necessary for an update. For delete the branch is removed.
55 */
56 NO_CHANGE,
57
58 /**
59 * The ref was created locally for an update, but ignored for delete.
60 * <p>
61 * The ref did not exist when the update started, but it was created
62 * successfully with the new value.
63 */
64 NEW,
65
66 /**
67 * The ref had to be forcefully updated/deleted.
68 * <p>
69 * The ref already existed but its old value was not fully merged into
70 * the new value. The configuration permitted a forced update to take
71 * place, so ref now contains the new value. History associated with the
72 * objects not merged may no longer be reachable.
73 */
74 FORCED,
75
76 /**
77 * The ref was updated/deleted in a fast-forward way.
78 * <p>
79 * The tracking ref already existed and its old value was fully merged
80 * into the new value. No history was made unreachable.
81 */
82 FAST_FORWARD,
83
84 /**
85 * Not a fast-forward and not stored.
86 * <p>
87 * The tracking ref already existed but its old value was not fully
88 * merged into the new value. The configuration did not allow a forced
89 * update/delete to take place, so ref still contains the old value. No
90 * previous history was lost.
91 * <p>
92 * <em>Note:</em> Despite the general name, this result only refers to the
93 * non-fast-forward case. For more general errors, see {@link
94 * #REJECTED_OTHER_REASON}.
95 */
96 REJECTED,
97
98 /**
99 * Rejected because trying to delete the current branch.
100 * <p>
101 * Has no meaning for update.
102 */
103 REJECTED_CURRENT_BRANCH,
104
105 /**
106 * The ref was probably not updated/deleted because of I/O error.
107 * <p>
108 * Unexpected I/O error occurred when writing new ref. Such error may
109 * result in uncertain state, but most probably ref was not updated.
110 * <p>
111 * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
112 * different case.
113 */
114 IO_FAILURE,
115
116 /**
117 * The ref was renamed from another name
118 * <p>
119 */
120 RENAMED,
121
122 /**
123 * One or more objects aren't in the repository.
124 * <p>
125 * This is severe indication of either repository corruption on the
126 * server side, or a bug in the client wherein the client did not supply
127 * all required objects during the pack transfer.
128 *
129 * @since 4.9
130 */
131 REJECTED_MISSING_OBJECT,
132
133 /**
134 * Rejected for some other reason not covered by another enum value.
135 *
136 * @since 4.9
137 */
138 REJECTED_OTHER_REASON;
139 }
140
141 /** New value the caller wants this ref to have. */
142 private ObjectId newValue;
143
144 /** Does this specification ask for forced updated (rewind/reset)? */
145 private boolean force;
146
147 /** Identity to record action as within the reflog. */
148 private PersonIdent refLogIdent;
149
150 /** Message the caller wants included in the reflog. */
151 private String refLogMessage;
152
153 /** Should the Result value be appended to {@link #refLogMessage}. */
154 private boolean refLogIncludeResult;
155
156 /**
157 * Should reflogs be written even if the configured default for this ref is
158 * not to write it.
159 */
160 private boolean forceRefLog;
161
162 /** Old value of the ref, obtained after we lock it. */
163 private ObjectId oldValue;
164
165 /** If non-null, the value {@link #oldValue} must have to continue. */
166 private ObjectId expValue;
167
168 /** Result of the update operation. */
169 private Result result = Result.NOT_ATTEMPTED;
170
171 /** Push certificate associated with this update. */
172 private PushCertificate pushCert;
173
174 private final Ref ref;
175
176 /**
177 * Is this RefUpdate detaching a symbolic ref?
178 *
179 * We need this info since this.ref will normally be peeled of in case of
180 * detaching a symbolic ref (HEAD for example).
181 *
182 * Without this flag we cannot decide whether the ref has to be updated or
183 * not in case when it was a symbolic ref and the newValue == oldValue.
184 */
185 private boolean detachingSymbolicRef;
186
187 private boolean checkConflicting = true;
188
189 /**
190 * Construct a new update operation for the reference.
191 * <p>
192 * {@code ref.getObjectId()} will be used to seed {@link #getOldObjectId()},
193 * which callers can use as part of their own update logic.
194 *
195 * @param ref
196 * the reference that will be updated by this operation.
197 */
198 protected RefUpdate(Ref ref) {
199 this.ref = ref;
200 oldValue = ref.getObjectId();
201 refLogMessage = ""; //$NON-NLS-1$
202 }
203
204 /**
205 * Get the reference database this update modifies.
206 *
207 * @return the reference database this update modifies.
208 */
209 protected abstract RefDatabase getRefDatabase();
210
211 /**
212 * Get the repository storing the database's objects.
213 *
214 * @return the repository storing the database's objects.
215 */
216 protected abstract Repository getRepository();
217
218 /**
219 * Try to acquire the lock on the reference.
220 * <p>
221 * If the locking was successful the implementor must set the current
222 * identity value by calling {@link #setOldObjectId(ObjectId)}.
223 *
224 * @param deref
225 * true if the lock should be taken against the leaf level
226 * reference; false if it should be taken exactly against the
227 * current reference.
228 * @return true if the lock was acquired and the reference is likely
229 * protected from concurrent modification; false if it failed.
230 * @throws java.io.IOException
231 * the lock couldn't be taken due to an unexpected storage
232 * failure, and not because of a concurrent update.
233 */
234 protected abstract boolean tryLock(boolean deref) throws IOException;
235
236 /**
237 * Releases the lock taken by {@link #tryLock} if it succeeded.
238 */
239 protected abstract void unlock();
240
241 /**
242 * Do update
243 *
244 * @param desiredResult
245 * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object.
246 * @return {@code result}
247 * @throws java.io.IOException
248 */
249 protected abstract Result doUpdate(Result desiredResult) throws IOException;
250
251 /**
252 * Do delete
253 *
254 * @param desiredResult
255 * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object.
256 * @return {@code result}
257 * @throws java.io.IOException
258 */
259 protected abstract Result doDelete(Result desiredResult) throws IOException;
260
261 /**
262 * Do link
263 *
264 * @param target
265 * a {@link java.lang.String} object.
266 * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} on success.
267 * @throws java.io.IOException
268 */
269 protected abstract Result doLink(String target) throws IOException;
270
271 /**
272 * Get the name of the ref this update will operate on.
273 *
274 * @return name of underlying ref.
275 */
276 public String getName() {
277 return getRef().getName();
278 }
279
280 /**
281 * Get the reference this update will create or modify.
282 *
283 * @return the reference this update will create or modify.
284 */
285 public Ref getRef() {
286 return ref;
287 }
288
289 /**
290 * Get the new value the ref will be (or was) updated to.
291 *
292 * @return new value. Null if the caller has not configured it.
293 */
294 public ObjectId getNewObjectId() {
295 return newValue;
296 }
297
298 /**
299 * Tells this RefUpdate that it is actually detaching a symbolic ref.
300 */
301 public void setDetachingSymbolicRef() {
302 detachingSymbolicRef = true;
303 }
304
305 /**
306 * Return whether this update is actually detaching a symbolic ref.
307 *
308 * @return true if detaching a symref.
309 * @since 4.9
310 */
311 public boolean isDetachingSymbolicRef() {
312 return detachingSymbolicRef;
313 }
314
315 /**
316 * Set the new value the ref will update to.
317 *
318 * @param id
319 * the new value.
320 */
321 public void setNewObjectId(AnyObjectId id) {
322 newValue = id.copy();
323 }
324
325 /**
326 * Get the expected value of the ref after the lock is taken, but before
327 * update occurs.
328 *
329 * @return the expected value of the ref after the lock is taken, but before
330 * update occurs. Null to avoid the compare and swap test. Use
331 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate
332 * expectation of a non-existant ref.
333 */
334 public ObjectId getExpectedOldObjectId() {
335 return expValue;
336 }
337
338 /**
339 * Set the expected value of the ref after the lock is taken, but before
340 * update occurs.
341 *
342 * @param id
343 * the expected value of the ref after the lock is taken, but
344 * before update occurs. Null to avoid the compare and swap test.
345 * Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to indicate
346 * expectation of a non-existant ref.
347 */
348 public void setExpectedOldObjectId(AnyObjectId id) {
349 expValue = id != null ? id.toObjectId() : null;
350 }
351
352 /**
353 * Check if this update wants to forcefully change the ref.
354 *
355 * @return true if this update should ignore merge tests.
356 */
357 public boolean isForceUpdate() {
358 return force;
359 }
360
361 /**
362 * Set if this update wants to forcefully change the ref.
363 *
364 * @param b
365 * true if this update should ignore merge tests.
366 */
367 public void setForceUpdate(boolean b) {
368 force = b;
369 }
370
371 /**
372 * Get identity of the user making the change in the reflog.
373 *
374 * @return identity of the user making the change in the reflog.
375 */
376 public PersonIdent getRefLogIdent() {
377 return refLogIdent;
378 }
379
380 /**
381 * Set the identity of the user appearing in the reflog.
382 * <p>
383 * The timestamp portion of the identity is ignored. A new identity with the
384 * current timestamp will be created automatically when the update occurs
385 * and the log record is written.
386 *
387 * @param pi
388 * identity of the user. If null the identity will be
389 * automatically determined based on the repository
390 * configuration.
391 */
392 public void setRefLogIdent(PersonIdent pi) {
393 refLogIdent = pi;
394 }
395
396 /**
397 * Get the message to include in the reflog.
398 *
399 * @return message the caller wants to include in the reflog; null if the
400 * update should not be logged.
401 */
402 public String getRefLogMessage() {
403 return refLogMessage;
404 }
405
406 /**
407 * Whether the ref log message should show the result.
408 *
409 * @return {@code true} if the ref log message should show the result.
410 */
411 protected boolean isRefLogIncludingResult() {
412 return refLogIncludeResult;
413 }
414
415 /**
416 * Set the message to include in the reflog.
417 * <p>
418 * Repository implementations may limit which reflogs are written by default,
419 * based on the project configuration. If a repo is not configured to write
420 * logs for this ref by default, setting the message alone may have no effect.
421 * To indicate that the repo should write logs for this update in spite of
422 * configured defaults, use {@link #setForceRefLog(boolean)}.
423 *
424 * @param msg
425 * the message to describe this change. It may be null if
426 * appendStatus is null in order not to append to the reflog
427 * @param appendStatus
428 * true if the status of the ref change (fast-forward or
429 * forced-update) should be appended to the user supplied
430 * message.
431 */
432 public void setRefLogMessage(String msg, boolean appendStatus) {
433 if (msg == null && !appendStatus)
434 disableRefLog();
435 else if (msg == null && appendStatus) {
436 refLogMessage = ""; //$NON-NLS-1$
437 refLogIncludeResult = true;
438 } else {
439 refLogMessage = msg;
440 refLogIncludeResult = appendStatus;
441 }
442 }
443
444 /**
445 * Don't record this update in the ref's associated reflog.
446 */
447 public void disableRefLog() {
448 refLogMessage = null;
449 refLogIncludeResult = false;
450 }
451
452 /**
453 * Force writing a reflog for the updated ref.
454 *
455 * @param force whether to force.
456 * @since 4.9
457 */
458 public void setForceRefLog(boolean force) {
459 forceRefLog = force;
460 }
461
462 /**
463 * Check whether the reflog should be written regardless of repo defaults.
464 *
465 * @return whether force writing is enabled.
466 * @since 4.9
467 */
468 protected boolean isForceRefLog() {
469 return forceRefLog;
470 }
471
472 /**
473 * The old value of the ref, prior to the update being attempted.
474 * <p>
475 * This value may differ before and after the update method. Initially it is
476 * populated with the value of the ref before the lock is taken, but the old
477 * value may change if someone else modified the ref between the time we
478 * last read it and when the ref was locked for update.
479 *
480 * @return the value of the ref prior to the update being attempted; null if
481 * the updated has not been attempted yet.
482 */
483 public ObjectId getOldObjectId() {
484 return oldValue;
485 }
486
487 /**
488 * Set the old value of the ref.
489 *
490 * @param old
491 * the old value.
492 */
493 protected void setOldObjectId(ObjectId old) {
494 oldValue = old;
495 }
496
497 /**
498 * Set a push certificate associated with this update.
499 * <p>
500 * This usually includes a command to update this ref, but is not required to.
501 *
502 * @param cert
503 * push certificate, may be null.
504 * @since 4.1
505 */
506 public void setPushCertificate(PushCertificate cert) {
507 pushCert = cert;
508 }
509
510 /**
511 * Set the push certificate associated with this update.
512 * <p>
513 * This usually includes a command to update this ref, but is not required to.
514 *
515 * @return push certificate, may be null.
516 * @since 4.1
517 */
518 protected PushCertificate getPushCertificate() {
519 return pushCert;
520 }
521
522 /**
523 * Get the status of this update.
524 * <p>
525 * The same value that was previously returned from an update method.
526 *
527 * @return the status of the update.
528 */
529 public Result getResult() {
530 return result;
531 }
532
533 private void requireCanDoUpdate() {
534 if (newValue == null)
535 throw new IllegalStateException(JGitText.get().aNewObjectIdIsRequired);
536 }
537
538 /**
539 * Force the ref to take the new value.
540 * <p>
541 * This is just a convenient helper for setting the force flag, and as such
542 * the merge test is performed.
543 *
544 * @return the result status of the update.
545 * @throws java.io.IOException
546 * an unexpected IO error occurred while writing changes.
547 */
548 public Result forceUpdate() throws IOException {
549 force = true;
550 return update();
551 }
552
553 /**
554 * Gracefully update the ref to the new value.
555 * <p>
556 * Merge test will be performed according to {@link #isForceUpdate()}.
557 * <p>
558 * This is the same as:
559 *
560 * <pre>
561 * return update(new RevWalk(getRepository()));
562 * </pre>
563 *
564 * @return the result status of the update.
565 * @throws java.io.IOException
566 * an unexpected IO error occurred while writing changes.
567 */
568 public Result update() throws IOException {
569 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(getRepository())) {
570 rw.setRetainBody(false);
571 return update(rw);
572 }
573 }
574
575 /**
576 * Gracefully update the ref to the new value.
577 * <p>
578 * Merge test will be performed according to {@link #isForceUpdate()}.
579 *
580 * @param walk
581 * a RevWalk instance this update command can borrow to perform
582 * the merge test. The walk will be reset to perform the test.
583 * @return the result status of the update.
584 * @throws java.io.IOException
585 * an unexpected IO error occurred while writing changes.
586 */
587 public Result update(RevWalk walk) throws IOException {
588 requireCanDoUpdate();
589 try {
590 return result = updateImpl(walk, new Store() {
591 @Override
592 Result execute(Result status) throws IOException {
593 if (status == Result.NO_CHANGE)
594 return status;
595 return doUpdate(status);
596 }
597 });
598 } catch (IOException x) {
599 result = Result.IO_FAILURE;
600 throw x;
601 }
602 }
603
604 /**
605 * Delete the ref.
606 * <p>
607 * This is the same as:
608 *
609 * <pre>
610 * return delete(new RevWalk(getRepository()));
611 * </pre>
612 *
613 * @return the result status of the delete.
614 * @throws java.io.IOException
615 */
616 public Result delete() throws IOException {
617 try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(getRepository())) {
618 rw.setRetainBody(false);
619 return delete(rw);
620 }
621 }
622
623 /**
624 * Delete the ref.
625 *
626 * @param walk
627 * a RevWalk instance this delete command can borrow to perform
628 * the merge test. The walk will be reset to perform the test.
629 * @return the result status of the delete.
630 * @throws java.io.IOException
631 */
632 public Result delete(RevWalk walk) throws IOException {
633 final String myName = detachingSymbolicRef
634 ? getRef().getName()
635 : getRef().getLeaf().getName();
636 if (myName.startsWith(Constants.R_HEADS) && !getRepository().isBare()) {
637 // Don't allow the currently checked out branch to be deleted.
638 Ref head = getRefDatabase().exactRef(Constants.HEAD);
639 while (head != null && head.isSymbolic()) {
640 head = head.getTarget();
641 if (myName.equals(head.getName()))
642 return result = Result.REJECTED_CURRENT_BRANCH;
643 }
644 }
645
646 try {
647 return result = updateImpl(walk, new Store() {
648 @Override
649 Result execute(Result status) throws IOException {
650 return doDelete(status);
651 }
652 });
653 } catch (IOException x) {
654 result = Result.IO_FAILURE;
655 throw x;
656 }
657 }
658
659 /**
660 * Replace this reference with a symbolic reference to another reference.
661 * <p>
662 * This exact reference (not its traversed leaf) is replaced with a symbolic
663 * reference to the requested name.
664 *
665 * @param target
666 * name of the new target for this reference. The new target name
667 * must be absolute, so it must begin with {@code refs/}.
668 * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} or
669 * {@link org.eclipse.jgit.lib.RefUpdate.Result#FORCED} on success.
670 * @throws java.io.IOException
671 */
672 public Result link(String target) throws IOException {
673 if (!target.startsWith(Constants.R_REFS))
674 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().illegalArgumentNotA, Constants.R_REFS));
675 if (checkConflicting && getRefDatabase().isNameConflicting(getName()))
676 return Result.LOCK_FAILURE;
677 try {
678 if (!tryLock(false))
679 return Result.LOCK_FAILURE;
680
681 final Ref old = getRefDatabase().exactRef(getName());
682 if (old != null && old.isSymbolic()) {
683 final Ref dst = old.getTarget();
684 if (target.equals(dst.getName()))
685 return result = Result.NO_CHANGE;
686 }
687
688 if (old != null && old.getObjectId() != null)
689 setOldObjectId(old.getObjectId());
690
691 final Ref dst = getRefDatabase().exactRef(target);
692 if (dst != null && dst.getObjectId() != null)
693 setNewObjectId(dst.getObjectId());
694
695 return result = doLink(target);
696 } catch (IOException x) {
697 result = Result.IO_FAILURE;
698 throw x;
699 } finally {
700 unlock();
701 }
702 }
703
704 private Result updateImpl(RevWalk walk, Store store)
705 throws IOException {
706 RevObject newObj;
707 RevObject oldObj;
708
709 // don't make expensive conflict check if this is an existing Ref
710 if (oldValue == null && checkConflicting
711 && getRefDatabase().isNameConflicting(getName())) {
712 return Result.LOCK_FAILURE;
713 }
714 try {
715 // If we're detaching a symbolic reference, we should update the reference
716 // itself. Otherwise, we will update the leaf reference, which should be
717 // an ObjectIdRef.
718 if (!tryLock(!detachingSymbolicRef)) {
719 return Result.LOCK_FAILURE;
720 }
721 if (expValue != null) {
722 final ObjectId o;
723 o = oldValue != null ? oldValue : ObjectId.zeroId();
724 if (!AnyObjectId.isEqual(expValue, o)) {
725 return Result.LOCK_FAILURE;
726 }
727 }
728 try {
729 newObj = safeParseNew(walk, newValue);
730 } catch (MissingObjectException e) {
731 return Result.REJECTED_MISSING_OBJECT;
732 }
733
734 if (oldValue == null) {
735 return store.execute(Result.NEW);
736 }
737
738 oldObj = safeParseOld(walk, oldValue);
739 if (References.isSameObject(newObj, oldObj)
740 && !detachingSymbolicRef) {
741 return store.execute(Result.NO_CHANGE);
742 }
743
744 if (isForceUpdate()) {
745 return store.execute(Result.FORCED);
746 }
747
748 if (newObj instanceof RevCommite/jgit/revwalk/RevCommit.html#RevCommit">RevCommit && oldObj instanceof RevCommit) {
749 if (walk.isMergedInto((RevCommit../../../org/eclipse/jgit/revwalk/RevCommit.html#RevCommit">RevCommit) oldObj, (RevCommit) newObj)) {
750 return store.execute(Result.FAST_FORWARD);
751 }
752 }
753
754 return Result.REJECTED;
755 } finally {
756 unlock();
757 }
758 }
759
760 /**
761 * Enable/disable the check for conflicting ref names. By default conflicts
762 * are checked explicitly.
763 *
764 * @param check
765 * whether to enable the check for conflicting ref names.
766 * @since 3.0
767 */
768 public void setCheckConflicting(boolean check) {
769 checkConflicting = check;
770 }
771
772 private static RevObject safeParseNew(RevWalk rw, AnyObjectId newId)
773 throws IOException {
774 if (newId == null || ObjectId.zeroId().equals(newId)) {
775 return null;
776 }
777 return rw.parseAny(newId);
778 }
779
780 private static RevObject safeParseOld(RevWalk rw, AnyObjectId oldId)
781 throws IOException {
782 try {
783 return oldId != null ? rw.parseAny(oldId) : null;
784 } catch (MissingObjectException e) {
785 // We can expect some old objects to be missing, like if we are trying to
786 // force a deletion of a branch and the object it points to has been
787 // pruned from the database due to freak corruption accidents (it happens
788 // with 'git new-work-dir').
789 return null;
790 }
791 }
792
793 /**
794 * Handle the abstraction of storing a ref update. This is because both
795 * updating and deleting of a ref have merge testing in common.
796 */
797 private abstract static class Store {
798 abstract Result execute(Result status) throws IOException;
799 }
800 }