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 (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 (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 RevCommit && oldObj instanceof RevCommit) { 749 if (walk.isMergedInto((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 }