{"thread":{"id":"18159","subject":"[JGIT PATCH 1/2] Fix merges involving subtrees with StrategySimpleTwoWayInCore","startedAt":"2009-03-06T01:05:23Z","lastAt":"2009-03-06T01:05:24Z","messageCount":2,"participants":["Shawn O. Pearce"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"107123","messageId":"1236301524-18540-1-git-send-email-spearce@spearce.org","threadId":"18159","inReplyTo":null,"subject":"[JGIT PATCH 1/2] Fix merges involving subtrees with StrategySimpleTwoWayInCore","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-03-06T01:05:23Z","receivedAt":"2009-03-06T01:05:23Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"JGit mismerged two trees when the common ancestor tree contained two\ntrees named libelf-po and libelf, and libelf was modified on one side\nof the merge, while libelf-po was unmodified by both:\n\n  040000 tree ...    libelf-po\n  040000 tree ...    libelf\n\nAbove is the correct sort order, as the second tree, libelf, must be\nsorted as though it ends with '/', and thus comes after libelf-po.\n\nJGit flipped the order during a merge as the strategy added the modified\nsubtree \"libelf\" directly to the DirCacheBuilder, rather than unfolding\nits contents into the DirCache.  The result was a tree entry where only\nfile type entries were expected, and the DirCacheBuilder resorted its\ncontents to place \"libelf\" before \"libelf-po\".\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n\n This patch fixes the data corruption I reported earlier today,\n and that Peff helped me find so quickly.\n\n The second patch in this series fixes a long standing set of\n bugs with the merge code when dealing with concurrent (but not\n conflicting) modifications in subtrees.\n\n .../org/spearce/jgit/merge/SimpleMergeTest.java    |   93 ++++++++++++++++++++\n .../org/spearce/jgit/dircache/DirCacheBuilder.java |   17 +++--\n .../jgit/merge/StrategySimpleTwoWayInCore.java     |   27 +++---\n 3 files changed, 119 insertions(+), 18 deletions(-)\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\nindex 96064f5..e99f017 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\n@@ -36,10 +36,20 @@\n  */\n package org.spearce.jgit.merge;\n \n+import java.io.ByteArrayInputStream;\n import java.io.IOException;\n \n+import org.spearce.jgit.dircache.DirCache;\n+import org.spearce.jgit.dircache.DirCacheBuilder;\n+import org.spearce.jgit.dircache.DirCacheEntry;\n+import org.spearce.jgit.lib.Commit;\n+import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.FileMode;\n import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.ObjectWriter;\n+import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.RepositoryTestCase;\n+import org.spearce.jgit.treewalk.TreeWalk;\n \n public class SimpleMergeTest extends RepositoryTestCase {\n \n@@ -83,4 +93,87 @@ public void testTrivialTwoWay_conflict() throws IOException {\n \t\tboolean merge = ourMerger.merge(new ObjectId[] { db.resolve(\"f\"), db.resolve(\"g\") });\n \t\tassertFalse(merge);\n \t}\n+\n+\tpublic void testTrivialTwoWay_validSubtreeSort() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"libelf-po/a\", FileMode.REGULAR_FILE));\n+\t\t\tb.add(makeEntry(\"libelf/c\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"Makefile\", FileMode.REGULAR_FILE));\n+\t\t\to.add(makeEntry(\"libelf-po/a\", FileMode.REGULAR_FILE));\n+\t\t\to.add(makeEntry(\"libelf/c\", FileMode.REGULAR_FILE));\n+\n+\t\t\tt.add(makeEntry(\"libelf-po/a\", FileMode.REGULAR_FILE));\n+\t\t\tt.add(makeEntry(\"libelf/c\", FileMode.REGULAR_FILE, \"blah\"));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertTrue(merge);\n+\n+\t\tfinal TreeWalk tw = new TreeWalk(db);\n+\t\ttw.setRecursive(true);\n+\t\ttw.reset(ourMerger.getResultTreeId());\n+\n+\t\tassertTrue(tw.next());\n+\t\tassertEquals(\"Makefile\", tw.getPathString());\n+\t\tassertCorrectId(treeO, tw);\n+\n+\t\tassertTrue(tw.next());\n+\t\tassertEquals(\"libelf-po/a\", tw.getPathString());\n+\t\tassertCorrectId(treeO, tw);\n+\n+\t\tassertTrue(tw.next());\n+\t\tassertEquals(\"libelf/c\", tw.getPathString());\n+\t\tassertCorrectId(treeT, tw);\n+\n+\t\tassertFalse(tw.next());\n+\t}\n+\n+\tprivate void assertCorrectId(final DirCache treeT, final TreeWalk tw) {\n+\t\tassertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw\n+\t\t\t\t.getObjectId(0));\n+\t}\n+\n+\tprivate ObjectId commit(final ObjectWriter ow, final DirCache treeB,\n+\t\t\tfinal ObjectId[] parentIds) throws Exception {\n+\t\tfinal Commit c = new Commit(db);\n+\t\tc.setTreeId(treeB.writeTree(ow));\n+\t\tc.setAuthor(new PersonIdent(\"A U Thor\", \"a.u.thor\", 1L, 0));\n+\t\tc.setCommitter(c.getAuthor());\n+\t\tc.setParentIds(parentIds);\n+\t\tc.setMessage(\"Tree \" + c.getTreeId().name());\n+\t\treturn ow.writeCommit(c);\n+\t}\n+\n+\tprivate DirCacheEntry makeEntry(final String path, final FileMode mode)\n+\t\t\tthrows Exception {\n+\t\treturn makeEntry(path, mode, path);\n+\t}\n+\n+\tprivate DirCacheEntry makeEntry(final String path, final FileMode mode,\n+\t\t\tfinal String content) throws Exception {\n+\t\tfinal DirCacheEntry ent = new DirCacheEntry(path);\n+\t\tent.setFileMode(mode);\n+\t\tfinal byte[] contentBytes = Constants.encode(content);\n+\t\tent.setObjectId(new ObjectWriter(db).computeBlobSha1(\n+\t\t\t\tcontentBytes.length, new ByteArrayInputStream(contentBytes)));\n+\t\treturn ent;\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java\nindex 9a9d174..aee12fb 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java\n@@ -41,6 +41,7 @@\n import java.util.Arrays;\n \n import org.spearce.jgit.lib.AnyObjectId;\n+import org.spearce.jgit.lib.FileMode;\n import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.lib.WindowCursor;\n import org.spearce.jgit.treewalk.AbstractTreeIterator;\n@@ -133,6 +134,8 @@ public void keep(final int pos, int cnt) {\n \t *            UTF-8 encoded prefix to mount the tree's entries at. If the\n \t *            path does not end with '/' one will be automatically inserted\n \t *            as necessary.\n+\t * @param stage\n+\t *            stage of the entries when adding them.\n \t * @param db\n \t *            repository the tree(s) will be read from during recursive\n \t *            traversal. This must be the same repository that the resulting\n@@ -146,8 +149,8 @@ public void keep(final int pos, int cnt) {\n \t * @throws IOException\n \t *             a tree cannot be read to iterate through its entries.\n \t */\n-\tpublic void addTree(final byte[] pathPrefix, final Repository db,\n-\t\t\tfinal AnyObjectId tree) throws IOException {\n+\tpublic void addTree(final byte[] pathPrefix, final int stage,\n+\t\t\tfinal Repository db, final AnyObjectId tree) throws IOException {\n \t\tfinal TreeWalk tw = new TreeWalk(db);\n \t\ttw.reset();\n \t\tfinal WindowCursor curs = new WindowCursor();\n@@ -159,16 +162,16 @@ public void addTree(final byte[] pathPrefix, final Repository db,\n \t\t}\n \t\ttw.setRecursive(true);\n \t\tif (tw.next()) {\n-\t\t\tfinal DirCacheEntry newEntry = toEntry(tw);\n+\t\t\tfinal DirCacheEntry newEntry = toEntry(stage, tw);\n \t\t\tbeforeAdd(newEntry);\n \t\t\tfastAdd(newEntry);\n \t\t\twhile (tw.next())\n-\t\t\t\tfastAdd(toEntry(tw));\n+\t\t\t\tfastAdd(toEntry(stage, tw));\n \t\t}\n \t}\n \n-\tprivate DirCacheEntry toEntry(final TreeWalk tw) {\n-\t\tfinal DirCacheEntry e = new DirCacheEntry(tw.getRawPath());\n+\tprivate DirCacheEntry toEntry(final int stage, final TreeWalk tw) {\n+\t\tfinal DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage);\n \t\tfinal AbstractTreeIterator i;\n \n \t\ti = tw.getTree(0, AbstractTreeIterator.class);\n@@ -184,6 +187,8 @@ public void finish() {\n \t}\n \n \tprivate void beforeAdd(final DirCacheEntry newEntry) {\n+\t\tif (FileMode.TREE.equals(newEntry.getRawMode()))\n+\t\t\tthrow bad(newEntry, \"Adding subtree not allowed\");\n \t\tif (sorted && entryCnt > 0) {\n \t\t\tfinal DirCacheEntry lastEntry = entries[entryCnt - 1];\n \t\t\tfinal int cr = DirCache.cmp(lastEntry, newEntry);\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java b/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\nindex 893add9..0f8b4e1 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\n@@ -43,6 +43,7 @@\n import org.spearce.jgit.dircache.DirCacheBuilder;\n import org.spearce.jgit.dircache.DirCacheEntry;\n import org.spearce.jgit.errors.UnmergedPathException;\n+import org.spearce.jgit.lib.FileMode;\n import org.spearce.jgit.lib.ObjectId;\n import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.treewalk.AbstractTreeIterator;\n@@ -143,27 +144,29 @@ else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))\n \t\t}\n \n \t\tprivate void same() throws IOException {\n-\t\t\tif (tw.isSubtree())\n-\t\t\t\tbuilder.addTree(tw.getRawPath(), db, tw.getObjectId(1));\n-\t\t\telse\n-\t\t\t\tadd(T_OURS, DirCacheEntry.STAGE_0);\n+\t\t\tadd(T_OURS, DirCacheEntry.STAGE_0);\n \t\t}\n \n-\t\tprivate void conflict() {\n+\t\tprivate void conflict() throws IOException {\n \t\t\tadd(T_BASE, DirCacheEntry.STAGE_1);\n \t\t\tadd(T_OURS, DirCacheEntry.STAGE_2);\n \t\t\tadd(T_THEIRS, DirCacheEntry.STAGE_3);\n \t\t}\n \n-\t\tprivate void add(final int tree, final int stage) {\n+\t\tprivate void add(final int tree, final int stage) throws IOException {\n \t\t\tfinal AbstractTreeIterator i = getTree(tree);\n \t\t\tif (i != null) {\n-\t\t\t\tfinal DirCacheEntry e;\n-\n-\t\t\t\te = new DirCacheEntry(tw.getRawPath(), stage);\n-\t\t\t\te.setObjectIdFromRaw(i.idBuffer(), i.idOffset());\n-\t\t\t\te.setFileMode(tw.getFileMode(tree));\n-\t\t\t\tbuilder.add(e);\n+\t\t\t\tif (FileMode.TREE.equals(tw.getRawMode(tree))) {\n+\t\t\t\t\tbuilder.addTree(tw.getRawPath(), stage, db, tw\n+\t\t\t\t\t\t\t.getObjectId(tree));\n+\t\t\t\t} else {\n+\t\t\t\t\tfinal DirCacheEntry e;\n+\n+\t\t\t\t\te = new DirCacheEntry(tw.getRawPath(), stage);\n+\t\t\t\t\te.setObjectIdFromRaw(i.idBuffer(), i.idOffset());\n+\t\t\t\t\te.setFileMode(tw.getFileMode(tree));\n+\t\t\t\t\tbuilder.add(e);\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n-- \n1.6.2.96.gc65e7\n"},{"id":"107124","messageId":"1236301524-18540-2-git-send-email-spearce@spearce.org","threadId":"18159","inReplyTo":"1236301524-18540-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 2/2] Fix merges involving clean subtrees with StrategySimpleTwoWayInCore","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-03-06T01:05:24Z","receivedAt":"2009-03-06T01:05:24Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If a both sides modify files in the same subtree, but do so in a\nnon-conflicting way, we should still be able to merge them by an\nautomated merge strategy.  Recursing into the subtree permits us\nto do this merge.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/merge/SimpleMergeTest.java    |  204 ++++++++++++++++++++\n .../jgit/merge/StrategySimpleTwoWayInCore.java     |   32 ++-\n 2 files changed, 225 insertions(+), 11 deletions(-)\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\nindex e99f017..c5755a8 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java\n@@ -146,6 +146,210 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception {\n \t\tassertFalse(tw.next());\n \t}\n \n+\tpublic void testTrivialTwoWay_concurrentSubtreeChange() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tb.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE, \"o !\"));\n+\t\t\to.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE));\n+\n+\t\t\tt.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tt.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE, \"t !\"));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertTrue(merge);\n+\n+\t\tfinal TreeWalk tw = new TreeWalk(db);\n+\t\ttw.setRecursive(true);\n+\t\ttw.reset(ourMerger.getResultTreeId());\n+\n+\t\tassertTrue(tw.next());\n+\t\tassertEquals(\"d/o\", tw.getPathString());\n+\t\tassertCorrectId(treeO, tw);\n+\n+\t\tassertTrue(tw.next());\n+\t\tassertEquals(\"d/t\", tw.getPathString());\n+\t\tassertCorrectId(treeT, tw);\n+\n+\t\tassertFalse(tw.next());\n+\t}\n+\t\n+\tpublic void testTrivialTwoWay_conflictSubtreeChange() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tb.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\to.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE, \"o !\"));\n+\n+\t\t\tt.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE, \"t !\"));\n+\t\t\tt.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE, \"t !\"));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertFalse(merge);\n+\t}\n+\n+\tpublic void testTrivialTwoWay_leftDFconflict1() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tb.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d\", FileMode.REGULAR_FILE));\n+\n+\t\t\tt.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tt.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE, \"t !\"));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertFalse(merge);\n+\t}\n+\n+\tpublic void testTrivialTwoWay_rightDFconflict1() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\tb.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\t\t\to.add(makeEntry(\"d/t\", FileMode.REGULAR_FILE, \"o !\"));\n+\n+\t\t\tt.add(makeEntry(\"d\", FileMode.REGULAR_FILE));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertFalse(merge);\n+\t}\n+\n+\tpublic void testTrivialTwoWay_leftDFconflict2() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d\", FileMode.REGULAR_FILE, \"o !\"));\n+\n+\t\t\tt.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertFalse(merge);\n+\t}\n+\n+\tpublic void testTrivialTwoWay_rightDFconflict2() throws Exception {\n+\t\tfinal DirCache treeB = DirCache.read(db);\n+\t\tfinal DirCache treeO = DirCache.read(db);\n+\t\tfinal DirCache treeT = DirCache.read(db);\n+\t\t{\n+\t\t\tfinal DirCacheBuilder b = treeB.builder();\n+\t\t\tfinal DirCacheBuilder o = treeO.builder();\n+\t\t\tfinal DirCacheBuilder t = treeT.builder();\n+\n+\t\t\tb.add(makeEntry(\"d\", FileMode.REGULAR_FILE));\n+\n+\t\t\to.add(makeEntry(\"d/o\", FileMode.REGULAR_FILE));\n+\n+\t\t\tt.add(makeEntry(\"d\", FileMode.REGULAR_FILE, \"t !\"));\n+\n+\t\t\tb.finish();\n+\t\t\to.finish();\n+\t\t\tt.finish();\n+\t\t}\n+\n+\t\tfinal ObjectWriter ow = new ObjectWriter(db);\n+\t\tfinal ObjectId b = commit(ow, treeB, new ObjectId[] {});\n+\t\tfinal ObjectId o = commit(ow, treeO, new ObjectId[] { b });\n+\t\tfinal ObjectId t = commit(ow, treeT, new ObjectId[] { b });\n+\n+\t\tMerger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);\n+\t\tboolean merge = ourMerger.merge(new ObjectId[] { o, t });\n+\t\tassertFalse(merge);\n+\t}\n+\n \tprivate void assertCorrectId(final DirCache treeT, final TreeWalk tw) {\n \t\tassertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw\n \t\t\t\t.getObjectId(0));\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java b/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\nindex 0f8b4e1..3ebe397 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/merge/StrategySimpleTwoWayInCore.java\n@@ -115,7 +115,7 @@ protected boolean mergeImpl() throws IOException {\n \t\t\t\tfinal int modeO = tw.getRawMode(T_OURS);\n \t\t\t\tfinal int modeT = tw.getRawMode(T_THEIRS);\n \t\t\t\tif (modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) {\n-\t\t\t\t\tsame();\n+\t\t\t\t\tadd(T_OURS, DirCacheEntry.STAGE_0);\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \n@@ -124,8 +124,24 @@ protected boolean mergeImpl() throws IOException {\n \t\t\t\t\tadd(T_THEIRS, DirCacheEntry.STAGE_0);\n \t\t\t\telse if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))\n \t\t\t\t\tadd(T_OURS, DirCacheEntry.STAGE_0);\n-\t\t\t\telse {\n-\t\t\t\t\tconflict();\n+\t\t\t\telse if (tw.isSubtree()) {\n+\t\t\t\t\tif (nonTree(modeB)) {\n+\t\t\t\t\t\tadd(T_BASE, DirCacheEntry.STAGE_1);\n+\t\t\t\t\t\thasConflict = true;\n+\t\t\t\t\t}\n+\t\t\t\t\tif (nonTree(modeO)) {\n+\t\t\t\t\t\tadd(T_OURS, DirCacheEntry.STAGE_2);\n+\t\t\t\t\t\thasConflict = true;\n+\t\t\t\t\t}\n+\t\t\t\t\tif (nonTree(modeT)) {\n+\t\t\t\t\t\tadd(T_THEIRS, DirCacheEntry.STAGE_3);\n+\t\t\t\t\t\thasConflict = true;\n+\t\t\t\t\t}\n+\t\t\t\t\ttw.enterSubtree();\n+\t\t\t\t} else {\n+\t\t\t\t\tadd(T_BASE, DirCacheEntry.STAGE_1);\n+\t\t\t\t\tadd(T_OURS, DirCacheEntry.STAGE_2);\n+\t\t\t\t\tadd(T_THEIRS, DirCacheEntry.STAGE_3);\n \t\t\t\t\thasConflict = true;\n \t\t\t\t}\n \t\t\t}\n@@ -143,14 +159,8 @@ else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))\n \t\t\t}\n \t\t}\n \n-\t\tprivate void same() throws IOException {\n-\t\t\tadd(T_OURS, DirCacheEntry.STAGE_0);\n-\t\t}\n-\n-\t\tprivate void conflict() throws IOException {\n-\t\t\tadd(T_BASE, DirCacheEntry.STAGE_1);\n-\t\t\tadd(T_OURS, DirCacheEntry.STAGE_2);\n-\t\t\tadd(T_THEIRS, DirCacheEntry.STAGE_3);\n+\t\tprivate static boolean nonTree(final int mode) {\n+\t\t\treturn mode != 0 && !FileMode.TREE.equals(mode);\n \t\t}\n \n \t\tprivate void add(final int tree, final int stage) throws IOException {\n-- \n1.6.2.96.gc65e7\n"}]}