diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java | 241 |
1 files changed, 156 insertions, 85 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index d2075a70f2..7064f5a57a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -1,56 +1,26 @@ /* * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> - * Copyright (C) 2010-2014, Stefan Lay <stefan.lay@sap.com> - * and other copyright owners as documented in the project's IP log. + * Copyright (C) 2010, 2014, Stefan Lay <stefan.lay@sap.com> + * 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 java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; @@ -61,17 +31,22 @@ import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Config.ConfigEnum; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeConfig; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; @@ -98,7 +73,9 @@ public class MergeCommand extends GitCommand<MergeResult> { private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE; - private List<Ref> commits = new LinkedList<Ref>(); + private ContentMergeStrategy contentStrategy; + + private List<Ref> commits = new ArrayList<>(); private Boolean squash; @@ -106,6 +83,24 @@ public class MergeCommand extends GitCommand<MergeResult> { private String message; + private boolean insertChangeId; + + private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + + /** + * Values for the "merge.conflictStyle" git config. + * + * @since 5.12 + */ + public enum ConflictStyle { + + /** "merge" style: only ours/theirs. This is the default. */ + MERGE, + + /** "diff3" style: ours/base/theirs. */ + DIFF3 + } + /** * The modes available for fast forward merges corresponding to the * <code>--ff</code>, <code>--no-ff</code> and <code>--ff-only</code> @@ -128,10 +123,12 @@ public class MergeCommand extends GitCommand<MergeResult> { */ FF_ONLY; + @Override public String toConfigValue() { - return "--" + name().toLowerCase().replace('_', '-'); //$NON-NLS-1$ + return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$ } + @Override public boolean matchConfigValue(String in) { if (StringUtils.isEmptyOrNull(in)) return false; @@ -201,20 +198,24 @@ public class MergeCommand extends GitCommand<MergeResult> { private Boolean commit; /** + * Constructor for MergeCommand. + * * @param repo + * the {@link org.eclipse.jgit.lib.Repository} */ protected MergeCommand(Repository repo) { super(repo); } /** - * Executes the {@code Merge} command with all the options and parameters + * {@inheritDoc} + * <p> + * Execute the {@code Merge} command with all the options and parameters * collected by the setter methods (e.g. {@link #include(Ref)}) 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 merge */ + @Override @SuppressWarnings("boxing") public MergeResult call() throws GitAPIException, NoHeadException, ConcurrentRefUpdateException, CheckoutConflictException, @@ -223,17 +224,15 @@ public class MergeCommand extends GitCommand<MergeResult> { fallBackToConfiguration(); checkParameters(); - RevWalk revWalk = null; DirCacheCheckout dco = null; - try { - Ref head = repo.getRef(Constants.HEAD); + try (RevWalk revWalk = new RevWalk(repo)) { + Ref head = repo.exactRef(Constants.HEAD); if (head == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$ // Check for FAST_FORWARD, ALREADY_UP_TO_DATE - revWalk = new RevWalk(repo); // we know for now there is only one commit Ref ref = commits.get(0); @@ -241,7 +240,7 @@ public class MergeCommand extends GitCommand<MergeResult> { refLogMessage.append(ref.getName()); // handle annotated tags - ref = repo.peel(ref); + ref = repo.getRefDatabase().peel(ref); ObjectId objectId = ref.getPeeledObjectId(); if (objectId == null) objectId = ref.getObjectId(); @@ -254,6 +253,7 @@ public class MergeCommand extends GitCommand<MergeResult> { dco = new DirCacheCheckout(repo, repo.lockDirCache(), srcCommit.getTree()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); RefUpdate refUpdate = repo .updateRef(head.getTarget().getName()); @@ -284,6 +284,7 @@ public class MergeCommand extends GitCommand<MergeResult> { dco = new DirCacheCheckout(repo, headCommit.getTree(), repo.lockDirCache(), srcCommit.getTree()); + dco.setProgressMonitor(monitor); dco.setFailOnConflict(true); dco.checkout(); String msg = null; @@ -330,12 +331,14 @@ public class MergeCommand extends GitCommand<MergeResult> { repo.writeSquashCommitMsg(squashMessage); } Merger merger = mergeStrategy.newMerger(repo); + merger.setProgressMonitor(monitor); boolean noProblems; Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults = null; Map<String, MergeFailureReason> failingPaths = null; List<String> unmergedPaths = null; if (merger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) merger; + resolveMerger.setContentMergeStrategy(contentStrategy); resolveMerger.setCommitNames(new String[] { "BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$ resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo)); @@ -344,6 +347,10 @@ public class MergeCommand extends GitCommand<MergeResult> { .getMergeResults(); failingPaths = resolveMerger.getFailingPaths(); unmergedPaths = resolveMerger.getUnmergedPaths(); + if (!resolveMerger.getModifiedFiles().isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent( + resolveMerger.getModifiedFiles(), null)); + } } else noProblems = merger.merge(headCommit, srcCommit); refLogMessage.append(": Merge made by "); //$NON-NLS-1$ @@ -357,6 +364,7 @@ public class MergeCommand extends GitCommand<MergeResult> { headCommit.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); + dco.setProgressMonitor(monitor); dco.checkout(); String msg = null; @@ -372,9 +380,11 @@ public class MergeCommand extends GitCommand<MergeResult> { try (Git git = new Git(getRepository())) { newHeadId = git.commit() .setReflogComment(refLogMessage.toString()) + .setInsertChangeId(insertChangeId) .call().getId(); } mergeStatus = MergeStatus.MERGED; + getRepository().autoGC(monitor); } if (commit && squash) { msg = JGitText.get().squashCommitNotUpdatingHEAD; @@ -385,27 +395,27 @@ public class MergeCommand extends GitCommand<MergeResult> { new ObjectId[] { headCommit.getId(), srcCommit.getId() }, mergeStatus, mergeStrategy, null, msg); - } else { - if (failingPaths != null) { - repo.writeMergeCommitMsg(null); - repo.writeMergeHeads(null); - return new MergeResult(null, merger.getBaseCommitId(), - new ObjectId[] { - headCommit.getId(), srcCommit.getId() }, - MergeStatus.FAILED, mergeStrategy, - lowLevelResults, failingPaths, null); - } else { - String mergeMessageWithConflicts = new MergeMessageFormatter() - .formatWithConflicts(mergeMessage, - unmergedPaths); - repo.writeMergeCommitMsg(mergeMessageWithConflicts); - return new MergeResult(null, merger.getBaseCommitId(), - new ObjectId[] { headCommit.getId(), - srcCommit.getId() }, - MergeStatus.CONFLICTING, mergeStrategy, - lowLevelResults, null); - } } + if (failingPaths != null) { + repo.writeMergeCommitMsg(null); + repo.writeMergeHeads(null); + return new MergeResult(null, merger.getBaseCommitId(), + new ObjectId[] { headCommit.getId(), + srcCommit.getId() }, + MergeStatus.FAILED, mergeStrategy, lowLevelResults, + failingPaths, null); + } + CommitConfig cfg = repo.getConfig().get(CommitConfig.KEY); + char commentChar = cfg.getCommentChar(message); + String mergeMessageWithConflicts = new MergeMessageFormatter() + .formatWithConflicts(mergeMessage, unmergedPaths, + commentChar); + repo.writeMergeCommitMsg(mergeMessageWithConflicts); + return new MergeResult(null, merger.getBaseCommitId(), + new ObjectId[] { headCommit.getId(), + srcCommit.getId() }, + MergeStatus.CONFLICTING, mergeStrategy, lowLevelResults, + null); } } catch (org.eclipse.jgit.errors.CheckoutConflictException e) { List<String> conflicts = (dco == null) ? Collections @@ -416,9 +426,6 @@ public class MergeCommand extends GitCommand<MergeResult> { MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand, e), e); - } finally { - if (revWalk != null) - revWalk.close(); } } @@ -438,8 +445,8 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** - * Use values from the configuation if they have not been explicitly defined - * via the setters + * Use values from the configuration if they have not been explicitly + * defined via the setters */ private void fallBackToConfiguration() { MergeConfig config = MergeConfig.getConfigForCurrentBranch(repo); @@ -475,9 +482,10 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** + * Set merge strategy * * @param mergeStrategy - * the {@link MergeStrategy} to be used + * the {@link org.eclipse.jgit.merge.MergeStrategy} to be used * @return {@code this} */ public MergeCommand setStrategy(MergeStrategy mergeStrategy) { @@ -487,6 +495,24 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** + * 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 MergeCommand setContentMergeStrategy(ContentMergeStrategy strategy) { + checkCallable(); + this.contentStrategy = strategy; + return this; + } + + /** + * Reference to a commit to be merged with the current head + * * @param aCommit * a reference to a commit which is merged with the current head * @return {@code this} @@ -498,6 +524,8 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** + * Id of a commit which is to be merged with the current head + * * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} @@ -507,8 +535,10 @@ public class MergeCommand extends GitCommand<MergeResult> { } /** + * Include a commit + * * @param name - * a name given to the commit + * a name of a {@code Ref} pointing to the commit * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} @@ -524,9 +554,10 @@ public class MergeCommand extends GitCommand<MergeResult> { * HEAD. Otherwise, perform the merge and commit the result. * <p> * In case the merge was successful but this flag was set to - * <code>true</code> a {@link MergeResult} with status - * {@link MergeStatus#MERGED_SQUASHED} or - * {@link MergeStatus#FAST_FORWARD_SQUASHED} is returned. + * <code>true</code> a {@link org.eclipse.jgit.api.MergeResult} with status + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_SQUASHED} or + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#FAST_FORWARD_SQUASHED} + * is returned. * * @param squash * whether to squash commits or not @@ -543,12 +574,15 @@ public class MergeCommand extends GitCommand<MergeResult> { * Sets the fast forward mode. * * @param fastForwardMode - * corresponds to the --ff/--no-ff/--ff-only options. --ff is the - * default option. + * corresponds to the --ff/--no-ff/--ff-only options. If + * {@code null} use the value of the {@code merge.ff} option + * configured in git config. If this option is not configured + * --ff is the built-in default. * @return {@code this} * @since 2.2 */ - public MergeCommand setFastForward(FastForwardMode fastForwardMode) { + public MergeCommand setFastForward( + @Nullable FastForwardMode fastForwardMode) { checkCallable(); this.fastForwardMode = fastForwardMode; return this; @@ -562,9 +596,11 @@ public class MergeCommand extends GitCommand<MergeResult> { * <code>true</code> if this command should commit (this is the * default behavior). <code>false</code> if this command should * not commit. In case the merge was successful but this flag was - * set to <code>false</code> a {@link MergeResult} with type - * {@link MergeResult} with status - * {@link MergeStatus#MERGED_NOT_COMMITTED} is returned + * set to <code>false</code> a + * {@link org.eclipse.jgit.api.MergeResult} with type + * {@link org.eclipse.jgit.api.MergeResult} with status + * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_NOT_COMMITTED} + * is returned * @return {@code this} * @since 3.0 */ @@ -586,4 +622,39 @@ public class MergeCommand extends GitCommand<MergeResult> { this.message = message; return this; } + + /** + * If set to true a change id will be inserted into the commit message + * + * An existing change id is not replaced. An initial change id (I000...) + * will be replaced by the change id. + * + * @param insertChangeId + * whether to insert a change id + * @return {@code this} + * @since 5.0 + */ + public MergeCommand setInsertChangeId(boolean insertChangeId) { + checkCallable(); + this.insertChangeId = insertChangeId; + return this; + } + + /** + * The progress monitor associated with the diff operation. By default, this + * is set to <code>NullProgressMonitor</code> + * + * @see NullProgressMonitor + * @param monitor + * A progress monitor + * @return this instance + * @since 4.2 + */ + public MergeCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } + this.monitor = monitor; + return this; + } } |