BatchRefUpdate.java

  1. /*
  2.  * Copyright (C) 2008-2012, 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. package org.eclipse.jgit.lib;

  12. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  13. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;

  14. import java.io.IOException;
  15. import java.text.MessageFormat;
  16. import java.time.Duration;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.concurrent.TimeoutException;

  24. import org.eclipse.jgit.annotations.Nullable;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.revwalk.RevWalk;
  28. import org.eclipse.jgit.transport.PushCertificate;
  29. import org.eclipse.jgit.transport.ReceiveCommand;
  30. import org.eclipse.jgit.util.time.ProposedTimestamp;

  31. /**
  32.  * Batch of reference updates to be applied to a repository.
  33.  * <p>
  34.  * The batch update is primarily useful in the transport code, where a client or
  35.  * server is making changes to more than one reference at a time.
  36.  */
  37. public class BatchRefUpdate {
  38.     /**
  39.      * Maximum delay the calling thread will tolerate while waiting for a
  40.      * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
  41.      * <p>
  42.      * A default of 5 seconds was chosen by guessing. A common assumption is
  43.      * clock skew between machines on the same LAN using an NTP server also on
  44.      * the same LAN should be under 5 seconds. 5 seconds is also not that long
  45.      * for a large `git push` operation to complete.
  46.      *
  47.      * @since 4.9
  48.      */
  49.     protected static final Duration MAX_WAIT = Duration.ofSeconds(5);

  50.     private final RefDatabase refdb;

  51.     /** Commands to apply during this batch. */
  52.     private final List<ReceiveCommand> commands;

  53.     /** Does the caller permit a forced update on a reference? */
  54.     private boolean allowNonFastForwards;

  55.     /** Identity to record action as within the reflog. */
  56.     private PersonIdent refLogIdent;

  57.     /** Message the caller wants included in the reflog. */
  58.     private String refLogMessage;

  59.     /** Should the result value be appended to {@link #refLogMessage}. */
  60.     private boolean refLogIncludeResult;

  61.     /**
  62.      * Should reflogs be written even if the configured default for this ref is
  63.      * not to write it.
  64.      */
  65.     private boolean forceRefLog;

  66.     /** Push certificate associated with this update. */
  67.     private PushCertificate pushCert;

  68.     /** Whether updates should be atomic. */
  69.     private boolean atomic;

  70.     /** Push options associated with this update. */
  71.     private List<String> pushOptions;

  72.     /** Associated timestamps that should be blocked on before update. */
  73.     private List<ProposedTimestamp> timestamps;

  74.     /**
  75.      * Initialize a new batch update.
  76.      *
  77.      * @param refdb
  78.      *            the reference database of the repository to be updated.
  79.      */
  80.     protected BatchRefUpdate(RefDatabase refdb) {
  81.         this.refdb = refdb;
  82.         this.commands = new ArrayList<>();
  83.         this.atomic = refdb.performsAtomicTransactions();
  84.     }

  85.     /**
  86.      * Whether the batch update will permit a non-fast-forward update to an
  87.      * existing reference.
  88.      *
  89.      * @return true if the batch update will permit a non-fast-forward update to
  90.      *         an existing reference.
  91.      */
  92.     public boolean isAllowNonFastForwards() {
  93.         return allowNonFastForwards;
  94.     }

  95.     /**
  96.      * Set if this update wants to permit a forced update.
  97.      *
  98.      * @param allow
  99.      *            true if this update batch should ignore merge tests.
  100.      * @return {@code this}.
  101.      */
  102.     public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
  103.         allowNonFastForwards = allow;
  104.         return this;
  105.     }

  106.     /**
  107.      * Get identity of the user making the change in the reflog.
  108.      *
  109.      * @return identity of the user making the change in the reflog.
  110.      */
  111.     public PersonIdent getRefLogIdent() {
  112.         return refLogIdent;
  113.     }

  114.     /**
  115.      * Set the identity of the user appearing in the reflog.
  116.      * <p>
  117.      * The timestamp portion of the identity is ignored. A new identity with the
  118.      * current timestamp will be created automatically when the update occurs
  119.      * and the log record is written.
  120.      *
  121.      * @param pi
  122.      *            identity of the user. If null the identity will be
  123.      *            automatically determined based on the repository
  124.      *            configuration.
  125.      * @return {@code this}.
  126.      */
  127.     public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
  128.         refLogIdent = pi;
  129.         return this;
  130.     }

  131.     /**
  132.      * Get the message to include in the reflog.
  133.      *
  134.      * @return message the caller wants to include in the reflog; null if the
  135.      *         update should not be logged.
  136.      */
  137.     @Nullable
  138.     public String getRefLogMessage() {
  139.         return refLogMessage;
  140.     }

  141.     /**
  142.      * Check whether the reflog message should include the result of the update,
  143.      * such as fast-forward or force-update.
  144.      * <p>
  145.      * Describes the default for commands in this batch that do not override it
  146.      * with
  147.      * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  148.      *
  149.      * @return true if the message should include the result.
  150.      */
  151.     public boolean isRefLogIncludingResult() {
  152.         return refLogIncludeResult;
  153.     }

  154.     /**
  155.      * Set the message to include in the reflog.
  156.      * <p>
  157.      * Repository implementations may limit which reflogs are written by
  158.      * default, based on the project configuration. If a repo is not configured
  159.      * to write logs for this ref by default, setting the message alone may have
  160.      * no effect. To indicate that the repo should write logs for this update in
  161.      * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
  162.      * <p>
  163.      * Describes the default for commands in this batch that do not override it
  164.      * with
  165.      * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  166.      *
  167.      * @param msg
  168.      *            the message to describe this change. If null and appendStatus
  169.      *            is false, the reflog will not be updated.
  170.      * @param appendStatus
  171.      *            true if the status of the ref change (fast-forward or
  172.      *            forced-update) should be appended to the user supplied
  173.      *            message.
  174.      * @return {@code this}.
  175.      */
  176.     public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
  177.         if (msg == null && !appendStatus)
  178.             disableRefLog();
  179.         else if (msg == null && appendStatus) {
  180.             refLogMessage = ""; //$NON-NLS-1$
  181.             refLogIncludeResult = true;
  182.         } else {
  183.             refLogMessage = msg;
  184.             refLogIncludeResult = appendStatus;
  185.         }
  186.         return this;
  187.     }

  188.     /**
  189.      * Don't record this update in the ref's associated reflog.
  190.      * <p>
  191.      * Equivalent to {@code setRefLogMessage(null, false)}.
  192.      *
  193.      * @return {@code this}.
  194.      */
  195.     public BatchRefUpdate disableRefLog() {
  196.         refLogMessage = null;
  197.         refLogIncludeResult = false;
  198.         return this;
  199.     }

  200.     /**
  201.      * Force writing a reflog for the updated ref.
  202.      *
  203.      * @param force whether to force.
  204.      * @return {@code this}
  205.      * @since 4.9
  206.      */
  207.     public BatchRefUpdate setForceRefLog(boolean force) {
  208.         forceRefLog = force;
  209.         return this;
  210.     }

  211.     /**
  212.      * Check whether log has been disabled by {@link #disableRefLog()}.
  213.      *
  214.      * @return true if disabled.
  215.      */
  216.     public boolean isRefLogDisabled() {
  217.         return refLogMessage == null;
  218.     }

  219.     /**
  220.      * Check whether the reflog should be written regardless of repo defaults.
  221.      *
  222.      * @return whether force writing is enabled.
  223.      * @since 4.9
  224.      */
  225.     protected boolean isForceRefLog() {
  226.         return forceRefLog;
  227.     }

  228.     /**
  229.      * Request that all updates in this batch be performed atomically.
  230.      * <p>
  231.      * When atomic updates are used, either all commands apply successfully, or
  232.      * none do. Commands that might have otherwise succeeded are rejected with
  233.      * {@code REJECTED_OTHER_REASON}.
  234.      * <p>
  235.      * This method only works if the underlying ref database supports atomic
  236.      * transactions, i.e.
  237.      * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
  238.      * returns true. Calling this method with true if the underlying ref
  239.      * database does not support atomic transactions will cause all commands to
  240.      * fail with {@code
  241.      * REJECTED_OTHER_REASON}.
  242.      *
  243.      * @param atomic
  244.      *            whether updates should be atomic.
  245.      * @return {@code this}
  246.      * @since 4.4
  247.      */
  248.     public BatchRefUpdate setAtomic(boolean atomic) {
  249.         this.atomic = atomic;
  250.         return this;
  251.     }

  252.     /**
  253.      * Whether updates should be atomic.
  254.      *
  255.      * @return atomic whether updates should be atomic.
  256.      * @since 4.4
  257.      */
  258.     public boolean isAtomic() {
  259.         return atomic;
  260.     }

  261.     /**
  262.      * Set a push certificate associated with this update.
  263.      * <p>
  264.      * This usually includes commands to update the refs in this batch, but is not
  265.      * required to.
  266.      *
  267.      * @param cert
  268.      *            push certificate, may be null.
  269.      * @since 4.1
  270.      */
  271.     public void setPushCertificate(PushCertificate cert) {
  272.         pushCert = cert;
  273.     }

  274.     /**
  275.      * Set the push certificate associated with this update.
  276.      * <p>
  277.      * This usually includes commands to update the refs in this batch, but is not
  278.      * required to.
  279.      *
  280.      * @return push certificate, may be null.
  281.      * @since 4.1
  282.      */
  283.     protected PushCertificate getPushCertificate() {
  284.         return pushCert;
  285.     }

  286.     /**
  287.      * Get commands this update will process.
  288.      *
  289.      * @return commands this update will process.
  290.      */
  291.     public List<ReceiveCommand> getCommands() {
  292.         return Collections.unmodifiableList(commands);
  293.     }

  294.     /**
  295.      * Add a single command to this batch update.
  296.      *
  297.      * @param cmd
  298.      *            the command to add, must not be null.
  299.      * @return {@code this}.
  300.      */
  301.     public BatchRefUpdate addCommand(ReceiveCommand cmd) {
  302.         commands.add(cmd);
  303.         return this;
  304.     }

  305.     /**
  306.      * Add commands to this batch update.
  307.      *
  308.      * @param cmd
  309.      *            the commands to add, must not be null.
  310.      * @return {@code this}.
  311.      */
  312.     public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
  313.         return addCommand(Arrays.asList(cmd));
  314.     }

  315.     /**
  316.      * Add commands to this batch update.
  317.      *
  318.      * @param cmd
  319.      *            the commands to add, must not be null.
  320.      * @return {@code this}.
  321.      */
  322.     public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
  323.         commands.addAll(cmd);
  324.         return this;
  325.     }

  326.     /**
  327.      * Gets the list of option strings associated with this update.
  328.      *
  329.      * @return push options that were passed to {@link #execute}; prior to calling
  330.      *         {@link #execute}, always returns null.
  331.      * @since 4.5
  332.      */
  333.     @Nullable
  334.     public List<String> getPushOptions() {
  335.         return pushOptions;
  336.     }

  337.     /**
  338.      * Set push options associated with this update.
  339.      * <p>
  340.      * Implementations must call this at the top of {@link #execute(RevWalk,
  341.      * ProgressMonitor, List)}.
  342.      *
  343.      * @param options options passed to {@code execute}.
  344.      * @since 4.9
  345.      */
  346.     protected void setPushOptions(List<String> options) {
  347.         pushOptions = options;
  348.     }

  349.     /**
  350.      * Get list of timestamps the batch must wait for.
  351.      *
  352.      * @return list of timestamps the batch must wait for.
  353.      * @since 4.6
  354.      */
  355.     public List<ProposedTimestamp> getProposedTimestamps() {
  356.         if (timestamps != null) {
  357.             return Collections.unmodifiableList(timestamps);
  358.         }
  359.         return Collections.emptyList();
  360.     }

  361.     /**
  362.      * Request the batch to wait for the affected timestamps to resolve.
  363.      *
  364.      * @param ts
  365.      *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
  366.      * @return {@code this}.
  367.      * @since 4.6
  368.      */
  369.     public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
  370.         if (timestamps == null) {
  371.             timestamps = new ArrayList<>(4);
  372.         }
  373.         timestamps.add(ts);
  374.         return this;
  375.     }

  376.     /**
  377.      * Execute this batch update.
  378.      * <p>
  379.      * The default implementation of this method performs a sequential reference
  380.      * update over each reference.
  381.      * <p>
  382.      * Implementations must respect the atomicity requirements of the underlying
  383.      * database as described in {@link #setAtomic(boolean)} and
  384.      * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
  385.      *
  386.      * @param walk
  387.      *            a RevWalk to parse tags in case the storage system wants to
  388.      *            store them pre-peeled, a common performance optimization.
  389.      * @param monitor
  390.      *            progress monitor to receive update status on.
  391.      * @param options
  392.      *            a list of option strings; set null to execute without
  393.      * @throws java.io.IOException
  394.      *             the database is unable to accept the update. Individual
  395.      *             command status must be tested to determine if there is a
  396.      *             partial failure, or a total failure.
  397.      * @since 4.5
  398.      */
  399.     public void execute(RevWalk walk, ProgressMonitor monitor,
  400.             List<String> options) throws IOException {

  401.         if (atomic && !refdb.performsAtomicTransactions()) {
  402.             for (ReceiveCommand c : commands) {
  403.                 if (c.getResult() == NOT_ATTEMPTED) {
  404.                     c.setResult(REJECTED_OTHER_REASON,
  405.                             JGitText.get().atomicRefUpdatesNotSupported);
  406.                 }
  407.             }
  408.             return;
  409.         }
  410.         if (!blockUntilTimestamps(MAX_WAIT)) {
  411.             return;
  412.         }

  413.         if (options != null) {
  414.             setPushOptions(options);
  415.         }

  416.         monitor.beginTask(JGitText.get().updatingReferences, commands.size());
  417.         List<ReceiveCommand> commands2 = new ArrayList<>(
  418.                 commands.size());
  419.         // First delete refs. This may free the name space for some of the
  420.         // updates.
  421.         for (ReceiveCommand cmd : commands) {
  422.             try {
  423.                 if (cmd.getResult() == NOT_ATTEMPTED) {
  424.                     if (isMissing(walk, cmd.getOldId())
  425.                             || isMissing(walk, cmd.getNewId())) {
  426.                         cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  427.                         continue;
  428.                     }
  429.                     cmd.updateType(walk);
  430.                     switch (cmd.getType()) {
  431.                     case CREATE:
  432.                         commands2.add(cmd);
  433.                         break;
  434.                     case UPDATE:
  435.                     case UPDATE_NONFASTFORWARD:
  436.                         commands2.add(cmd);
  437.                         break;
  438.                     case DELETE:
  439.                         RefUpdate rud = newUpdate(cmd);
  440.                         monitor.update(1);
  441.                         cmd.setResult(rud.delete(walk));
  442.                     }
  443.                 }
  444.             } catch (IOException err) {
  445.                 cmd.setResult(
  446.                         REJECTED_OTHER_REASON,
  447.                         MessageFormat.format(JGitText.get().lockError,
  448.                                 err.getMessage()));
  449.             }
  450.         }
  451.         if (!commands2.isEmpty()) {
  452.             // Perform updates that may require more room in the name space
  453.             for (ReceiveCommand cmd : commands2) {
  454.                 try {
  455.                     if (cmd.getResult() == NOT_ATTEMPTED) {
  456.                         cmd.updateType(walk);
  457.                         RefUpdate ru = newUpdate(cmd);
  458.                         switch (cmd.getType()) {
  459.                             case DELETE:
  460.                                 // Performed in the first phase
  461.                                 break;
  462.                             case UPDATE:
  463.                             case UPDATE_NONFASTFORWARD:
  464.                                 RefUpdate ruu = newUpdate(cmd);
  465.                                 cmd.setResult(ruu.update(walk));
  466.                                 break;
  467.                             case CREATE:
  468.                                 cmd.setResult(ru.update(walk));
  469.                                 break;
  470.                         }
  471.                     }
  472.                 } catch (IOException err) {
  473.                     cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
  474.                             JGitText.get().lockError, err.getMessage()));
  475.                 } finally {
  476.                     monitor.update(1);
  477.                 }
  478.             }
  479.         }
  480.         monitor.endTask();
  481.     }

  482.     private static boolean isMissing(RevWalk walk, ObjectId id)
  483.             throws IOException {
  484.         if (id.equals(ObjectId.zeroId())) {
  485.             return false; // Explicit add or delete is not missing.
  486.         }
  487.         try {
  488.             walk.parseAny(id);
  489.             return false;
  490.         } catch (MissingObjectException e) {
  491.             return true;
  492.         }
  493.     }

  494.     /**
  495.      * Wait for timestamps to be in the past, aborting commands on timeout.
  496.      *
  497.      * @param maxWait
  498.      *            maximum amount of time to wait for timestamps to resolve.
  499.      * @return true if timestamps were successfully waited for; false if
  500.      *         commands were aborted.
  501.      * @since 4.6
  502.      */
  503.     protected boolean blockUntilTimestamps(Duration maxWait) {
  504.         if (timestamps == null) {
  505.             return true;
  506.         }
  507.         try {
  508.             ProposedTimestamp.blockUntil(timestamps, maxWait);
  509.             return true;
  510.         } catch (TimeoutException | InterruptedException e) {
  511.             String msg = JGitText.get().timeIsUncertain;
  512.             for (ReceiveCommand c : commands) {
  513.                 if (c.getResult() == NOT_ATTEMPTED) {
  514.                     c.setResult(REJECTED_OTHER_REASON, msg);
  515.                 }
  516.             }
  517.             return false;
  518.         }
  519.     }

  520.     /**
  521.      * Execute this batch update without option strings.
  522.      *
  523.      * @param walk
  524.      *            a RevWalk to parse tags in case the storage system wants to
  525.      *            store them pre-peeled, a common performance optimization.
  526.      * @param monitor
  527.      *            progress monitor to receive update status on.
  528.      * @throws java.io.IOException
  529.      *             the database is unable to accept the update. Individual
  530.      *             command status must be tested to determine if there is a
  531.      *             partial failure, or a total failure.
  532.      */
  533.     public void execute(RevWalk walk, ProgressMonitor monitor)
  534.             throws IOException {
  535.         execute(walk, monitor, null);
  536.     }

  537.     /**
  538.      * Get all path prefixes of a ref name.
  539.      *
  540.      * @param name
  541.      *            ref name.
  542.      * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
  543.      *         {@code refs} and {@code refs/heads}.
  544.      * @since 4.9
  545.      */
  546.     protected static Collection<String> getPrefixes(String name) {
  547.         Collection<String> ret = new HashSet<>();
  548.         addPrefixesTo(name, ret);
  549.         return ret;
  550.     }

  551.     /**
  552.      * Add prefixes of a ref name to an existing collection.
  553.      *
  554.      * @param name
  555.      *            ref name.
  556.      * @param out
  557.      *            path prefixes of the ref name. For {@code refs/heads/foo},
  558.      *            returns {@code refs} and {@code refs/heads}.
  559.      * @since 4.9
  560.      */
  561.     protected static void addPrefixesTo(String name, Collection<String> out) {
  562.         int p1 = name.indexOf('/');
  563.         while (p1 > 0) {
  564.             out.add(name.substring(0, p1));
  565.             p1 = name.indexOf('/', p1 + 1);
  566.         }
  567.     }

  568.     /**
  569.      * Create a new RefUpdate copying the batch settings.
  570.      *
  571.      * @param cmd
  572.      *            specific command the update should be created to copy.
  573.      * @return a single reference update command.
  574.      * @throws java.io.IOException
  575.      *             the reference database cannot make a new update object for
  576.      *             the given reference.
  577.      */
  578.     protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
  579.         RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
  580.         if (isRefLogDisabled(cmd)) {
  581.             ru.disableRefLog();
  582.         } else {
  583.             ru.setRefLogIdent(refLogIdent);
  584.             ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
  585.             ru.setForceRefLog(isForceRefLog(cmd));
  586.         }
  587.         ru.setPushCertificate(pushCert);
  588.         switch (cmd.getType()) {
  589.         case DELETE:
  590.             if (!ObjectId.zeroId().equals(cmd.getOldId()))
  591.                 ru.setExpectedOldObjectId(cmd.getOldId());
  592.             ru.setForceUpdate(true);
  593.             return ru;

  594.         case CREATE:
  595.         case UPDATE:
  596.         case UPDATE_NONFASTFORWARD:
  597.         default:
  598.             ru.setForceUpdate(isAllowNonFastForwards());
  599.             ru.setExpectedOldObjectId(cmd.getOldId());
  600.             ru.setNewObjectId(cmd.getNewId());
  601.             return ru;
  602.         }
  603.     }

  604.     /**
  605.      * Check whether reflog is disabled for a command.
  606.      *
  607.      * @param cmd
  608.      *            specific command.
  609.      * @return whether the reflog is disabled, taking into account the state from
  610.      *         this instance as well as overrides in the given command.
  611.      * @since 4.9
  612.      */
  613.     protected boolean isRefLogDisabled(ReceiveCommand cmd) {
  614.         return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
  615.     }

  616.     /**
  617.      * Get reflog message for a command.
  618.      *
  619.      * @param cmd
  620.      *            specific command.
  621.      * @return reflog message, taking into account the state from this instance as
  622.      *         well as overrides in the given command.
  623.      * @since 4.9
  624.      */
  625.     protected String getRefLogMessage(ReceiveCommand cmd) {
  626.         return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
  627.     }

  628.     /**
  629.      * Check whether the reflog message for a command should include the result.
  630.      *
  631.      * @param cmd
  632.      *            specific command.
  633.      * @return whether the reflog message should show the result, taking into
  634.      *         account the state from this instance as well as overrides in the
  635.      *         given command.
  636.      * @since 4.9
  637.      */
  638.     protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
  639.         return cmd.hasCustomRefLog()
  640.                 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
  641.     }

  642.     /**
  643.      * Check whether the reflog for a command should be written regardless of repo
  644.      * defaults.
  645.      *
  646.      * @param cmd
  647.      *            specific command.
  648.      * @return whether force writing is enabled.
  649.      * @since 4.9
  650.      */
  651.     protected boolean isForceRefLog(ReceiveCommand cmd) {
  652.         Boolean isForceRefLog = cmd.isForceRefLog();
  653.         return isForceRefLog != null ? isForceRefLog.booleanValue()
  654.                 : isForceRefLog();
  655.     }

  656.     /** {@inheritDoc} */
  657.     @Override
  658.     public String toString() {
  659.         StringBuilder r = new StringBuilder();
  660.         r.append(getClass().getSimpleName()).append('[');
  661.         if (commands.isEmpty())
  662.             return r.append(']').toString();

  663.         r.append('\n');
  664.         for (ReceiveCommand cmd : commands) {
  665.             r.append("  "); //$NON-NLS-1$
  666.             r.append(cmd);
  667.             r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
  668.             if (cmd.getMessage() != null) {
  669.                 r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
  670.             }
  671.             r.append(")\n"); //$NON-NLS-1$
  672.         }
  673.         return r.append(']').toString();
  674.     }
  675. }