Fix a number of failing conflict situations

Adds further tests where the working tree is dirty (differs from
index) and where we have staged but uncommitted changes.

Fixed the test case 9 for file/directory conflicts.

Bug: 428819
Change-Id: Ie44a288b052abe936ebb74272d0fefef3b218a7a
Signed-off-by: Axel Richard <axel.richard@obeo.fr>
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 5869d76..b467d2d 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -8,6 +8,8 @@
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 Import-Package: org.eclipse.jgit.api;version="[3.4.0,3.5.0)",
+ org.eclipse.jgit.api.errors;version="[3.4.0,3.5.0)",
+ org.eclipse.jgit.diff;version="[3.4.0,3.5.0)",
  org.eclipse.jgit.dircache;version="[3.4.0,3.5.0)",
  org.eclipse.jgit.junit;version="[3.4.0,3.5.0)",
  org.eclipse.jgit.lib;version="[3.4.0,3.5.0)",
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index 2c1f59f..9028473 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -43,15 +43,21 @@
 package org.eclipse.jgit.pgm;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.File;
+import java.util.List;
 
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
+import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -150,6 +156,7 @@ public void testCheckoutExistingBranchWithConflict() throws Exception {
 	 * <li>Delete file 'a' in the working tree
 	 * <li>Checkout branch '1'
 	 * </ol>
+	 * <p>
 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
 	 * checkout.
 	 *
@@ -181,6 +188,359 @@ public void testCheckoutWithMissingWorkingTreeFile() throws Exception {
 		assertEquals("Hello world a", read(fileA));
 	}
 
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add file 'b'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Add folder 'a'
+	 * <li>Commit
+	 * <li>Replace folder 'a' by file 'a' in the working tree
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
+	 * checkout.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestMissingThenFolderWithFileInWorkingTree()
+			throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add file b").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		File folderA = new File(db.getWorkTree(), "a");
+		FileUtils.mkdirs(folderA);
+		writeTrashFile("a/c", "Hello world c");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add folder a").call();
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+
+		FileUtils.delete(folderA, FileUtils.RECURSIVE);
+		writeTrashFile("a", "b");
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+		git.checkout().setName(branch_1.getName()).call();
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add file 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Replace file 'a' by folder 'a'
+	 * <li>Commit
+	 * <li>Delete folder 'a' in the working tree
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
+	 * checkout.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestFolderWithMissingInWorkingTree() throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("b", "Hello world b");
+		writeTrashFile("a", "b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add file b & file a").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		git.rm().addFilepattern("a").call();
+		File folderA = new File(db.getWorkTree(), "a");
+		FileUtils.mkdirs(folderA);
+		writeTrashFile("a/c", "Hello world c");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add folder a").call();
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+
+		FileUtils.delete(folderA, FileUtils.RECURSIVE);
+
+		git.checkout().setName(branch_1.getName()).call();
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add file 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Delete file 'a'
+	 * <li>Commit
+	 * <li>Add folder 'a' in the working tree
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The checkout command should raise an error. The conflicting paths are 'a'
+	 * and 'a/c'.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestMissingWithFolderInWorkingTree() throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("b", "Hello world b");
+		writeTrashFile("a", "b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add file b & file a").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		git.rm().addFilepattern("a").call();
+		git.commit().setMessage("delete file a").call();
+
+		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+		writeTrashFile("a/c", "Hello world c");
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+
+		CheckoutConflictException exception = null;
+		try {
+			git.checkout().setName(branch_1.getName()).call();
+		} catch (CheckoutConflictException e) {
+			exception = e;
+		}
+		assertNotNull(exception);
+		assertEquals(2, exception.getConflictingPaths().size());
+		assertEquals("a", exception.getConflictingPaths().get(0));
+		assertEquals("a/c", exception.getConflictingPaths().get(1));
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add folder 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Delete folder 'a'
+	 * <li>Commit
+	 * <li>Add file 'a' in the working tree
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The checkout command should raise an error. The conflicting path is 'a'.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestFolderThenMissingWithFileInWorkingTree()
+			throws Exception {
+		Git git = new Git(db);
+		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+		writeTrashFile("a/c", "Hello world c");
+		writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern(".").call();
+		RevCommit commit1 = git.commit().setMessage("add folder a & file b")
+				.call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		git.rm().addFilepattern("a").call();
+		RevCommit commit2 = git.commit().setMessage("delete folder a").call();
+
+		TreeWalk tw = new TreeWalk(db);
+		tw.addTree(commit1.getTree());
+		tw.addTree(commit2.getTree());
+		List<DiffEntry> scan = DiffEntry.scan(tw);
+		assertEquals(1, scan.size());
+		assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
+		assertEquals(FileMode.TREE, scan.get(0).getOldMode());
+
+		writeTrashFile("a", "b");
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+		CheckoutConflictException exception = null;
+		try {
+			git.checkout().setName(branch_1.getName()).call();
+		} catch (CheckoutConflictException e) {
+			exception = e;
+		}
+		assertNotNull(exception);
+		assertEquals(1, exception.getConflictingPaths().size());
+		assertEquals("a", exception.getConflictingPaths().get(0));
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add folder 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Replace folder 'a'by file 'a'
+	 * <li>Commit
+	 * <li>Delete file 'a' in the working tree
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The working tree should contain 'a' with FileMode.TREE after the
+	 * checkout.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestFolderThenFileWithMissingInWorkingTree()
+			throws Exception {
+		Git git = new Git(db);
+		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+		writeTrashFile("a/c", "Hello world c");
+		writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add folder a & file b").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		git.rm().addFilepattern("a").call();
+		File fileA = new File(db.getWorkTree(), "a");
+		writeTrashFile("a", "b");
+		git.add().addFilepattern("a").call();
+		git.commit().setMessage("add file a").call();
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+		FileUtils.delete(fileA);
+
+		git.checkout().setName(branch_1.getName()).call();
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add file 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Modify file 'a'
+	 * <li>Commit
+	 * <li>Delete file 'a' & replace by folder 'a' in the working tree & index
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The checkout command should raise an error. The conflicting path is 'a'.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestFileThenFileWithFolderInIndex() throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("a", "Hello world a");
+		writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add files a & b").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		writeTrashFile("a", "b");
+		git.add().addFilepattern("a").call();
+		git.commit().setMessage("add file a").call();
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+		git.rm().addFilepattern("a").call();
+		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+		writeTrashFile("a/c", "Hello world c");
+		git.add().addFilepattern(".").call();
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+
+		CheckoutConflictException exception = null;
+		try {
+			git.checkout().setName(branch_1.getName()).call();
+		} catch (CheckoutConflictException e) {
+			exception = e;
+		}
+		assertNotNull(exception);
+		assertEquals(1, exception.getConflictingPaths().size());
+		assertEquals("a", exception.getConflictingPaths().get(0));
+	}
+
+	/**
+	 * Steps:
+	 * <ol>
+	 * <li>Add file 'a'
+	 * <li>Commit
+	 * <li>Create branch '1'
+	 * <li>Modify file 'a'
+	 * <li>Commit
+	 * <li>Delete file 'a' & replace by folder 'a' in the working tree & index
+	 * <li>Checkout branch '1'
+	 * </ol>
+	 * <p>
+	 * The checkout command should raise an error. The conflicting paths are 'a'
+	 * and 'a/c'.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void fileModeTestFileWithFolderInIndex() throws Exception {
+		Git git = new Git(db);
+		writeTrashFile("b", "Hello world b");
+		writeTrashFile("a", "b");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("add file b & file a").call();
+		Ref branch_1 = git.branchCreate().setName("branch_1").call();
+		git.rm().addFilepattern("a").call();
+		writeTrashFile("a", "Hello world a");
+		git.add().addFilepattern("a").call();
+		git.commit().setMessage("add file a").call();
+
+		FileEntry entry = new FileTreeIterator.FileEntry(new File(
+				db.getWorkTree(), "a"), db.getFS());
+		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+
+		git.rm().addFilepattern("a").call();
+		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+		writeTrashFile("a/c", "Hello world c");
+		git.add().addFilepattern(".").call();
+
+		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+				db.getFS());
+		assertEquals(FileMode.TREE, entry.getMode());
+
+		CheckoutConflictException exception = null;
+		try {
+			git.checkout().setName(branch_1.getName()).call();
+		} catch (CheckoutConflictException e) {
+			exception = e;
+		}
+		assertNotNull(exception);
+		assertEquals(1, exception.getConflictingPaths().size());
+		assertEquals("a", exception.getConflictingPaths().get(0));
+
+		// TODO: ideally we'd like to get two paths from this exception
+		// assertEquals(2, exception.getConflictingPaths().size());
+		// assertEquals("a", exception.getConflictingPaths().get(0));
+		// assertEquals("a/c", exception.getConflictingPaths().get(1));
+	}
+
 	static private void assertEquals(Object expected, Object actual) {
 		Assert.assertEquals(expected, actual);
 	}
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 98ec706..afbad6a 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
@@ -619,7 +619,7 @@ public void testDirectoryFileConflicts_8() throws Exception {
 	@Test
 	public void testDirectoryFileConflicts_9() throws Exception {
 		// 9
-		doit(mk("DF"), mkmap("DF", "QP"), mk("DF/DF"));
+		doit(mkmap("DF", "QP"), mkmap("DF", "QP"), mkmap("DF/DF", "DF/DF"));
 		assertRemoved("DF/DF");
 		assertUpdated("DF");
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index fdf8c05..80dda8e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -562,7 +562,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
 		 * 6b   D        F       F       N         N       N       N           Conflict
 		 * 7    F        D       F       Y         Y       N       N           Update
 		 * 8    F        D       F       N         Y       N       N           Conflict
-		 * 9    F        D       F       Y         N       N       N           Update
+		 * 9    F        D       F                 N       N       N           Conflict
 		 * 10   F        D       D                 N       N       Y           Keep
 		 * 11   F        D       D                 N       N       N           Conflict
 		 * 12   F        F       D       Y         N       Y       N           Update
@@ -610,7 +610,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
 			// switch processes all relevant cases.
 			switch (ffMask) {
 			case 0xDDF: // 1 2
-				if (isModified(name)) {
+				if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
 					conflict(name, dce, h, m); // 1
 				} else {
 					update(name, mId, mMode); // 2
@@ -647,32 +647,29 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
 				break;
 			case 0xFDF: // 7 8 9
 				if (equalIdAndMode(hId, hMode, mId, mMode)) {
-					if (isModified(name))
+					if (isModifiedSubtree_IndexWorkingtree(name))
 						conflict(name, dce, h, m); // 8
 					else
 						update(name, mId, mMode); // 7
-				} else if (!isModified(name))
-					update(name, mId, mMode); // 9
-				else
-					// To be confirmed - this case is not in the table.
-					conflict(name, dce, h, m);
+				} else
+					conflict(name, dce, h, m); // 9
 				break;
 			case 0xFD0: // keep without a rule
 				keep(dce);
 				break;
 			case 0xFFD: // 12 13 14
 				if (equalIdAndMode(hId, hMode, iId, iMode))
-					if (f == null
-							|| f.isModified(dce, true,
+					if (f != null
+							&& f.isModified(dce, true,
 									this.walk.getObjectReader()))
-						conflict(name, dce, h, m);
+						conflict(name, dce, h, m); // 13
 					else
-						remove(name);
+						remove(name); // 12
 				else
-					conflict(name, dce, h, m);
+					conflict(name, dce, h, m); // 14
 				break;
 			case 0x0DF: // 16 17
-				if (!isModified(name))
+				if (!isModifiedSubtree_IndexWorkingtree(name))
 					update(name, mId, mMode);
 				else
 					conflict(name, dce, h, m);
@@ -684,12 +681,14 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
 		}
 
 		// if we have no file at all then there is nothing to do
-		if ((ffMask & 0x222) == 0)
+		if ((ffMask & 0x222) == 0
+				&& (f == null || FileMode.TREE.equals(f.getEntryFileMode())))
 			return;
 
 		if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
 			// File/Directory conflict case #20
 			conflict(name, null, h, m);
+			return;
 		}
 
 		if (i == null) {
@@ -768,7 +767,9 @@ else if (m == null)
 				 * </pre>
 				 */
 
-				if (m == null || equalIdAndMode(mId, mMode, iId, iMode)) {
+				if (m == null
+						|| !isModified_IndexTree(name, iId, iMode, mId, mMode,
+								mergeCommitTree)) {
 					// Merge contains nothing or the same as Index
 					// Nothing in Head
 					// Something in Index
@@ -824,7 +825,7 @@ else if (m == null)
 				 * 	           clean I==H  I==M       H        M        Result
 				 * 	         -----------------------------------------------------
 				 * 	        10 yes   yes   N/A     exists   nothing  remove path from index
-				 * 	        11 no    yes   N/A     exists   nothing  fail
+				 * 	        11 no    yes   N/A     exists   nothing  keep file
 				 * 	        12 yes   no    N/A     exists   nothing  fail
 				 * 	        13 no    no    N/A     exists   nothing  fail
 				 * </pre>
@@ -841,23 +842,31 @@ else if (m == null)
 					// Something different from a submodule in Index
 					// Nothing in Merge
 					// Something in Head
-					if (equalIdAndMode(hId, hMode, iId, iMode)) {
+					if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
+							headCommitTree)) {
 						// Index contains the same as Head
 						// Something different from a submodule in Index
 						// Nothing in Merge
 						// Something in Head
-						if (f == null
-								|| f.isModified(dce, true,
-										this.walk.getObjectReader()))
+						if (f != null
+								&& f.isModified(dce, true,
+										this.walk.getObjectReader())) {
 							// file is dirty
 							// Index contains the same as Head
 							// Something different from a submodule in Index
 							// Nothing in Merge
 							// Something in Head
-							// -> file is dirty but is should be removed. That's
-							// a conflict
-							conflict(name, dce, h, m);
-						else
+
+							if (!FileMode.TREE.equals(f.getEntryFileMode())
+									&& FileMode.TREE.equals(iMode))
+								// The workingtree contains a file and the index semantically contains a folder.
+								// Git considers the workingtree file as untracked. Just keep the untracked file.
+								return;
+							else
+								// -> file is dirty and tracked but is should be
+								// removed. That's a conflict
+								conflict(name, dce, h, m);
+						} else
 							// file doesn't exist or is clean
 							// Index contains the same as Head
 							// Something different from a submodule in Index
@@ -880,8 +889,10 @@ else if (m == null)
 				// Something in Head
 				// Something in Index
 				if (!equalIdAndMode(hId, hMode, mId, mMode)
-						&& !equalIdAndMode(hId, hMode, iId, iMode)
-						&& !equalIdAndMode(mId, mMode, iId, iMode))
+						&& isModified_IndexTree(name, iId, iMode, hId, hMode,
+								headCommitTree)
+						&& isModified_IndexTree(name, iId, iMode, mId, mMode,
+								mergeCommitTree))
 					// All three contents in Head, Merge, Index differ from each
 					// other
 					// -> All contents differ. Report a conflict.
@@ -893,8 +904,10 @@ else if (m == null)
 					// Something in Head
 					// Something in Index
 
-					if (equalIdAndMode(hId, hMode, iId, iMode)
-						&& !equalIdAndMode(mId, mMode, iId, iMode)) {
+				if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
+						headCommitTree)
+						&& isModified_IndexTree(name, iId, iMode, mId, mMode,
+								mergeCommitTree)) {
 						// Head contains the same as Index. Merge differs
 						// Something in Merge
 
@@ -1036,25 +1049,88 @@ private void cleanUpConflicts() throws CheckoutConflictException {
 		}
 	}
 
-	private boolean isModified(String path) throws CorruptObjectException, IOException {
+	/**
+	 * Checks whether the subtree starting at a given path differs between Index and
+	 * workingtree.
+	 *
+	 * @param path
+	 * @return true if the subtrees differ
+	 * @throws CorruptObjectException
+	 * @throws IOException
+	 */
+	private boolean isModifiedSubtree_IndexWorkingtree(String path)
+			throws CorruptObjectException, IOException {
 		NameConflictTreeWalk tw = new NameConflictTreeWalk(repo);
-		tw.addTree(new DirCacheIterator(dc));
-		tw.addTree(new FileTreeIterator(repo));
-		tw.setRecursive(true);
-		tw.setFilter(PathFilter.create(path));
-		DirCacheIterator dcIt;
-		WorkingTreeIterator wtIt;
-		while(tw.next()) {
-			dcIt = tw.getTree(0, DirCacheIterator.class);
-			wtIt = tw.getTree(1, WorkingTreeIterator.class);
-			if (dcIt == null || wtIt == null)
-				return true;
-			if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
-					this.walk.getObjectReader())) {
-				return true;
+		try {
+			tw.addTree(new DirCacheIterator(dc));
+			tw.addTree(new FileTreeIterator(repo));
+			tw.setRecursive(true);
+			tw.setFilter(PathFilter.create(path));
+			DirCacheIterator dcIt;
+			WorkingTreeIterator wtIt;
+			while (tw.next()) {
+				dcIt = tw.getTree(0, DirCacheIterator.class);
+				wtIt = tw.getTree(1, WorkingTreeIterator.class);
+				if (dcIt == null || wtIt == null)
+					return true;
+				if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
+						this.walk.getObjectReader())) {
+					return true;
+				}
 			}
+			return false;
+		} finally {
+			tw.release();
 		}
-		return false;
+	}
+
+	private boolean isModified_IndexTree(String path, ObjectId iId,
+			FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree)
+			throws CorruptObjectException, IOException {
+		if (iMode != tMode)
+			return true;
+		if (FileMode.TREE.equals(iMode)
+				&& (iId == null || ObjectId.zeroId().equals(iId)))
+			return isModifiedSubtree_IndexTree(path, rootTree);
+		else
+			return !equalIdAndMode(iId, iMode, tId, tMode);
+	}
+
+	/**
+	 * Checks whether the subtree starting at a given path differs between Index and
+	 * some tree.
+	 *
+	 * @param path
+	 * @param tree
+	 *            the tree to compare
+	 * @return true if the subtrees differ
+	 * @throws CorruptObjectException
+	 * @throws IOException
+	 */
+	private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
+			throws CorruptObjectException, IOException {
+		NameConflictTreeWalk tw = new NameConflictTreeWalk(repo);
+		try {
+			tw.addTree(new DirCacheIterator(dc));
+			tw.addTree(tree);
+			tw.setRecursive(true);
+			tw.setFilter(PathFilter.create(path));
+			while (tw.next()) {
+				AbstractTreeIterator dcIt = tw.getTree(0,
+						DirCacheIterator.class);
+				AbstractTreeIterator treeIt = tw.getTree(1,
+						AbstractTreeIterator.class);
+				if (dcIt == null || treeIt == null)
+					return true;
+				if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode())
+					return true;
+				if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId()))
+					return true;
+			}
+			return false;
+		} finally {
+			tw.release();
+		}
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 964869b..9eb4285 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -832,6 +832,8 @@ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck) {
 	 */
 	public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
 			ObjectReader reader) throws IOException {
+		if (entry == null)
+			return !FileMode.MISSING.equals(getEntryFileMode());
 		MetadataDiff diff = compareMetadata(entry);
 		switch (diff) {
 		case DIFFER_BY_TIMESTAMP: