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 }