PushCommand.java

  1. /*
  2.  * Copyright (C) 2010, 2022 Chris Aniszczyk <caniszczyk@gmail.com> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.api;

  11. import java.io.IOException;
  12. import java.io.OutputStream;
  13. import java.io.PrintStream;
  14. import java.net.URISyntaxException;
  15. import java.text.MessageFormat;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;

  23. import org.eclipse.jgit.api.errors.DetachedHeadException;
  24. import org.eclipse.jgit.api.errors.GitAPIException;
  25. import org.eclipse.jgit.api.errors.InvalidRefNameException;
  26. import org.eclipse.jgit.api.errors.InvalidRemoteException;
  27. import org.eclipse.jgit.api.errors.JGitInternalException;
  28. import org.eclipse.jgit.errors.NotSupportedException;
  29. import org.eclipse.jgit.errors.TooLargeObjectInPackException;
  30. import org.eclipse.jgit.errors.TooLargePackException;
  31. import org.eclipse.jgit.errors.TransportException;
  32. import org.eclipse.jgit.internal.JGitText;
  33. import org.eclipse.jgit.lib.BranchConfig;
  34. import org.eclipse.jgit.lib.Config;
  35. import org.eclipse.jgit.lib.ConfigConstants;
  36. import org.eclipse.jgit.lib.Constants;
  37. import org.eclipse.jgit.lib.NullProgressMonitor;
  38. import org.eclipse.jgit.lib.ProgressMonitor;
  39. import org.eclipse.jgit.lib.Ref;
  40. import org.eclipse.jgit.lib.Repository;
  41. import org.eclipse.jgit.transport.PushConfig;
  42. import org.eclipse.jgit.transport.PushConfig.PushDefault;
  43. import org.eclipse.jgit.transport.PushResult;
  44. import org.eclipse.jgit.transport.RefLeaseSpec;
  45. import org.eclipse.jgit.transport.RefSpec;
  46. import org.eclipse.jgit.transport.RemoteConfig;
  47. import org.eclipse.jgit.transport.RemoteRefUpdate;
  48. import org.eclipse.jgit.transport.Transport;

  49. /**
  50.  * A class used to execute a {@code Push} command. It has setters for all
  51.  * supported options and arguments of this command and a {@link #call()} method
  52.  * to finally execute the command.
  53.  *
  54.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-push.html"
  55.  *      >Git documentation about Push</a>
  56.  */
  57. public class PushCommand extends
  58.         TransportCommand<PushCommand, Iterable<PushResult>> {

  59.     private String remote;

  60.     private final List<RefSpec> refSpecs;

  61.     private final Map<String, RefLeaseSpec> refLeaseSpecs;

  62.     private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

  63.     private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;

  64.     private boolean dryRun;
  65.     private boolean atomic;
  66.     private boolean force;
  67.     private boolean thin = Transport.DEFAULT_PUSH_THIN;
  68.     private boolean useBitmaps = Transport.DEFAULT_PUSH_USE_BITMAPS;

  69.     private PrintStream hookOutRedirect;

  70.     private PrintStream hookErrRedirect;

  71.     private OutputStream out;

  72.     private List<String> pushOptions;

  73.     // Legacy behavior as default. Use setPushDefault(null) to determine the
  74.     // value from the git config.
  75.     private PushDefault pushDefault = PushDefault.CURRENT;

  76.     /**
  77.      * <p>
  78.      * Constructor for PushCommand.
  79.      * </p>
  80.      *
  81.      * @param repo
  82.      *            the {@link org.eclipse.jgit.lib.Repository}
  83.      */
  84.     protected PushCommand(Repository repo) {
  85.         super(repo);
  86.         refSpecs = new ArrayList<>(3);
  87.         refLeaseSpecs = new HashMap<>();
  88.     }

  89.     /**
  90.      * {@inheritDoc}
  91.      * <p>
  92.      * Execute the {@code push} command with all the options and parameters
  93.      * collected by the setter methods of this class. Each instance of this
  94.      * class should only be used for one invocation of the command (means: one
  95.      * call to {@link #call()})
  96.      */
  97.     @Override
  98.     public Iterable<PushResult> call() throws GitAPIException,
  99.             InvalidRemoteException,
  100.             org.eclipse.jgit.api.errors.TransportException {
  101.         checkCallable();
  102.         setCallable(false);

  103.         ArrayList<PushResult> pushResults = new ArrayList<>(3);

  104.         try {
  105.             Config config = repo.getConfig();
  106.             remote = determineRemote(config, remote);
  107.             if (refSpecs.isEmpty()) {
  108.                 RemoteConfig rc = new RemoteConfig(config,
  109.                         getRemote());
  110.                 refSpecs.addAll(rc.getPushRefSpecs());
  111.                 if (refSpecs.isEmpty()) {
  112.                     determineDefaultRefSpecs(config);
  113.                 }
  114.             }

  115.             if (force) {
  116.                 for (int i = 0; i < refSpecs.size(); i++)
  117.                     refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
  118.             }

  119.             List<Transport> transports = Transport.openAll(repo, remote,
  120.                     Transport.Operation.PUSH);
  121.             for (@SuppressWarnings("resource") // Explicitly closed in finally
  122.                     final Transport transport : transports) {
  123.                 transport.setPushThin(thin);
  124.                 transport.setPushAtomic(atomic);
  125.                 if (receivePack != null)
  126.                     transport.setOptionReceivePack(receivePack);
  127.                 transport.setDryRun(dryRun);
  128.                 transport.setPushOptions(pushOptions);
  129.                 transport.setPushUseBitmaps(useBitmaps);
  130.                 transport.setHookOutputStream(hookOutRedirect);
  131.                 transport.setHookErrorStream(hookErrRedirect);
  132.                 configure(transport);

  133.                 final Collection<RemoteRefUpdate> toPush = transport
  134.                         .findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs);

  135.                 try {
  136.                     PushResult result = transport.push(monitor, toPush, out);
  137.                     pushResults.add(result);

  138.                 } catch (TooLargePackException e) {
  139.                     throw new org.eclipse.jgit.api.errors.TooLargePackException(
  140.                             e.getMessage(), e);
  141.                 } catch (TooLargeObjectInPackException e) {
  142.                     throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException(
  143.                             e.getMessage(), e);
  144.                 } catch (TransportException e) {
  145.                     throw new org.eclipse.jgit.api.errors.TransportException(
  146.                             e.getMessage(), e);
  147.                 } finally {
  148.                     transport.close();
  149.                 }
  150.             }

  151.         } catch (URISyntaxException e) {
  152.             throw new InvalidRemoteException(
  153.                     MessageFormat.format(JGitText.get().invalidRemote, remote),
  154.                     e);
  155.         } catch (TransportException e) {
  156.             throw new org.eclipse.jgit.api.errors.TransportException(
  157.                     e.getMessage(), e);
  158.         } catch (NotSupportedException e) {
  159.             throw new JGitInternalException(
  160.                     JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
  161.                     e);
  162.         } catch (IOException e) {
  163.             throw new JGitInternalException(
  164.                     JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
  165.                     e);
  166.         }

  167.         return pushResults;
  168.     }

  169.     private String determineRemote(Config config, String remoteName)
  170.             throws IOException {
  171.         if (remoteName != null) {
  172.             return remoteName;
  173.         }
  174.         Ref head = repo.exactRef(Constants.HEAD);
  175.         String effectiveRemote = null;
  176.         BranchConfig branchCfg = null;
  177.         if (head != null && head.isSymbolic()) {
  178.             String currentBranch = head.getLeaf().getName();
  179.             branchCfg = new BranchConfig(config,
  180.                     Repository.shortenRefName(currentBranch));
  181.             effectiveRemote = branchCfg.getPushRemote();
  182.         }
  183.         if (effectiveRemote == null) {
  184.             effectiveRemote = config.getString(
  185.                     ConfigConstants.CONFIG_REMOTE_SECTION, null,
  186.                     ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
  187.             if (effectiveRemote == null && branchCfg != null) {
  188.                 effectiveRemote = branchCfg.getRemote();
  189.             }
  190.         }
  191.         if (effectiveRemote == null) {
  192.             effectiveRemote = Constants.DEFAULT_REMOTE_NAME;
  193.         }
  194.         return effectiveRemote;
  195.     }

  196.     private String getCurrentBranch()
  197.             throws IOException, DetachedHeadException {
  198.         Ref head = repo.exactRef(Constants.HEAD);
  199.         if (head != null && head.isSymbolic()) {
  200.             return head.getLeaf().getName();
  201.         }
  202.         throw new DetachedHeadException();
  203.     }

  204.     private void determineDefaultRefSpecs(Config config)
  205.             throws IOException, GitAPIException {
  206.         if (pushDefault == null) {
  207.             pushDefault = config.get(PushConfig::new).getPushDefault();
  208.         }
  209.         switch (pushDefault) {
  210.         case CURRENT:
  211.             refSpecs.add(new RefSpec(getCurrentBranch()));
  212.             break;
  213.         case MATCHING:
  214.             refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
  215.             break;
  216.         case NOTHING:
  217.             throw new InvalidRefNameException(
  218.                     JGitText.get().pushDefaultNothing);
  219.         case SIMPLE:
  220.         case UPSTREAM:
  221.             String currentBranch = getCurrentBranch();
  222.             BranchConfig branchCfg = new BranchConfig(config,
  223.                     Repository.shortenRefName(currentBranch));
  224.             String fetchRemote = branchCfg.getRemote();
  225.             if (fetchRemote == null) {
  226.                 fetchRemote = Constants.DEFAULT_REMOTE_NAME;
  227.             }
  228.             boolean isTriangular = !fetchRemote.equals(remote);
  229.             if (isTriangular) {
  230.                 if (PushDefault.UPSTREAM.equals(pushDefault)) {
  231.                     throw new InvalidRefNameException(MessageFormat.format(
  232.                             JGitText.get().pushDefaultTriangularUpstream,
  233.                             remote, fetchRemote));
  234.                 }
  235.                 // Strange, but consistent with C git: "simple" doesn't even
  236.                 // check whether there is a configured upstream, and if so, that
  237.                 // it is equal to the local branch name. It just becomes
  238.                 // "current".
  239.                 refSpecs.add(new RefSpec(currentBranch));
  240.             } else {
  241.                 String trackedBranch = branchCfg.getMerge();
  242.                 if (branchCfg.isRemoteLocal() || trackedBranch == null
  243.                         || !trackedBranch.startsWith(Constants.R_HEADS)) {
  244.                     throw new InvalidRefNameException(MessageFormat.format(
  245.                             JGitText.get().pushDefaultNoUpstream,
  246.                             currentBranch));
  247.                 }
  248.                 if (PushDefault.SIMPLE.equals(pushDefault)
  249.                         && !trackedBranch.equals(currentBranch)) {
  250.                     throw new InvalidRefNameException(MessageFormat.format(
  251.                             JGitText.get().pushDefaultSimple, currentBranch,
  252.                             trackedBranch));
  253.                 }
  254.                 refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch));
  255.             }
  256.             break;
  257.         default:
  258.             throw new InvalidRefNameException(MessageFormat
  259.                     .format(JGitText.get().pushDefaultUnknown, pushDefault));
  260.         }
  261.     }

  262.     /**
  263.      * The remote (uri or name) used for the push operation. If no remote is
  264.      * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
  265.      * be used.
  266.      *
  267.      * @see Constants#DEFAULT_REMOTE_NAME
  268.      * @param remote
  269.      *            the remote name
  270.      * @return {@code this}
  271.      */
  272.     public PushCommand setRemote(String remote) {
  273.         checkCallable();
  274.         this.remote = remote;
  275.         return this;
  276.     }

  277.     /**
  278.      * Get remote name
  279.      *
  280.      * @return the remote used for the remote operation
  281.      */
  282.     public String getRemote() {
  283.         return remote;
  284.     }

  285.     /**
  286.      * Sets a {@link PrintStream} a "pre-push" hook may write its stdout to. If
  287.      * not set, {@link System#out} will be used.
  288.      * <p>
  289.      * When pushing to several remote repositories the stream is shared for all
  290.      * pushes.
  291.      * </p>
  292.      *
  293.      * @param redirect
  294.      *            {@link PrintStream} to use; if {@code null},
  295.      *            {@link System#out} will be used
  296.      * @return {@code this}
  297.      * @since 6.4
  298.      */
  299.     public PushCommand setHookOutputStream(PrintStream redirect) {
  300.         checkCallable();
  301.         hookOutRedirect = redirect;
  302.         return this;
  303.     }

  304.     /**
  305.      * Sets a {@link PrintStream} a "pre-push" hook may write its stderr to. If
  306.      * not set, {@link System#err} will be used.
  307.      * <p>
  308.      * When pushing to several remote repositories the stream is shared for all
  309.      * pushes.
  310.      * </p>
  311.      *
  312.      * @param redirect
  313.      *            {@link PrintStream} to use; if {@code null},
  314.      *            {@link System#err} will be used
  315.      * @return {@code this}
  316.      * @since 6.4
  317.      */
  318.     public PushCommand setHookErrorStream(PrintStream redirect) {
  319.         checkCallable();
  320.         hookErrRedirect = redirect;
  321.         return this;
  322.     }

  323.     /**
  324.      * The remote executable providing receive-pack service for pack transports.
  325.      * If no receive-pack is set, the default value of
  326.      * <code>RemoteConfig.DEFAULT_RECEIVE_PACK</code> will be used.
  327.      *
  328.      * @see RemoteConfig#DEFAULT_RECEIVE_PACK
  329.      * @param receivePack
  330.      *            name of the remote executable providing the receive-pack
  331.      *            service
  332.      * @return {@code this}
  333.      */
  334.     public PushCommand setReceivePack(String receivePack) {
  335.         checkCallable();
  336.         this.receivePack = receivePack;
  337.         return this;
  338.     }

  339.     /**
  340.      * Get the name of the remote executable providing the receive-pack service
  341.      *
  342.      * @return the receive-pack used for the remote operation
  343.      */
  344.     public String getReceivePack() {
  345.         return receivePack;
  346.     }

  347.     /**
  348.      * Get timeout used for push operation
  349.      *
  350.      * @return the timeout used for the push operation
  351.      */
  352.     public int getTimeout() {
  353.         return timeout;
  354.     }

  355.     /**
  356.      * Get the progress monitor
  357.      *
  358.      * @return the progress monitor for the push operation
  359.      */
  360.     public ProgressMonitor getProgressMonitor() {
  361.         return monitor;
  362.     }

  363.     /**
  364.      * The progress monitor associated with the push operation. By default, this
  365.      * is set to <code>NullProgressMonitor</code>
  366.      *
  367.      * @see NullProgressMonitor
  368.      * @param monitor
  369.      *            a {@link org.eclipse.jgit.lib.ProgressMonitor}
  370.      * @return {@code this}
  371.      */
  372.     public PushCommand setProgressMonitor(ProgressMonitor monitor) {
  373.         checkCallable();
  374.         if (monitor == null) {
  375.             monitor = NullProgressMonitor.INSTANCE;
  376.         }
  377.         this.monitor = monitor;
  378.         return this;
  379.     }

  380.     /**
  381.      * Get the <code>RefLeaseSpec</code>s.
  382.      *
  383.      * @return the <code>RefLeaseSpec</code>s
  384.      * @since 4.7
  385.      */
  386.     public List<RefLeaseSpec> getRefLeaseSpecs() {
  387.         return new ArrayList<>(refLeaseSpecs.values());
  388.     }

  389.     /**
  390.      * The ref lease specs to be used in the push operation, for a
  391.      * force-with-lease push operation.
  392.      *
  393.      * @param specs
  394.      *            a {@link org.eclipse.jgit.transport.RefLeaseSpec} object.
  395.      * @return {@code this}
  396.      * @since 4.7
  397.      */
  398.     public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) {
  399.         return setRefLeaseSpecs(Arrays.asList(specs));
  400.     }

  401.     /**
  402.      * The ref lease specs to be used in the push operation, for a
  403.      * force-with-lease push operation.
  404.      *
  405.      * @param specs
  406.      *            list of {@code RefLeaseSpec}s
  407.      * @return {@code this}
  408.      * @since 4.7
  409.      */
  410.     public PushCommand setRefLeaseSpecs(List<RefLeaseSpec> specs) {
  411.         checkCallable();
  412.         this.refLeaseSpecs.clear();
  413.         for (RefLeaseSpec spec : specs) {
  414.             refLeaseSpecs.put(spec.getRef(), spec);
  415.         }
  416.         return this;
  417.     }

  418.     /**
  419.      * Get {@code RefSpec}s.
  420.      *
  421.      * @return the ref specs
  422.      */
  423.     public List<RefSpec> getRefSpecs() {
  424.         return refSpecs;
  425.     }

  426.     /**
  427.      * The ref specs to be used in the push operation
  428.      *
  429.      * @param specs a {@link org.eclipse.jgit.transport.RefSpec} object.
  430.      * @return {@code this}
  431.      */
  432.     public PushCommand setRefSpecs(RefSpec... specs) {
  433.         checkCallable();
  434.         this.refSpecs.clear();
  435.         Collections.addAll(refSpecs, specs);
  436.         return this;
  437.     }

  438.     /**
  439.      * The ref specs to be used in the push operation
  440.      *
  441.      * @param specs
  442.      *            list of {@link org.eclipse.jgit.transport.RefSpec}s
  443.      * @return {@code this}
  444.      */
  445.     public PushCommand setRefSpecs(List<RefSpec> specs) {
  446.         checkCallable();
  447.         this.refSpecs.clear();
  448.         this.refSpecs.addAll(specs);
  449.         return this;
  450.     }

  451.     /**
  452.      * Retrieves the {@link PushDefault} currently set.
  453.      *
  454.      * @return the {@link PushDefault}, or {@code null} if not set
  455.      * @since 6.1
  456.      */
  457.     public PushDefault getPushDefault() {
  458.         return pushDefault;
  459.     }

  460.     /**
  461.      * Sets an explicit {@link PushDefault}. The default used if this is not
  462.      * called is {@link PushDefault#CURRENT} for compatibility reasons with
  463.      * earlier JGit versions.
  464.      *
  465.      * @param pushDefault
  466.      *            {@link PushDefault} to set; if {@code null} the value defined
  467.      *            in the git config will be used.
  468.      *
  469.      * @return {@code this}
  470.      * @since 6.1
  471.      */
  472.     public PushCommand setPushDefault(PushDefault pushDefault) {
  473.         checkCallable();
  474.         this.pushDefault = pushDefault;
  475.         return this;
  476.     }

  477.     /**
  478.      * Push all branches under refs/heads/*.
  479.      *
  480.      * @return {@code this}
  481.      */
  482.     public PushCommand setPushAll() {
  483.         refSpecs.add(Transport.REFSPEC_PUSH_ALL);
  484.         return this;
  485.     }

  486.     /**
  487.      * Push all tags under refs/tags/*.
  488.      *
  489.      * @return {@code this}
  490.      */
  491.     public PushCommand setPushTags() {
  492.         refSpecs.add(Transport.REFSPEC_TAGS);
  493.         return this;
  494.     }

  495.     /**
  496.      * Add a reference to push.
  497.      *
  498.      * @param ref
  499.      *            the source reference. The remote name will match.
  500.      * @return {@code this}.
  501.      */
  502.     public PushCommand add(Ref ref) {
  503.         refSpecs.add(new RefSpec(ref.getLeaf().getName()));
  504.         return this;
  505.     }

  506.     /**
  507.      * Add a reference to push.
  508.      *
  509.      * @param nameOrSpec
  510.      *            any reference name, or a reference specification.
  511.      * @return {@code this}.
  512.      * @throws JGitInternalException
  513.      *             the reference name cannot be resolved.
  514.      */
  515.     public PushCommand add(String nameOrSpec) {
  516.         if (0 <= nameOrSpec.indexOf(':')) {
  517.             refSpecs.add(new RefSpec(nameOrSpec));
  518.         } else {
  519.             Ref src;
  520.             try {
  521.                 src = repo.findRef(nameOrSpec);
  522.             } catch (IOException e) {
  523.                 throw new JGitInternalException(
  524.                         JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
  525.                         e);
  526.             }
  527.             if (src != null)
  528.                 add(src);
  529.         }
  530.         return this;
  531.     }

  532.     /**
  533.      * Whether to run the push operation as a dry run
  534.      *
  535.      * @return the dry run preference for the push operation
  536.      */
  537.     public boolean isDryRun() {
  538.         return dryRun;
  539.     }

  540.     /**
  541.      * Sets whether the push operation should be a dry run
  542.      *
  543.      * @param dryRun a boolean.
  544.      * @return {@code this}
  545.      */
  546.     public PushCommand setDryRun(boolean dryRun) {
  547.         checkCallable();
  548.         this.dryRun = dryRun;
  549.         return this;
  550.     }

  551.     /**
  552.      * Get the thin-pack preference
  553.      *
  554.      * @return the thin-pack preference for push operation
  555.      */
  556.     public boolean isThin() {
  557.         return thin;
  558.     }

  559.     /**
  560.      * Set the thin-pack preference for push operation.
  561.      *
  562.      * Default setting is Transport.DEFAULT_PUSH_THIN
  563.      *
  564.      * @param thin
  565.      *            the thin-pack preference value
  566.      * @return {@code this}
  567.      */
  568.     public PushCommand setThin(boolean thin) {
  569.         checkCallable();
  570.         this.thin = thin;
  571.         return this;
  572.     }

  573.     /**
  574.      * Whether to use bitmaps for push.
  575.      *
  576.      * @return true if push use bitmaps.
  577.      * @since 6.4
  578.      */
  579.     public boolean isUseBitmaps() {
  580.         return useBitmaps;
  581.     }

  582.     /**
  583.      * Set whether to use bitmaps for push.
  584.      *
  585.      * Default setting is {@value Transport#DEFAULT_PUSH_USE_BITMAPS}
  586.      *
  587.      * @param useBitmaps
  588.      *            false to disable use of bitmaps for push, true otherwise.
  589.      * @return {@code this}
  590.      * @since 6.4
  591.      */
  592.     public PushCommand setUseBitmaps(boolean useBitmaps) {
  593.         checkCallable();
  594.         this.useBitmaps = useBitmaps;
  595.         return this;
  596.     }

  597.     /**
  598.      * Whether this push should be executed atomically (all references updated,
  599.      * or none)
  600.      *
  601.      * @return true if all-or-nothing behavior is requested.
  602.      * @since 4.2
  603.      */
  604.     public boolean isAtomic() {
  605.         return atomic;
  606.     }

  607.     /**
  608.      * Requests atomic push (all references updated, or no updates).
  609.      *
  610.      * Default setting is false.
  611.      *
  612.      * @param atomic
  613.      *            whether to run the push atomically
  614.      * @return {@code this}
  615.      * @since 4.2
  616.      */
  617.     public PushCommand setAtomic(boolean atomic) {
  618.         checkCallable();
  619.         this.atomic = atomic;
  620.         return this;
  621.     }

  622.     /**
  623.      * Whether to push forcefully
  624.      *
  625.      * @return the force preference for push operation
  626.      */
  627.     public boolean isForce() {
  628.         return force;
  629.     }

  630.     /**
  631.      * Sets the force preference for push operation.
  632.      *
  633.      * @param force
  634.      *            whether to push forcefully
  635.      * @return {@code this}
  636.      */
  637.     public PushCommand setForce(boolean force) {
  638.         checkCallable();
  639.         this.force = force;
  640.         return this;
  641.     }

  642.     /**
  643.      * Sets the output stream to write sideband messages to
  644.      *
  645.      * @param out
  646.      *            an {@link java.io.OutputStream}
  647.      * @return {@code this}
  648.      * @since 3.0
  649.      */
  650.     public PushCommand setOutputStream(OutputStream out) {
  651.         this.out = out;
  652.         return this;
  653.     }

  654.     /**
  655.      * Get push options
  656.      *
  657.      * @return the option strings associated with the push operation
  658.      * @since 4.5
  659.      */
  660.     public List<String> getPushOptions() {
  661.         return pushOptions;
  662.     }

  663.     /**
  664.      * Set the option strings associated with the push operation.
  665.      *
  666.      * @param pushOptions
  667.      *            a {@link java.util.List} of push option strings
  668.      * @return {@code this}
  669.      * @since 4.5
  670.      */
  671.     public PushCommand setPushOptions(List<String> pushOptions) {
  672.         this.pushOptions = pushOptions;
  673.         return this;
  674.     }
  675. }