1 /* 2 * Copyright (C) 2008-2012, 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 static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; 48 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; 49 50 import java.io.IOException; 51 import java.text.MessageFormat; 52 import java.time.Duration; 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.Collection; 56 import java.util.Collections; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.concurrent.TimeoutException; 60 61 import org.eclipse.jgit.annotations.Nullable; 62 import org.eclipse.jgit.errors.MissingObjectException; 63 import org.eclipse.jgit.internal.JGitText; 64 import org.eclipse.jgit.lib.RefUpdate.Result; 65 import org.eclipse.jgit.revwalk.RevWalk; 66 import org.eclipse.jgit.transport.PushCertificate; 67 import org.eclipse.jgit.transport.ReceiveCommand; 68 import org.eclipse.jgit.util.time.ProposedTimestamp; 69 70 /** 71 * Batch of reference updates to be applied to a repository. 72 * <p> 73 * The batch update is primarily useful in the transport code, where a client or 74 * server is making changes to more than one reference at a time. 75 */ 76 public class BatchRefUpdate { 77 /** 78 * Maximum delay the calling thread will tolerate while waiting for a 79 * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s. 80 * <p> 81 * A default of 5 seconds was chosen by guessing. A common assumption is 82 * clock skew between machines on the same LAN using an NTP server also on 83 * the same LAN should be under 5 seconds. 5 seconds is also not that long 84 * for a large `git push` operation to complete. 85 * 86 * @since 4.9 87 */ 88 protected static final Duration MAX_WAIT = Duration.ofSeconds(5); 89 90 private final RefDatabase refdb; 91 92 /** Commands to apply during this batch. */ 93 private final List<ReceiveCommand> commands; 94 95 /** Does the caller permit a forced update on a reference? */ 96 private boolean allowNonFastForwards; 97 98 /** Identity to record action as within the reflog. */ 99 private PersonIdent refLogIdent; 100 101 /** Message the caller wants included in the reflog. */ 102 private String refLogMessage; 103 104 /** Should the result value be appended to {@link #refLogMessage}. */ 105 private boolean refLogIncludeResult; 106 107 /** 108 * Should reflogs be written even if the configured default for this ref is 109 * not to write it. 110 */ 111 private boolean forceRefLog; 112 113 /** Push certificate associated with this update. */ 114 private PushCertificate pushCert; 115 116 /** Whether updates should be atomic. */ 117 private boolean atomic; 118 119 /** Push options associated with this update. */ 120 private List<String> pushOptions; 121 122 /** Associated timestamps that should be blocked on before update. */ 123 private List<ProposedTimestamp> timestamps; 124 125 /** 126 * Initialize a new batch update. 127 * 128 * @param refdb 129 * the reference database of the repository to be updated. 130 */ 131 protected BatchRefUpdate(RefDatabase refdb) { 132 this.refdb = refdb; 133 this.commands = new ArrayList<>(); 134 this.atomic = refdb.performsAtomicTransactions(); 135 } 136 137 /** 138 * Whether the batch update will permit a non-fast-forward update to an 139 * existing reference. 140 * 141 * @return true if the batch update will permit a non-fast-forward update to 142 * an existing reference. 143 */ 144 public boolean isAllowNonFastForwards() { 145 return allowNonFastForwards; 146 } 147 148 /** 149 * Set if this update wants to permit a forced update. 150 * 151 * @param allow 152 * true if this update batch should ignore merge tests. 153 * @return {@code this}. 154 */ 155 public BatchRefUpdate setAllowNonFastForwards(boolean allow) { 156 allowNonFastForwards = allow; 157 return this; 158 } 159 160 /** 161 * Get identity of the user making the change in the reflog. 162 * 163 * @return identity of the user making the change in the reflog. 164 */ 165 public PersonIdent getRefLogIdent() { 166 return refLogIdent; 167 } 168 169 /** 170 * Set the identity of the user appearing in the reflog. 171 * <p> 172 * The timestamp portion of the identity is ignored. A new identity with the 173 * current timestamp will be created automatically when the update occurs 174 * and the log record is written. 175 * 176 * @param pi 177 * identity of the user. If null the identity will be 178 * automatically determined based on the repository 179 * configuration. 180 * @return {@code this}. 181 */ 182 public BatchRefUpdate setRefLogIdent(final PersonIdent pi) { 183 refLogIdent = pi; 184 return this; 185 } 186 187 /** 188 * Get the message to include in the reflog. 189 * 190 * @return message the caller wants to include in the reflog; null if the 191 * update should not be logged. 192 */ 193 @Nullable 194 public String getRefLogMessage() { 195 return refLogMessage; 196 } 197 198 /** 199 * Check whether the reflog message should include the result of the update, 200 * such as fast-forward or force-update. 201 * <p> 202 * Describes the default for commands in this batch that do not override it 203 * with 204 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}. 205 * 206 * @return true if the message should include the result. 207 */ 208 public boolean isRefLogIncludingResult() { 209 return refLogIncludeResult; 210 } 211 212 /** 213 * Set the message to include in the reflog. 214 * <p> 215 * Repository implementations may limit which reflogs are written by 216 * default, based on the project configuration. If a repo is not configured 217 * to write logs for this ref by default, setting the message alone may have 218 * no effect. To indicate that the repo should write logs for this update in 219 * spite of configured defaults, use {@link #setForceRefLog(boolean)}. 220 * <p> 221 * Describes the default for commands in this batch that do not override it 222 * with 223 * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}. 224 * 225 * @param msg 226 * the message to describe this change. If null and appendStatus 227 * is false, the reflog will not be updated. 228 * @param appendStatus 229 * true if the status of the ref change (fast-forward or 230 * forced-update) should be appended to the user supplied 231 * message. 232 * @return {@code this}. 233 */ 234 public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) { 235 if (msg == null && !appendStatus) 236 disableRefLog(); 237 else if (msg == null && appendStatus) { 238 refLogMessage = ""; //$NON-NLS-1$ 239 refLogIncludeResult = true; 240 } else { 241 refLogMessage = msg; 242 refLogIncludeResult = appendStatus; 243 } 244 return this; 245 } 246 247 /** 248 * Don't record this update in the ref's associated reflog. 249 * <p> 250 * Equivalent to {@code setRefLogMessage(null, false)}. 251 * 252 * @return {@code this}. 253 */ 254 public BatchRefUpdate disableRefLog() { 255 refLogMessage = null; 256 refLogIncludeResult = false; 257 return this; 258 } 259 260 /** 261 * Force writing a reflog for the updated ref. 262 * 263 * @param force whether to force. 264 * @return {@code this} 265 * @since 4.9 266 */ 267 public BatchRefUpdate setForceRefLog(boolean force) { 268 forceRefLog = force; 269 return this; 270 } 271 272 /** 273 * Check whether log has been disabled by {@link #disableRefLog()}. 274 * 275 * @return true if disabled. 276 */ 277 public boolean isRefLogDisabled() { 278 return refLogMessage == null; 279 } 280 281 /** 282 * Check whether the reflog should be written regardless of repo defaults. 283 * 284 * @return whether force writing is enabled. 285 * @since 4.9 286 */ 287 protected boolean isForceRefLog() { 288 return forceRefLog; 289 } 290 291 /** 292 * Request that all updates in this batch be performed atomically. 293 * <p> 294 * When atomic updates are used, either all commands apply successfully, or 295 * none do. Commands that might have otherwise succeeded are rejected with 296 * {@code REJECTED_OTHER_REASON}. 297 * <p> 298 * This method only works if the underlying ref database supports atomic 299 * transactions, i.e. 300 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()} 301 * returns true. Calling this method with true if the underlying ref 302 * database does not support atomic transactions will cause all commands to 303 * fail with {@code 304 * REJECTED_OTHER_REASON}. 305 * 306 * @param atomic 307 * whether updates should be atomic. 308 * @return {@code this} 309 * @since 4.4 310 */ 311 public BatchRefUpdate setAtomic(boolean atomic) { 312 this.atomic = atomic; 313 return this; 314 } 315 316 /** 317 * Whether updates should be atomic. 318 * 319 * @return atomic whether updates should be atomic. 320 * @since 4.4 321 */ 322 public boolean isAtomic() { 323 return atomic; 324 } 325 326 /** 327 * Set a push certificate associated with this update. 328 * <p> 329 * This usually includes commands to update the refs in this batch, but is not 330 * required to. 331 * 332 * @param cert 333 * push certificate, may be null. 334 * @since 4.1 335 */ 336 public void setPushCertificate(PushCertificate cert) { 337 pushCert = cert; 338 } 339 340 /** 341 * Set the push certificate associated with this update. 342 * <p> 343 * This usually includes commands to update the refs in this batch, but is not 344 * required to. 345 * 346 * @return push certificate, may be null. 347 * @since 4.1 348 */ 349 protected PushCertificate getPushCertificate() { 350 return pushCert; 351 } 352 353 /** 354 * Get commands this update will process. 355 * 356 * @return commands this update will process. 357 */ 358 public List<ReceiveCommand> getCommands() { 359 return Collections.unmodifiableList(commands); 360 } 361 362 /** 363 * Add a single command to this batch update. 364 * 365 * @param cmd 366 * the command to add, must not be null. 367 * @return {@code this}. 368 */ 369 public BatchRefUpdate addCommand(ReceiveCommand cmd) { 370 commands.add(cmd); 371 return this; 372 } 373 374 /** 375 * Add commands to this batch update. 376 * 377 * @param cmd 378 * the commands to add, must not be null. 379 * @return {@code this}. 380 */ 381 public BatchRefUpdate addCommand(ReceiveCommand... cmd) { 382 return addCommand(Arrays.asList(cmd)); 383 } 384 385 /** 386 * Add commands to this batch update. 387 * 388 * @param cmd 389 * the commands to add, must not be null. 390 * @return {@code this}. 391 */ 392 public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) { 393 commands.addAll(cmd); 394 return this; 395 } 396 397 /** 398 * Gets the list of option strings associated with this update. 399 * 400 * @return push options that were passed to {@link #execute}; prior to calling 401 * {@link #execute}, always returns null. 402 * @since 4.5 403 */ 404 @Nullable 405 public List<String> getPushOptions() { 406 return pushOptions; 407 } 408 409 /** 410 * Set push options associated with this update. 411 * <p> 412 * Implementations must call this at the top of {@link #execute(RevWalk, 413 * ProgressMonitor, List)}. 414 * 415 * @param options options passed to {@code execute}. 416 * @since 4.9 417 */ 418 protected void setPushOptions(List<String> options) { 419 pushOptions = options; 420 } 421 422 /** 423 * Get list of timestamps the batch must wait for. 424 * 425 * @return list of timestamps the batch must wait for. 426 * @since 4.6 427 */ 428 public List<ProposedTimestamp> getProposedTimestamps() { 429 if (timestamps != null) { 430 return Collections.unmodifiableList(timestamps); 431 } 432 return Collections.emptyList(); 433 } 434 435 /** 436 * Request the batch to wait for the affected timestamps to resolve. 437 * 438 * @param ts 439 * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. 440 * @return {@code this}. 441 * @since 4.6 442 */ 443 public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) { 444 if (timestamps == null) { 445 timestamps = new ArrayList<>(4); 446 } 447 timestamps.add(ts); 448 return this; 449 } 450 451 /** 452 * Execute this batch update. 453 * <p> 454 * The default implementation of this method performs a sequential reference 455 * update over each reference. 456 * <p> 457 * Implementations must respect the atomicity requirements of the underlying 458 * database as described in {@link #setAtomic(boolean)} and 459 * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}. 460 * 461 * @param walk 462 * a RevWalk to parse tags in case the storage system wants to 463 * store them pre-peeled, a common performance optimization. 464 * @param monitor 465 * progress monitor to receive update status on. 466 * @param options 467 * a list of option strings; set null to execute without 468 * @throws java.io.IOException 469 * the database is unable to accept the update. Individual 470 * command status must be tested to determine if there is a 471 * partial failure, or a total failure. 472 * @since 4.5 473 */ 474 public void execute(RevWalk walk, ProgressMonitor monitor, 475 List<String> options) throws IOException { 476 477 if (atomic && !refdb.performsAtomicTransactions()) { 478 for (ReceiveCommand c : commands) { 479 if (c.getResult() == NOT_ATTEMPTED) { 480 c.setResult(REJECTED_OTHER_REASON, 481 JGitText.get().atomicRefUpdatesNotSupported); 482 } 483 } 484 return; 485 } 486 if (!blockUntilTimestamps(MAX_WAIT)) { 487 return; 488 } 489 490 if (options != null) { 491 setPushOptions(options); 492 } 493 494 monitor.beginTask(JGitText.get().updatingReferences, commands.size()); 495 List<ReceiveCommand> commands2 = new ArrayList<>( 496 commands.size()); 497 // First delete refs. This may free the name space for some of the 498 // updates. 499 for (ReceiveCommand cmd : commands) { 500 try { 501 if (cmd.getResult() == NOT_ATTEMPTED) { 502 if (isMissing(walk, cmd.getOldId()) 503 || isMissing(walk, cmd.getNewId())) { 504 cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT); 505 continue; 506 } 507 cmd.updateType(walk); 508 switch (cmd.getType()) { 509 case CREATE: 510 commands2.add(cmd); 511 break; 512 case UPDATE: 513 case UPDATE_NONFASTFORWARD: 514 commands2.add(cmd); 515 break; 516 case DELETE: 517 RefUpdate rud = newUpdate(cmd); 518 monitor.update(1); 519 cmd.setResult(rud.delete(walk)); 520 } 521 } 522 } catch (IOException err) { 523 cmd.setResult( 524 REJECTED_OTHER_REASON, 525 MessageFormat.format(JGitText.get().lockError, 526 err.getMessage())); 527 } 528 } 529 if (!commands2.isEmpty()) { 530 // What part of the name space is already taken 531 Collection<String> takenNames = new HashSet<>(refdb.getRefs( 532 RefDatabase.ALL).keySet()); 533 Collection<String> takenPrefixes = getTakenPrefixes(takenNames); 534 535 // Now to the update that may require more room in the name space 536 for (ReceiveCommand cmd : commands2) { 537 try { 538 if (cmd.getResult() == NOT_ATTEMPTED) { 539 cmd.updateType(walk); 540 RefUpdate ru = newUpdate(cmd); 541 SWITCH: switch (cmd.getType()) { 542 case DELETE: 543 // Performed in the first phase 544 break; 545 case UPDATE: 546 case UPDATE_NONFASTFORWARD: 547 RefUpdate ruu = newUpdate(cmd); 548 cmd.setResult(ruu.update(walk)); 549 break; 550 case CREATE: 551 for (String prefix : getPrefixes(cmd.getRefName())) { 552 if (takenNames.contains(prefix)) { 553 cmd.setResult(Result.LOCK_FAILURE); 554 break SWITCH; 555 } 556 } 557 if (takenPrefixes.contains(cmd.getRefName())) { 558 cmd.setResult(Result.LOCK_FAILURE); 559 break SWITCH; 560 } 561 ru.setCheckConflicting(false); 562 takenPrefixes.addAll(getPrefixes(cmd.getRefName())); 563 takenNames.add(cmd.getRefName()); 564 cmd.setResult(ru.update(walk)); 565 } 566 } 567 } catch (IOException err) { 568 cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format( 569 JGitText.get().lockError, err.getMessage())); 570 } finally { 571 monitor.update(1); 572 } 573 } 574 } 575 monitor.endTask(); 576 } 577 578 private static boolean isMissing(RevWalk walk, ObjectId id) 579 throws IOException { 580 if (id.equals(ObjectId.zeroId())) { 581 return false; // Explicit add or delete is not missing. 582 } 583 try { 584 walk.parseAny(id); 585 return false; 586 } catch (MissingObjectException e) { 587 return true; 588 } 589 } 590 591 /** 592 * Wait for timestamps to be in the past, aborting commands on timeout. 593 * 594 * @param maxWait 595 * maximum amount of time to wait for timestamps to resolve. 596 * @return true if timestamps were successfully waited for; false if 597 * commands were aborted. 598 * @since 4.6 599 */ 600 protected boolean blockUntilTimestamps(Duration maxWait) { 601 if (timestamps == null) { 602 return true; 603 } 604 try { 605 ProposedTimestamp.blockUntil(timestamps, maxWait); 606 return true; 607 } catch (TimeoutException | InterruptedException e) { 608 String msg = JGitText.get().timeIsUncertain; 609 for (ReceiveCommand c : commands) { 610 if (c.getResult() == NOT_ATTEMPTED) { 611 c.setResult(REJECTED_OTHER_REASON, msg); 612 } 613 } 614 return false; 615 } 616 } 617 618 /** 619 * Execute this batch update without option strings. 620 * 621 * @param walk 622 * a RevWalk to parse tags in case the storage system wants to 623 * store them pre-peeled, a common performance optimization. 624 * @param monitor 625 * progress monitor to receive update status on. 626 * @throws java.io.IOException 627 * the database is unable to accept the update. Individual 628 * command status must be tested to determine if there is a 629 * partial failure, or a total failure. 630 */ 631 public void execute(RevWalk walk, ProgressMonitor monitor) 632 throws IOException { 633 execute(walk, monitor, null); 634 } 635 636 private static Collection<String> getTakenPrefixes(Collection<String> names) { 637 Collection<String> ref = new HashSet<>(); 638 for (String name : names) { 639 addPrefixesTo(name, ref); 640 } 641 return ref; 642 } 643 644 /** 645 * Get all path prefixes of a ref name. 646 * 647 * @param name 648 * ref name. 649 * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns 650 * {@code refs} and {@code refs/heads}. 651 * @since 4.9 652 */ 653 protected static Collection<String> getPrefixes(String name) { 654 Collection<String> ret = new HashSet<>(); 655 addPrefixesTo(name, ret); 656 return ret; 657 } 658 659 /** 660 * Add prefixes of a ref name to an existing collection. 661 * 662 * @param name 663 * ref name. 664 * @param out 665 * path prefixes of the ref name. For {@code refs/heads/foo}, 666 * returns {@code refs} and {@code refs/heads}. 667 * @since 4.9 668 */ 669 protected static void addPrefixesTo(String name, Collection<String> out) { 670 int p1 = name.indexOf('/'); 671 while (p1 > 0) { 672 out.add(name.substring(0, p1)); 673 p1 = name.indexOf('/', p1 + 1); 674 } 675 } 676 677 /** 678 * Create a new RefUpdate copying the batch settings. 679 * 680 * @param cmd 681 * specific command the update should be created to copy. 682 * @return a single reference update command. 683 * @throws java.io.IOException 684 * the reference database cannot make a new update object for 685 * the given reference. 686 */ 687 protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException { 688 RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false); 689 if (isRefLogDisabled(cmd)) { 690 ru.disableRefLog(); 691 } else { 692 ru.setRefLogIdent(refLogIdent); 693 ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd)); 694 ru.setForceRefLog(isForceRefLog(cmd)); 695 } 696 ru.setPushCertificate(pushCert); 697 switch (cmd.getType()) { 698 case DELETE: 699 if (!ObjectId.zeroId().equals(cmd.getOldId())) 700 ru.setExpectedOldObjectId(cmd.getOldId()); 701 ru.setForceUpdate(true); 702 return ru; 703 704 case CREATE: 705 case UPDATE: 706 case UPDATE_NONFASTFORWARD: 707 default: 708 ru.setForceUpdate(isAllowNonFastForwards()); 709 ru.setExpectedOldObjectId(cmd.getOldId()); 710 ru.setNewObjectId(cmd.getNewId()); 711 return ru; 712 } 713 } 714 715 /** 716 * Check whether reflog is disabled for a command. 717 * 718 * @param cmd 719 * specific command. 720 * @return whether the reflog is disabled, taking into account the state from 721 * this instance as well as overrides in the given command. 722 * @since 4.9 723 */ 724 protected boolean isRefLogDisabled(ReceiveCommand cmd) { 725 return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled(); 726 } 727 728 /** 729 * Get reflog message for a command. 730 * 731 * @param cmd 732 * specific command. 733 * @return reflog message, taking into account the state from this instance as 734 * well as overrides in the given command. 735 * @since 4.9 736 */ 737 protected String getRefLogMessage(ReceiveCommand cmd) { 738 return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage(); 739 } 740 741 /** 742 * Check whether the reflog message for a command should include the result. 743 * 744 * @param cmd 745 * specific command. 746 * @return whether the reflog message should show the result, taking into 747 * account the state from this instance as well as overrides in the 748 * given command. 749 * @since 4.9 750 */ 751 protected boolean isRefLogIncludingResult(ReceiveCommand cmd) { 752 return cmd.hasCustomRefLog() 753 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult(); 754 } 755 756 /** 757 * Check whether the reflog for a command should be written regardless of repo 758 * defaults. 759 * 760 * @param cmd 761 * specific command. 762 * @return whether force writing is enabled. 763 * @since 4.9 764 */ 765 protected boolean isForceRefLog(ReceiveCommand cmd) { 766 Boolean isForceRefLog = cmd.isForceRefLog(); 767 return isForceRefLog != null ? isForceRefLog.booleanValue() 768 : isForceRefLog(); 769 } 770 771 /** {@inheritDoc} */ 772 @Override 773 public String toString() { 774 StringBuilder r = new StringBuilder(); 775 r.append(getClass().getSimpleName()).append('['); 776 if (commands.isEmpty()) 777 return r.append(']').toString(); 778 779 r.append('\n'); 780 for (ReceiveCommand cmd : commands) { 781 r.append(" "); //$NON-NLS-1$ 782 r.append(cmd); 783 r.append(" (").append(cmd.getResult()); //$NON-NLS-1$ 784 if (cmd.getMessage() != null) { 785 r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$ 786 } 787 r.append(")\n"); //$NON-NLS-1$ 788 } 789 return r.append(']').toString(); 790 } 791 }