BatchRefUpdate.java

  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. package org.eclipse.jgit.lib;

  45. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  46. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
  47. import static java.util.stream.Collectors.toCollection;

  48. import java.io.IOException;
  49. import java.text.MessageFormat;
  50. import java.time.Duration;
  51. import java.util.ArrayList;
  52. import java.util.Arrays;
  53. import java.util.Collection;
  54. import java.util.Collections;
  55. import java.util.HashSet;
  56. import java.util.List;
  57. import java.util.concurrent.TimeoutException;

  58. import org.eclipse.jgit.annotations.Nullable;
  59. import org.eclipse.jgit.errors.MissingObjectException;
  60. import org.eclipse.jgit.internal.JGitText;
  61. import org.eclipse.jgit.lib.RefUpdate.Result;
  62. import org.eclipse.jgit.revwalk.RevWalk;
  63. import org.eclipse.jgit.transport.PushCertificate;
  64. import org.eclipse.jgit.transport.ReceiveCommand;
  65. import org.eclipse.jgit.util.time.ProposedTimestamp;

  66. /**
  67.  * Batch of reference updates to be applied to a repository.
  68.  * <p>
  69.  * The batch update is primarily useful in the transport code, where a client or
  70.  * server is making changes to more than one reference at a time.
  71.  */
  72. public class BatchRefUpdate {
  73.     /**
  74.      * Maximum delay the calling thread will tolerate while waiting for a
  75.      * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
  76.      * <p>
  77.      * A default of 5 seconds was chosen by guessing. A common assumption is
  78.      * clock skew between machines on the same LAN using an NTP server also on
  79.      * the same LAN should be under 5 seconds. 5 seconds is also not that long
  80.      * for a large `git push` operation to complete.
  81.      *
  82.      * @since 4.9
  83.      */
  84.     protected static final Duration MAX_WAIT = Duration.ofSeconds(5);

  85.     private final RefDatabase refdb;

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

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

  90.     /** Identity to record action as within the reflog. */
  91.     private PersonIdent refLogIdent;

  92.     /** Message the caller wants included in the reflog. */
  93.     private String refLogMessage;

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

  96.     /**
  97.      * Should reflogs be written even if the configured default for this ref is
  98.      * not to write it.
  99.      */
  100.     private boolean forceRefLog;

  101.     /** Push certificate associated with this update. */
  102.     private PushCertificate pushCert;

  103.     /** Whether updates should be atomic. */
  104.     private boolean atomic;

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

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

  109.     /**
  110.      * Initialize a new batch update.
  111.      *
  112.      * @param refdb
  113.      *            the reference database of the repository to be updated.
  114.      */
  115.     protected BatchRefUpdate(RefDatabase refdb) {
  116.         this.refdb = refdb;
  117.         this.commands = new ArrayList<>();
  118.         this.atomic = refdb.performsAtomicTransactions();
  119.     }

  120.     /**
  121.      * Whether the batch update will permit a non-fast-forward update to an
  122.      * existing reference.
  123.      *
  124.      * @return true if the batch update will permit a non-fast-forward update to
  125.      *         an existing reference.
  126.      */
  127.     public boolean isAllowNonFastForwards() {
  128.         return allowNonFastForwards;
  129.     }

  130.     /**
  131.      * Set if this update wants to permit a forced update.
  132.      *
  133.      * @param allow
  134.      *            true if this update batch should ignore merge tests.
  135.      * @return {@code this}.
  136.      */
  137.     public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
  138.         allowNonFastForwards = allow;
  139.         return this;
  140.     }

  141.     /**
  142.      * Get identity of the user making the change in the reflog.
  143.      *
  144.      * @return identity of the user making the change in the reflog.
  145.      */
  146.     public PersonIdent getRefLogIdent() {
  147.         return refLogIdent;
  148.     }

  149.     /**
  150.      * Set the identity of the user appearing in the reflog.
  151.      * <p>
  152.      * The timestamp portion of the identity is ignored. A new identity with the
  153.      * current timestamp will be created automatically when the update occurs
  154.      * and the log record is written.
  155.      *
  156.      * @param pi
  157.      *            identity of the user. If null the identity will be
  158.      *            automatically determined based on the repository
  159.      *            configuration.
  160.      * @return {@code this}.
  161.      */
  162.     public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
  163.         refLogIdent = pi;
  164.         return this;
  165.     }

  166.     /**
  167.      * Get the message to include in the reflog.
  168.      *
  169.      * @return message the caller wants to include in the reflog; null if the
  170.      *         update should not be logged.
  171.      */
  172.     @Nullable
  173.     public String getRefLogMessage() {
  174.         return refLogMessage;
  175.     }

  176.     /**
  177.      * Check whether the reflog message should include the result of the update,
  178.      * such as fast-forward or force-update.
  179.      * <p>
  180.      * Describes the default for commands in this batch that do not override it
  181.      * with
  182.      * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  183.      *
  184.      * @return true if the message should include the result.
  185.      */
  186.     public boolean isRefLogIncludingResult() {
  187.         return refLogIncludeResult;
  188.     }

  189.     /**
  190.      * Set the message to include in the reflog.
  191.      * <p>
  192.      * Repository implementations may limit which reflogs are written by
  193.      * default, based on the project configuration. If a repo is not configured
  194.      * to write logs for this ref by default, setting the message alone may have
  195.      * no effect. To indicate that the repo should write logs for this update in
  196.      * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
  197.      * <p>
  198.      * Describes the default for commands in this batch that do not override it
  199.      * with
  200.      * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  201.      *
  202.      * @param msg
  203.      *            the message to describe this change. If null and appendStatus
  204.      *            is false, the reflog will not be updated.
  205.      * @param appendStatus
  206.      *            true if the status of the ref change (fast-forward or
  207.      *            forced-update) should be appended to the user supplied
  208.      *            message.
  209.      * @return {@code this}.
  210.      */
  211.     public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
  212.         if (msg == null && !appendStatus)
  213.             disableRefLog();
  214.         else if (msg == null && appendStatus) {
  215.             refLogMessage = ""; //$NON-NLS-1$
  216.             refLogIncludeResult = true;
  217.         } else {
  218.             refLogMessage = msg;
  219.             refLogIncludeResult = appendStatus;
  220.         }
  221.         return this;
  222.     }

  223.     /**
  224.      * Don't record this update in the ref's associated reflog.
  225.      * <p>
  226.      * Equivalent to {@code setRefLogMessage(null, false)}.
  227.      *
  228.      * @return {@code this}.
  229.      */
  230.     public BatchRefUpdate disableRefLog() {
  231.         refLogMessage = null;
  232.         refLogIncludeResult = false;
  233.         return this;
  234.     }

  235.     /**
  236.      * Force writing a reflog for the updated ref.
  237.      *
  238.      * @param force whether to force.
  239.      * @return {@code this}
  240.      * @since 4.9
  241.      */
  242.     public BatchRefUpdate setForceRefLog(boolean force) {
  243.         forceRefLog = force;
  244.         return this;
  245.     }

  246.     /**
  247.      * Check whether log has been disabled by {@link #disableRefLog()}.
  248.      *
  249.      * @return true if disabled.
  250.      */
  251.     public boolean isRefLogDisabled() {
  252.         return refLogMessage == null;
  253.     }

  254.     /**
  255.      * Check whether the reflog should be written regardless of repo defaults.
  256.      *
  257.      * @return whether force writing is enabled.
  258.      * @since 4.9
  259.      */
  260.     protected boolean isForceRefLog() {
  261.         return forceRefLog;
  262.     }

  263.     /**
  264.      * Request that all updates in this batch be performed atomically.
  265.      * <p>
  266.      * When atomic updates are used, either all commands apply successfully, or
  267.      * none do. Commands that might have otherwise succeeded are rejected with
  268.      * {@code REJECTED_OTHER_REASON}.
  269.      * <p>
  270.      * This method only works if the underlying ref database supports atomic
  271.      * transactions, i.e.
  272.      * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
  273.      * returns true. Calling this method with true if the underlying ref
  274.      * database does not support atomic transactions will cause all commands to
  275.      * fail with {@code
  276.      * REJECTED_OTHER_REASON}.
  277.      *
  278.      * @param atomic
  279.      *            whether updates should be atomic.
  280.      * @return {@code this}
  281.      * @since 4.4
  282.      */
  283.     public BatchRefUpdate setAtomic(boolean atomic) {
  284.         this.atomic = atomic;
  285.         return this;
  286.     }

  287.     /**
  288.      * Whether updates should be atomic.
  289.      *
  290.      * @return atomic whether updates should be atomic.
  291.      * @since 4.4
  292.      */
  293.     public boolean isAtomic() {
  294.         return atomic;
  295.     }

  296.     /**
  297.      * Set a push certificate associated with this update.
  298.      * <p>
  299.      * This usually includes commands to update the refs in this batch, but is not
  300.      * required to.
  301.      *
  302.      * @param cert
  303.      *            push certificate, may be null.
  304.      * @since 4.1
  305.      */
  306.     public void setPushCertificate(PushCertificate cert) {
  307.         pushCert = cert;
  308.     }

  309.     /**
  310.      * Set the push certificate associated with this update.
  311.      * <p>
  312.      * This usually includes commands to update the refs in this batch, but is not
  313.      * required to.
  314.      *
  315.      * @return push certificate, may be null.
  316.      * @since 4.1
  317.      */
  318.     protected PushCertificate getPushCertificate() {
  319.         return pushCert;
  320.     }

  321.     /**
  322.      * Get commands this update will process.
  323.      *
  324.      * @return commands this update will process.
  325.      */
  326.     public List<ReceiveCommand> getCommands() {
  327.         return Collections.unmodifiableList(commands);
  328.     }

  329.     /**
  330.      * Add a single command to this batch update.
  331.      *
  332.      * @param cmd
  333.      *            the command to add, must not be null.
  334.      * @return {@code this}.
  335.      */
  336.     public BatchRefUpdate addCommand(ReceiveCommand cmd) {
  337.         commands.add(cmd);
  338.         return this;
  339.     }

  340.     /**
  341.      * Add commands to this batch update.
  342.      *
  343.      * @param cmd
  344.      *            the commands to add, must not be null.
  345.      * @return {@code this}.
  346.      */
  347.     public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
  348.         return addCommand(Arrays.asList(cmd));
  349.     }

  350.     /**
  351.      * Add commands to this batch update.
  352.      *
  353.      * @param cmd
  354.      *            the commands to add, must not be null.
  355.      * @return {@code this}.
  356.      */
  357.     public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
  358.         commands.addAll(cmd);
  359.         return this;
  360.     }

  361.     /**
  362.      * Gets the list of option strings associated with this update.
  363.      *
  364.      * @return push options that were passed to {@link #execute}; prior to calling
  365.      *         {@link #execute}, always returns null.
  366.      * @since 4.5
  367.      */
  368.     @Nullable
  369.     public List<String> getPushOptions() {
  370.         return pushOptions;
  371.     }

  372.     /**
  373.      * Set push options associated with this update.
  374.      * <p>
  375.      * Implementations must call this at the top of {@link #execute(RevWalk,
  376.      * ProgressMonitor, List)}.
  377.      *
  378.      * @param options options passed to {@code execute}.
  379.      * @since 4.9
  380.      */
  381.     protected void setPushOptions(List<String> options) {
  382.         pushOptions = options;
  383.     }

  384.     /**
  385.      * Get list of timestamps the batch must wait for.
  386.      *
  387.      * @return list of timestamps the batch must wait for.
  388.      * @since 4.6
  389.      */
  390.     public List<ProposedTimestamp> getProposedTimestamps() {
  391.         if (timestamps != null) {
  392.             return Collections.unmodifiableList(timestamps);
  393.         }
  394.         return Collections.emptyList();
  395.     }

  396.     /**
  397.      * Request the batch to wait for the affected timestamps to resolve.
  398.      *
  399.      * @param ts
  400.      *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
  401.      * @return {@code this}.
  402.      * @since 4.6
  403.      */
  404.     public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
  405.         if (timestamps == null) {
  406.             timestamps = new ArrayList<>(4);
  407.         }
  408.         timestamps.add(ts);
  409.         return this;
  410.     }

  411.     /**
  412.      * Execute this batch update.
  413.      * <p>
  414.      * The default implementation of this method performs a sequential reference
  415.      * update over each reference.
  416.      * <p>
  417.      * Implementations must respect the atomicity requirements of the underlying
  418.      * database as described in {@link #setAtomic(boolean)} and
  419.      * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
  420.      *
  421.      * @param walk
  422.      *            a RevWalk to parse tags in case the storage system wants to
  423.      *            store them pre-peeled, a common performance optimization.
  424.      * @param monitor
  425.      *            progress monitor to receive update status on.
  426.      * @param options
  427.      *            a list of option strings; set null to execute without
  428.      * @throws java.io.IOException
  429.      *             the database is unable to accept the update. Individual
  430.      *             command status must be tested to determine if there is a
  431.      *             partial failure, or a total failure.
  432.      * @since 4.5
  433.      */
  434.     public void execute(RevWalk walk, ProgressMonitor monitor,
  435.             List<String> options) throws IOException {

  436.         if (atomic && !refdb.performsAtomicTransactions()) {
  437.             for (ReceiveCommand c : commands) {
  438.                 if (c.getResult() == NOT_ATTEMPTED) {
  439.                     c.setResult(REJECTED_OTHER_REASON,
  440.                             JGitText.get().atomicRefUpdatesNotSupported);
  441.                 }
  442.             }
  443.             return;
  444.         }
  445.         if (!blockUntilTimestamps(MAX_WAIT)) {
  446.             return;
  447.         }

  448.         if (options != null) {
  449.             setPushOptions(options);
  450.         }

  451.         monitor.beginTask(JGitText.get().updatingReferences, commands.size());
  452.         List<ReceiveCommand> commands2 = new ArrayList<>(
  453.                 commands.size());
  454.         // First delete refs. This may free the name space for some of the
  455.         // updates.
  456.         for (ReceiveCommand cmd : commands) {
  457.             try {
  458.                 if (cmd.getResult() == NOT_ATTEMPTED) {
  459.                     if (isMissing(walk, cmd.getOldId())
  460.                             || isMissing(walk, cmd.getNewId())) {
  461.                         cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  462.                         continue;
  463.                     }
  464.                     cmd.updateType(walk);
  465.                     switch (cmd.getType()) {
  466.                     case CREATE:
  467.                         commands2.add(cmd);
  468.                         break;
  469.                     case UPDATE:
  470.                     case UPDATE_NONFASTFORWARD:
  471.                         commands2.add(cmd);
  472.                         break;
  473.                     case DELETE:
  474.                         RefUpdate rud = newUpdate(cmd);
  475.                         monitor.update(1);
  476.                         cmd.setResult(rud.delete(walk));
  477.                     }
  478.                 }
  479.             } catch (IOException err) {
  480.                 cmd.setResult(
  481.                         REJECTED_OTHER_REASON,
  482.                         MessageFormat.format(JGitText.get().lockError,
  483.                                 err.getMessage()));
  484.             }
  485.         }
  486.         if (!commands2.isEmpty()) {
  487.             // What part of the name space is already taken
  488.             Collection<String> takenNames = refdb.getRefs().stream()
  489.                     .map(Ref::getName)
  490.                     .collect(toCollection(HashSet::new));
  491.             Collection<String> takenPrefixes = getTakenPrefixes(takenNames);

  492.             // Now to the update that may require more room in the name space
  493.             for (ReceiveCommand cmd : commands2) {
  494.                 try {
  495.                     if (cmd.getResult() == NOT_ATTEMPTED) {
  496.                         cmd.updateType(walk);
  497.                         RefUpdate ru = newUpdate(cmd);
  498.                         SWITCH: switch (cmd.getType()) {
  499.                         case DELETE:
  500.                             // Performed in the first phase
  501.                             break;
  502.                         case UPDATE:
  503.                         case UPDATE_NONFASTFORWARD:
  504.                             RefUpdate ruu = newUpdate(cmd);
  505.                             cmd.setResult(ruu.update(walk));
  506.                             break;
  507.                         case CREATE:
  508.                             for (String prefix : getPrefixes(cmd.getRefName())) {
  509.                                 if (takenNames.contains(prefix)) {
  510.                                     cmd.setResult(Result.LOCK_FAILURE);
  511.                                     break SWITCH;
  512.                                 }
  513.                             }
  514.                             if (takenPrefixes.contains(cmd.getRefName())) {
  515.                                 cmd.setResult(Result.LOCK_FAILURE);
  516.                                 break SWITCH;
  517.                             }
  518.                             ru.setCheckConflicting(false);
  519.                             takenPrefixes.addAll(getPrefixes(cmd.getRefName()));
  520.                             takenNames.add(cmd.getRefName());
  521.                             cmd.setResult(ru.update(walk));
  522.                         }
  523.                     }
  524.                 } catch (IOException err) {
  525.                     cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
  526.                             JGitText.get().lockError, err.getMessage()));
  527.                 } finally {
  528.                     monitor.update(1);
  529.                 }
  530.             }
  531.         }
  532.         monitor.endTask();
  533.     }

  534.     private static boolean isMissing(RevWalk walk, ObjectId id)
  535.             throws IOException {
  536.         if (id.equals(ObjectId.zeroId())) {
  537.             return false; // Explicit add or delete is not missing.
  538.         }
  539.         try {
  540.             walk.parseAny(id);
  541.             return false;
  542.         } catch (MissingObjectException e) {
  543.             return true;
  544.         }
  545.     }

  546.     /**
  547.      * Wait for timestamps to be in the past, aborting commands on timeout.
  548.      *
  549.      * @param maxWait
  550.      *            maximum amount of time to wait for timestamps to resolve.
  551.      * @return true if timestamps were successfully waited for; false if
  552.      *         commands were aborted.
  553.      * @since 4.6
  554.      */
  555.     protected boolean blockUntilTimestamps(Duration maxWait) {
  556.         if (timestamps == null) {
  557.             return true;
  558.         }
  559.         try {
  560.             ProposedTimestamp.blockUntil(timestamps, maxWait);
  561.             return true;
  562.         } catch (TimeoutException | InterruptedException e) {
  563.             String msg = JGitText.get().timeIsUncertain;
  564.             for (ReceiveCommand c : commands) {
  565.                 if (c.getResult() == NOT_ATTEMPTED) {
  566.                     c.setResult(REJECTED_OTHER_REASON, msg);
  567.                 }
  568.             }
  569.             return false;
  570.         }
  571.     }

  572.     /**
  573.      * Execute this batch update without option strings.
  574.      *
  575.      * @param walk
  576.      *            a RevWalk to parse tags in case the storage system wants to
  577.      *            store them pre-peeled, a common performance optimization.
  578.      * @param monitor
  579.      *            progress monitor to receive update status on.
  580.      * @throws java.io.IOException
  581.      *             the database is unable to accept the update. Individual
  582.      *             command status must be tested to determine if there is a
  583.      *             partial failure, or a total failure.
  584.      */
  585.     public void execute(RevWalk walk, ProgressMonitor monitor)
  586.             throws IOException {
  587.         execute(walk, monitor, null);
  588.     }

  589.     private static Collection<String> getTakenPrefixes(Collection<String> names) {
  590.         Collection<String> ref = new HashSet<>();
  591.         for (String name : names) {
  592.             addPrefixesTo(name, ref);
  593.         }
  594.         return ref;
  595.     }

  596.     /**
  597.      * Get all path prefixes of a ref name.
  598.      *
  599.      * @param name
  600.      *            ref name.
  601.      * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
  602.      *         {@code refs} and {@code refs/heads}.
  603.      * @since 4.9
  604.      */
  605.     protected static Collection<String> getPrefixes(String name) {
  606.         Collection<String> ret = new HashSet<>();
  607.         addPrefixesTo(name, ret);
  608.         return ret;
  609.     }

  610.     /**
  611.      * Add prefixes of a ref name to an existing collection.
  612.      *
  613.      * @param name
  614.      *            ref name.
  615.      * @param out
  616.      *            path prefixes of the ref name. For {@code refs/heads/foo},
  617.      *            returns {@code refs} and {@code refs/heads}.
  618.      * @since 4.9
  619.      */
  620.     protected static void addPrefixesTo(String name, Collection<String> out) {
  621.         int p1 = name.indexOf('/');
  622.         while (p1 > 0) {
  623.             out.add(name.substring(0, p1));
  624.             p1 = name.indexOf('/', p1 + 1);
  625.         }
  626.     }

  627.     /**
  628.      * Create a new RefUpdate copying the batch settings.
  629.      *
  630.      * @param cmd
  631.      *            specific command the update should be created to copy.
  632.      * @return a single reference update command.
  633.      * @throws java.io.IOException
  634.      *             the reference database cannot make a new update object for
  635.      *             the given reference.
  636.      */
  637.     protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
  638.         RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
  639.         if (isRefLogDisabled(cmd)) {
  640.             ru.disableRefLog();
  641.         } else {
  642.             ru.setRefLogIdent(refLogIdent);
  643.             ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
  644.             ru.setForceRefLog(isForceRefLog(cmd));
  645.         }
  646.         ru.setPushCertificate(pushCert);
  647.         switch (cmd.getType()) {
  648.         case DELETE:
  649.             if (!ObjectId.zeroId().equals(cmd.getOldId()))
  650.                 ru.setExpectedOldObjectId(cmd.getOldId());
  651.             ru.setForceUpdate(true);
  652.             return ru;

  653.         case CREATE:
  654.         case UPDATE:
  655.         case UPDATE_NONFASTFORWARD:
  656.         default:
  657.             ru.setForceUpdate(isAllowNonFastForwards());
  658.             ru.setExpectedOldObjectId(cmd.getOldId());
  659.             ru.setNewObjectId(cmd.getNewId());
  660.             return ru;
  661.         }
  662.     }

  663.     /**
  664.      * Check whether reflog is disabled for a command.
  665.      *
  666.      * @param cmd
  667.      *            specific command.
  668.      * @return whether the reflog is disabled, taking into account the state from
  669.      *         this instance as well as overrides in the given command.
  670.      * @since 4.9
  671.      */
  672.     protected boolean isRefLogDisabled(ReceiveCommand cmd) {
  673.         return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
  674.     }

  675.     /**
  676.      * Get reflog message for a command.
  677.      *
  678.      * @param cmd
  679.      *            specific command.
  680.      * @return reflog message, taking into account the state from this instance as
  681.      *         well as overrides in the given command.
  682.      * @since 4.9
  683.      */
  684.     protected String getRefLogMessage(ReceiveCommand cmd) {
  685.         return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
  686.     }

  687.     /**
  688.      * Check whether the reflog message for a command should include the result.
  689.      *
  690.      * @param cmd
  691.      *            specific command.
  692.      * @return whether the reflog message should show the result, taking into
  693.      *         account the state from this instance as well as overrides in the
  694.      *         given command.
  695.      * @since 4.9
  696.      */
  697.     protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
  698.         return cmd.hasCustomRefLog()
  699.                 ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
  700.     }

  701.     /**
  702.      * Check whether the reflog for a command should be written regardless of repo
  703.      * defaults.
  704.      *
  705.      * @param cmd
  706.      *            specific command.
  707.      * @return whether force writing is enabled.
  708.      * @since 4.9
  709.      */
  710.     protected boolean isForceRefLog(ReceiveCommand cmd) {
  711.         Boolean isForceRefLog = cmd.isForceRefLog();
  712.         return isForceRefLog != null ? isForceRefLog.booleanValue()
  713.                 : isForceRefLog();
  714.     }

  715.     /** {@inheritDoc} */
  716.     @Override
  717.     public String toString() {
  718.         StringBuilder r = new StringBuilder();
  719.         r.append(getClass().getSimpleName()).append('[');
  720.         if (commands.isEmpty())
  721.             return r.append(']').toString();

  722.         r.append('\n');
  723.         for (ReceiveCommand cmd : commands) {
  724.             r.append("  "); //$NON-NLS-1$
  725.             r.append(cmd);
  726.             r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
  727.             if (cmd.getMessage() != null) {
  728.                 r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
  729.             }
  730.             r.append(")\n"); //$NON-NLS-1$
  731.         }
  732.         return r.append(']').toString();
  733.     }
  734. }