diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java | 413 |
1 files changed, 284 insertions, 129 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index cdaf3ec97e..4b2cee45c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -1,73 +1,54 @@ /* * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> - * and other copyright owners as documented in the project's IP log. + * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.MergeCommand.FastForwardMode; +import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge; import org.eclipse.jgit.api.RebaseCommand.Operation; import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.api.errors.RefNotAdvertisedException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; +import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.TagOpt; /** * The Pull command @@ -77,11 +58,11 @@ import org.eclipse.jgit.transport.FetchResult; */ public class PullCommand extends TransportCommand<PullCommand, PullResult> { - private final static String DOT = "."; //$NON-NLS-1$ + private static final String DOT = "."; //$NON-NLS-1$ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; - private PullRebaseMode pullRebaseMode = null; + private BranchRebaseMode pullRebaseMode = null; private String remote; @@ -89,46 +70,35 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { private MergeStrategy strategy = MergeStrategy.RECURSIVE; - private enum PullRebaseMode implements Config.ConfigEnum { - REBASE_PRESERVE("preserve", true, true), //$NON-NLS-1$ - REBASE("true", true, false), //$NON-NLS-1$ - NO_REBASE("false", false, false); //$NON-NLS-1$ - - private final String configValue; + private ContentMergeStrategy contentStrategy; - private final boolean rebase; + private TagOpt tagOption; - private final boolean preserveMerges; + private FastForwardMode fastForwardMode; - PullRebaseMode(String configValue, boolean rebase, - boolean preserveMerges) { - this.configValue = configValue; - this.rebase = rebase; - this.preserveMerges = preserveMerges; - } - - public String toConfigValue() { - return configValue; - } - - public boolean matchConfigValue(String in) { - return in.equals(configValue); - } - } + private FetchRecurseSubmodulesMode submoduleRecurseMode = null; /** + * Constructor for PullCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected PullCommand(Repository repo) { super(repo); } /** + * Set progress monitor + * * @param monitor * a progress monitor * @return this instance */ public PullCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } @@ -149,90 +119,125 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { * branch.[name].rebase and branch.autosetuprebase. * * @param useRebase + * whether to use rebase after fetching * @return {@code this} */ public PullCommand setRebase(boolean useRebase) { checkCallable(); - pullRebaseMode = useRebase ? PullRebaseMode.REBASE : PullRebaseMode.NO_REBASE; + pullRebaseMode = useRebase ? BranchRebaseMode.REBASE + : BranchRebaseMode.NONE; + return this; + } + + /** + * Sets the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} to + * use after fetching. + * + * <dl> + * <dt>BranchRebaseMode.REBASE</dt> + * <dd>Equivalent to {@code --rebase} on the command line: use rebase + * instead of merge after fetching.</dd> + * <dt>BranchRebaseMode.MERGES</dt> + * <dd>Equivalent to {@code --rebase-merges} on the command line: rebase + * preserving local merge commits.</dd> + * <dt>BranchRebaseMode.INTERACTIVE</dt> + * <dd>Equivalent to {@code --interactive} on the command line: use + * interactive rebase.</dd> + * <dt>BranchRebaseMode.NONE</dt> + * <dd>Equivalent to {@code --no-rebase}: merge instead of rebasing. + * <dt>{@code null}</dt> + * <dd>Use the setting defined in the git configuration, either {@code + * branch.[name].rebase} or, if not set, {@code pull.rebase}</dd> + * </dl> + * + * This setting overrides the settings in the configuration file. By + * default, the setting in the repository configuration file is used. + * <p> + * A branch can be configured to use rebase by default. See + * {@code branch.[name].rebase}, {@code branch.autosetuprebase}, and + * {@code pull.rebase}. + * + * @param rebaseMode + * the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} + * to use + * @return {@code this} + * @since 4.5 + */ + public PullCommand setRebase(BranchRebaseMode rebaseMode) { + checkCallable(); + pullRebaseMode = rebaseMode; return this; } /** - * Executes the {@code Pull} command with all the options and parameters + * {@inheritDoc} + * <p> + * Execute the {@code Pull} command with all the options and parameters * collected by the setter methods (e.g. * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each * instance of this class should only be used for one invocation of the * command. Don't call this method twice on an instance. - * - * @return the result of the pull - * @throws WrongRepositoryStateException - * @throws InvalidConfigurationException - * @throws DetachedHeadException - * @throws InvalidRemoteException - * @throws CanceledException - * @throws RefNotFoundException - * @throws NoHeadException - * @throws org.eclipse.jgit.api.errors.TransportException - * @throws GitAPIException */ + @Override public PullResult call() throws GitAPIException, WrongRepositoryStateException, InvalidConfigurationException, - DetachedHeadException, InvalidRemoteException, CanceledException, - RefNotFoundException, NoHeadException, + InvalidRemoteException, CanceledException, + RefNotFoundException, RefNotAdvertisedException, NoHeadException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); monitor.beginTask(JGitText.get().pullTaskName, 2); + Config repoConfig = repo.getConfig(); - String branchName; + String branchName = null; try { String fullBranch = repo.getFullBranch(); - if (fullBranch == null) - throw new NoHeadException( - JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported); - if (!fullBranch.startsWith(Constants.R_HEADS)) { - // we can not pull if HEAD is detached and branch is not - // specified explicitly - throw new DetachedHeadException(); + if (fullBranch != null + && fullBranch.startsWith(Constants.R_HEADS)) { + branchName = fullBranch.substring(Constants.R_HEADS.length()); } - branchName = fullBranch.substring(Constants.R_HEADS.length()); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, e); } + if (remoteBranchName == null && branchName != null) { + // get the name of the branch in the remote repository + // stored in configuration key branch.<branch name>.merge + remoteBranchName = repoConfig.getString( + ConfigConstants.CONFIG_BRANCH_SECTION, branchName, + ConfigConstants.CONFIG_KEY_MERGE); + } + if (remoteBranchName == null) { + remoteBranchName = branchName; + } + if (remoteBranchName == null) { + throw new NoHeadException( + JGitText.get().cannotCheckoutFromUnbornBranch); + } if (!repo.getRepositoryState().equals(RepositoryState.SAFE)) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().cannotPullOnARepoWithState, repo .getRepositoryState().name())); - Config repoConfig = repo.getConfig(); - if (remote == null) { + if (remote == null && branchName != null) { // get the configured remote for the currently checked out branch // stored in configuration key branch.<branch name>.remote remote = repoConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE); } - if (remote == null) + if (remote == null) { // fall back to default remote remote = Constants.DEFAULT_REMOTE_NAME; - - if (remoteBranchName == null) - // get the name of the branch in the remote repository - // stored in configuration key branch.<branch name>.merge - remoteBranchName = repoConfig.getString( - ConfigConstants.CONFIG_BRANCH_SECTION, branchName, - ConfigConstants.CONFIG_KEY_MERGE); + } // determines whether rebase should be used after fetching - if (pullRebaseMode == null) { + if (pullRebaseMode == null && branchName != null) { pullRebaseMode = getRebaseMode(branchName, repoConfig); } - if (remoteBranchName == null) - remoteBranchName = branchName; final boolean isRemote = !remote.equals("."); //$NON-NLS-1$ String remoteUri; @@ -253,15 +258,15 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { JGitText.get().operationCanceled, JGitText.get().pullTaskName)); - FetchCommand fetch = new FetchCommand(repo); - fetch.setRemote(remote); - fetch.setProgressMonitor(monitor); + FetchCommand fetch = new FetchCommand(repo).setRemote(remote) + .setProgressMonitor(monitor).setTagOpt(tagOption) + .setRecurseSubmodules(submoduleRecurseMode); configure(fetch); fetchRes = fetch.call(); } else { // we can skip the fetch altogether - remoteUri = "local repository"; + remoteUri = JGitText.get().localRepository; fetchRes = null; } @@ -273,28 +278,30 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { JGitText.get().pullTaskName)); // we check the updates to see which of the updated branches - // corresponds - // to the remote branch name + // corresponds to the remote branch name AnyObjectId commitToMerge; if (isRemote) { Ref r = null; if (fetchRes != null) { r = fetchRes.getAdvertisedRef(remoteBranchName); - if (r == null) + if (r == null) { r = fetchRes.getAdvertisedRef(Constants.R_HEADS + remoteBranchName); + } } - if (r == null) - throw new JGitInternalException(MessageFormat.format(JGitText - .get().couldNotGetAdvertisedRef, remoteBranchName)); - else - commitToMerge = r.getObjectId(); + if (r == null) { + throw new RefNotAdvertisedException(MessageFormat.format( + JGitText.get().couldNotGetAdvertisedRef, remote, + remoteBranchName)); + } + commitToMerge = r.getObjectId(); } else { try { commitToMerge = repo.resolve(remoteBranchName); - if (commitToMerge == null) + if (commitToMerge == null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, remoteBranchName)); + } } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, @@ -302,24 +309,69 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } } - String upstreamName = "branch \'" - + Repository.shortenRefName(remoteBranchName) + "\' of " - + remoteUri; + String upstreamName = MessageFormat.format( + JGitText.get().upstreamBranchName, + Repository.shortenRefName(remoteBranchName), remoteUri); PullResult result; - if (pullRebaseMode.rebase) { + if (pullRebaseMode != BranchRebaseMode.NONE) { + try { + Ref head = repo.exactRef(Constants.HEAD); + if (head == null) { + throw new NoHeadException(JGitText + .get().commitOnRepoWithoutHEADCurrentlyNotSupported); + } + ObjectId headId = head.getObjectId(); + if (headId == null) { + // Pull on an unborn branch: checkout + try (RevWalk revWalk = new RevWalk(repo)) { + RevCommit srcCommit = revWalk + .parseCommit(commitToMerge); + DirCacheCheckout dco = new DirCacheCheckout(repo, + repo.lockDirCache(), srcCommit.getTree()); + dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); + dco.checkout(); + RefUpdate refUpdate = repo + .updateRef(head.getTarget().getName()); + refUpdate.setNewObjectId(commitToMerge); + refUpdate.setExpectedOldObjectId(null); + refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$ + if (refUpdate.update() != Result.NEW) { + throw new NoHeadException(JGitText + .get().commitOnRepoWithoutHEADCurrentlyNotSupported); + } + monitor.endTask(); + return new PullResult(fetchRes, remote, + RebaseResult.result( + RebaseResult.Status.FAST_FORWARD, + srcCommit)); + } + } + } catch (NoHeadException e) { + throw e; + } catch (IOException e) { + throw new JGitInternalException(JGitText + .get().exceptionCaughtDuringExecutionOfPullCommand, e); + } RebaseCommand rebase = new RebaseCommand(repo); RebaseResult rebaseRes = rebase.setUpstream(commitToMerge) - .setUpstreamName(upstreamName).setProgressMonitor(monitor) - .setOperation(Operation.BEGIN).setStrategy(strategy) - .setPreserveMerges(pullRebaseMode.preserveMerges) + .setProgressMonitor(monitor) + .setUpstreamName(upstreamName) + .setOperation(Operation.BEGIN) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) + .setPreserveMerges( + pullRebaseMode == BranchRebaseMode.MERGES) .call(); result = new PullResult(fetchRes, remote, rebaseRes); } else { MergeCommand merge = new MergeCommand(repo); - merge.include(upstreamName, commitToMerge); - merge.setStrategy(strategy); - MergeResult mergeRes = merge.call(); + MergeResult mergeRes = merge.include(upstreamName, commitToMerge) + .setProgressMonitor(monitor) + .setStrategy(strategy) + .setContentMergeStrategy(contentStrategy) + .setFastForward(getFastForwardMode()).call(); monitor.update(1); result = new PullResult(fetchRes, remote, mergeRes); } @@ -335,6 +387,7 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { * * @see Constants#DEFAULT_REMOTE_NAME * @param remote + * name of the remote to pull from * @return {@code this} * @since 3.3 */ @@ -351,6 +404,7 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { * the current branch is used. * * @param remoteBranchName + * remote branch name to be used for pull operation * @return {@code this} * @since 3.3 */ @@ -361,6 +415,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } /** + * Get the remote name used for pull operation + * * @return the remote used for the pull operation if it was set explicitly * @since 3.3 */ @@ -369,6 +425,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } /** + * Get the remote branch name for the pull operation + * * @return the remote branch name used for the pull operation if it was set * explicitly * @since 3.3 @@ -378,6 +436,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { } /** + * Set the @{code MergeStrategy} + * * @param strategy * The merge strategy to use during this pull operation. * @return {@code this} @@ -388,13 +448,108 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { return this; } - private static PullRebaseMode getRebaseMode(String branchName, Config config) { - PullRebaseMode mode = config.getEnum(PullRebaseMode.values(), - ConfigConstants.CONFIG_PULL_SECTION, null, - ConfigConstants.CONFIG_KEY_REBASE, PullRebaseMode.NO_REBASE); - mode = config.getEnum(PullRebaseMode.values(), + /** + * Sets the content merge strategy to use if the + * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or + * "recursive". + * + * @param strategy + * the {@link ContentMergeStrategy} to be used + * @return {@code this} + * @since 5.12 + */ + public PullCommand setContentMergeStrategy(ContentMergeStrategy strategy) { + this.contentStrategy = strategy; + return this; + } + + /** + * Set the specification of annotated tag behavior during fetch + * + * @param tagOpt + * the {@link org.eclipse.jgit.transport.TagOpt} + * @return {@code this} + * @since 4.7 + */ + public PullCommand setTagOpt(TagOpt tagOpt) { + checkCallable(); + this.tagOption = tagOpt; + return this; + } + + /** + * Set the fast forward mode. It is used if pull is configured to do a merge + * as opposed to rebase. If non-{@code null} takes precedence over the + * fast-forward mode configured in git config. + * + * @param fastForwardMode + * corresponds to the --ff/--no-ff/--ff-only options. If + * {@code null} use the value of {@code pull.ff} configured in + * git config. If {@code pull.ff} is not configured fall back to + * the value of {@code merge.ff}. If {@code merge.ff} is not + * configured --ff is the built-in default. + * @return {@code this} + * @since 4.9 + */ + public PullCommand setFastForward( + @Nullable FastForwardMode fastForwardMode) { + checkCallable(); + this.fastForwardMode = fastForwardMode; + return this; + } + + /** + * Set the mode to be used for recursing into submodules. + * + * @param recurse + * the + * {@link org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode} + * to be used for recursing into submodules + * @return {@code this} + * @since 4.7 + * @see FetchCommand#setRecurseSubmodules(FetchRecurseSubmodulesMode) + */ + public PullCommand setRecurseSubmodules( + @Nullable FetchRecurseSubmodulesMode recurse) { + this.submoduleRecurseMode = recurse; + return this; + } + + /** + * Reads the rebase mode to use for a pull command from the repository + * configuration. This is the value defined for the configurations + * {@code branch.[branchName].rebase}, or,if not set, {@code pull.rebase}. + * If neither is set, yields + * {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode#NONE}. + * + * @param branchName + * name of the local branch + * @param config + * the {@link org.eclipse.jgit.lib.Config} to read the value from + * @return the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} + * @since 4.5 + */ + public static BranchRebaseMode getRebaseMode(String branchName, + Config config) { + BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(), ConfigConstants.CONFIG_BRANCH_SECTION, - branchName, ConfigConstants.CONFIG_KEY_REBASE, mode); + branchName, ConfigConstants.CONFIG_KEY_REBASE); + if (mode == null) { + mode = config.getEnum( + ConfigConstants.CONFIG_PULL_SECTION, null, + ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); + } return mode; } + + private FastForwardMode getFastForwardMode() { + if (fastForwardMode != null) { + return fastForwardMode; + } + Config config = repo.getConfig(); + Merge ffMode = config.getEnum(Merge.values(), + ConfigConstants.CONFIG_PULL_SECTION, null, + ConfigConstants.CONFIG_KEY_FF); + return ffMode != null ? FastForwardMode.valueOf(ffMode) : null; + } } |