From 98b019f4bdc45bd122e4d81009c4e814350e051c Mon Sep 17 00:00:00 2001 From: Bernard Leach Date: Fri, 29 Apr 2011 19:37:00 +1000 Subject: [PATCH] Implemented merge for parallel delete/modification Duplicates cgit behaviour for merging the case where OURS is deleted and THEIRS is modified as well as OURS is modified and THEIRS id deleted. Change-Id: Ia2ab4f8dc95020f2914ff01c2bf3b1bc62a9d45d Signed-off-by: Chris Aniszczyk --- .../eclipse/jgit/api/MergeCommandTest.java | 63 +++++++++++++++++++ .../jgit/lib/DirCacheCheckoutTest.java | 2 +- .../org/eclipse/jgit/merge/ResolveMerger.java | 23 +++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index d5c00257b..9e7588234 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java @@ -587,6 +587,69 @@ public class MergeCommandTest extends RepositoryTestCase { assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d"))); } + @Test + public void testDeletionOnMasterConflict() throws Exception { + 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.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + // result should be 'a' conflicting with workspace content from side + assertTrue(new File(db.getWorkTree(), "a").exists()); + assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + } + + @Test + public void testDeletionOnSideConflict() throws Exception { + 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.RESOLVE).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + + assertTrue(new File(db.getWorkTree(), "a").exists()); + assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a"))); + assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b"))); + } + @Test public void testMergeFailingWithDirtyWorkingTree() throws Exception { Git git = new Git(db); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 160eb653b..9fb4b3e17 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -138,7 +138,7 @@ public class DirCacheCheckoutTest extends ReadTreeTest { assertEquals(MergeStatus.CONFLICTING, git.merge().include(master) .call().getMergeStatus()); assertEquals( - "[E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]", + "[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]", indexState(0)); resetHard(master); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index cf969107d..875842758 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -452,6 +452,29 @@ public class ResolveMerger extends ThreeWayMerger { unmergedPaths.add(tw.getPathString()); } modifiedFiles.add(tw.getPathString()); + } else if (modeO != modeT) { + // OURS or THEIRS has been deleted + if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw + .idEqual(T_BASE, T_THEIRS)))) { + + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2); + DirCacheEntry e = add(tw.getRawPath(), theirs, + DirCacheEntry.STAGE_3); + + // OURS was deleted checkout THEIRS + if (modeO == 0) { + // Check worktree before checking out THEIRS + if (isWorktreeDirty()) + return false; + if (nonTree(modeT)) { + if (e != null) + toBeCheckedOut.put(tw.getPathString(), e); + } + } + + unmergedPaths.add(tw.getPathString()); + } } return true; }