PullCommand.java

  1. /*
  2.  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  3.  * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  4.  * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */
  12. package org.eclipse.jgit.api;

  13. import java.io.IOException;
  14. import java.text.MessageFormat;

  15. import org.eclipse.jgit.annotations.Nullable;
  16. import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
  17. import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge;
  18. import org.eclipse.jgit.api.RebaseCommand.Operation;
  19. import org.eclipse.jgit.api.errors.CanceledException;
  20. import org.eclipse.jgit.api.errors.GitAPIException;
  21. import org.eclipse.jgit.api.errors.InvalidConfigurationException;
  22. import org.eclipse.jgit.api.errors.InvalidRemoteException;
  23. import org.eclipse.jgit.api.errors.JGitInternalException;
  24. import org.eclipse.jgit.api.errors.NoHeadException;
  25. import org.eclipse.jgit.api.errors.RefNotAdvertisedException;
  26. import org.eclipse.jgit.api.errors.RefNotFoundException;
  27. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  28. import org.eclipse.jgit.dircache.DirCacheCheckout;
  29. import org.eclipse.jgit.internal.JGitText;
  30. import org.eclipse.jgit.lib.AnyObjectId;
  31. import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
  32. import org.eclipse.jgit.lib.Config;
  33. import org.eclipse.jgit.lib.ConfigConstants;
  34. import org.eclipse.jgit.lib.Constants;
  35. import org.eclipse.jgit.lib.NullProgressMonitor;
  36. import org.eclipse.jgit.lib.ObjectId;
  37. import org.eclipse.jgit.lib.ProgressMonitor;
  38. import org.eclipse.jgit.lib.Ref;
  39. import org.eclipse.jgit.lib.RefUpdate;
  40. import org.eclipse.jgit.lib.RefUpdate.Result;
  41. import org.eclipse.jgit.lib.Repository;
  42. import org.eclipse.jgit.lib.RepositoryState;
  43. import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
  44. import org.eclipse.jgit.merge.ContentMergeStrategy;
  45. import org.eclipse.jgit.merge.MergeStrategy;
  46. import org.eclipse.jgit.revwalk.RevCommit;
  47. import org.eclipse.jgit.revwalk.RevWalk;
  48. import org.eclipse.jgit.transport.FetchResult;
  49. import org.eclipse.jgit.transport.TagOpt;

  50. /**
  51.  * The Pull command
  52.  *
  53.  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-pull.html"
  54.  *      >Git documentation about Pull</a>
  55.  */
  56. public class PullCommand extends TransportCommand<PullCommand, PullResult> {

  57.     private static final String DOT = "."; //$NON-NLS-1$

  58.     private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

  59.     private BranchRebaseMode pullRebaseMode = null;

  60.     private String remote;

  61.     private String remoteBranchName;

  62.     private MergeStrategy strategy = MergeStrategy.RECURSIVE;

  63.     private ContentMergeStrategy contentStrategy;

  64.     private TagOpt tagOption;

  65.     private FastForwardMode fastForwardMode;

  66.     private FetchRecurseSubmodulesMode submoduleRecurseMode = null;

  67.     /**
  68.      * Constructor for PullCommand.
  69.      *
  70.      * @param repo
  71.      *            the {@link org.eclipse.jgit.lib.Repository}
  72.      */
  73.     protected PullCommand(Repository repo) {
  74.         super(repo);
  75.     }

  76.     /**
  77.      * Set progress monitor
  78.      *
  79.      * @param monitor
  80.      *            a progress monitor
  81.      * @return this instance
  82.      */
  83.     public PullCommand setProgressMonitor(ProgressMonitor monitor) {
  84.         if (monitor == null) {
  85.             monitor = NullProgressMonitor.INSTANCE;
  86.         }
  87.         this.monitor = monitor;
  88.         return this;
  89.     }

  90.     /**
  91.      * Set if rebase should be used after fetching. If set to true, rebase is
  92.      * used instead of merge. This is equivalent to --rebase on the command
  93.      * line.
  94.      * <p>
  95.      * If set to false, merge is used after fetching, overriding the
  96.      * configuration file. This is equivalent to --no-rebase on the command
  97.      * line.
  98.      * <p>
  99.      * This setting overrides the settings in the configuration file. By
  100.      * default, the setting in the repository configuration file is used.
  101.      * <p>
  102.      * A branch can be configured to use rebase by default. See
  103.      * branch.[name].rebase and branch.autosetuprebase.
  104.      *
  105.      * @param useRebase
  106.      *            whether to use rebase after fetching
  107.      * @return {@code this}
  108.      */
  109.     public PullCommand setRebase(boolean useRebase) {
  110.         checkCallable();
  111.         pullRebaseMode = useRebase ? BranchRebaseMode.REBASE
  112.                 : BranchRebaseMode.NONE;
  113.         return this;
  114.     }

  115.     /**
  116.      * Sets the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} to
  117.      * use after fetching.
  118.      *
  119.      * <dl>
  120.      * <dt>BranchRebaseMode.REBASE</dt>
  121.      * <dd>Equivalent to {@code --rebase} on the command line: use rebase
  122.      * instead of merge after fetching.</dd>
  123.      * <dt>BranchRebaseMode.PRESERVE</dt>
  124.      * <dd>Equivalent to {@code --preserve-merges} on the command line: rebase
  125.      * preserving local merge commits.</dd>
  126.      * <dt>BranchRebaseMode.INTERACTIVE</dt>
  127.      * <dd>Equivalent to {@code --interactive} on the command line: use
  128.      * interactive rebase.</dd>
  129.      * <dt>BranchRebaseMode.NONE</dt>
  130.      * <dd>Equivalent to {@code --no-rebase}: merge instead of rebasing.
  131.      * <dt>{@code null}</dt>
  132.      * <dd>Use the setting defined in the git configuration, either {@code
  133.      * branch.[name].rebase} or, if not set, {@code pull.rebase}</dd>
  134.      * </dl>
  135.      *
  136.      * This setting overrides the settings in the configuration file. By
  137.      * default, the setting in the repository configuration file is used.
  138.      * <p>
  139.      * A branch can be configured to use rebase by default. See
  140.      * {@code branch.[name].rebase}, {@code branch.autosetuprebase}, and
  141.      * {@code pull.rebase}.
  142.      *
  143.      * @param rebaseMode
  144.      *            the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode}
  145.      *            to use
  146.      * @return {@code this}
  147.      * @since 4.5
  148.      */
  149.     public PullCommand setRebase(BranchRebaseMode rebaseMode) {
  150.         checkCallable();
  151.         pullRebaseMode = rebaseMode;
  152.         return this;
  153.     }

  154.     /**
  155.      * {@inheritDoc}
  156.      * <p>
  157.      * Execute the {@code Pull} command with all the options and parameters
  158.      * collected by the setter methods (e.g.
  159.      * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each
  160.      * instance of this class should only be used for one invocation of the
  161.      * command. Don't call this method twice on an instance.
  162.      */
  163.     @Override
  164.     public PullResult call() throws GitAPIException,
  165.             WrongRepositoryStateException, InvalidConfigurationException,
  166.             InvalidRemoteException, CanceledException,
  167.             RefNotFoundException, RefNotAdvertisedException, NoHeadException,
  168.             org.eclipse.jgit.api.errors.TransportException {
  169.         checkCallable();

  170.         monitor.beginTask(JGitText.get().pullTaskName, 2);
  171.         Config repoConfig = repo.getConfig();

  172.         String branchName = null;
  173.         try {
  174.             String fullBranch = repo.getFullBranch();
  175.             if (fullBranch != null
  176.                     && fullBranch.startsWith(Constants.R_HEADS)) {
  177.                 branchName = fullBranch.substring(Constants.R_HEADS.length());
  178.             }
  179.         } catch (IOException e) {
  180.             throw new JGitInternalException(
  181.                     JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
  182.                     e);
  183.         }
  184.         if (remoteBranchName == null && branchName != null) {
  185.             // get the name of the branch in the remote repository
  186.             // stored in configuration key branch.<branch name>.merge
  187.             remoteBranchName = repoConfig.getString(
  188.                     ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
  189.                     ConfigConstants.CONFIG_KEY_MERGE);
  190.         }
  191.         if (remoteBranchName == null) {
  192.             remoteBranchName = branchName;
  193.         }
  194.         if (remoteBranchName == null) {
  195.             throw new NoHeadException(
  196.                     JGitText.get().cannotCheckoutFromUnbornBranch);
  197.         }

  198.         if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
  199.             throw new WrongRepositoryStateException(MessageFormat.format(
  200.                     JGitText.get().cannotPullOnARepoWithState, repo
  201.                             .getRepositoryState().name()));

  202.         if (remote == null && branchName != null) {
  203.             // get the configured remote for the currently checked out branch
  204.             // stored in configuration key branch.<branch name>.remote
  205.             remote = repoConfig.getString(
  206.                     ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
  207.                     ConfigConstants.CONFIG_KEY_REMOTE);
  208.         }
  209.         if (remote == null) {
  210.             // fall back to default remote
  211.             remote = Constants.DEFAULT_REMOTE_NAME;
  212.         }

  213.         // determines whether rebase should be used after fetching
  214.         if (pullRebaseMode == null && branchName != null) {
  215.             pullRebaseMode = getRebaseMode(branchName, repoConfig);
  216.         }


  217.         final boolean isRemote = !remote.equals("."); //$NON-NLS-1$
  218.         String remoteUri;
  219.         FetchResult fetchRes;
  220.         if (isRemote) {
  221.             remoteUri = repoConfig.getString(
  222.                     ConfigConstants.CONFIG_REMOTE_SECTION, remote,
  223.                     ConfigConstants.CONFIG_KEY_URL);
  224.             if (remoteUri == null) {
  225.                 String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT
  226.                         + remote + DOT + ConfigConstants.CONFIG_KEY_URL;
  227.                 throw new InvalidConfigurationException(MessageFormat.format(
  228.                         JGitText.get().missingConfigurationForKey, missingKey));
  229.             }

  230.             if (monitor.isCancelled())
  231.                 throw new CanceledException(MessageFormat.format(
  232.                         JGitText.get().operationCanceled,
  233.                         JGitText.get().pullTaskName));

  234.             FetchCommand fetch = new FetchCommand(repo).setRemote(remote)
  235.                     .setProgressMonitor(monitor).setTagOpt(tagOption)
  236.                     .setRecurseSubmodules(submoduleRecurseMode);
  237.             configure(fetch);

  238.             fetchRes = fetch.call();
  239.         } else {
  240.             // we can skip the fetch altogether
  241.             remoteUri = JGitText.get().localRepository;
  242.             fetchRes = null;
  243.         }

  244.         monitor.update(1);

  245.         if (monitor.isCancelled())
  246.             throw new CanceledException(MessageFormat.format(
  247.                     JGitText.get().operationCanceled,
  248.                     JGitText.get().pullTaskName));

  249.         // we check the updates to see which of the updated branches
  250.         // corresponds to the remote branch name
  251.         AnyObjectId commitToMerge;
  252.         if (isRemote) {
  253.             Ref r = null;
  254.             if (fetchRes != null) {
  255.                 r = fetchRes.getAdvertisedRef(remoteBranchName);
  256.                 if (r == null) {
  257.                     r = fetchRes.getAdvertisedRef(Constants.R_HEADS
  258.                             + remoteBranchName);
  259.                 }
  260.             }
  261.             if (r == null) {
  262.                 throw new RefNotAdvertisedException(MessageFormat.format(
  263.                         JGitText.get().couldNotGetAdvertisedRef, remote,
  264.                         remoteBranchName));
  265.             }
  266.             commitToMerge = r.getObjectId();
  267.         } else {
  268.             try {
  269.                 commitToMerge = repo.resolve(remoteBranchName);
  270.                 if (commitToMerge == null) {
  271.                     throw new RefNotFoundException(MessageFormat.format(
  272.                             JGitText.get().refNotResolved, remoteBranchName));
  273.                 }
  274.             } catch (IOException e) {
  275.                 throw new JGitInternalException(
  276.                         JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
  277.                         e);
  278.             }
  279.         }

  280.         String upstreamName = MessageFormat.format(
  281.                 JGitText.get().upstreamBranchName,
  282.                 Repository.shortenRefName(remoteBranchName), remoteUri);

  283.         PullResult result;
  284.         if (pullRebaseMode != BranchRebaseMode.NONE) {
  285.             try {
  286.                 Ref head = repo.exactRef(Constants.HEAD);
  287.                 if (head == null) {
  288.                     throw new NoHeadException(JGitText
  289.                             .get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  290.                 }
  291.                 ObjectId headId = head.getObjectId();
  292.                 if (headId == null) {
  293.                     // Pull on an unborn branch: checkout
  294.                     try (RevWalk revWalk = new RevWalk(repo)) {
  295.                         RevCommit srcCommit = revWalk
  296.                                 .parseCommit(commitToMerge);
  297.                         DirCacheCheckout dco = new DirCacheCheckout(repo,
  298.                                 repo.lockDirCache(), srcCommit.getTree());
  299.                         dco.setFailOnConflict(true);
  300.                         dco.setProgressMonitor(monitor);
  301.                         dco.checkout();
  302.                         RefUpdate refUpdate = repo
  303.                                 .updateRef(head.getTarget().getName());
  304.                         refUpdate.setNewObjectId(commitToMerge);
  305.                         refUpdate.setExpectedOldObjectId(null);
  306.                         refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$
  307.                         if (refUpdate.update() != Result.NEW) {
  308.                             throw new NoHeadException(JGitText
  309.                                     .get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  310.                         }
  311.                         monitor.endTask();
  312.                         return new PullResult(fetchRes, remote,
  313.                                 RebaseResult.result(
  314.                                         RebaseResult.Status.FAST_FORWARD,
  315.                                         srcCommit));
  316.                     }
  317.                 }
  318.             } catch (NoHeadException e) {
  319.                 throw e;
  320.             } catch (IOException e) {
  321.                 throw new JGitInternalException(JGitText
  322.                         .get().exceptionCaughtDuringExecutionOfPullCommand, e);
  323.             }
  324.             RebaseCommand rebase = new RebaseCommand(repo);
  325.             RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
  326.                     .setProgressMonitor(monitor)
  327.                     .setUpstreamName(upstreamName)
  328.                     .setOperation(Operation.BEGIN)
  329.                     .setStrategy(strategy)
  330.                     .setContentMergeStrategy(contentStrategy)
  331.                     .setPreserveMerges(
  332.                             pullRebaseMode == BranchRebaseMode.PRESERVE)
  333.                     .call();
  334.             result = new PullResult(fetchRes, remote, rebaseRes);
  335.         } else {
  336.             MergeCommand merge = new MergeCommand(repo);
  337.             MergeResult mergeRes = merge.include(upstreamName, commitToMerge)
  338.                     .setProgressMonitor(monitor)
  339.                     .setStrategy(strategy)
  340.                     .setContentMergeStrategy(contentStrategy)
  341.                     .setFastForward(getFastForwardMode()).call();
  342.             monitor.update(1);
  343.             result = new PullResult(fetchRes, remote, mergeRes);
  344.         }
  345.         monitor.endTask();
  346.         return result;
  347.     }

  348.     /**
  349.      * The remote (uri or name) to be used for the pull operation. If no remote
  350.      * is set, the branch's configuration will be used. If the branch
  351.      * configuration is missing the default value of
  352.      * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used.
  353.      *
  354.      * @see Constants#DEFAULT_REMOTE_NAME
  355.      * @param remote
  356.      *            name of the remote to pull from
  357.      * @return {@code this}
  358.      * @since 3.3
  359.      */
  360.     public PullCommand setRemote(String remote) {
  361.         checkCallable();
  362.         this.remote = remote;
  363.         return this;
  364.     }

  365.     /**
  366.      * The remote branch name to be used for the pull operation. If no
  367.      * remoteBranchName is set, the branch's configuration will be used. If the
  368.      * branch configuration is missing the remote branch with the same name as
  369.      * the current branch is used.
  370.      *
  371.      * @param remoteBranchName
  372.      *            remote branch name to be used for pull operation
  373.      * @return {@code this}
  374.      * @since 3.3
  375.      */
  376.     public PullCommand setRemoteBranchName(String remoteBranchName) {
  377.         checkCallable();
  378.         this.remoteBranchName = remoteBranchName;
  379.         return this;
  380.     }

  381.     /**
  382.      * Get the remote name used for pull operation
  383.      *
  384.      * @return the remote used for the pull operation if it was set explicitly
  385.      * @since 3.3
  386.      */
  387.     public String getRemote() {
  388.         return remote;
  389.     }

  390.     /**
  391.      * Get the remote branch name for the pull operation
  392.      *
  393.      * @return the remote branch name used for the pull operation if it was set
  394.      *         explicitly
  395.      * @since 3.3
  396.      */
  397.     public String getRemoteBranchName() {
  398.         return remoteBranchName;
  399.     }

  400.     /**
  401.      * Set the @{code MergeStrategy}
  402.      *
  403.      * @param strategy
  404.      *            The merge strategy to use during this pull operation.
  405.      * @return {@code this}
  406.      * @since 3.4
  407.      */
  408.     public PullCommand setStrategy(MergeStrategy strategy) {
  409.         this.strategy = strategy;
  410.         return this;
  411.     }

  412.     /**
  413.      * Sets the content merge strategy to use if the
  414.      * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or
  415.      * "recursive".
  416.      *
  417.      * @param strategy
  418.      *            the {@link ContentMergeStrategy} to be used
  419.      * @return {@code this}
  420.      * @since 5.12
  421.      */
  422.     public PullCommand setContentMergeStrategy(ContentMergeStrategy strategy) {
  423.         this.contentStrategy = strategy;
  424.         return this;
  425.     }

  426.     /**
  427.      * Set the specification of annotated tag behavior during fetch
  428.      *
  429.      * @param tagOpt
  430.      *            the {@link org.eclipse.jgit.transport.TagOpt}
  431.      * @return {@code this}
  432.      * @since 4.7
  433.      */
  434.     public PullCommand setTagOpt(TagOpt tagOpt) {
  435.         checkCallable();
  436.         this.tagOption = tagOpt;
  437.         return this;
  438.     }

  439.     /**
  440.      * Set the fast forward mode. It is used if pull is configured to do a merge
  441.      * as opposed to rebase. If non-{@code null} takes precedence over the
  442.      * fast-forward mode configured in git config.
  443.      *
  444.      * @param fastForwardMode
  445.      *            corresponds to the --ff/--no-ff/--ff-only options. If
  446.      *            {@code null} use the value of {@code pull.ff} configured in
  447.      *            git config. If {@code pull.ff} is not configured fall back to
  448.      *            the value of {@code merge.ff}. If {@code merge.ff} is not
  449.      *            configured --ff is the built-in default.
  450.      * @return {@code this}
  451.      * @since 4.9
  452.      */
  453.     public PullCommand setFastForward(
  454.             @Nullable FastForwardMode fastForwardMode) {
  455.         checkCallable();
  456.         this.fastForwardMode = fastForwardMode;
  457.         return this;
  458.     }

  459.     /**
  460.      * Set the mode to be used for recursing into submodules.
  461.      *
  462.      * @param recurse
  463.      *            the
  464.      *            {@link org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode}
  465.      *            to be used for recursing into submodules
  466.      * @return {@code this}
  467.      * @since 4.7
  468.      * @see FetchCommand#setRecurseSubmodules(FetchRecurseSubmodulesMode)
  469.      */
  470.     public PullCommand setRecurseSubmodules(
  471.             @Nullable FetchRecurseSubmodulesMode recurse) {
  472.         this.submoduleRecurseMode = recurse;
  473.         return this;
  474.     }

  475.     /**
  476.      * Reads the rebase mode to use for a pull command from the repository
  477.      * configuration. This is the value defined for the configurations
  478.      * {@code branch.[branchName].rebase}, or,if not set, {@code pull.rebase}.
  479.      * If neither is set, yields
  480.      * {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode#NONE}.
  481.      *
  482.      * @param branchName
  483.      *            name of the local branch
  484.      * @param config
  485.      *            the {@link org.eclipse.jgit.lib.Config} to read the value from
  486.      * @return the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode}
  487.      * @since 4.5
  488.      */
  489.     public static BranchRebaseMode getRebaseMode(String branchName,
  490.             Config config) {
  491.         BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(),
  492.                 ConfigConstants.CONFIG_BRANCH_SECTION,
  493.                 branchName, ConfigConstants.CONFIG_KEY_REBASE, null);
  494.         if (mode == null) {
  495.             mode = config.getEnum(BranchRebaseMode.values(),
  496.                     ConfigConstants.CONFIG_PULL_SECTION, null,
  497.                     ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
  498.         }
  499.         return mode;
  500.     }

  501.     private FastForwardMode getFastForwardMode() {
  502.         if (fastForwardMode != null) {
  503.             return fastForwardMode;
  504.         }
  505.         Config config = repo.getConfig();
  506.         Merge ffMode = config.getEnum(Merge.values(),
  507.                 ConfigConstants.CONFIG_PULL_SECTION, null,
  508.                 ConfigConstants.CONFIG_KEY_FF, null);
  509.         return ffMode != null ? FastForwardMode.valueOf(ffMode) : null;
  510.     }
  511. }