From bf05108d0b1aa7253659237d6848abc72c5e185e Mon Sep 17 00:00:00 2001 From: Philipp Thun Date: Thu, 17 Mar 2011 10:48:44 +0100 Subject: [PATCH] Abort merge when file to be checked out is dirty In case a file needs to be checked out (from THEIRS) during a merge operation, it has to be checked if the worktree version of this file is dirty. If this is true, merge shall fail. Change-Id: I17c24845584700aad953c3d4f2bea77a0d665ec4 Signed-off-by: Philipp Thun --- .../eclipse/jgit/api/MergeCommandTest.java | 158 ++++++++++++++++++ .../org/eclipse/jgit/merge/ResolveMerger.java | 4 + 2 files changed, 162 insertions(+) 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 987e64731..2761989c7 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 @@ -638,6 +638,164 @@ public class MergeCommandTest extends RepositoryTestCase { assertEquals(RepositoryState.MERGING, db.getRepositoryState()); } + @Test + public void testSuccessfulMergeFailsDueToDirtyIndex() throws Exception { + Git git = new Git(db); + + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify and add file a + write(fileA, "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + + checkMergeFailedResult(result, indexState, fileA); + } + + @Test + public void testConflictingMergeFailsDueToDirtyIndex() throws Exception { + Git git = new Git(db); + + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + // modify file a - this will cause a conflict during merge + write(fileA, "a(master)"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify and add file a + write(fileA, "a(modified)"); + git.add().addFilepattern("a").call(); + // do not commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + + checkMergeFailedResult(result, indexState, fileA); + } + + @Test + public void testSuccessfulMergeFailsDueToDirtyWorktree() throws Exception { + Git git = new Git(db); + + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify file a + write(fileA, "a(modified)"); + // do not add and commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + + checkMergeFailedResult(result, indexState, fileA); + } + + @Test + public void testConflictingMergeFailsDueToDirtyWorktree() throws Exception { + Git git = new Git(db); + + File fileA = writeTrashFile("a", "a"); + RevCommit initialCommit = addAllAndCommit(git); + + // switch branch + createBranch(initialCommit, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + // modify file a + write(fileA, "a(side)"); + writeTrashFile("b", "b"); + RevCommit sideCommit = addAllAndCommit(git); + + // switch branch + checkoutBranch("refs/heads/master"); + // modify file a - this will cause a conflict during merge + write(fileA, "a(master)"); + writeTrashFile("c", "c"); + addAllAndCommit(git); + + // modify file a + write(fileA, "a(modified)"); + // do not add and commit + + // get current index state + String indexState = indexState(CONTENT); + + // merge + MergeResult result = git.merge().include(sideCommit.getId()) + .setStrategy(MergeStrategy.RESOLVE).call(); + + checkMergeFailedResult(result, indexState, fileA); + } + + private RevCommit addAllAndCommit(final Git git) throws Exception { + git.add().addFilepattern(".").call(); + return git.commit().setMessage("message").call(); + } + + private void checkMergeFailedResult(final MergeResult result, + final String indexState, final File fileA) throws Exception { + assertEquals(MergeStatus.FAILED, result.getMergeStatus()); + assertEquals("a(modified)", read(fileA)); + assertFalse(new File(db.getWorkTree(), "b").exists()); + assertEquals("c", read(new File(db.getWorkTree(), "c"))); + assertEquals(indexState, indexState(CONTENT)); + assertEquals(null, result.getConflicts()); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } + private void createBranch(ObjectId objectId, String branchName) throws IOException { RefUpdate updateRef = db.updateRef(branchName); updateRef.setNewObjectId(objectId); 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 a055131d6..5e99fc00f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -393,6 +393,10 @@ public class ResolveMerger extends ThreeWayMerger { if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) { // OURS was not changed compared to BASE. All changes must be in // THEIRS. THEIRS is chosen. + + // Check worktree before checking out THEIRS + if (isWorktreeDirty()) + return false; if (nonTree(modeT)) { DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0);