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 static java.util.stream.Collectors.toCollection;

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

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

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

  52.     private final RefDatabase refdb;

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

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

  57.     /** Identity to record action as within the reflog. */
  58.     private PersonIdent refLogIdent;

  59.     /** Message the caller wants included in the reflog. */
  60.     private String refLogMessage;

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

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

  68.     /** Push certificate associated with this update. */
  69.     private PushCertificate pushCert;

  70.     /** Whether updates should be atomic. */
  71.     private boolean atomic;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  415.         if (options != null) {
  416.             setPushOptions(options);
  417.         }

  418.         monitor.beginTask(JGitText.get().updatingReferences, commands.size());
  419.         List<ReceiveCommand> commands2 = new ArrayList<>(
  420.                 commands.size());
  421.         // First delete refs. This may free the name space for some of the
  422.         // updates.
  423.         for (ReceiveCommand cmd : commands) {
  424.             try {
  425.                 if (cmd.getResult() == NOT_ATTEMPTED) {
  426.                     if (isMissing(walk, cmd.getOldId())
  427.                             || isMissing(walk, cmd.getNewId())) {
  428.                         cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  429.                         continue;
  430.                     }
  431.                     cmd.updateType(walk);
  432.                     switch (cmd.getType()) {
  433.                     case CREATE:
  434.                         commands2.add(cmd);
  435.                         break;
  436.                     case UPDATE:
  437.                     case UPDATE_NONFASTFORWARD:
  438.                         commands2.add(cmd);
  439.                         break;
  440.                     case DELETE:
  441.                         RefUpdate rud = newUpdate(cmd);
  442.                         monitor.update(1);
  443.                         cmd.setResult(rud.delete(walk));
  444.                     }
  445.                 }
  446.             } catch (IOException err) {
  447.                 cmd.setResult(
  448.                         REJECTED_OTHER_REASON,
  449.                         MessageFormat.format(JGitText.get().lockError,
  450.                                 err.getMessage()));
  451.             }
  452.         }
  453.         if (!commands2.isEmpty()) {
  454.             // What part of the name space is already taken
  455.             Collection<String> takenNames = refdb.getRefs().stream()
  456.                     .map(Ref::getName)
  457.                     .collect(toCollection(HashSet::new));
  458.             Collection<String> takenPrefixes = getTakenPrefixes(takenNames);

  459.             // Now to the update that may require more room in the name space
  460.             for (ReceiveCommand cmd : commands2) {
  461.                 try {
  462.                     if (cmd.getResult() == NOT_ATTEMPTED) {
  463.                         cmd.updateType(walk);
  464.                         RefUpdate ru = newUpdate(cmd);
  465.                         SWITCH: switch (cmd.getType()) {
  466.                         case DELETE:
  467.                             // Performed in the first phase
  468.                             break;
  469.                         case UPDATE:
  470.                         case UPDATE_NONFASTFORWARD:
  471.                             RefUpdate ruu = newUpdate(cmd);
  472.                             cmd.setResult(ruu.update(walk));
  473.                             break;
  474.                         case CREATE:
  475.                             for (String prefix : getPrefixes(cmd.getRefName())) {
  476.                                 if (takenNames.contains(prefix)) {
  477.                                     cmd.setResult(Result.LOCK_FAILURE);
  478.                                     break SWITCH;
  479.                                 }
  480.                             }
  481.                             if (takenPrefixes.contains(cmd.getRefName())) {
  482.                                 cmd.setResult(Result.LOCK_FAILURE);
  483.                                 break SWITCH;
  484.                             }
  485.                             ru.setCheckConflicting(false);
  486.                             takenPrefixes.addAll(getPrefixes(cmd.getRefName()));
  487.                             takenNames.add(cmd.getRefName());
  488.                             cmd.setResult(ru.update(walk));
  489.                         }
  490.                     }
  491.                 } catch (IOException err) {
  492.                     cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
  493.                             JGitText.get().lockError, err.getMessage()));
  494.                 } finally {
  495.                     monitor.update(1);
  496.                 }
  497.             }
  498.         }
  499.         monitor.endTask();
  500.     }

  501.     private static boolean isMissing(RevWalk walk, ObjectId id)
  502.             throws IOException {
  503.         if (id.equals(ObjectId.zeroId())) {
  504.             return false; // Explicit add or delete is not missing.
  505.         }
  506.         try {
  507.             walk.parseAny(id);
  508.             return false;
  509.         } catch (MissingObjectException e) {
  510.             return true;
  511.         }
  512.     }

  513.     /**
  514.      * Wait for timestamps to be in the past, aborting commands on timeout.
  515.      *
  516.      * @param maxWait
  517.      *            maximum amount of time to wait for timestamps to resolve.
  518.      * @return true if timestamps were successfully waited for; false if
  519.      *         commands were aborted.
  520.      * @since 4.6
  521.      */
  522.     protected boolean blockUntilTimestamps(Duration maxWait) {
  523.         if (timestamps == null) {
  524.             return true;
  525.         }
  526.         try {
  527.             ProposedTimestamp.blockUntil(timestamps, maxWait);
  528.             return true;
  529.         } catch (TimeoutException | InterruptedException e) {
  530.             String msg = JGitText.get().timeIsUncertain;
  531.             for (ReceiveCommand c : commands) {
  532.                 if (c.getResult() == NOT_ATTEMPTED) {
  533.                     c.setResult(REJECTED_OTHER_REASON, msg);
  534.                 }
  535.             }
  536.             return false;
  537.         }
  538.     }

  539.     /**
  540.      * Execute this batch update without option strings.
  541.      *
  542.      * @param walk
  543.      *            a RevWalk to parse tags in case the storage system wants to
  544.      *            store them pre-peeled, a common performance optimization.
  545.      * @param monitor
  546.      *            progress monitor to receive update status on.
  547.      * @throws java.io.IOException
  548.      *             the database is unable to accept the update. Individual
  549.      *             command status must be tested to determine if there is a
  550.      *             partial failure, or a total failure.
  551.      */
  552.     public void execute(RevWalk walk, ProgressMonitor monitor)
  553.             throws IOException {
  554.         execute(walk, monitor, null);
  555.     }

  556.     private static Collection<String> getTakenPrefixes(Collection<String> names) {
  557.         Collection<String> ref = new HashSet<>();
  558.         for (String name : names) {
  559.             addPrefixesTo(name, ref);
  560.         }
  561.         return ref;
  562.     }

  563.     /**
  564.      * Get all path prefixes of a ref name.
  565.      *
  566.      * @param name
  567.      *            ref name.
  568.      * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
  569.      *         {@code refs} and {@code refs/heads}.
  570.      * @since 4.9
  571.      */
  572.     protected static Collection<String> getPrefixes(String name) {
  573.         Collection<String> ret = new HashSet<>();
  574.         addPrefixesTo(name, ret);
  575.         return ret;
  576.     }

  577.     /**
  578.      * Add prefixes of a ref name to an existing collection.
  579.      *
  580.      * @param name
  581.      *            ref name.
  582.      * @param out
  583.      *            path prefixes of the ref name. For {@code refs/heads/foo},
  584.      *            returns {@code refs} and {@code refs/heads}.
  585.      * @since 4.9
  586.      */
  587.     protected static void addPrefixesTo(String name, Collection<String> out) {
  588.         int p1 = name.indexOf('/');
  589.         while (p1 > 0) {
  590.             out.add(name.substring(0, p1));
  591.             p1 = name.indexOf('/', p1 + 1);
  592.         }
  593.     }

  594.     /**
  595.      * Create a new RefUpdate copying the batch settings.
  596.      *
  597.      * @param cmd
  598.      *            specific command the update should be created to copy.
  599.      * @return a single reference update command.
  600.      * @throws java.io.IOException
  601.      *             the reference database cannot make a new update object for
  602.      *             the given reference.
  603.      */
  604.     protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
  605.         RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
  606.         if (isRefLogDisabled(cmd)) {
  607.             ru.disableRefLog();
  608.         } else {
  609.             ru.setRefLogIdent(refLogIdent);
  610.             ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
  611.             ru.setForceRefLog(isForceRefLog(cmd));
  612.         }
  613.         ru.setPushCertificate(pushCert);
  614.         switch (cmd.getType()) {
  615.         case DELETE:
  616.             if (!ObjectId.zeroId().equals(cmd.getOldId()))
  617.                 ru.setExpectedOldObjectId(cmd.getOldId());
  618.             ru.setForceUpdate(true);
  619.             return ru;

  620.         case CREATE:
  621.         case UPDATE:
  622.         case UPDATE_NONFASTFORWARD:
  623.         default:
  624.             ru.setForceUpdate(isAllowNonFastForwards());
  625.             ru.setExpectedOldObjectId(cmd.getOldId());
  626.             ru.setNewObjectId(cmd.getNewId());
  627.             return ru;
  628.         }
  629.     }

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

  642.     /**
  643.      * Get reflog message for a command.
  644.      *
  645.      * @param cmd
  646.      *            specific command.
  647.      * @return reflog message, taking into account the state from this instance as
  648.      *         well as overrides in the given command.
  649.      * @since 4.9
  650.      */
  651.     protected String getRefLogMessage(ReceiveCommand cmd) {
  652.         return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
  653.     }

  654.     /**
  655.      * Check whether the reflog message for a command should include the result.
  656.      *
  657.      * @param cmd
  658.      *            specific command.
  659.      * @return whether the reflog message should show the result, taking into
  660.      *         account the state from this instance as well as overrides in the
  661.      *         given command.
  662.      * @since 4.9
  663.      */
  664.     protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
  665.         return cmd.hasCustomRefLog()
  666.                 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
  667.     }

  668.     /**
  669.      * Check whether the reflog for a command should be written regardless of repo
  670.      * defaults.
  671.      *
  672.      * @param cmd
  673.      *            specific command.
  674.      * @return whether force writing is enabled.
  675.      * @since 4.9
  676.      */
  677.     protected boolean isForceRefLog(ReceiveCommand cmd) {
  678.         Boolean isForceRefLog = cmd.isForceRefLog();
  679.         return isForceRefLog != null ? isForceRefLog.booleanValue()
  680.                 : isForceRefLog();
  681.     }

  682.     /** {@inheritDoc} */
  683.     @Override
  684.     public String toString() {
  685.         StringBuilder r = new StringBuilder();
  686.         r.append(getClass().getSimpleName()).append('[');
  687.         if (commands.isEmpty())
  688.             return r.append(']').toString();

  689.         r.append('\n');
  690.         for (ReceiveCommand cmd : commands) {
  691.             r.append("  "); //$NON-NLS-1$
  692.             r.append(cmd);
  693.             r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
  694.             if (cmd.getMessage() != null) {
  695.                 r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
  696.             }
  697.             r.append(")\n"); //$NON-NLS-1$
  698.         }
  699.         return r.append(']').toString();
  700.     }
  701. }