Implement ours/theirs content conflict resolution
Git has different conflict resolution strategies: * There is a tree merge strategy "ours" which just ignores any changes from theirs ("-s ours"). JGit also has the mirror strategy "theirs" ignoring any changes from "ours". (This doesn't exist in C git.) Adapt StashApplyCommand and CherrypickCommand to be able to use those tree merge strategies. * For the resolve/recursive tree merge strategies, there are content conflict resolution strategies "ours" and "theirs", which resolve any conflict hunks by taking the "ours" or "theirs" hunk. In C git those correspond to "-Xours" or -Xtheirs". Implement that in MergeAlgorithm, and add API to set and pass through such a strategy for resolving content conflicts. * The "ours/theirs" content conflict resolution strategies also apply for binary files. Handle these cases in ResolveMerger. Note that the content conflict resolution strategies ("-X ours/theirs") do _not_ apply to modify/delete or delete/modify conflicts. Such conflicts are always reported as conflicts by C git. They do apply, however, if one side completely clears a file's content. Bug: 501111 Change-Id: I2c9c170c61c440a2ab9c387991e7a0c3ab960e07 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
983c25064e
commit
8210f29fe4
|
@ -115,6 +115,7 @@ metaVar_configFile=FILE
|
||||||
metaVar_connProp=conn.prop
|
metaVar_connProp=conn.prop
|
||||||
metaVar_diffAlg=ALGORITHM
|
metaVar_diffAlg=ALGORITHM
|
||||||
metaVar_directory=DIRECTORY
|
metaVar_directory=DIRECTORY
|
||||||
|
metaVar_extraArgument=ours|theirs
|
||||||
metaVar_file=FILE
|
metaVar_file=FILE
|
||||||
metaVar_filepattern=filepattern
|
metaVar_filepattern=filepattern
|
||||||
metaVar_gitDir=GIT_DIR
|
metaVar_gitDir=GIT_DIR
|
||||||
|
@ -217,6 +218,7 @@ timeInMilliSeconds={0} ms
|
||||||
treeIsRequired=argument tree is required
|
treeIsRequired=argument tree is required
|
||||||
tooManyRefsGiven=Too many refs given
|
tooManyRefsGiven=Too many refs given
|
||||||
unknownIoErrorStdout=An unknown I/O error occurred on standard output
|
unknownIoErrorStdout=An unknown I/O error occurred on standard output
|
||||||
|
unknownExtraArgument=unknown extra argument -X {0} specified
|
||||||
unknownMergeStrategy=unknown merge strategy {0} specified
|
unknownMergeStrategy=unknown merge strategy {0} specified
|
||||||
unknownSubcommand=Unknown subcommand: {0}
|
unknownSubcommand=Unknown subcommand: {0}
|
||||||
unmergedPaths=Unmerged paths:
|
unmergedPaths=Unmerged paths:
|
||||||
|
@ -226,6 +228,7 @@ updating=Updating {0}..{1}
|
||||||
usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
|
usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
|
||||||
usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
|
usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
|
||||||
usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
|
usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
|
||||||
|
usage_extraArgument=Pass an extra argument to a merge driver. Currently supported are "-X ours" and "-X theirs".
|
||||||
usage_mirrorClone=Set up a mirror of the source repository. This implies --bare. Compared to --bare, --mirror not only maps \
|
usage_mirrorClone=Set up a mirror of the source repository. This implies --bare. Compared to --bare, --mirror not only maps \
|
||||||
local branches of the source to local branches of the target, it maps all refs (including remote-tracking branches, notes etc.) \
|
local branches of the source to local branches of the target, it maps all refs (including remote-tracking branches, notes etc.) \
|
||||||
and sets up a refspec configuration such that all these refs are overwritten by a git remote update in the target repository.
|
and sets up a refspec configuration such that all these refs are overwritten by a git remote update in the target repository.
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
||||||
import org.eclipse.jgit.pgm.internal.CLIText;
|
import org.eclipse.jgit.pgm.internal.CLIText;
|
||||||
|
@ -69,6 +70,20 @@ void ffonly(@SuppressWarnings("unused") final boolean ignored) {
|
||||||
@Option(name = "-m", usage = "usage_message")
|
@Option(name = "-m", usage = "usage_message")
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy = null;
|
||||||
|
|
||||||
|
@Option(name = "--strategy-option", aliases = { "-X" },
|
||||||
|
metaVar = "metaVar_extraArgument", usage = "usage_extraArgument")
|
||||||
|
void extraArg(String name) {
|
||||||
|
if (ContentMergeStrategy.OURS.name().equalsIgnoreCase(name)) {
|
||||||
|
contentStrategy = ContentMergeStrategy.OURS;
|
||||||
|
} else if (ContentMergeStrategy.THEIRS.name().equalsIgnoreCase(name)) {
|
||||||
|
contentStrategy = ContentMergeStrategy.THEIRS;
|
||||||
|
} else {
|
||||||
|
throw die(MessageFormat.format(CLIText.get().unknownExtraArgument, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
protected void run() {
|
protected void run() {
|
||||||
|
@ -96,8 +111,11 @@ protected void run() {
|
||||||
Ref oldHead = getOldHead();
|
Ref oldHead = getOldHead();
|
||||||
MergeResult result;
|
MergeResult result;
|
||||||
try (Git git = new Git(db)) {
|
try (Git git = new Git(db)) {
|
||||||
MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
|
MergeCommand mergeCmd = git.merge()
|
||||||
.setSquash(squash).setFastForward(ff)
|
.setStrategy(mergeStrategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
|
.setSquash(squash)
|
||||||
|
.setFastForward(ff)
|
||||||
.setCommit(!noCommit);
|
.setCommit(!noCommit);
|
||||||
if (srcRef != null) {
|
if (srcRef != null) {
|
||||||
mergeCmd.include(srcRef);
|
mergeCmd.include(srcRef);
|
||||||
|
|
|
@ -284,6 +284,7 @@ public static String fatalError(String message) {
|
||||||
/***/ public String tooManyRefsGiven;
|
/***/ public String tooManyRefsGiven;
|
||||||
/***/ public String treeIsRequired;
|
/***/ public String treeIsRequired;
|
||||||
/***/ public char[] unknownIoErrorStdout;
|
/***/ public char[] unknownIoErrorStdout;
|
||||||
|
/***/ public String unknownExtraArgument;
|
||||||
/***/ public String unknownMergeStrategy;
|
/***/ public String unknownMergeStrategy;
|
||||||
/***/ public String unknownSubcommand;
|
/***/ public String unknownSubcommand;
|
||||||
/***/ public String unmergedPaths;
|
/***/ public String unmergedPaths;
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ReflogReader;
|
import org.eclipse.jgit.lib.ReflogReader;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -193,7 +195,7 @@ public void testCherryPickConflictResolution() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCherryPickConflictResolutionNoCOmmit() throws Exception {
|
public void testCherryPickConflictResolutionNoCommit() throws Exception {
|
||||||
Git git = new Git(db);
|
Git git = new Git(db);
|
||||||
RevCommit sideCommit = prepareCherryPick(git);
|
RevCommit sideCommit = prepareCherryPick(git);
|
||||||
|
|
||||||
|
@ -279,6 +281,70 @@ public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCherryPickOurs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
RevCommit sideCommit = prepareCherryPick(git);
|
||||||
|
|
||||||
|
CherryPickResult result = git.cherryPick()
|
||||||
|
.include(sideCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.OURS)
|
||||||
|
.call();
|
||||||
|
assertEquals(CherryPickStatus.OK, result.getStatus());
|
||||||
|
|
||||||
|
String expected = "a(master)";
|
||||||
|
checkFile(new File(db.getWorkTree(), "a"), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCherryPickTheirs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
RevCommit sideCommit = prepareCherryPick(git);
|
||||||
|
|
||||||
|
CherryPickResult result = git.cherryPick()
|
||||||
|
.include(sideCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
assertEquals(CherryPickStatus.OK, result.getStatus());
|
||||||
|
|
||||||
|
String expected = "a(side)";
|
||||||
|
checkFile(new File(db.getWorkTree(), "a"), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCherryPickXours() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
RevCommit sideCommit = prepareCherryPickStrategyOption(git);
|
||||||
|
|
||||||
|
CherryPickResult result = git.cherryPick()
|
||||||
|
.include(sideCommit.getId())
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.OURS)
|
||||||
|
.call();
|
||||||
|
assertEquals(CherryPickStatus.OK, result.getStatus());
|
||||||
|
|
||||||
|
String expected = "a\nmaster\nc\nd\n";
|
||||||
|
checkFile(new File(db.getWorkTree(), "a"), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCherryPickXtheirs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
RevCommit sideCommit = prepareCherryPickStrategyOption(git);
|
||||||
|
|
||||||
|
CherryPickResult result = git.cherryPick()
|
||||||
|
.include(sideCommit.getId())
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
assertEquals(CherryPickStatus.OK, result.getStatus());
|
||||||
|
|
||||||
|
String expected = "a\nside\nc\nd\n";
|
||||||
|
checkFile(new File(db.getWorkTree(), "a"), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCherryPickConflictMarkers() throws Exception {
|
public void testCherryPickConflictMarkers() throws Exception {
|
||||||
try (Git git = new Git(db)) {
|
try (Git git = new Git(db)) {
|
||||||
|
@ -384,6 +450,31 @@ private RevCommit prepareCherryPick(Git git) throws Exception {
|
||||||
return sideCommit;
|
return sideCommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RevCommit prepareCherryPickStrategyOption(Git git)
|
||||||
|
throws Exception {
|
||||||
|
// create, add and commit file a
|
||||||
|
writeTrashFile("a", "a\nb\nc\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit firstMasterCommit = git.commit().setMessage("first master")
|
||||||
|
.call();
|
||||||
|
|
||||||
|
// create and checkout side branch
|
||||||
|
createBranch(firstMasterCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
// modify, add and commit file a
|
||||||
|
writeTrashFile("a", "a\nside\nc\nd\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit sideCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
// checkout master branch
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
// modify, add and commit file a
|
||||||
|
writeTrashFile("a", "a\nmaster\nc\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("second master").call();
|
||||||
|
return sideCommit;
|
||||||
|
}
|
||||||
|
|
||||||
private void doCherryPickAndCheckResult(final Git git,
|
private void doCherryPickAndCheckResult(final Git git,
|
||||||
final RevCommit sideCommit, final MergeFailureReason reason)
|
final RevCommit sideCommit, final MergeFailureReason reason)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
import static org.eclipse.jgit.lib.Constants.R_HEADS;
|
import static org.eclipse.jgit.lib.Constants.R_HEADS;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
|
|
||||||
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
|
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
|
||||||
import org.eclipse.jgit.api.MergeResult.MergeStatus;
|
import org.eclipse.jgit.api.MergeResult.MergeStatus;
|
||||||
|
import org.eclipse.jgit.api.ResetCommand.ResetType;
|
||||||
import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
|
import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
|
@ -34,6 +36,7 @@
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
import org.eclipse.jgit.lib.Sets;
|
import org.eclipse.jgit.lib.Sets;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
@ -305,6 +308,200 @@ public void testContentMerge() throws Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContentMergeXtheirs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
writeTrashFile("c/c/c", "1\nc\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b")
|
||||||
|
.addFilepattern("c/c/c").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
|
||||||
|
writeTrashFile("a", "1\na(side)\n3\n4\n");
|
||||||
|
writeTrashFile("b", "1\nb(side)\n3\n4\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
assertEquals("1\nb(side)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
|
||||||
|
writeTrashFile("a", "1\na(main)\n3\n");
|
||||||
|
writeTrashFile("c/c/c", "1\nc(main)\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("c/c/c").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertEquals("1\na(side)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
|
assertEquals("1\nb(side)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertEquals("1\nc(main)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "c/c/c")));
|
||||||
|
|
||||||
|
assertNull(result.getConflicts());
|
||||||
|
|
||||||
|
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContentMergeXours() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
writeTrashFile("c/c/c", "1\nc\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b")
|
||||||
|
.addFilepattern("c/c/c").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
|
||||||
|
writeTrashFile("a", "1\na(side)\n3\n4\n");
|
||||||
|
writeTrashFile("b", "1\nb(side)\n3\n4\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
assertEquals("1\nb(side)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
|
||||||
|
writeTrashFile("a", "1\na(main)\n3\n");
|
||||||
|
writeTrashFile("c/c/c", "1\nc(main)\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("c/c/c").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.OURS).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertEquals("1\na(main)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
|
assertEquals("1\nb(side)\n3\n4\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertEquals("1\nc(main)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "c/c/c")));
|
||||||
|
|
||||||
|
assertNull(result.getConflicts());
|
||||||
|
|
||||||
|
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBinaryContentMerge() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile(".gitattributes", "a binary");
|
||||||
|
writeTrashFile("a", "initial");
|
||||||
|
git.add().addFilepattern(".").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
|
||||||
|
writeTrashFile("a", "side");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
|
||||||
|
writeTrashFile("a", "main");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE).call();
|
||||||
|
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertEquals("main", read(new File(db.getWorkTree(), "a")));
|
||||||
|
|
||||||
|
// Hmmm... there doesn't seem to be a way to figure out which files
|
||||||
|
// had a binary conflict from a MergeResult...
|
||||||
|
|
||||||
|
assertEquals(RepositoryState.MERGING, db.getRepositoryState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBinaryContentMergeXtheirs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile(".gitattributes", "a binary");
|
||||||
|
writeTrashFile("a", "initial");
|
||||||
|
git.add().addFilepattern(".").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
|
||||||
|
writeTrashFile("a", "side");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
|
||||||
|
writeTrashFile("a", "main");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertEquals("side", read(new File(db.getWorkTree(), "a")));
|
||||||
|
|
||||||
|
assertNull(result.getConflicts());
|
||||||
|
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBinaryContentMergeXours() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile(".gitattributes", "a binary");
|
||||||
|
writeTrashFile("a", "initial");
|
||||||
|
git.add().addFilepattern(".").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
|
||||||
|
writeTrashFile("a", "side");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
|
||||||
|
writeTrashFile("a", "main");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(ContentMergeStrategy.OURS).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertEquals("main", read(new File(db.getWorkTree(), "a")));
|
||||||
|
|
||||||
|
assertNull(result.getConflicts());
|
||||||
|
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMergeTag() throws Exception {
|
public void testMergeTag() throws Exception {
|
||||||
try (Git git = new Git(db)) {
|
try (Git git = new Git(db)) {
|
||||||
|
@ -774,6 +971,51 @@ public void testDeletionAndConflict() throws Exception {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletionOnMasterConflict() throws Exception {
|
public void testDeletionOnMasterConflict() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
// create side branch and modify "a"
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
writeTrashFile("a", "1\na(side)\n3\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
// delete a on master to generate conflict
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
git.rm().addFilepattern("a").call();
|
||||||
|
RevCommit thirdCommit = git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
for (ContentMergeStrategy contentStrategy : ContentMergeStrategy
|
||||||
|
.values()) {
|
||||||
|
// merge side with master
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
|
.call();
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(),
|
||||||
|
MergeStatus.CONFLICTING, result.getMergeStatus());
|
||||||
|
|
||||||
|
// result should be 'a' conflicting with workspace content from
|
||||||
|
// side
|
||||||
|
assertTrue("merge -X " + contentStrategy.name(),
|
||||||
|
new File(db.getWorkTree(), "a").exists());
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(),
|
||||||
|
"1\na(side)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(), "1\nb\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
git.reset().setMode(ResetType.HARD).setRef(thirdCommit.name())
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletionOnMasterTheirs() throws Exception {
|
||||||
try (Git git = new Git(db)) {
|
try (Git git = new Git(db)) {
|
||||||
writeTrashFile("a", "1\na\n3\n");
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
writeTrashFile("b", "1\nb\n3\n");
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
@ -794,18 +1036,102 @@ public void testDeletionOnMasterConflict() throws Exception {
|
||||||
|
|
||||||
// merge side with master
|
// merge side with master
|
||||||
MergeResult result = git.merge().include(secondCommit.getId())
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
.setStrategy(MergeStrategy.RESOLVE).call();
|
.setStrategy(MergeStrategy.THEIRS)
|
||||||
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
|
.call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
// result should be 'a' conflicting with workspace content from side
|
// result should be 'a'
|
||||||
assertTrue(new File(db.getWorkTree(), "a").exists());
|
assertTrue(new File(db.getWorkTree(), "a").exists());
|
||||||
assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a")));
|
assertEquals("1\na(side)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletionOnMasterOurs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
// create side branch and modify "a"
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
writeTrashFile("a", "1\na(side)\n3\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
// delete a on master to generate conflict
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
git.rm().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
// merge side with master
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.OURS).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertFalse(new File(db.getWorkTree(), "a").exists());
|
||||||
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletionOnSideConflict() throws Exception {
|
public void testDeletionOnSideConflict() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
// create side branch and delete "a"
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
git.rm().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
// update a on master to generate conflict
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
writeTrashFile("a", "1\na(main)\n3\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
RevCommit thirdCommit = git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
for (ContentMergeStrategy contentStrategy : ContentMergeStrategy
|
||||||
|
.values()) {
|
||||||
|
// merge side with master
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.RESOLVE)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
|
.call();
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(),
|
||||||
|
MergeStatus.CONFLICTING, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertTrue("merge -X " + contentStrategy.name(),
|
||||||
|
new File(db.getWorkTree(), "a").exists());
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(),
|
||||||
|
"1\na(main)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(), "1\nb\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "b")));
|
||||||
|
|
||||||
|
assertNotNull("merge -X " + contentStrategy.name(),
|
||||||
|
result.getConflicts());
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(), 1,
|
||||||
|
result.getConflicts().size());
|
||||||
|
assertEquals("merge -X " + contentStrategy.name(), 3,
|
||||||
|
result.getConflicts().get("a")[0].length);
|
||||||
|
git.reset().setMode(ResetType.HARD).setRef(thirdCommit.name())
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletionOnSideTheirs() throws Exception {
|
||||||
try (Git git = new Git(db)) {
|
try (Git git = new Git(db)) {
|
||||||
writeTrashFile("a", "1\na\n3\n");
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
writeTrashFile("b", "1\nb\n3\n");
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
@ -826,15 +1152,45 @@ public void testDeletionOnSideConflict() throws Exception {
|
||||||
|
|
||||||
// merge side with master
|
// merge side with master
|
||||||
MergeResult result = git.merge().include(secondCommit.getId())
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
.setStrategy(MergeStrategy.RESOLVE).call();
|
.setStrategy(MergeStrategy.THEIRS).call();
|
||||||
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
|
assertFalse(new File(db.getWorkTree(), "a").exists());
|
||||||
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletionOnSideOurs() throws Exception {
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
writeTrashFile("a", "1\na\n3\n");
|
||||||
|
writeTrashFile("b", "1\nb\n3\n");
|
||||||
|
git.add().addFilepattern("a").addFilepattern("b").call();
|
||||||
|
RevCommit initialCommit = git.commit().setMessage("initial").call();
|
||||||
|
|
||||||
|
// create side branch and delete "a"
|
||||||
|
createBranch(initialCommit, "refs/heads/side");
|
||||||
|
checkoutBranch("refs/heads/side");
|
||||||
|
git.rm().addFilepattern("a").call();
|
||||||
|
RevCommit secondCommit = git.commit().setMessage("side").call();
|
||||||
|
|
||||||
|
// update a on master to generate conflict
|
||||||
|
checkoutBranch("refs/heads/master");
|
||||||
|
writeTrashFile("a", "1\na(main)\n3\n");
|
||||||
|
git.add().addFilepattern("a").call();
|
||||||
|
git.commit().setMessage("main").call();
|
||||||
|
|
||||||
|
// merge side with master
|
||||||
|
MergeResult result = git.merge().include(secondCommit.getId())
|
||||||
|
.setStrategy(MergeStrategy.OURS).call();
|
||||||
|
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
|
||||||
|
|
||||||
assertTrue(new File(db.getWorkTree(), "a").exists());
|
assertTrue(new File(db.getWorkTree(), "a").exists());
|
||||||
assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a")));
|
assertEquals("1\na(main)\n3\n",
|
||||||
|
read(new File(db.getWorkTree(), "a")));
|
||||||
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
assertEquals(1, result.getConflicts().size());
|
|
||||||
assertEquals(3, result.getConflicts().get("a")[0].length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevSort;
|
import org.eclipse.jgit.revwalk.RevSort;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
@ -153,6 +155,75 @@ public void testPullConflict() throws Exception {
|
||||||
.getRepositoryState());
|
.getRepositoryState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPullConflictTheirs() throws Exception {
|
||||||
|
PullResult res = target.pull().call();
|
||||||
|
// nothing to update since we don't have different data yet
|
||||||
|
assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
|
||||||
|
assertTrue(res.getMergeResult().getMergeStatus()
|
||||||
|
.equals(MergeStatus.ALREADY_UP_TO_DATE));
|
||||||
|
|
||||||
|
assertFileContentsEqual(targetFile, "Hello world");
|
||||||
|
|
||||||
|
// change the source file
|
||||||
|
writeToFile(sourceFile, "Source change");
|
||||||
|
source.add().addFilepattern("SomeFile.txt").call();
|
||||||
|
source.commit().setMessage("Source change in remote").call();
|
||||||
|
|
||||||
|
// change the target file
|
||||||
|
writeToFile(targetFile, "Target change");
|
||||||
|
target.add().addFilepattern("SomeFile.txt").call();
|
||||||
|
target.commit().setMessage("Target change in local").call();
|
||||||
|
|
||||||
|
res = target.pull().setStrategy(MergeStrategy.THEIRS).call();
|
||||||
|
|
||||||
|
assertTrue(res.isSuccessful());
|
||||||
|
assertFileContentsEqual(targetFile, "Source change");
|
||||||
|
assertEquals(RepositoryState.SAFE,
|
||||||
|
target.getRepository().getRepositoryState());
|
||||||
|
assertTrue(target.status().call().isClean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPullConflictXtheirs() throws Exception {
|
||||||
|
PullResult res = target.pull().call();
|
||||||
|
// nothing to update since we don't have different data yet
|
||||||
|
assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
|
||||||
|
assertTrue(res.getMergeResult().getMergeStatus()
|
||||||
|
.equals(MergeStatus.ALREADY_UP_TO_DATE));
|
||||||
|
|
||||||
|
assertFileContentsEqual(targetFile, "Hello world");
|
||||||
|
|
||||||
|
// change the source file
|
||||||
|
writeToFile(sourceFile, "a\nHello\nb\n");
|
||||||
|
source.add().addFilepattern("SomeFile.txt").call();
|
||||||
|
source.commit().setMessage("Multi-line change in remote").call();
|
||||||
|
|
||||||
|
// Pull again
|
||||||
|
res = target.pull().call();
|
||||||
|
assertTrue(res.isSuccessful());
|
||||||
|
assertFileContentsEqual(targetFile, "a\nHello\nb\n");
|
||||||
|
|
||||||
|
// change the source file
|
||||||
|
writeToFile(sourceFile, "a\nSource change\nb\n");
|
||||||
|
source.add().addFilepattern("SomeFile.txt").call();
|
||||||
|
source.commit().setMessage("Source change in remote").call();
|
||||||
|
|
||||||
|
// change the target file
|
||||||
|
writeToFile(targetFile, "a\nTarget change\nb\nc\n");
|
||||||
|
target.add().addFilepattern("SomeFile.txt").call();
|
||||||
|
target.commit().setMessage("Target change in local").call();
|
||||||
|
|
||||||
|
res = target.pull().setContentMergeStrategy(ContentMergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
|
||||||
|
assertTrue(res.isSuccessful());
|
||||||
|
assertFileContentsEqual(targetFile, "a\nSource change\nb\nc\n");
|
||||||
|
assertEquals(RepositoryState.SAFE,
|
||||||
|
target.getRepository().getRepositoryState());
|
||||||
|
assertTrue(target.status().call().isClean());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPullWithUntrackedStash() throws Exception {
|
public void testPullWithUntrackedStash() throws Exception {
|
||||||
target.pull().call();
|
target.pull().call();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2012, GitHub Inc. and others
|
* Copyright (C) 2012, 2021 GitHub Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -28,6 +28,8 @@
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -426,6 +428,135 @@ public void stashedContentMerge() throws Exception {
|
||||||
read(PATH));
|
read(PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stashedContentMergeXtheirs() throws Exception {
|
||||||
|
writeTrashFile(PATH, "content\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("more content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nhead change\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("even content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
|
||||||
|
|
||||||
|
RevCommit stashed = git.stashCreate().call();
|
||||||
|
assertNotNull(stashed);
|
||||||
|
assertEquals("content\nhead change\nmore content\n",
|
||||||
|
read(committedFile));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("committed change").call();
|
||||||
|
recorder.assertNoEvent();
|
||||||
|
|
||||||
|
git.stashApply().setContentMergeStrategy(ContentMergeStrategy.THEIRS)
|
||||||
|
.call();
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
Status status = new StatusCommand(db).call();
|
||||||
|
assertEquals('[' + PATH + ']', status.getModified().toString());
|
||||||
|
assertEquals(
|
||||||
|
"content\nstashed change\nmore content\ncommitted change\n",
|
||||||
|
read(PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stashedContentMergeXours() throws Exception {
|
||||||
|
writeTrashFile(PATH, "content\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("more content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nhead change\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("even content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
|
||||||
|
|
||||||
|
RevCommit stashed = git.stashCreate().call();
|
||||||
|
assertNotNull(stashed);
|
||||||
|
assertEquals("content\nhead change\nmore content\n",
|
||||||
|
read(committedFile));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
|
||||||
|
writeTrashFile(PATH,
|
||||||
|
"content\nnew head\nmore content\ncommitted change\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("committed change").call();
|
||||||
|
recorder.assertNoEvent();
|
||||||
|
|
||||||
|
git.stashApply().setContentMergeStrategy(ContentMergeStrategy.OURS)
|
||||||
|
.call();
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
assertEquals("content\nnew head\nmore content\ncommitted change\n",
|
||||||
|
read(PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stashedContentMergeTheirs() throws Exception {
|
||||||
|
writeTrashFile(PATH, "content\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("more content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nhead change\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("even content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
|
||||||
|
|
||||||
|
RevCommit stashed = git.stashCreate().call();
|
||||||
|
assertNotNull(stashed);
|
||||||
|
assertEquals("content\nhead change\nmore content\n",
|
||||||
|
read(committedFile));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("committed change").call();
|
||||||
|
recorder.assertNoEvent();
|
||||||
|
|
||||||
|
git.stashApply().setStrategy(MergeStrategy.THEIRS).call();
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
Status status = new StatusCommand(db).call();
|
||||||
|
assertEquals('[' + PATH + ']', status.getModified().toString());
|
||||||
|
assertEquals("content\nstashed change\nmore content\n", read(PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stashedContentMergeOurs() throws Exception {
|
||||||
|
writeTrashFile(PATH, "content\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("more content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nhead change\nmore content\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("even content").call();
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
|
||||||
|
|
||||||
|
RevCommit stashed = git.stashCreate().call();
|
||||||
|
assertNotNull(stashed);
|
||||||
|
assertEquals("content\nhead change\nmore content\n",
|
||||||
|
read(committedFile));
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
|
||||||
|
|
||||||
|
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
|
||||||
|
git.add().addFilepattern(PATH).call();
|
||||||
|
git.commit().setMessage("committed change").call();
|
||||||
|
recorder.assertNoEvent();
|
||||||
|
|
||||||
|
// Doesn't make any sense... should be a no-op
|
||||||
|
git.stashApply().setStrategy(MergeStrategy.OURS).call();
|
||||||
|
recorder.assertNoEvent();
|
||||||
|
assertTrue(git.status().call().isClean());
|
||||||
|
assertEquals("content\nmore content\ncommitted change\n", read(PATH));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stashedApplyOnOtherBranch() throws Exception {
|
public void stashedApplyOnOtherBranch() throws Exception {
|
||||||
writeTrashFile(PATH, "content\nmore content\n");
|
writeTrashFile(PATH, "content\nmore content\n");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
|
* Copyright (C) 2010, 2021 Christian Halstrick <christian.halstrick@sap.com> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
|
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
@ -35,9 +36,12 @@
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.Ref.Storage;
|
import org.eclipse.jgit.lib.Ref.Storage;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeMessageFormatter;
|
import org.eclipse.jgit.merge.MergeMessageFormatter;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.Merger;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger;
|
import org.eclipse.jgit.merge.ResolveMerger;
|
||||||
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
|
@ -61,6 +65,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
|
||||||
|
|
||||||
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy;
|
||||||
|
|
||||||
private Integer mainlineParentNumber;
|
private Integer mainlineParentNumber;
|
||||||
|
|
||||||
private boolean noCommit = false;
|
private boolean noCommit = false;
|
||||||
|
@ -121,16 +127,30 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
|
||||||
String cherryPickName = srcCommit.getId().abbreviate(7).name()
|
String cherryPickName = srcCommit.getId().abbreviate(7).name()
|
||||||
+ " " + srcCommit.getShortMessage(); //$NON-NLS-1$
|
+ " " + srcCommit.getShortMessage(); //$NON-NLS-1$
|
||||||
|
|
||||||
ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
|
Merger merger = strategy.newMerger(repo);
|
||||||
merger.setWorkingTreeIterator(new FileTreeIterator(repo));
|
merger.setProgressMonitor(monitor);
|
||||||
merger.setBase(srcParent.getTree());
|
boolean noProblems;
|
||||||
merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$
|
Map<String, MergeFailureReason> failingPaths = null;
|
||||||
cherryPickName });
|
List<String> unmergedPaths = null;
|
||||||
if (merger.merge(newHead, srcCommit)) {
|
if (merger instanceof ResolveMerger) {
|
||||||
if (!merger.getModifiedFiles().isEmpty()) {
|
ResolveMerger resolveMerger = (ResolveMerger) merger;
|
||||||
|
resolveMerger.setContentMergeStrategy(contentStrategy);
|
||||||
|
resolveMerger.setCommitNames(
|
||||||
|
new String[] { "BASE", ourName, cherryPickName }); //$NON-NLS-1$
|
||||||
|
resolveMerger
|
||||||
|
.setWorkingTreeIterator(new FileTreeIterator(repo));
|
||||||
|
resolveMerger.setBase(srcParent.getTree());
|
||||||
|
noProblems = merger.merge(newHead, srcCommit);
|
||||||
|
failingPaths = resolveMerger.getFailingPaths();
|
||||||
|
unmergedPaths = resolveMerger.getUnmergedPaths();
|
||||||
|
if (!resolveMerger.getModifiedFiles().isEmpty()) {
|
||||||
repo.fireEvent(new WorkingTreeModifiedEvent(
|
repo.fireEvent(new WorkingTreeModifiedEvent(
|
||||||
merger.getModifiedFiles(), null));
|
resolveMerger.getModifiedFiles(), null));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
noProblems = merger.merge(newHead, srcCommit);
|
||||||
|
}
|
||||||
|
if (noProblems) {
|
||||||
if (AnyObjectId.isEqual(newHead.getTree().getId(),
|
if (AnyObjectId.isEqual(newHead.getTree().getId(),
|
||||||
merger.getResultTreeId())) {
|
merger.getResultTreeId())) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -153,24 +173,26 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
|
||||||
}
|
}
|
||||||
cherryPickedRefs.add(src);
|
cherryPickedRefs.add(src);
|
||||||
} else {
|
} else {
|
||||||
if (merger.failed()) {
|
if (failingPaths != null && !failingPaths.isEmpty()) {
|
||||||
return new CherryPickResult(merger.getFailingPaths());
|
return new CherryPickResult(failingPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
// there are merge conflicts
|
// there are merge conflicts
|
||||||
|
|
||||||
String message = new MergeMessageFormatter()
|
String message;
|
||||||
|
if (unmergedPaths != null) {
|
||||||
|
message = new MergeMessageFormatter()
|
||||||
.formatWithConflicts(srcCommit.getFullMessage(),
|
.formatWithConflicts(srcCommit.getFullMessage(),
|
||||||
merger.getUnmergedPaths());
|
unmergedPaths);
|
||||||
|
} else {
|
||||||
|
message = srcCommit.getFullMessage();
|
||||||
|
}
|
||||||
|
|
||||||
if (!noCommit) {
|
if (!noCommit) {
|
||||||
repo.writeCherryPickHead(srcCommit.getId());
|
repo.writeCherryPickHead(srcCommit.getId());
|
||||||
}
|
}
|
||||||
repo.writeMergeCommitMsg(message);
|
repo.writeMergeCommitMsg(message);
|
||||||
|
|
||||||
repo.fireEvent(new WorkingTreeModifiedEvent(
|
|
||||||
merger.getModifiedFiles(), null));
|
|
||||||
|
|
||||||
return CherryPickResult.CONFLICT;
|
return CherryPickResult.CONFLICT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,6 +312,22 @@ public CherryPickCommand setStrategy(MergeStrategy strategy) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 CherryPickCommand setContentMergeStrategy(
|
||||||
|
ContentMergeStrategy strategy) {
|
||||||
|
this.contentStrategy = strategy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the (1-based) parent number to diff against
|
* Set the (1-based) parent number to diff against
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
|
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
|
||||||
* Copyright (C) 2010-2014, Stefan Lay <stefan.lay@sap.com>
|
* Copyright (C) 2010, 2014, Stefan Lay <stefan.lay@sap.com>
|
||||||
* Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
* Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
import org.eclipse.jgit.lib.RefUpdate;
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeConfig;
|
import org.eclipse.jgit.merge.MergeConfig;
|
||||||
import org.eclipse.jgit.merge.MergeMessageFormatter;
|
import org.eclipse.jgit.merge.MergeMessageFormatter;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
@ -71,6 +72,8 @@ public class MergeCommand extends GitCommand<MergeResult> {
|
||||||
|
|
||||||
private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
|
private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy;
|
||||||
|
|
||||||
private List<Ref> commits = new LinkedList<>();
|
private List<Ref> commits = new LinkedList<>();
|
||||||
|
|
||||||
private Boolean squash;
|
private Boolean squash;
|
||||||
|
@ -320,6 +323,7 @@ public MergeResult call() throws GitAPIException, NoHeadException,
|
||||||
List<String> unmergedPaths = null;
|
List<String> unmergedPaths = null;
|
||||||
if (merger instanceof ResolveMerger) {
|
if (merger instanceof ResolveMerger) {
|
||||||
ResolveMerger resolveMerger = (ResolveMerger) merger;
|
ResolveMerger resolveMerger = (ResolveMerger) merger;
|
||||||
|
resolveMerger.setContentMergeStrategy(contentStrategy);
|
||||||
resolveMerger.setCommitNames(new String[] {
|
resolveMerger.setCommitNames(new String[] {
|
||||||
"BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$
|
"BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo));
|
resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo));
|
||||||
|
@ -472,6 +476,22 @@ public MergeCommand setStrategy(MergeStrategy mergeStrategy) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Reference to a commit to be merged with the current head
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
|
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
|
||||||
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
|
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
|
||||||
* Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
* Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
|
import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
@ -69,6 +70,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
|
||||||
|
|
||||||
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy;
|
||||||
|
|
||||||
private TagOpt tagOption;
|
private TagOpt tagOption;
|
||||||
|
|
||||||
private FastForwardMode fastForwardMode;
|
private FastForwardMode fastForwardMode;
|
||||||
|
@ -275,8 +278,7 @@ public PullResult call() throws GitAPIException,
|
||||||
JGitText.get().pullTaskName));
|
JGitText.get().pullTaskName));
|
||||||
|
|
||||||
// we check the updates to see which of the updated branches
|
// we check the updates to see which of the updated branches
|
||||||
// corresponds
|
// corresponds to the remote branch name
|
||||||
// to the remote branch name
|
|
||||||
AnyObjectId commitToMerge;
|
AnyObjectId commitToMerge;
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
Ref r = null;
|
Ref r = null;
|
||||||
|
@ -354,8 +356,11 @@ public PullResult call() throws GitAPIException,
|
||||||
}
|
}
|
||||||
RebaseCommand rebase = new RebaseCommand(repo);
|
RebaseCommand rebase = new RebaseCommand(repo);
|
||||||
RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
|
RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
|
||||||
.setUpstreamName(upstreamName).setProgressMonitor(monitor)
|
.setProgressMonitor(monitor)
|
||||||
.setOperation(Operation.BEGIN).setStrategy(strategy)
|
.setUpstreamName(upstreamName)
|
||||||
|
.setOperation(Operation.BEGIN)
|
||||||
|
.setStrategy(strategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
.setPreserveMerges(
|
.setPreserveMerges(
|
||||||
pullRebaseMode == BranchRebaseMode.PRESERVE)
|
pullRebaseMode == BranchRebaseMode.PRESERVE)
|
||||||
.call();
|
.call();
|
||||||
|
@ -363,7 +368,9 @@ public PullResult call() throws GitAPIException,
|
||||||
} else {
|
} else {
|
||||||
MergeCommand merge = new MergeCommand(repo);
|
MergeCommand merge = new MergeCommand(repo);
|
||||||
MergeResult mergeRes = merge.include(upstreamName, commitToMerge)
|
MergeResult mergeRes = merge.include(upstreamName, commitToMerge)
|
||||||
.setStrategy(strategy).setProgressMonitor(monitor)
|
.setProgressMonitor(monitor)
|
||||||
|
.setStrategy(strategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
.setFastForward(getFastForwardMode()).call();
|
.setFastForward(getFastForwardMode()).call();
|
||||||
monitor.update(1);
|
monitor.update(1);
|
||||||
result = new PullResult(fetchRes, remote, mergeRes);
|
result = new PullResult(fetchRes, remote, mergeRes);
|
||||||
|
@ -441,6 +448,21 @@ public PullCommand setStrategy(MergeStrategy strategy) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Set the specification of annotated tag behavior during fetch
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com>
|
* Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com>
|
||||||
* Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
* Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -65,6 +65,7 @@
|
||||||
import org.eclipse.jgit.lib.RefUpdate;
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevSort;
|
import org.eclipse.jgit.revwalk.RevSort;
|
||||||
|
@ -212,6 +213,8 @@ public enum Operation {
|
||||||
|
|
||||||
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy;
|
||||||
|
|
||||||
private boolean preserveMerges = false;
|
private boolean preserveMerges = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -501,8 +504,11 @@ private RebaseResult cherryPickCommitFlattening(RevCommit commitToPick)
|
||||||
String ourCommitName = getOurCommitName();
|
String ourCommitName = getOurCommitName();
|
||||||
try (Git git = new Git(repo)) {
|
try (Git git = new Git(repo)) {
|
||||||
CherryPickResult cherryPickResult = git.cherryPick()
|
CherryPickResult cherryPickResult = git.cherryPick()
|
||||||
.include(commitToPick).setOurCommitName(ourCommitName)
|
.include(commitToPick)
|
||||||
.setReflogPrefix(REFLOG_PREFIX).setStrategy(strategy)
|
.setOurCommitName(ourCommitName)
|
||||||
|
.setReflogPrefix(REFLOG_PREFIX)
|
||||||
|
.setStrategy(strategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
.call();
|
.call();
|
||||||
switch (cherryPickResult.getStatus()) {
|
switch (cherryPickResult.getStatus()) {
|
||||||
case FAILED:
|
case FAILED:
|
||||||
|
@ -556,7 +562,8 @@ private RebaseResult cherryPickCommitPreservingMerges(RevCommit commitToPick)
|
||||||
.include(commitToPick)
|
.include(commitToPick)
|
||||||
.setOurCommitName(ourCommitName)
|
.setOurCommitName(ourCommitName)
|
||||||
.setReflogPrefix(REFLOG_PREFIX)
|
.setReflogPrefix(REFLOG_PREFIX)
|
||||||
.setStrategy(strategy);
|
.setStrategy(strategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy);
|
||||||
if (isMerge) {
|
if (isMerge) {
|
||||||
pickCommand.setMainlineParentNumber(1);
|
pickCommand.setMainlineParentNumber(1);
|
||||||
// We write a MERGE_HEAD and later commit explicitly
|
// We write a MERGE_HEAD and later commit explicitly
|
||||||
|
@ -592,6 +599,8 @@ private RebaseResult cherryPickCommitPreservingMerges(RevCommit commitToPick)
|
||||||
MergeCommand merge = git.merge()
|
MergeCommand merge = git.merge()
|
||||||
.setFastForward(MergeCommand.FastForwardMode.NO_FF)
|
.setFastForward(MergeCommand.FastForwardMode.NO_FF)
|
||||||
.setProgressMonitor(monitor)
|
.setProgressMonitor(monitor)
|
||||||
|
.setStrategy(strategy)
|
||||||
|
.setContentMergeStrategy(contentStrategy)
|
||||||
.setCommit(false);
|
.setCommit(false);
|
||||||
for (int i = 1; i < commitToPick.getParentCount(); i++)
|
for (int i = 1; i < commitToPick.getParentCount(); i++)
|
||||||
merge.include(newParents.get(i));
|
merge.include(newParents.get(i));
|
||||||
|
@ -1137,7 +1146,7 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RevCommit> calculatePickList(RevCommit headCommit)
|
private List<RevCommit> calculatePickList(RevCommit headCommit)
|
||||||
throws GitAPIException, NoHeadException, IOException {
|
throws IOException {
|
||||||
List<RevCommit> cherryPickList = new ArrayList<>();
|
List<RevCommit> cherryPickList = new ArrayList<>();
|
||||||
try (RevWalk r = new RevWalk(repo)) {
|
try (RevWalk r = new RevWalk(repo)) {
|
||||||
r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true);
|
r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true);
|
||||||
|
@ -1586,6 +1595,21 @@ public RebaseCommand setStrategy(MergeStrategy strategy) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 RebaseCommand setContentMergeStrategy(ContentMergeStrategy strategy) {
|
||||||
|
this.contentStrategy = strategy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to preserve merges during rebase
|
* Whether to preserve merges during rebase
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2012, 2017 GitHub Inc. and others
|
* Copyright (C) 2012, 2021 GitHub Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -38,7 +38,9 @@
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryState;
|
import org.eclipse.jgit.lib.RepositoryState;
|
||||||
|
import org.eclipse.jgit.merge.ContentMergeStrategy;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.Merger;
|
||||||
import org.eclipse.jgit.merge.ResolveMerger;
|
import org.eclipse.jgit.merge.ResolveMerger;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
|
@ -71,6 +73,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
|
||||||
|
|
||||||
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
|
||||||
|
|
||||||
|
private ContentMergeStrategy contentStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create command to apply the changes of a stashed commit
|
* Create command to apply the changes of a stashed commit
|
||||||
*
|
*
|
||||||
|
@ -166,16 +170,25 @@ public ObjectId call() throws GitAPIException,
|
||||||
if (restoreUntracked && stashCommit.getParentCount() == 3)
|
if (restoreUntracked && stashCommit.getParentCount() == 3)
|
||||||
untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2));
|
untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2));
|
||||||
|
|
||||||
ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
|
Merger merger = strategy.newMerger(repo);
|
||||||
merger.setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$
|
boolean mergeSucceeded;
|
||||||
|
if (merger instanceof ResolveMerger) {
|
||||||
|
ResolveMerger resolveMerger = (ResolveMerger) merger;
|
||||||
|
resolveMerger
|
||||||
|
.setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
"stash" }); //$NON-NLS-1$
|
"stash" }); //$NON-NLS-1$
|
||||||
merger.setBase(stashHeadCommit);
|
resolveMerger.setBase(stashHeadCommit);
|
||||||
merger.setWorkingTreeIterator(new FileTreeIterator(repo));
|
resolveMerger
|
||||||
boolean mergeSucceeded = merger.merge(headCommit, stashCommit);
|
.setWorkingTreeIterator(new FileTreeIterator(repo));
|
||||||
List<String> modifiedByMerge = merger.getModifiedFiles();
|
resolveMerger.setContentMergeStrategy(contentStrategy);
|
||||||
|
mergeSucceeded = resolveMerger.merge(headCommit, stashCommit);
|
||||||
|
List<String> modifiedByMerge = resolveMerger.getModifiedFiles();
|
||||||
if (!modifiedByMerge.isEmpty()) {
|
if (!modifiedByMerge.isEmpty()) {
|
||||||
repo.fireEvent(
|
repo.fireEvent(new WorkingTreeModifiedEvent(modifiedByMerge,
|
||||||
new WorkingTreeModifiedEvent(modifiedByMerge, null));
|
null));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mergeSucceeded = merger.merge(headCommit, stashCommit);
|
||||||
}
|
}
|
||||||
if (mergeSucceeded) {
|
if (mergeSucceeded) {
|
||||||
DirCache dc = repo.lockDirCache();
|
DirCache dc = repo.lockDirCache();
|
||||||
|
@ -184,11 +197,14 @@ public ObjectId call() throws GitAPIException,
|
||||||
dco.setFailOnConflict(true);
|
dco.setFailOnConflict(true);
|
||||||
dco.checkout(); // Ignoring failed deletes....
|
dco.checkout(); // Ignoring failed deletes....
|
||||||
if (restoreIndex) {
|
if (restoreIndex) {
|
||||||
ResolveMerger ixMerger = (ResolveMerger) strategy
|
Merger ixMerger = strategy.newMerger(repo, true);
|
||||||
.newMerger(repo, true);
|
if (ixMerger instanceof ResolveMerger) {
|
||||||
ixMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$
|
ResolveMerger resolveMerger = (ResolveMerger) ixMerger;
|
||||||
|
resolveMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$
|
||||||
"HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$
|
"HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$
|
||||||
ixMerger.setBase(stashHeadCommit);
|
resolveMerger.setBase(stashHeadCommit);
|
||||||
|
resolveMerger.setContentMergeStrategy(contentStrategy);
|
||||||
|
}
|
||||||
boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
|
boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
resetIndex(revWalk
|
resetIndex(revWalk
|
||||||
|
@ -200,16 +216,20 @@ public ObjectId call() throws GitAPIException,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (untrackedCommit != null) {
|
if (untrackedCommit != null) {
|
||||||
ResolveMerger untrackedMerger = (ResolveMerger) strategy
|
Merger untrackedMerger = strategy.newMerger(repo, true);
|
||||||
.newMerger(repo, true);
|
if (untrackedMerger instanceof ResolveMerger) {
|
||||||
untrackedMerger.setCommitNames(new String[] {
|
ResolveMerger resolveMerger = (ResolveMerger) untrackedMerger;
|
||||||
"null", "HEAD", "untracked files" }); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
|
resolveMerger.setCommitNames(new String[] { "null", "HEAD", //$NON-NLS-1$//$NON-NLS-2$
|
||||||
|
"untracked files" }); //$NON-NLS-1$
|
||||||
// There is no common base for HEAD & untracked files
|
// There is no common base for HEAD & untracked files
|
||||||
// because the commit for untracked files has no parent. If
|
// because the commit for untracked files has no parent.
|
||||||
// we use stashHeadCommit as common base (as in the other
|
// If we use stashHeadCommit as common base (as in the
|
||||||
// merges) we potentially report conflicts for files
|
// other merges) we potentially report conflicts for
|
||||||
// which are not even member of untracked files commit
|
// files which are not even member of untracked files
|
||||||
untrackedMerger.setBase(null);
|
// commit.
|
||||||
|
resolveMerger.setBase(null);
|
||||||
|
resolveMerger.setContentMergeStrategy(contentStrategy);
|
||||||
|
}
|
||||||
boolean ok = untrackedMerger.merge(headCommit,
|
boolean ok = untrackedMerger.merge(headCommit,
|
||||||
untrackedCommit);
|
untrackedCommit);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
@ -278,6 +298,23 @@ public StashApplyCommand setStrategy(MergeStrategy strategy) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 StashApplyCommand setContentMergeStrategy(
|
||||||
|
ContentMergeStrategy strategy) {
|
||||||
|
checkCallable();
|
||||||
|
this.contentStrategy = strategy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the command should restore untracked files
|
* Whether the command should restore untracked files
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.merge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to handle content conflicts.
|
||||||
|
*
|
||||||
|
* @since 5.12
|
||||||
|
*/
|
||||||
|
public enum ContentMergeStrategy {
|
||||||
|
|
||||||
|
/** Produce a conflict. */
|
||||||
|
CONFLICT,
|
||||||
|
|
||||||
|
/** Resolve the conflict hunk using the ours version. */
|
||||||
|
OURS,
|
||||||
|
|
||||||
|
/** Resolve the conflict hunk using the theirs version. */
|
||||||
|
THEIRS
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.diff.DiffAlgorithm;
|
import org.eclipse.jgit.diff.DiffAlgorithm;
|
||||||
import org.eclipse.jgit.diff.Edit;
|
import org.eclipse.jgit.diff.Edit;
|
||||||
import org.eclipse.jgit.diff.EditList;
|
import org.eclipse.jgit.diff.EditList;
|
||||||
|
@ -28,8 +29,12 @@
|
||||||
* diff algorithm.
|
* diff algorithm.
|
||||||
*/
|
*/
|
||||||
public final class MergeAlgorithm {
|
public final class MergeAlgorithm {
|
||||||
|
|
||||||
private final DiffAlgorithm diffAlg;
|
private final DiffAlgorithm diffAlg;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ContentMergeStrategy strategy = ContentMergeStrategy.CONFLICT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new MergeAlgorithm which uses
|
* Creates a new MergeAlgorithm which uses
|
||||||
* {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm
|
* {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm
|
||||||
|
@ -48,6 +53,30 @@ public MergeAlgorithm(DiffAlgorithm diff) {
|
||||||
this.diffAlg = diff;
|
this.diffAlg = diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the {@link ContentMergeStrategy}.
|
||||||
|
*
|
||||||
|
* @return the {@link ContentMergeStrategy} in effect
|
||||||
|
* @since 5.12
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public ContentMergeStrategy getContentMergeStrategy() {
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ContentMergeStrategy}.
|
||||||
|
*
|
||||||
|
* @param strategy
|
||||||
|
* {@link ContentMergeStrategy} to set; if {@code null}, set
|
||||||
|
* {@link ContentMergeStrategy#CONFLICT}
|
||||||
|
* @since 5.12
|
||||||
|
*/
|
||||||
|
public void setContentMergeStrategy(ContentMergeStrategy strategy) {
|
||||||
|
this.strategy = strategy == null ? ContentMergeStrategy.CONFLICT
|
||||||
|
: strategy;
|
||||||
|
}
|
||||||
|
|
||||||
// An special edit which acts as a sentinel value by marking the end the
|
// An special edit which acts as a sentinel value by marking the end the
|
||||||
// list of edits
|
// list of edits
|
||||||
private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE,
|
private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE,
|
||||||
|
@ -79,29 +108,54 @@ public <S extends Sequence> MergeResult<S> merge(
|
||||||
if (theirs.size() != 0) {
|
if (theirs.size() != 0) {
|
||||||
EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
|
EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
|
||||||
if (!theirsEdits.isEmpty()) {
|
if (!theirsEdits.isEmpty()) {
|
||||||
// we deleted, they modified -> Let their complete content
|
// we deleted, they modified
|
||||||
// conflict with empty text
|
switch (strategy) {
|
||||||
result.add(1, 0, 0, ConflictState.FIRST_CONFLICTING_RANGE);
|
case OURS:
|
||||||
|
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
case THEIRS:
|
||||||
|
result.add(2, 0, theirs.size(),
|
||||||
|
ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Let their complete content conflict with empty text
|
||||||
|
result.add(1, 0, 0,
|
||||||
|
ConflictState.FIRST_CONFLICTING_RANGE);
|
||||||
result.add(2, 0, theirs.size(),
|
result.add(2, 0, theirs.size(),
|
||||||
ConflictState.NEXT_CONFLICTING_RANGE);
|
ConflictState.NEXT_CONFLICTING_RANGE);
|
||||||
} else
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// we deleted, they didn't modify -> Let our deletion win
|
// we deleted, they didn't modify -> Let our deletion win
|
||||||
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
|
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
|
||||||
} else
|
}
|
||||||
|
} else {
|
||||||
// we and they deleted -> return a single chunk of nothing
|
// we and they deleted -> return a single chunk of nothing
|
||||||
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
|
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
} else if (theirs.size() == 0) {
|
} else if (theirs.size() == 0) {
|
||||||
EditList oursEdits = diffAlg.diff(cmp, base, ours);
|
EditList oursEdits = diffAlg.diff(cmp, base, ours);
|
||||||
if (!oursEdits.isEmpty()) {
|
if (!oursEdits.isEmpty()) {
|
||||||
// we modified, they deleted -> Let our complete content
|
// we modified, they deleted
|
||||||
// conflict with empty text
|
switch (strategy) {
|
||||||
|
case OURS:
|
||||||
|
result.add(1, 0, ours.size(), ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
case THEIRS:
|
||||||
|
result.add(2, 0, 0, ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Let our complete content conflict with empty text
|
||||||
result.add(1, 0, ours.size(),
|
result.add(1, 0, ours.size(),
|
||||||
ConflictState.FIRST_CONFLICTING_RANGE);
|
ConflictState.FIRST_CONFLICTING_RANGE);
|
||||||
result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
|
result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
|
||||||
} else
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// they deleted, we didn't modify -> Let their deletion win
|
// they deleted, we didn't modify -> Let their deletion win
|
||||||
result.add(2, 0, 0, ConflictState.NO_CONFLICT);
|
result.add(2, 0, 0, ConflictState.NO_CONFLICT);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,12 +303,26 @@ public <S extends Sequence> MergeResult<S> merge(
|
||||||
|
|
||||||
// Add the conflict (Only if there is a conflict left to report)
|
// Add the conflict (Only if there is a conflict left to report)
|
||||||
if (minBSize > 0 || BSizeDelta != 0) {
|
if (minBSize > 0 || BSizeDelta != 0) {
|
||||||
result.add(1, oursBeginB + commonPrefix, oursEndB
|
switch (strategy) {
|
||||||
- commonSuffix,
|
case OURS:
|
||||||
|
result.add(1, oursBeginB + commonPrefix,
|
||||||
|
oursEndB - commonSuffix,
|
||||||
|
ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
case THEIRS:
|
||||||
|
result.add(2, theirsBeginB + commonPrefix,
|
||||||
|
theirsEndB - commonSuffix,
|
||||||
|
ConflictState.NO_CONFLICT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result.add(1, oursBeginB + commonPrefix,
|
||||||
|
oursEndB - commonSuffix,
|
||||||
ConflictState.FIRST_CONFLICTING_RANGE);
|
ConflictState.FIRST_CONFLICTING_RANGE);
|
||||||
result.add(2, theirsBeginB + commonPrefix, theirsEndB
|
result.add(2, theirsBeginB + commonPrefix,
|
||||||
- commonSuffix,
|
theirsEndB - commonSuffix,
|
||||||
ConflictState.NEXT_CONFLICTING_RANGE);
|
ConflictState.NEXT_CONFLICTING_RANGE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the common lines at end of conflict
|
// Add the common lines at end of conflict
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.attributes.Attributes;
|
import org.eclipse.jgit.attributes.Attributes;
|
||||||
import org.eclipse.jgit.diff.DiffAlgorithm;
|
import org.eclipse.jgit.diff.DiffAlgorithm;
|
||||||
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
|
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
|
||||||
|
@ -267,6 +268,13 @@ public enum MergeFailureReason {
|
||||||
*/
|
*/
|
||||||
private int inCoreLimit;
|
private int inCoreLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ContentMergeStrategy} to use for "resolve" and "recursive"
|
||||||
|
* merges.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps {@link CheckoutMetadata} for {@link #checkout()} and
|
* Keeps {@link CheckoutMetadata} for {@link #checkout()} and
|
||||||
* {@link #cleanUp()}.
|
* {@link #cleanUp()}.
|
||||||
|
@ -344,6 +352,29 @@ protected ResolveMerger(ObjectInserter inserter, Config config) {
|
||||||
dircache = DirCache.newInCore();
|
dircache = DirCache.newInCore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the content merge strategy for content conflicts.
|
||||||
|
*
|
||||||
|
* @return the {@link ContentMergeStrategy} in effect
|
||||||
|
* @since 5.12
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public ContentMergeStrategy getContentMergeStrategy() {
|
||||||
|
return contentStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the content merge strategy for content conflicts.
|
||||||
|
*
|
||||||
|
* @param strategy
|
||||||
|
* {@link ContentMergeStrategy} to use
|
||||||
|
* @since 5.12
|
||||||
|
*/
|
||||||
|
public void setContentMergeStrategy(ContentMergeStrategy strategy) {
|
||||||
|
contentStrategy = strategy == null ? ContentMergeStrategy.CONFLICT
|
||||||
|
: strategy;
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
protected boolean mergeImpl() throws IOException {
|
protected boolean mergeImpl() throws IOException {
|
||||||
|
@ -654,7 +685,8 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
|
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
|
||||||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
|
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
|
||||||
unmergedPaths.add(tw.getPathString());
|
unmergedPaths.add(tw.getPathString());
|
||||||
mergeResults.put(tw.getPathString(), new MergeResult<>(Collections.<RawText>emptyList()));
|
mergeResults.put(tw.getPathString(),
|
||||||
|
new MergeResult<>(Collections.emptyList()));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -760,6 +792,19 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
unmergedPaths.add(tw.getPathString());
|
unmergedPaths.add(tw.getPathString());
|
||||||
return true;
|
return true;
|
||||||
} else if (!attributes.canBeContentMerged()) {
|
} else if (!attributes.canBeContentMerged()) {
|
||||||
|
// File marked as binary
|
||||||
|
switch (getContentMergeStrategy()) {
|
||||||
|
case OURS:
|
||||||
|
keep(ourDce);
|
||||||
|
return true;
|
||||||
|
case THEIRS:
|
||||||
|
DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
|
||||||
|
DirCacheEntry.STAGE_0, EPOCH, 0);
|
||||||
|
addToCheckout(tw.getPathString(), theirEntry, attributes);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
|
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
|
||||||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
|
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
|
||||||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
|
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
|
||||||
|
@ -774,8 +819,26 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MergeResult<RawText> result = contentMerge(base, ours, theirs,
|
MergeResult<RawText> result = null;
|
||||||
attributes);
|
try {
|
||||||
|
result = contentMerge(base, ours, theirs, attributes,
|
||||||
|
getContentMergeStrategy());
|
||||||
|
} catch (BinaryBlobException e) {
|
||||||
|
switch (getContentMergeStrategy()) {
|
||||||
|
case OURS:
|
||||||
|
keep(ourDce);
|
||||||
|
return true;
|
||||||
|
case THEIRS:
|
||||||
|
DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
|
||||||
|
DirCacheEntry.STAGE_0, EPOCH, 0);
|
||||||
|
addToCheckout(tw.getPathString(), theirEntry, attributes);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
result = new MergeResult<>(Collections.emptyList());
|
||||||
|
result.setContainsConflicts(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ignoreConflicts) {
|
if (ignoreConflicts) {
|
||||||
result.setContainsConflicts(false);
|
result.setContainsConflicts(false);
|
||||||
}
|
}
|
||||||
|
@ -802,9 +865,16 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
mergeResults.put(tw.getPathString(), result);
|
mergeResults.put(tw.getPathString(), result);
|
||||||
unmergedPaths.add(tw.getPathString());
|
unmergedPaths.add(tw.getPathString());
|
||||||
} else {
|
} else {
|
||||||
MergeResult<RawText> result = contentMerge(base, ours,
|
// Content merge strategy does not apply to delete-modify
|
||||||
theirs, attributes);
|
// conflicts!
|
||||||
|
MergeResult<RawText> result;
|
||||||
|
try {
|
||||||
|
result = contentMerge(base, ours, theirs, attributes,
|
||||||
|
ContentMergeStrategy.CONFLICT);
|
||||||
|
} catch (BinaryBlobException e) {
|
||||||
|
result = new MergeResult<>(Collections.emptyList());
|
||||||
|
result.setContainsConflicts(true);
|
||||||
|
}
|
||||||
if (ignoreConflicts) {
|
if (ignoreConflicts) {
|
||||||
// In case a conflict is detected the working tree file
|
// In case a conflict is detected the working tree file
|
||||||
// is again filled with new content (containing conflict
|
// is again filled with new content (containing conflict
|
||||||
|
@ -866,32 +936,26 @@ private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
|
||||||
* @param ours
|
* @param ours
|
||||||
* @param theirs
|
* @param theirs
|
||||||
* @param attributes
|
* @param attributes
|
||||||
|
* @param strategy
|
||||||
*
|
*
|
||||||
* @return the result of the content merge
|
* @return the result of the content merge
|
||||||
|
* @throws BinaryBlobException
|
||||||
|
* if any of the blobs looks like a binary blob
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||||
Attributes attributes)
|
Attributes attributes, ContentMergeStrategy strategy)
|
||||||
throws IOException {
|
throws BinaryBlobException, IOException {
|
||||||
RawText baseText;
|
RawText baseText = base == null ? RawText.EMPTY_TEXT
|
||||||
RawText ourText;
|
: getRawText(base.getEntryObjectId(), attributes);
|
||||||
RawText theirsText;
|
RawText ourText = ours == null ? RawText.EMPTY_TEXT
|
||||||
|
: getRawText(ours.getEntryObjectId(), attributes);
|
||||||
try {
|
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
|
||||||
baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
|
: getRawText(theirs.getEntryObjectId(), attributes);
|
||||||
base.getEntryObjectId(), attributes);
|
mergeAlgorithm.setContentMergeStrategy(strategy);
|
||||||
ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
|
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
|
||||||
ours.getEntryObjectId(), attributes);
|
ourText, theirsText);
|
||||||
theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
|
|
||||||
theirs.getEntryObjectId(), attributes);
|
|
||||||
} catch (BinaryBlobException e) {
|
|
||||||
MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList());
|
|
||||||
r.setContainsConflicts(true);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
return (mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
|
|
||||||
ourText, theirsText));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isIndexDirty() {
|
private boolean isIndexDirty() {
|
||||||
|
|
Loading…
Reference in New Issue