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