]> source.dussan.org Git - jgit.git/commitdiff
Rename RecursiveMergerTest and ResolveMergerTest to reduce confusion 60/119660/3
authorDavid Pursehouse <david.pursehouse@gmail.com>
Mon, 19 Mar 2018 12:13:53 +0000 (21:13 +0900)
committerMatthias Sohn <matthias.sohn@sap.com>
Sat, 24 Mar 2018 21:15:44 +0000 (22:15 +0100)
The class names imply that RecursiveMergerTest tests the RecursiveMerger
and ResolveMergerTest tests the ResolveMerger.

In fact, both of them include coverage of both strategies; the difference
is that RecursiveMergerTest is only testing criss-cross merges.

The tests cannot be combined into a single class because the criss-cross
test methods have additional data points.

Instead, rename the classes to more meaningful names.

Change-Id: I7ca8a03a3b7e351e2d4fcaca3b3186c098a3ca66
Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java [deleted file]
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java [deleted file]

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java
new file mode 100644 (file)
index 0000000..ee99aa1
--- /dev/null
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoMergeBaseException;
+import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.junit.Before;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.runner.RunWith;
+
+@RunWith(Theories.class)
+public class CrissCrossMergeTest extends RepositoryTestCase {
+       static int counter = 0;
+
+       @DataPoints
+       public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
+                       MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
+
+       public enum IndexState {
+               Bare, Missing, SameAsHead, SameAsOther, SameAsWorkTree, DifferentFromHeadAndOtherAndWorktree
+       }
+
+       @DataPoints
+       public static IndexState[] indexStates = IndexState.values();
+
+       public enum WorktreeState {
+               Bare, Missing, SameAsHead, DifferentFromHeadAndOther, SameAsOther;
+       }
+
+       @DataPoints
+       public static WorktreeState[] worktreeStates = WorktreeState.values();
+
+       private TestRepository<FileRepository> db_t;
+
+       @Override
+       @Before
+       public void setUp() throws Exception {
+               super.setUp();
+               db_t = new TestRepository<>(db);
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. In master and side different
+        * files are touched. No need to do a real content merge.
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge(MergeStrategy strategy, IndexState indexState,
+                       WorktreeState worktreeState) throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+               // fill the repo
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("m", ",m0").message("m0").create();
+               RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0).add("s", "s1").message("s1")
+                               .create();
+               RevCommit s2 = side.commit().parent(m1).add("m", "m1")
+                               .message("s2(merge)").create();
+               RevCommit m2 = master.commit().parent(s1).add("s", "s1")
+                               .message("m2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "m", "side");
+               modifyWorktree(worktreeState, "s", "side");
+               modifyIndex(indexState, "m", "side");
+               modifyIndex(indexState, "s", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing
+                                       || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
+                               // index is dirty
+                               expectSuccess = false;
+
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       assertEquals("m1",
+                                       contentAsString(db, merger.getResultTreeId(), "m"));
+                       assertEquals("s1",
+                                       contentAsString(db, merger.getResultTreeId(), "s"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. m1 and s1 are the two root
+        * commits of the repo. In master and side different files are touched.
+        * No need to do a real content merge.
+        *
+        * <pre>
+        * m1--m2
+        *   \/
+        *   /\
+        * s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_twoRoots(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+               // fill the repo
+               BranchBuilder master = db_t.branch("master");
+               BranchBuilder side = db_t.branch("side");
+               RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               RevCommit s1 = side.commit().add("s", "s1").message("s1").create();
+               RevCommit s2 = side.commit().parent(m1).add("m", "m1")
+                               .message("s2(merge)").create();
+               RevCommit m2 = master.commit().parent(s1).add("s", "s1")
+                               .message("m2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "m", "side");
+               modifyWorktree(worktreeState, "s", "side");
+               modifyIndex(indexState, "m", "side");
+               modifyIndex(indexState, "s", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing
+                                       || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
+                               // index is dirty
+                               expectSuccess = false;
+
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       assertEquals("m1",
+                                       contentAsString(db, merger.getResultTreeId(), "m"));
+                       assertEquals("s1",
+                                       contentAsString(db, merger.getResultTreeId(), "s"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. The same file is modified
+        * in both branches. The modifications should be mergeable. m2 and s2
+        * contain branch specific conflict resolutions. Therefore m2 and s2 don't contain the same content.
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_mergeable(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
+                               .message("m0").create();
+               RevCommit m1 = master.commit()
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
+                               .create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0)
+                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
+                               .create();
+               RevCommit s2 = side.commit().parent(m1)
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
+                               .message("s2(merge)").create();
+               RevCommit m2 = master
+                               .commit()
+                               .parent(s1)
+                               .add("f", "1-master\n2\n3-res(master)\n4\n5\n6\n7\n8\n9-side\n")
+                               .message("m2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+                               // index is dirty
+                               expectSuccess = false;
+                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+                                       || worktreeState == WorktreeState.SameAsOther)
+                               expectSuccess = false;
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (!expectSuccess)
+                               // if the merge was not successful skip testing the state of index and workingtree
+                               return;
+                       assertEquals(
+                                       "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side",
+                                       contentAsString(db, merger.getResultTreeId(), "f"));
+                       if (indexState != IndexState.Bare)
+                               assertEquals(
+                                               "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                       if (worktreeState != WorktreeState.Bare
+                                       && worktreeState != WorktreeState.Missing)
+                               assertEquals(
+                                               "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n",
+                                               read("f"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. The same file is modified
+        * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
+        * is choosen as parent. Choosing m0 as parent would not be sufficient (in contrast to the merge in
+        * crissCrossMerge_mergeable). m2 and s2 contain branch specific conflict resolutions. Therefore m2
+        * and s2 don't contain the same content.
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_mergeable2(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n")
+                               .message("m0")
+                               .create();
+               RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
+                               .message("m1").create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
+                               .message("s1").create();
+               RevCommit s2 = side.commit().parent(m1)
+                               .add("f", "1-master\n2\n3-side-r\n")
+                               .message("s2(merge)")
+                               .create();
+               RevCommit m2 = master.commit().parent(s1)
+                               .add("f", "1-master-r\n2\n3-side\n")
+                               .message("m2(merge)")
+                               .create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+                               // index is dirty
+                               expectSuccess = false;
+                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+                                       || worktreeState == WorktreeState.SameAsOther)
+                               expectSuccess = false;
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (!expectSuccess)
+                               // if the merge was not successful skip testing the state of
+                               // index and workingtree
+                               return;
+                       assertEquals(
+                                       "1-master-r\n2\n3-side-r",
+                                       contentAsString(db, merger.getResultTreeId(), "f"));
+                       if (indexState != IndexState.Bare)
+                               assertEquals(
+                                               "[f, mode:100644, content:1-master-r\n2\n3-side-r\n]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                       if (worktreeState != WorktreeState.Bare
+                                       && worktreeState != WorktreeState.Missing)
+                               assertEquals(
+                                               "1-master-r\n2\n3-side-r\n",
+                                               read("f"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. m1 and s1 are not mergeable
+        * without conflicts. The same file is modified in both branches. The
+        * modifications should be mergeable but only if the merge result of
+        * merging m1 and s1 is choosen as parent (including the conflict markers).
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_ParentsNotMergeable(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n").message("m0")
+                               .create();
+               RevCommit m1 = master.commit().add("f", "1\nx(master)\n2\n3\n")
+                               .message("m1").create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0)
+                               .add("f", "1\nx(side)\n2\n3\ny(side)\n")
+                               .message("s1").create();
+               RevCommit s2 = side.commit().parent(m1)
+                               .add("f", "1\nx(side)\n2\n3\ny(side-again)\n")
+                               .message("s2(merge)")
+                               .create();
+               RevCommit m2 = master.commit().parent(s1)
+                               .add("f", "1\nx(side)\n2\n3\ny(side)\n").message("m2(merge)")
+                               .create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+                               // index is dirty
+                               expectSuccess = false;
+                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+                                       || worktreeState == WorktreeState.SameAsOther)
+                               expectSuccess = false;
+                       assertEquals("Merge didn't return as expected: strategy:"
+                                       + strategy.getName() + ", indexState:" + indexState
+                                       + ", worktreeState:" + worktreeState + " . ",
+                                       Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (!expectSuccess)
+                               // if the merge was not successful skip testing the state of
+                               // index and workingtree
+                               return;
+                       assertEquals("1\nx(side)\n2\n3\ny(side-again)",
+                                       contentAsString(db, merger.getResultTreeId(), "f"));
+                       if (indexState != IndexState.Bare)
+                               assertEquals(
+                                               "[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                       if (worktreeState != WorktreeState.Bare
+                                       && worktreeState != WorktreeState.Missing)
+                               assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. The same file is modified
+        * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
+        * is choosen as parent. On both branches delete and modify files untouched on the other branch.
+        * On both branches create new files. Make sure these files are correctly merged and
+        * exist in the workingtree.
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_checkOtherFiles(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n").add("m.m", "0")
+                               .add("m.d", "0").add("s.m", "0").add("s.d", "0").message("m0")
+                               .create();
+               RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
+                               .add("m.c", "0").add("m.m", "1").rm("m.d").message("m1")
+                               .create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
+                               .add("s.c", "0").add("s.m", "1").rm("s.d").message("s1")
+                               .create();
+               RevCommit s2 = side.commit().parent(m1)
+                               .add("f", "1-master\n2\n3-side-r\n").add("m.m", "1")
+                               .add("m.c", "0").rm("m.d").message("s2(merge)").create();
+               RevCommit m2 = master.commit().parent(s1)
+                               .add("f", "1-master-r\n2\n3-side\n").add("s.m", "1")
+                               .add("s.c", "0").rm("s.d").message("m2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+                               // index is dirty
+                               expectSuccess = false;
+                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+                                       || worktreeState == WorktreeState.SameAsOther)
+                               expectSuccess = false;
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (!expectSuccess)
+                               // if the merge was not successful skip testing the state of
+                               // index and workingtree
+                               return;
+                       assertEquals(
+                                       "1-master-r\n2\n3-side-r",
+                                       contentAsString(db, merger.getResultTreeId(), "f"));
+                       if (indexState != IndexState.Bare)
+                               assertEquals(
+                                               "[f, mode:100644, content:1-master-r\n2\n3-side-r\n][m.c, mode:100644, content:0][m.m, mode:100644, content:1][s.c, mode:100644, content:0][s.m, mode:100644, content:1]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                       if (worktreeState != WorktreeState.Bare
+                                       && worktreeState != WorktreeState.Missing) {
+                               assertEquals(
+                                               "1-master-r\n2\n3-side-r\n",
+                                               read("f"));
+                               assertTrue(check("s.c"));
+                               assertFalse(check("s.d"));
+                               assertTrue(check("s.m"));
+                               assertTrue(check("m.c"));
+                               assertFalse(check("m.d"));
+                               assertTrue(check("m.m"));
+                       }
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 from the following topology. The same file is modified
+        * in both branches. The modifications are not automatically
+        * mergeable. m2 and s2 contain branch specific conflict resolutions.
+        * Therefore m2 and s2 don't contain the same content.
+        *
+        * <pre>
+        * m0--m1--m2
+        *   \   \/
+        *    \  /\
+        *     s1--s2
+        * </pre>
+        */
+       public void crissCrossMerge_nonmergeable(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
+                               .message("m0").create();
+               RevCommit m1 = master.commit()
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
+                               .create();
+               db_t.getRevWalk().parseCommit(m1);
+
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0)
+                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
+                               .create();
+               RevCommit s2 = side.commit().parent(m1)
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
+                               .message("s2(merge)").create();
+               RevCommit m2 = master.commit().parent(s1)
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n")
+                               .message("m2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       assertFalse(merger.merge(new RevCommit[] { m2, s2 }));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (indexState == IndexState.SameAsHead
+                                       && worktreeState == WorktreeState.SameAsHead) {
+                               assertEquals(
+                                               "[f, mode:100644, stage:1, content:1-master\n2\n3\n4\n5\n6\n7\n8\n9-side\n]"
+                                                               + "[f, mode:100644, stage:2, content:1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n]"
+                                                               + "[f, mode:100644, stage:3, content:1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                               assertEquals(
+                                               "1-master\n2\n3\n4\n5\n6\n<<<<<<< OURS\n7-conflict\n=======\n7-res(side)\n>>>>>>> THEIRS\n8\n9-side\n",
+                                               read("f"));
+                       }
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       /**
+        * Merging m2,s2 which have three common predecessors.The same file is modified
+        * in all branches. The modifications should be mergeable. m2 and s2
+        * contain branch specific conflict resolutions. Therefore m2 and s2
+        * don't contain the same content.
+        *
+        * <pre>
+        *     m1-----m2
+        *    /  \/  /
+        *   /   /\ /
+        * m0--o1  x
+        *   \   \/ \
+        *    \  /\  \
+        *     s1-----s2
+        * </pre>
+        */
+       public void crissCrossMerge_ThreeCommonPredecessors(MergeStrategy strategy,
+                       IndexState indexState, WorktreeState worktreeState)
+                       throws Exception {
+               if (!validateStates(indexState, worktreeState))
+                       return;
+
+               BranchBuilder master = db_t.branch("master");
+               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
+                               .message("m0").create();
+               RevCommit m1 = master.commit()
+                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
+                               .create();
+               BranchBuilder side = db_t.branch("side");
+               RevCommit s1 = side.commit().parent(m0)
+                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
+                               .create();
+               BranchBuilder other = db_t.branch("other");
+               RevCommit o1 = other.commit().parent(m0)
+                               .add("f", "1\n2\n3\n4\n5-other\n6\n7\n8\n9\n").message("o1")
+                               .create();
+
+               RevCommit m2 = master
+                               .commit()
+                               .parent(s1)
+                               .parent(o1)
+                               .add("f",
+                                               "1-master\n2\n3-res(master)\n4\n5-other\n6\n7\n8\n9-side\n")
+                               .message("m2(merge)").create();
+
+               RevCommit s2 = side
+                               .commit()
+                               .parent(m1)
+                               .parent(o1)
+                               .add("f",
+                                               "1-master\n2\n3\n4\n5-other\n6\n7-res(side)\n8\n9-side\n")
+                               .message("s2(merge)").create();
+
+               Git git = Git.wrap(db);
+               git.checkout().setName("master").call();
+               modifyWorktree(worktreeState, "f", "side");
+               modifyIndex(indexState, "f", "side");
+
+               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+                               worktreeState == WorktreeState.Bare);
+               if (worktreeState != WorktreeState.Bare)
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+               try {
+                       boolean expectSuccess = true;
+                       if (!(indexState == IndexState.Bare
+                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+                               // index is dirty
+                               expectSuccess = false;
+                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+                                       || worktreeState == WorktreeState.SameAsOther)
+                               // workingtree is dirty
+                               expectSuccess = false;
+
+                       assertEquals(Boolean.valueOf(expectSuccess),
+                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       if (!expectSuccess)
+                               // if the merge was not successful skip testing the state of index and workingtree
+                               return;
+                       assertEquals(
+                                       "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side",
+                                       contentAsString(db, merger.getResultTreeId(), "f"));
+                       if (indexState != IndexState.Bare)
+                               assertEquals(
+                                               "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n]",
+                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
+                       if (worktreeState != WorktreeState.Bare
+                                       && worktreeState != WorktreeState.Missing)
+                               assertEquals(
+                                               "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n",
+                                               read("f"));
+               } catch (NoMergeBaseException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertEquals(e.getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       void modifyIndex(IndexState indexState, String path, String other)
+                       throws Exception {
+               RevBlob blob;
+               switch (indexState) {
+               case Missing:
+                       setIndex(null, path);
+                       break;
+               case SameAsHead:
+                       setIndex(contentId(Constants.HEAD, path), path);
+                       break;
+               case SameAsOther:
+                       setIndex(contentId(other, path), path);
+                       break;
+               case SameAsWorkTree:
+                       blob = db_t.blob(read(path));
+                       setIndex(blob, path);
+                       break;
+               case DifferentFromHeadAndOtherAndWorktree:
+                       blob = db_t.blob(Integer.toString(counter++));
+                       setIndex(blob, path);
+                       break;
+               case Bare:
+                       File file = new File(db.getDirectory(), "index");
+                       if (!file.exists())
+                               return;
+                       db.close();
+                       file.delete();
+                       db = new FileRepository(db.getDirectory());
+                       db_t = new TestRepository<>(db);
+                       break;
+               }
+       }
+
+       private void setIndex(final ObjectId id, String path)
+                       throws MissingObjectException, IOException {
+               DirCache lockedDircache;
+               DirCacheEditor dcedit;
+
+               lockedDircache = db.lockDirCache();
+               dcedit = lockedDircache.editor();
+               try {
+                       if (id != null) {
+                               final ObjectLoader contLoader = db.newObjectReader().open(id);
+                               dcedit.add(new DirCacheEditor.PathEdit(path) {
+                                       @Override
+                                       public void apply(DirCacheEntry ent) {
+                                               ent.setFileMode(FileMode.REGULAR_FILE);
+                                               ent.setLength(contLoader.getSize());
+                                               ent.setObjectId(id);
+                                       }
+                               });
+                       } else
+                               dcedit.add(new DirCacheEditor.DeletePath(path));
+               } finally {
+                       dcedit.commit();
+               }
+       }
+
+       private ObjectId contentId(String revName, String path) throws Exception {
+               RevCommit headCommit = db_t.getRevWalk().parseCommit(
+                               db.resolve(revName));
+               db_t.parseBody(headCommit);
+               return db_t.get(headCommit.getTree(), path).getId();
+       }
+
+       void modifyWorktree(WorktreeState worktreeState, String path, String other)
+                       throws Exception {
+               switch (worktreeState) {
+               case Missing:
+                       new File(db.getWorkTree(), path).delete();
+                       break;
+               case DifferentFromHeadAndOther:
+                       write(new File(db.getWorkTree(), path),
+                                       Integer.toString(counter++));
+                       break;
+               case SameAsHead:
+                       try (FileOutputStream fos = new FileOutputStream(
+                                       new File(db.getWorkTree(), path))) {
+                               db.newObjectReader().open(contentId(Constants.HEAD, path))
+                                               .copyTo(fos);
+                       }
+                       break;
+               case SameAsOther:
+                       try (FileOutputStream fos = new FileOutputStream(
+                                       new File(db.getWorkTree(), path))) {
+                               db.newObjectReader().open(contentId(other, path)).copyTo(fos);
+                       }
+                       break;
+               case Bare:
+                       if (db.isBare())
+                               return;
+                       File workTreeFile = db.getWorkTree();
+                       db.getConfig().setBoolean("core", null, "bare", true);
+                       db.getDirectory().renameTo(new File(workTreeFile, "test.git"));
+                       db = new FileRepository(new File(workTreeFile, "test.git"));
+                       db_t = new TestRepository<>(db);
+               }
+       }
+
+       private boolean validateStates(IndexState indexState,
+                       WorktreeState worktreeState) {
+               if (worktreeState == WorktreeState.Bare
+                               && indexState != IndexState.Bare)
+                       return false;
+               if (worktreeState != WorktreeState.Bare
+                               && indexState == IndexState.Bare)
+                       return false;
+               if (worktreeState != WorktreeState.DifferentFromHeadAndOther
+                               && indexState == IndexState.SameAsWorkTree)
+                       // would be a duplicate: the combination WorktreeState.X and
+                       // IndexState.X already covered this
+                       return false;
+               return true;
+       }
+
+       private String contentAsString(Repository r, ObjectId treeId, String path)
+                       throws MissingObjectException, IOException {
+               AnyObjectId blobId;
+               try (TreeWalk tw = new TreeWalk(r)) {
+                       tw.addTree(treeId);
+                       tw.setFilter(PathFilter.create(path));
+                       tw.setRecursive(true);
+                       if (!tw.next()) {
+                               return null;
+                       }
+                       blobId = tw.getObjectId(0);
+               }
+
+               StringBuilder result = new StringBuilder();
+               ObjectReader or = r.newObjectReader();
+               try (BufferedReader br = new BufferedReader(
+                               new InputStreamReader(or.open(blobId).openStream()))) {
+                       String line;
+                       boolean first = true;
+                       while ((line = br.readLine()) != null) {
+                               if (!first) {
+                                       result.append('\n');
+                               }
+                               result.append(line);
+                               first = false;
+                       }
+                       return result.toString();
+               }
+       }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
new file mode 100644 (file)
index 0000000..58093a3
--- /dev/null
@@ -0,0 +1,1333 @@
+/*
+ * Copyright (C) 2012, Robin Stocker <robin@nibor.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.merge;
+
+import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.NoMergeBaseException;
+import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assert;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.runner.RunWith;
+
+@RunWith(Theories.class)
+public class MergerTest extends RepositoryTestCase {
+
+       @DataPoints
+       public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
+                       MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
+
+       @Theory
+       public void failingDeleteOfDirectoryWithUntrackedContent(
+                       MergeStrategy strategy) throws Exception {
+               File folder1 = new File(db.getWorkTree(), "folder1");
+               FileUtils.mkdir(folder1);
+               File file = new File(folder1, "file1.txt");
+               write(file, "folder1--file1.txt");
+               file = new File(folder1, "file2.txt");
+               write(file, "folder1--file2.txt");
+
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern(folder1.getName()).call();
+                       RevCommit base = git.commit().setMessage("adding folder").call();
+
+                       recursiveDelete(folder1);
+                       git.rm().addFilepattern("folder1/file1.txt")
+                                       .addFilepattern("folder1/file2.txt").call();
+                       RevCommit other = git.commit()
+                                       .setMessage("removing folders on 'other'").call();
+
+                       git.checkout().setName(base.name()).call();
+
+                       file = new File(db.getWorkTree(), "unrelated.txt");
+                       write(file, "unrelated");
+
+                       git.add().addFilepattern("unrelated.txt").call();
+                       RevCommit head = git.commit().setMessage("Adding another file").call();
+
+                       // Untracked file to cause failing path for delete() of folder1
+                       // but that's ok.
+                       file = new File(folder1, "file3.txt");
+                       write(file, "folder1--file3.txt");
+
+                       ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
+                       merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
+                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
+                       boolean ok = merger.merge(head.getId(), other.getId());
+                       assertTrue(ok);
+                       assertTrue(file.exists());
+               }
+       }
+
+       /**
+        * Merging two conflicting subtrees when the index does not contain any file
+        * in that subtree should lead to a conflicting state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("d/1", "master");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "side");
+               git.commit().setAll(true).setMessage("modified d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two different but mergeable subtrees when the index does not
+        * contain any file in that subtree should lead to a merged state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "1\n2\n3");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("d/1", "1master\n2\n3");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "1\n2\n3side");
+               git.commit().setAll(true).setMessage("modified d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * An existing directory without tracked content should not prevent merging
+        * a tree where that directory exists.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkUntrackedFolderIsNotAConflict(
+                       MergeStrategy strategy) throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "1");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("e/1", "4");
+               git.add().addFilepattern("e/1").call();
+               RevCommit masterCommit = git.commit().setMessage("added e/1").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("f/1", "5");
+               git.add().addFilepattern("f/1").call();
+               git.commit().setAll(true).setMessage("added f/1")
+                               .call();
+
+               // Untracked directory e shall not conflict with merged e/1
+               writeTrashFile("e/2", "d two");
+
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * A tracked file is replaced by a folder in THEIRS.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("sub", "file");
+               git.add().addFilepattern("sub").call();
+               RevCommit first = git.commit().setMessage("initial").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+
+               git.rm().addFilepattern("sub").call();
+               writeTrashFile("sub/file", "subfile");
+               git.add().addFilepattern("sub/file").call();
+               RevCommit masterCommit = git.commit().setMessage("file -> folder")
+                               .call();
+
+               git.checkout().setName("master").call();
+               writeTrashFile("noop", "other");
+               git.add().addFilepattern("noop").call();
+               git.commit().setAll(true).setMessage("noop").call();
+
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * A tracked file is replaced by a folder in OURS.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("sub", "file");
+               git.add().addFilepattern("sub").call();
+               RevCommit first = git.commit().setMessage("initial").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("noop", "other");
+               git.add().addFilepattern("noop").call();
+               RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
+                               .call();
+
+               git.checkout().setName("master").call();
+               git.rm().addFilepattern("sub").call();
+               writeTrashFile("sub/file", "subfile");
+               git.add().addFilepattern("sub/file").call();
+               git.commit().setMessage("file -> folder")
+                               .call();
+
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(sideCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * An existing directory without tracked content should not prevent merging
+        * a file with that name.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
+                       MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "1");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("e", "4");
+               git.add().addFilepattern("e").call();
+               RevCommit masterCommit = git.commit().setMessage("added e").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("f/1", "5");
+               git.add().addFilepattern("f/1").call();
+               git.commit().setAll(true).setMessage("added f/1").call();
+
+               // Untracked empty directory hierarcy e/1 shall not conflict with merged
+               // e/1
+               FileUtils.mkdirs(new File(trash, "e/1"), true);
+
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
+                               indexState(CONTENT));
+       }
+
+       @Theory
+       public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
+                       GitAPIException {
+               Git git = Git.wrap(db);
+               db.getConfig().setString("core", null, "autocrlf", "false");
+               db.getConfig().save();
+               writeTrashFile("crlf.txt", "some\r\ndata\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("base").call();
+
+               git.branchCreate().setName("brancha").call();
+
+               writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("on master").call();
+
+               git.checkout().setName("brancha").call();
+               writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("on brancha").call();
+
+               db.getConfig().setString("core", null, "autocrlf", "input");
+               db.getConfig().save();
+
+               MergeResult mergeResult = git.merge().setStrategy(strategy)
+                               .include(db.resolve("master"))
+                               .call();
+               assertEquals(MergeResult.MergeStatus.MERGED,
+                               mergeResult.getMergeStatus());
+       }
+
+       @Theory
+       public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
+                       throws IOException, GitAPIException {
+               Git git = Git.wrap(db);
+               db.getConfig().setString("core", null, "autocrlf", "true");
+               db.getConfig().save();
+               writeTrashFile("crlf.txt", "a crlf file\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("base").call();
+
+               git.branchCreate().setName("brancha").call();
+
+               writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("on master").call();
+
+               git.checkout().setName("brancha").call();
+               File testFile = writeTrashFile("crlf.txt",
+                               "a first line\r\na crlf file\r\n");
+               git.add().addFilepattern("crlf.txt").call();
+               git.commit().setMessage("on brancha").call();
+
+               MergeResult mergeResult = git.merge().setStrategy(strategy)
+                               .include(db.resolve("master")).call();
+               assertEquals(MergeResult.MergeStatus.MERGED,
+                               mergeResult.getMergeStatus());
+               checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
+               assertEquals(
+                               "[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two equal subtrees when the index does not contain any file in
+        * that subtree should lead to a merged state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("d/1", "modified");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "modified");
+               git.commit().setAll(true).setMessage("modified d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals("[d/1, mode:100644, content:modified]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two equal subtrees with an incore merger should lead to a merged
+        * state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeEqualTreesInCore(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("d/1", "modified");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "modified");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+
+               ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
+                               true);
+               boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
+               assertTrue(noProblems);
+       }
+
+       /**
+        * Merging two equal subtrees with an incore merger should lead to a merged
+        * state, without using a Repository (the 'Gerrit' use case).
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               RevCommit first = git.commit().setMessage("added d/1").call();
+
+               writeTrashFile("d/1", "modified");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "modified");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+
+               try (ObjectInserter ins = db.newObjectInserter()) {
+                       ThreeWayMerger resolveMerger =
+                                       (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
+                       boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
+                       assertTrue(noProblems);
+               }
+       }
+
+       /**
+        * Merging two equal subtrees when the index and HEAD does not contain any
+        * file in that subtree should lead to a merged state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeEqualNewTrees(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("2", "orig");
+               git.add().addFilepattern("2").call();
+               RevCommit first = git.commit().setMessage("added 2").call();
+
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("added d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "orig");
+               git.add().addFilepattern("d/1").call();
+               git.commit().setAll(true).setMessage("added d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two conflicting subtrees when the index and HEAD does not contain
+        * any file in that subtree should lead to a conflicting state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeConflictingNewTrees(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("2", "orig");
+               git.add().addFilepattern("2").call();
+               RevCommit first = git.commit().setMessage("added 2").call();
+
+               writeTrashFile("d/1", "master");
+               git.add().addFilepattern("d/1").call();
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("added d/1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("d/1", "side");
+               git.add().addFilepattern("d/1").call();
+               git.commit().setAll(true).setMessage("added d/1 on side").call();
+
+               git.rm().addFilepattern("d/1").call();
+               git.rm().addFilepattern("d").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
+               assertEquals(
+                               "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two conflicting files when the index contains a tree for that
+        * path should lead to a failed state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("0", "orig");
+               git.add().addFilepattern("0").call();
+               RevCommit first = git.commit().setMessage("added 0").call();
+
+               writeTrashFile("0", "master");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified 0 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("0", "side");
+               git.commit().setAll(true).setMessage("modified 0 on side").call();
+
+               git.rm().addFilepattern("0").call();
+               writeTrashFile("0/0", "side");
+               git.add().addFilepattern("0/0").call();
+               MergeResult mergeRes = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
+       }
+
+       /**
+        * Merging two equal files when the index contains a tree for that path
+        * should lead to a failed state.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("0", "orig");
+               writeTrashFile("1", "1\n2\n3");
+               git.add().addFilepattern("0").addFilepattern("1").call();
+               RevCommit first = git.commit().setMessage("added 0, 1").call();
+
+               writeTrashFile("1", "1master\n2\n3");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified 1 on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("1", "1\n2\n3side");
+               git.commit().setAll(true).setMessage("modified 1 on side").call();
+
+               git.rm().addFilepattern("0").call();
+               writeTrashFile("0/0", "modified");
+               git.add().addFilepattern("0/0").call();
+               try {
+                       git.merge().setStrategy(strategy).include(masterCommit).call();
+                       Assert.fail("Didn't get the expected exception");
+               } catch (CheckoutConflictException e) {
+                       assertEquals(1, e.getConflictingPaths().size());
+                       assertEquals("0/0", e.getConflictingPaths().get(0));
+               }
+       }
+
+       @Theory
+       public void checkContentMergeNoConflict(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("file", "1\n2\n3");
+               git.add().addFilepattern("file").call();
+               RevCommit first = git.commit().setMessage("added file").call();
+
+               writeTrashFile("file", "1master\n2\n3");
+               git.commit().setAll(true).setMessage("modified file on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("file", "1\n2\n3side");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified file on side").call();
+
+               git.checkout().setName("master").call();
+               MergeResult result =
+                               git.merge().setStrategy(strategy).include(sideCommit).call();
+               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+               String expected = "1master\n2\n3side";
+               assertEquals(expected, read("file"));
+       }
+
+       @Theory
+       public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("file", "1\n2\n3");
+               git.add().addFilepattern("file").call();
+               RevCommit first = git.commit().setMessage("added file").call();
+
+               writeTrashFile("file", "1master\n2\n3");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified file on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("file", "1\n2\n3side");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified file on side").call();
+
+               try (ObjectInserter ins = db.newObjectInserter()) {
+                       ResolveMerger merger =
+                                       (ResolveMerger) strategy.newMerger(ins, db.getConfig());
+                       boolean noProblems = merger.merge(masterCommit, sideCommit);
+                       assertTrue(noProblems);
+                       assertEquals("1master\n2\n3side",
+                                       readBlob(merger.getResultTreeId(), "file"));
+               }
+       }
+
+
+       /**
+        * Merging a change involving large binary files should short-circuit reads.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
+               Git git = Git.wrap(db);
+               final int LINELEN = 72;
+
+               // setup a merge that would work correctly if we disconsider the stray '\0'
+               // that the file contains near the start.
+               byte[] binary = new byte[LINELEN * 2000];
+               for (int i = 0; i < binary.length; i++) {
+                       binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
+               }
+               binary[50] = '\0';
+
+               writeTrashFile("file", new String(binary, CHARSET));
+               git.add().addFilepattern("file").call();
+               RevCommit first = git.commit().setMessage("added file").call();
+
+               // Generate an edit in a single line.
+               int idx = LINELEN * 1200 + 1;
+               byte save = binary[idx];
+               binary[idx] = '@';
+               writeTrashFile("file", new String(binary, CHARSET));
+
+               binary[idx] = save;
+               git.add().addFilepattern("file").call();
+               RevCommit masterCommit = git.commit().setAll(true)
+                       .setMessage("modified file l 1200").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
+               binary[LINELEN * 1500 + 1] = '!';
+               writeTrashFile("file", new String(binary, CHARSET));
+               git.add().addFilepattern("file").call();
+               RevCommit sideCommit = git.commit().setAll(true)
+                       .setMessage("modified file l 1500").call();
+
+               try (ObjectInserter ins = db.newObjectInserter()) {
+                       // Check that we don't read the large blobs.
+                       ObjectInserter forbidInserter = new ObjectInserter.Filter() {
+                               @Override
+                               protected ObjectInserter delegate() {
+                                       return ins;
+                               }
+
+                               @Override
+                               public ObjectReader newReader() {
+                                       return new BigReadForbiddenReader(super.newReader(), 8000);
+                               }
+                       };
+
+                       ResolveMerger merger =
+                               (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
+                       boolean noProblems = merger.merge(masterCommit, sideCommit);
+                       assertFalse(noProblems);
+               }
+       }
+
+       /**
+        * Throws an exception if reading beyond limit.
+        */
+       class BigReadForbiddenStream extends ObjectStream.Filter {
+               int limit;
+
+               BigReadForbiddenStream(ObjectStream orig, int limit) {
+                       super(orig.getType(), orig.getSize(), orig);
+                       this.limit = limit;
+               }
+
+               @Override
+               public long skip(long n) throws IOException {
+                       limit -= n;
+                       if (limit < 0) {
+                               throw new IllegalStateException();
+                       }
+
+                       return super.skip(n);
+               }
+
+               @Override
+               public int read() throws IOException {
+                       int r = super.read();
+                       limit--;
+                       if (limit < 0) {
+                               throw new IllegalStateException();
+                       }
+                       return r;
+               }
+
+               @Override
+               public int read(byte[] b, int off, int len) throws IOException {
+                       int n = super.read(b, off, len);
+                       limit -= n;
+                       if (limit < 0) {
+                               throw new IllegalStateException();
+                       }
+                       return n;
+               }
+       }
+
+       class BigReadForbiddenReader extends ObjectReader.Filter {
+               ObjectReader delegate;
+               int limit;
+
+               @Override
+               protected ObjectReader delegate() {
+                       return delegate;
+               }
+
+               BigReadForbiddenReader(ObjectReader delegate, int limit) {
+                       this.delegate = delegate;
+                       this.limit = limit;
+               }
+
+               @Override
+               public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
+                       ObjectLoader orig = super.open(objectId, typeHint);
+                       return new ObjectLoader.Filter() {
+                               @Override
+                               protected ObjectLoader delegate() {
+                                       return orig;
+                               }
+
+                               @Override
+                               public ObjectStream openStream() throws IOException {
+                                       ObjectStream os = orig.openStream();
+                                       return new BigReadForbiddenStream(os, limit);
+                               }
+                       };
+               }
+       }
+
+       @Theory
+       public void checkContentMergeConflict(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("file", "1\n2\n3");
+               git.add().addFilepattern("file").call();
+               RevCommit first = git.commit().setMessage("added file").call();
+
+               writeTrashFile("file", "1master\n2\n3");
+               git.commit().setAll(true).setMessage("modified file on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("file", "1side\n2\n3");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified file on side").call();
+
+               git.checkout().setName("master").call();
+               MergeResult result =
+                               git.merge().setStrategy(strategy).include(sideCommit).call();
+               assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+               String expected = "<<<<<<< HEAD\n"
+                               + "1master\n"
+                               + "=======\n"
+                               + "1side\n"
+                               + ">>>>>>> " + sideCommit.name() + "\n"
+                               + "2\n"
+                               + "3";
+               assertEquals(expected, read("file"));
+       }
+
+       @Theory
+       public void checkContentMergeConflict_noTree(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("file", "1\n2\n3");
+               git.add().addFilepattern("file").call();
+               RevCommit first = git.commit().setMessage("added file").call();
+
+               writeTrashFile("file", "1master\n2\n3");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified file on master").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("file", "1side\n2\n3");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified file on side").call();
+
+               try (ObjectInserter ins = db.newObjectInserter()) {
+                       ResolveMerger merger =
+                                       (ResolveMerger) strategy.newMerger(ins, db.getConfig());
+                       boolean noProblems = merger.merge(masterCommit, sideCommit);
+                       assertFalse(noProblems);
+                       assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
+
+                       MergeFormatter fmt = new MergeFormatter();
+                       merger.getMergeResults().get("file");
+                       try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                               fmt.formatMerge(out, merger.getMergeResults().get("file"),
+                                               "BASE", "OURS", "THEIRS", CHARSET.name());
+                               String expected = "<<<<<<< OURS\n"
+                                               + "1master\n"
+                                               + "=======\n"
+                                               + "1side\n"
+                                               + ">>>>>>> THEIRS\n"
+                                               + "2\n"
+                                               + "3";
+                               assertEquals(expected, new String(out.toByteArray(), CHARSET));
+                       }
+               }
+       }
+
+       /**
+        * Merging after criss-cross merges. In this case we merge together two
+        * commits which have two equally good common ancestors
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("1", "1\n2\n3");
+               git.add().addFilepattern("1").call();
+               RevCommit first = git.commit().setMessage("added 1").call();
+
+               writeTrashFile("1", "1master\n2\n3");
+               RevCommit masterCommit = git.commit().setAll(true)
+                               .setMessage("modified 1 on master").call();
+
+               writeTrashFile("1", "1master2\n2\n3");
+               git.commit().setAll(true)
+                               .setMessage("modified 1 on master again").call();
+
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("1", "1\n2\na\nb\nc\n3side");
+               RevCommit sideCommit = git.commit().setAll(true)
+                               .setMessage("modified 1 on side").call();
+
+               writeTrashFile("1", "1\n2\n3side2");
+               git.commit().setAll(true)
+                               .setMessage("modified 1 on side again").call();
+
+               MergeResult result = git.merge().setStrategy(strategy)
+                               .include(masterCommit).call();
+               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+               result.getNewHead();
+               git.checkout().setName("master").call();
+               result = git.merge().setStrategy(strategy).include(sideCommit).call();
+               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+
+               // we have two branches which are criss-cross merged. Try to merge the
+               // tips. This should succeed with RecursiveMerge and fail with
+               // ResolveMerge
+               try {
+                       MergeResult mergeResult = git.merge().setStrategy(strategy)
+                                       .include(git.getRepository().exactRef("refs/heads/side"))
+                                       .call();
+                       assertEquals(MergeStrategy.RECURSIVE, strategy);
+                       assertEquals(MergeResult.MergeStatus.MERGED,
+                                       mergeResult.getMergeStatus());
+                       assertEquals("1master2\n2\n3side2", read("1"));
+               } catch (JGitInternalException e) {
+                       assertEquals(MergeStrategy.RESOLVE, strategy);
+                       assertTrue(e.getCause() instanceof NoMergeBaseException);
+                       assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
+                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+               }
+       }
+
+       @Theory
+       public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
+                       throws Exception {
+               Git git = Git.wrap(db);
+
+               writeTrashFile("a.txt", "orig");
+               writeTrashFile("b.txt", "orig");
+               git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+               RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
+
+               // modify and delete files on the master branch
+               writeTrashFile("a.txt", "master");
+               git.rm().addFilepattern("b.txt").call();
+               RevCommit masterCommit = git.commit()
+                               .setMessage("modified a.txt, deleted b.txt").setAll(true)
+                               .call();
+
+               // switch back to a side branch
+               git.checkout().setCreateBranch(true).setStartPoint(first)
+                               .setName("side").call();
+               writeTrashFile("c.txt", "side");
+               git.add().addFilepattern("c.txt").call();
+               git.commit().setMessage("added c.txt").call();
+
+               // Get a handle to the the file so on windows it can't be deleted.
+               try (FileInputStream fis = new FileInputStream(
+                               new File(db.getWorkTree(), "b.txt"))) {
+                       MergeResult mergeRes = git.merge().setStrategy(strategy)
+                                       .include(masterCommit).call();
+                       if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
+                               // probably windows
+                               assertEquals(1, mergeRes.getFailingPaths().size());
+                               assertEquals(MergeFailureReason.COULD_NOT_DELETE,
+                                               mergeRes.getFailingPaths().get("b.txt"));
+                       }
+                       assertEquals(
+                                       "[a.txt, mode:100644, content:master]"
+                                                       + "[c.txt, mode:100644, content:side]",
+                                       indexState(CONTENT));
+               }
+       }
+
+       @Theory
+       public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
+               File f;
+               long lastTs4, lastTsIndex;
+               Git git = Git.wrap(db);
+               File indexFile = db.getIndexFile();
+
+               // Create initial content and remember when the last file was written.
+               f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
+               lastTs4 = FS.DETECTED.lastModified(f);
+
+               // add all files, commit and check this doesn't update any working tree
+               // files and that the index is in a new file system timer tick. Make
+               // sure to wait long enough before adding so the index doesn't contain
+               // racily clean entries
+               fsTick(f);
+               git.add().addFilepattern(".").call();
+               RevCommit firstCommit = git.commit().setMessage("initial commit")
+                               .call();
+               checkConsistentLastModified("0", "1", "2", "3", "4");
+               checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
+               assertEquals("Commit should not touch working tree file 4", lastTs4,
+                               FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
+               lastTsIndex = FS.DETECTED.lastModified(indexFile);
+
+               // Do modifications on the master branch. Then add and commit. This
+               // should touch only "0", "2 and "3"
+               fsTick(indexFile);
+               f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
+                               null);
+               fsTick(f);
+               git.add().addFilepattern(".").call();
+               RevCommit masterCommit = git.commit().setMessage("master commit")
+                               .call();
+               checkConsistentLastModified("0", "1", "2", "3", "4");
+               checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+                               + lastTsIndex, "<0", "2", "3", "<.git/index");
+               lastTsIndex = FS.DETECTED.lastModified(indexFile);
+
+               // Checkout a side branch. This should touch only "0", "2 and "3"
+               fsTick(indexFile);
+               git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
+                               .setName("side").call();
+               checkConsistentLastModified("0", "1", "2", "3", "4");
+               checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+                               + lastTsIndex, "<0", "2", "3", ".git/index");
+               lastTsIndex = FS.DETECTED.lastModified(indexFile);
+
+               // This checkout may have populated worktree and index so fast that we
+               // may have smudged entries now. Check that we have the right content
+               // and then rewrite the index to get rid of smudged state
+               assertEquals("[0, mode:100644, content:orig]" //
+                               + "[1, mode:100644, content:orig]" //
+                               + "[2, mode:100644, content:1\n2\n3]" //
+                               + "[3, mode:100644, content:orig]" //
+                               + "[4, mode:100644, content:orig]", //
+                               indexState(CONTENT));
+               fsTick(indexFile);
+               f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
+               lastTs4 = FS.DETECTED.lastModified(f);
+               fsTick(f);
+               git.add().addFilepattern(".").call();
+               checkConsistentLastModified("0", "1", "2", "3", "4");
+               checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
+                               "4", "<.git/index");
+               lastTsIndex = FS.DETECTED.lastModified(indexFile);
+
+               // Do modifications on the side branch. Touch only "1", "2 and "3"
+               fsTick(indexFile);
+               f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
+               fsTick(f);
+               git.add().addFilepattern(".").call();
+               git.commit().setMessage("side commit").call();
+               checkConsistentLastModified("0", "1", "2", "3", "4");
+               checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
+                               + lastTsIndex, "<1", "2", "3", "<.git/index");
+               lastTsIndex = FS.DETECTED.lastModified(indexFile);
+
+               // merge master and side. Should only touch "0," "2" and "3"
+               fsTick(indexFile);
+               git.merge().setStrategy(strategy).include(masterCommit).call();
+               checkConsistentLastModified("0", "1", "2", "4");
+               checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
+                               + lastTsIndex, "<0", "2", "3", ".git/index");
+               assertEquals(
+                               "[0, mode:100644, content:master]" //
+                                               + "[1, mode:100644, content:side]" //
+                                               + "[2, mode:100644, content:1master\n2\n3side]" //
+                                               + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
+                                               + "[4, mode:100644, content:orig]", //
+                               indexState(CONTENT));
+       }
+
+       /**
+        * Merging two conflicting submodules when the index does not contain any
+        * entry for that submodule.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeConflictingSubmodulesWithoutIndex(
+                       MergeStrategy strategy) throws Exception {
+               Git git = Git.wrap(db);
+               writeTrashFile("initial", "initial");
+               git.add().addFilepattern("initial").call();
+               RevCommit initial = git.commit().setMessage("initial").call();
+
+               writeSubmodule("one", ObjectId
+                               .fromString("1000000000000000000000000000000000000000"));
+               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
+               RevCommit right = git.commit().setMessage("added one").call();
+
+               // a second commit in the submodule
+
+               git.checkout().setStartPoint(initial).setName("left")
+                               .setCreateBranch(true).call();
+               writeSubmodule("one", ObjectId
+                               .fromString("2000000000000000000000000000000000000000"));
+
+               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
+               git.commit().setMessage("a different one").call();
+
+               MergeResult result = git.merge().setStrategy(strategy).include(right)
+                               .call();
+
+               assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+               Map<String, int[][]> conflicts = result.getConflicts();
+               assertEquals(1, conflicts.size());
+               assertNotNull(conflicts.get("one"));
+       }
+
+       /**
+        * Merging two non-conflicting submodules when the index does not contain
+        * any entry for either submodule.
+        *
+        * @param strategy
+        * @throws Exception
+        */
+       @Theory
+       public void checkMergeNonConflictingSubmodulesWithoutIndex(
+                       MergeStrategy strategy) throws Exception {
+               Git git = Git.wrap(db);
+               writeTrashFile("initial", "initial");
+               git.add().addFilepattern("initial").call();
+
+               writeSubmodule("one", ObjectId
+                               .fromString("1000000000000000000000000000000000000000"));
+
+               // Our initial commit should include a .gitmodules with a bunch of
+               // comment lines, so that
+               // we don't have a content merge issue when we add a new submodule at
+               // the top and a different
+               // one at the bottom. This is sort of a hack, but it should allow
+               // add/add submodule merges
+               String existing = read(Constants.DOT_GIT_MODULES);
+               String context = "\n# context\n# more context\n# yet more context\n";
+               write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
+                               existing + context + context + context);
+
+               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
+               RevCommit initial = git.commit().setMessage("initial").call();
+
+               writeSubmodule("two", ObjectId
+                               .fromString("1000000000000000000000000000000000000000"));
+               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
+
+               RevCommit right = git.commit().setMessage("added two").call();
+
+               git.checkout().setStartPoint(initial).setName("left")
+                               .setCreateBranch(true).call();
+
+               // we need to manually create the submodule for three for the
+               // .gitmodules hackery
+               addSubmoduleToIndex("three", ObjectId
+                               .fromString("1000000000000000000000000000000000000000"));
+               new File(db.getWorkTree(), "three").mkdir();
+
+               existing = read(Constants.DOT_GIT_MODULES);
+               String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
+                               + db.getDirectory().toURI() + "\n";
+               write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
+                               three + existing);
+
+               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
+               git.commit().setMessage("a different one").call();
+
+               MergeResult result = git.merge().setStrategy(strategy).include(right)
+                               .call();
+
+               assertNull(result.getCheckoutConflicts());
+               assertNull(result.getFailingPaths());
+               for (String dir : Arrays.asList("one", "two", "three")) {
+                       assertTrue(new File(db.getWorkTree(), dir).isDirectory());
+               }
+       }
+
+       private void writeSubmodule(String path, ObjectId commit)
+                       throws IOException, ConfigInvalidException {
+               addSubmoduleToIndex(path, commit);
+               new File(db.getWorkTree(), path).mkdir();
+
+               StoredConfig config = db.getConfig();
+               config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+                               ConfigConstants.CONFIG_KEY_URL,
+                               db.getDirectory().toURI().toString());
+               config.save();
+
+               FileBasedConfig modulesConfig = new FileBasedConfig(
+                               new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
+                               db.getFS());
+               modulesConfig.load();
+               modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+                               ConfigConstants.CONFIG_KEY_PATH, path);
+               modulesConfig.save();
+
+       }
+
+       private void addSubmoduleToIndex(String path, ObjectId commit)
+                       throws IOException {
+               DirCache cache = db.lockDirCache();
+               DirCacheEditor editor = cache.editor();
+               editor.add(new DirCacheEditor.PathEdit(path) {
+
+                       @Override
+                       public void apply(DirCacheEntry ent) {
+                               ent.setFileMode(FileMode.GITLINK);
+                               ent.setObjectId(commit);
+                       }
+               });
+               editor.commit();
+       }
+
+       // Assert that every specified index entry has the same last modification
+       // timestamp as the associated file
+       private void checkConsistentLastModified(String... pathes)
+                       throws IOException {
+               DirCache dc = db.readDirCache();
+               File workTree = db.getWorkTree();
+               for (String path : pathes)
+                       assertEquals(
+                                       "IndexEntry with path "
+                                                       + path
+                                                       + " has lastmodified with is different from the worktree file",
+                                       FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
+                                                       .getLastModified());
+       }
+
+       // Assert that modification timestamps of working tree files are as
+       // expected. You may specify n files. It is asserted that every file
+       // i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
+       // then this file must be younger then file i. A path "*<modtime>"
+       // represents a file with a modification time of <modtime>
+       // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
+       private void checkModificationTimeStampOrder(String... pathes)
+                       throws IOException {
+               long lastMod = Long.MIN_VALUE;
+               for (String p : pathes) {
+                       boolean strong = p.startsWith("<");
+                       boolean fixed = p.charAt(strong ? 1 : 0) == '*';
+                       p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
+                       long curMod = fixed ? Long.valueOf(p).longValue()
+                                       : FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
+                       if (strong)
+                               assertTrue("path " + p + " is not younger than predecesssor",
+                                               curMod > lastMod);
+                       else
+                               assertTrue("path " + p + " is older than predecesssor",
+                                               curMod >= lastMod);
+               }
+       }
+
+       private String readBlob(ObjectId treeish, String path) throws Exception {
+               TestRepository<?> tr = new TestRepository<>(db);
+               RevWalk rw = tr.getRevWalk();
+               RevTree tree = rw.parseTree(treeish);
+               RevObject obj = tr.get(tree, path);
+               if (obj == null) {
+                       return null;
+               }
+               return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), CHARSET);
+       }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
deleted file mode 100644 (file)
index 190224a..0000000
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheEditor;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.NoMergeBaseException;
-import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
-import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevBlob;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathFilter;
-import org.junit.Before;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.runner.RunWith;
-
-@RunWith(Theories.class)
-public class RecursiveMergerTest extends RepositoryTestCase {
-       static int counter = 0;
-
-       @DataPoints
-       public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
-                       MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
-
-       public enum IndexState {
-               Bare, Missing, SameAsHead, SameAsOther, SameAsWorkTree, DifferentFromHeadAndOtherAndWorktree
-       }
-
-       @DataPoints
-       public static IndexState[] indexStates = IndexState.values();
-
-       public enum WorktreeState {
-               Bare, Missing, SameAsHead, DifferentFromHeadAndOther, SameAsOther;
-       }
-
-       @DataPoints
-       public static WorktreeState[] worktreeStates = WorktreeState.values();
-
-       private TestRepository<FileRepository> db_t;
-
-       @Override
-       @Before
-       public void setUp() throws Exception {
-               super.setUp();
-               db_t = new TestRepository<>(db);
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. In master and side different
-        * files are touched. No need to do a real content merge.
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge(MergeStrategy strategy, IndexState indexState,
-                       WorktreeState worktreeState) throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-               // fill the repo
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("m", ",m0").message("m0").create();
-               RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0).add("s", "s1").message("s1")
-                               .create();
-               RevCommit s2 = side.commit().parent(m1).add("m", "m1")
-                               .message("s2(merge)").create();
-               RevCommit m2 = master.commit().parent(s1).add("s", "s1")
-                               .message("m2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "m", "side");
-               modifyWorktree(worktreeState, "s", "side");
-               modifyIndex(indexState, "m", "side");
-               modifyIndex(indexState, "s", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing
-                                       || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
-                               // index is dirty
-                               expectSuccess = false;
-
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       assertEquals("m1",
-                                       contentAsString(db, merger.getResultTreeId(), "m"));
-                       assertEquals("s1",
-                                       contentAsString(db, merger.getResultTreeId(), "s"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. m1 and s1 are the two root
-        * commits of the repo. In master and side different files are touched.
-        * No need to do a real content merge.
-        *
-        * <pre>
-        * m1--m2
-        *   \/
-        *   /\
-        * s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_twoRoots(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-               // fill the repo
-               BranchBuilder master = db_t.branch("master");
-               BranchBuilder side = db_t.branch("side");
-               RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               RevCommit s1 = side.commit().add("s", "s1").message("s1").create();
-               RevCommit s2 = side.commit().parent(m1).add("m", "m1")
-                               .message("s2(merge)").create();
-               RevCommit m2 = master.commit().parent(s1).add("s", "s1")
-                               .message("m2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "m", "side");
-               modifyWorktree(worktreeState, "s", "side");
-               modifyIndex(indexState, "m", "side");
-               modifyIndex(indexState, "s", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing
-                                       || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
-                               // index is dirty
-                               expectSuccess = false;
-
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       assertEquals("m1",
-                                       contentAsString(db, merger.getResultTreeId(), "m"));
-                       assertEquals("s1",
-                                       contentAsString(db, merger.getResultTreeId(), "s"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. The same file is modified
-        * in both branches. The modifications should be mergeable. m2 and s2
-        * contain branch specific conflict resolutions. Therefore m2 and s2 don't contain the same content.
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_mergeable(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
-                               .message("m0").create();
-               RevCommit m1 = master.commit()
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
-                               .create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0)
-                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
-                               .create();
-               RevCommit s2 = side.commit().parent(m1)
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
-                               .message("s2(merge)").create();
-               RevCommit m2 = master
-                               .commit()
-                               .parent(s1)
-                               .add("f", "1-master\n2\n3-res(master)\n4\n5\n6\n7\n8\n9-side\n")
-                               .message("m2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
-                               // index is dirty
-                               expectSuccess = false;
-                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
-                                       || worktreeState == WorktreeState.SameAsOther)
-                               expectSuccess = false;
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (!expectSuccess)
-                               // if the merge was not successful skip testing the state of index and workingtree
-                               return;
-                       assertEquals(
-                                       "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side",
-                                       contentAsString(db, merger.getResultTreeId(), "f"));
-                       if (indexState != IndexState.Bare)
-                               assertEquals(
-                                               "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                       if (worktreeState != WorktreeState.Bare
-                                       && worktreeState != WorktreeState.Missing)
-                               assertEquals(
-                                               "1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n",
-                                               read("f"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. The same file is modified
-        * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
-        * is choosen as parent. Choosing m0 as parent would not be sufficient (in contrast to the merge in
-        * crissCrossMerge_mergeable). m2 and s2 contain branch specific conflict resolutions. Therefore m2
-        * and s2 don't contain the same content.
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_mergeable2(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n")
-                               .message("m0")
-                               .create();
-               RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
-                               .message("m1").create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
-                               .message("s1").create();
-               RevCommit s2 = side.commit().parent(m1)
-                               .add("f", "1-master\n2\n3-side-r\n")
-                               .message("s2(merge)")
-                               .create();
-               RevCommit m2 = master.commit().parent(s1)
-                               .add("f", "1-master-r\n2\n3-side\n")
-                               .message("m2(merge)")
-                               .create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
-                               // index is dirty
-                               expectSuccess = false;
-                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
-                                       || worktreeState == WorktreeState.SameAsOther)
-                               expectSuccess = false;
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (!expectSuccess)
-                               // if the merge was not successful skip testing the state of
-                               // index and workingtree
-                               return;
-                       assertEquals(
-                                       "1-master-r\n2\n3-side-r",
-                                       contentAsString(db, merger.getResultTreeId(), "f"));
-                       if (indexState != IndexState.Bare)
-                               assertEquals(
-                                               "[f, mode:100644, content:1-master-r\n2\n3-side-r\n]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                       if (worktreeState != WorktreeState.Bare
-                                       && worktreeState != WorktreeState.Missing)
-                               assertEquals(
-                                               "1-master-r\n2\n3-side-r\n",
-                                               read("f"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. m1 and s1 are not mergeable
-        * without conflicts. The same file is modified in both branches. The
-        * modifications should be mergeable but only if the merge result of
-        * merging m1 and s1 is choosen as parent (including the conflict markers).
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_ParentsNotMergeable(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n").message("m0")
-                               .create();
-               RevCommit m1 = master.commit().add("f", "1\nx(master)\n2\n3\n")
-                               .message("m1").create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0)
-                               .add("f", "1\nx(side)\n2\n3\ny(side)\n")
-                               .message("s1").create();
-               RevCommit s2 = side.commit().parent(m1)
-                               .add("f", "1\nx(side)\n2\n3\ny(side-again)\n")
-                               .message("s2(merge)")
-                               .create();
-               RevCommit m2 = master.commit().parent(s1)
-                               .add("f", "1\nx(side)\n2\n3\ny(side)\n").message("m2(merge)")
-                               .create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
-                               // index is dirty
-                               expectSuccess = false;
-                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
-                                       || worktreeState == WorktreeState.SameAsOther)
-                               expectSuccess = false;
-                       assertEquals("Merge didn't return as expected: strategy:"
-                                       + strategy.getName() + ", indexState:" + indexState
-                                       + ", worktreeState:" + worktreeState + " . ",
-                                       Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (!expectSuccess)
-                               // if the merge was not successful skip testing the state of
-                               // index and workingtree
-                               return;
-                       assertEquals("1\nx(side)\n2\n3\ny(side-again)",
-                                       contentAsString(db, merger.getResultTreeId(), "f"));
-                       if (indexState != IndexState.Bare)
-                               assertEquals(
-                                               "[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                       if (worktreeState != WorktreeState.Bare
-                                       && worktreeState != WorktreeState.Missing)
-                               assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. The same file is modified
-        * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
-        * is choosen as parent. On both branches delete and modify files untouched on the other branch.
-        * On both branches create new files. Make sure these files are correctly merged and
-        * exist in the workingtree.
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_checkOtherFiles(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n").add("m.m", "0")
-                               .add("m.d", "0").add("s.m", "0").add("s.d", "0").message("m0")
-                               .create();
-               RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
-                               .add("m.c", "0").add("m.m", "1").rm("m.d").message("m1")
-                               .create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
-                               .add("s.c", "0").add("s.m", "1").rm("s.d").message("s1")
-                               .create();
-               RevCommit s2 = side.commit().parent(m1)
-                               .add("f", "1-master\n2\n3-side-r\n").add("m.m", "1")
-                               .add("m.c", "0").rm("m.d").message("s2(merge)").create();
-               RevCommit m2 = master.commit().parent(s1)
-                               .add("f", "1-master-r\n2\n3-side\n").add("s.m", "1")
-                               .add("s.c", "0").rm("s.d").message("m2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
-                               // index is dirty
-                               expectSuccess = false;
-                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
-                                       || worktreeState == WorktreeState.SameAsOther)
-                               expectSuccess = false;
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (!expectSuccess)
-                               // if the merge was not successful skip testing the state of
-                               // index and workingtree
-                               return;
-                       assertEquals(
-                                       "1-master-r\n2\n3-side-r",
-                                       contentAsString(db, merger.getResultTreeId(), "f"));
-                       if (indexState != IndexState.Bare)
-                               assertEquals(
-                                               "[f, mode:100644, content:1-master-r\n2\n3-side-r\n][m.c, mode:100644, content:0][m.m, mode:100644, content:1][s.c, mode:100644, content:0][s.m, mode:100644, content:1]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                       if (worktreeState != WorktreeState.Bare
-                                       && worktreeState != WorktreeState.Missing) {
-                               assertEquals(
-                                               "1-master-r\n2\n3-side-r\n",
-                                               read("f"));
-                               assertTrue(check("s.c"));
-                               assertFalse(check("s.d"));
-                               assertTrue(check("s.m"));
-                               assertTrue(check("m.c"));
-                               assertFalse(check("m.d"));
-                               assertTrue(check("m.m"));
-                       }
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 from the following topology. The same file is modified
-        * in both branches. The modifications are not automatically
-        * mergeable. m2 and s2 contain branch specific conflict resolutions.
-        * Therefore m2 and s2 don't contain the same content.
-        *
-        * <pre>
-        * m0--m1--m2
-        *   \   \/
-        *    \  /\
-        *     s1--s2
-        * </pre>
-        */
-       public void crissCrossMerge_nonmergeable(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
-                               .message("m0").create();
-               RevCommit m1 = master.commit()
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
-                               .create();
-               db_t.getRevWalk().parseCommit(m1);
-
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0)
-                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
-                               .create();
-               RevCommit s2 = side.commit().parent(m1)
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
-                               .message("s2(merge)").create();
-               RevCommit m2 = master.commit().parent(s1)
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n")
-                               .message("m2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       assertFalse(merger.merge(new RevCommit[] { m2, s2 }));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (indexState == IndexState.SameAsHead
-                                       && worktreeState == WorktreeState.SameAsHead) {
-                               assertEquals(
-                                               "[f, mode:100644, stage:1, content:1-master\n2\n3\n4\n5\n6\n7\n8\n9-side\n]"
-                                                               + "[f, mode:100644, stage:2, content:1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n]"
-                                                               + "[f, mode:100644, stage:3, content:1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                               assertEquals(
-                                               "1-master\n2\n3\n4\n5\n6\n<<<<<<< OURS\n7-conflict\n=======\n7-res(side)\n>>>>>>> THEIRS\n8\n9-side\n",
-                                               read("f"));
-                       }
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       /**
-        * Merging m2,s2 which have three common predecessors.The same file is modified
-        * in all branches. The modifications should be mergeable. m2 and s2
-        * contain branch specific conflict resolutions. Therefore m2 and s2
-        * don't contain the same content.
-        *
-        * <pre>
-        *     m1-----m2
-        *    /  \/  /
-        *   /   /\ /
-        * m0--o1  x
-        *   \   \/ \
-        *    \  /\  \
-        *     s1-----s2
-        * </pre>
-        */
-       public void crissCrossMerge_ThreeCommonPredecessors(MergeStrategy strategy,
-                       IndexState indexState, WorktreeState worktreeState)
-                       throws Exception {
-               if (!validateStates(indexState, worktreeState))
-                       return;
-
-               BranchBuilder master = db_t.branch("master");
-               RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
-                               .message("m0").create();
-               RevCommit m1 = master.commit()
-                               .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
-                               .create();
-               BranchBuilder side = db_t.branch("side");
-               RevCommit s1 = side.commit().parent(m0)
-                               .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
-                               .create();
-               BranchBuilder other = db_t.branch("other");
-               RevCommit o1 = other.commit().parent(m0)
-                               .add("f", "1\n2\n3\n4\n5-other\n6\n7\n8\n9\n").message("o1")
-                               .create();
-
-               RevCommit m2 = master
-                               .commit()
-                               .parent(s1)
-                               .parent(o1)
-                               .add("f",
-                                               "1-master\n2\n3-res(master)\n4\n5-other\n6\n7\n8\n9-side\n")
-                               .message("m2(merge)").create();
-
-               RevCommit s2 = side
-                               .commit()
-                               .parent(m1)
-                               .parent(o1)
-                               .add("f",
-                                               "1-master\n2\n3\n4\n5-other\n6\n7-res(side)\n8\n9-side\n")
-                               .message("s2(merge)").create();
-
-               Git git = Git.wrap(db);
-               git.checkout().setName("master").call();
-               modifyWorktree(worktreeState, "f", "side");
-               modifyIndex(indexState, "f", "side");
-
-               ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
-                               worktreeState == WorktreeState.Bare);
-               if (worktreeState != WorktreeState.Bare)
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-               try {
-                       boolean expectSuccess = true;
-                       if (!(indexState == IndexState.Bare
-                                       || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
-                               // index is dirty
-                               expectSuccess = false;
-                       else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
-                                       || worktreeState == WorktreeState.SameAsOther)
-                               // workingtree is dirty
-                               expectSuccess = false;
-
-                       assertEquals(Boolean.valueOf(expectSuccess),
-                                       Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       if (!expectSuccess)
-                               // if the merge was not successful skip testing the state of index and workingtree
-                               return;
-                       assertEquals(
-                                       "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side",
-                                       contentAsString(db, merger.getResultTreeId(), "f"));
-                       if (indexState != IndexState.Bare)
-                               assertEquals(
-                                               "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n]",
-                                               indexState(LocalDiskRepositoryTestCase.CONTENT));
-                       if (worktreeState != WorktreeState.Bare
-                                       && worktreeState != WorktreeState.Missing)
-                               assertEquals(
-                                               "1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n",
-                                               read("f"));
-               } catch (NoMergeBaseException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertEquals(e.getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       void modifyIndex(IndexState indexState, String path, String other)
-                       throws Exception {
-               RevBlob blob;
-               switch (indexState) {
-               case Missing:
-                       setIndex(null, path);
-                       break;
-               case SameAsHead:
-                       setIndex(contentId(Constants.HEAD, path), path);
-                       break;
-               case SameAsOther:
-                       setIndex(contentId(other, path), path);
-                       break;
-               case SameAsWorkTree:
-                       blob = db_t.blob(read(path));
-                       setIndex(blob, path);
-                       break;
-               case DifferentFromHeadAndOtherAndWorktree:
-                       blob = db_t.blob(Integer.toString(counter++));
-                       setIndex(blob, path);
-                       break;
-               case Bare:
-                       File file = new File(db.getDirectory(), "index");
-                       if (!file.exists())
-                               return;
-                       db.close();
-                       file.delete();
-                       db = new FileRepository(db.getDirectory());
-                       db_t = new TestRepository<>(db);
-                       break;
-               }
-       }
-
-       private void setIndex(final ObjectId id, String path)
-                       throws MissingObjectException, IOException {
-               DirCache lockedDircache;
-               DirCacheEditor dcedit;
-
-               lockedDircache = db.lockDirCache();
-               dcedit = lockedDircache.editor();
-               try {
-                       if (id != null) {
-                               final ObjectLoader contLoader = db.newObjectReader().open(id);
-                               dcedit.add(new DirCacheEditor.PathEdit(path) {
-                                       @Override
-                                       public void apply(DirCacheEntry ent) {
-                                               ent.setFileMode(FileMode.REGULAR_FILE);
-                                               ent.setLength(contLoader.getSize());
-                                               ent.setObjectId(id);
-                                       }
-                               });
-                       } else
-                               dcedit.add(new DirCacheEditor.DeletePath(path));
-               } finally {
-                       dcedit.commit();
-               }
-       }
-
-       private ObjectId contentId(String revName, String path) throws Exception {
-               RevCommit headCommit = db_t.getRevWalk().parseCommit(
-                               db.resolve(revName));
-               db_t.parseBody(headCommit);
-               return db_t.get(headCommit.getTree(), path).getId();
-       }
-
-       void modifyWorktree(WorktreeState worktreeState, String path, String other)
-                       throws Exception {
-               switch (worktreeState) {
-               case Missing:
-                       new File(db.getWorkTree(), path).delete();
-                       break;
-               case DifferentFromHeadAndOther:
-                       write(new File(db.getWorkTree(), path),
-                                       Integer.toString(counter++));
-                       break;
-               case SameAsHead:
-                       try (FileOutputStream fos = new FileOutputStream(
-                                       new File(db.getWorkTree(), path))) {
-                               db.newObjectReader().open(contentId(Constants.HEAD, path))
-                                               .copyTo(fos);
-                       }
-                       break;
-               case SameAsOther:
-                       try (FileOutputStream fos = new FileOutputStream(
-                                       new File(db.getWorkTree(), path))) {
-                               db.newObjectReader().open(contentId(other, path)).copyTo(fos);
-                       }
-                       break;
-               case Bare:
-                       if (db.isBare())
-                               return;
-                       File workTreeFile = db.getWorkTree();
-                       db.getConfig().setBoolean("core", null, "bare", true);
-                       db.getDirectory().renameTo(new File(workTreeFile, "test.git"));
-                       db = new FileRepository(new File(workTreeFile, "test.git"));
-                       db_t = new TestRepository<>(db);
-               }
-       }
-
-       private boolean validateStates(IndexState indexState,
-                       WorktreeState worktreeState) {
-               if (worktreeState == WorktreeState.Bare
-                               && indexState != IndexState.Bare)
-                       return false;
-               if (worktreeState != WorktreeState.Bare
-                               && indexState == IndexState.Bare)
-                       return false;
-               if (worktreeState != WorktreeState.DifferentFromHeadAndOther
-                               && indexState == IndexState.SameAsWorkTree)
-                       // would be a duplicate: the combination WorktreeState.X and
-                       // IndexState.X already covered this
-                       return false;
-               return true;
-       }
-
-       private String contentAsString(Repository r, ObjectId treeId, String path)
-                       throws MissingObjectException, IOException {
-               AnyObjectId blobId;
-               try (TreeWalk tw = new TreeWalk(r)) {
-                       tw.addTree(treeId);
-                       tw.setFilter(PathFilter.create(path));
-                       tw.setRecursive(true);
-                       if (!tw.next()) {
-                               return null;
-                       }
-                       blobId = tw.getObjectId(0);
-               }
-
-               StringBuilder result = new StringBuilder();
-               ObjectReader or = r.newObjectReader();
-               try (BufferedReader br = new BufferedReader(
-                               new InputStreamReader(or.open(blobId).openStream()))) {
-                       String line;
-                       boolean first = true;
-                       while ((line = br.readLine()) != null) {
-                               if (!first) {
-                                       result.append('\n');
-                               }
-                               result.append(line);
-                               first = false;
-                       }
-                       return result.toString();
-               }
-       }
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
deleted file mode 100644 (file)
index 2f8d606..0000000
+++ /dev/null
@@ -1,1333 +0,0 @@
-/*
- * Copyright (C) 2012, Robin Stocker <robin@nibor.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.merge;
-
-import static org.eclipse.jgit.lib.Constants.CHARSET;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.MergeResult;
-import org.eclipse.jgit.api.MergeResult.MergeStatus;
-import org.eclipse.jgit.api.errors.CheckoutConflictException;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheEditor;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.NoMergeBaseException;
-import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
-import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.ObjectStream;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
-import org.junit.Assert;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.runner.RunWith;
-
-@RunWith(Theories.class)
-public class ResolveMergerTest extends RepositoryTestCase {
-
-       @DataPoints
-       public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
-                       MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
-
-       @Theory
-       public void failingDeleteOfDirectoryWithUntrackedContent(
-                       MergeStrategy strategy) throws Exception {
-               File folder1 = new File(db.getWorkTree(), "folder1");
-               FileUtils.mkdir(folder1);
-               File file = new File(folder1, "file1.txt");
-               write(file, "folder1--file1.txt");
-               file = new File(folder1, "file2.txt");
-               write(file, "folder1--file2.txt");
-
-               try (Git git = new Git(db)) {
-                       git.add().addFilepattern(folder1.getName()).call();
-                       RevCommit base = git.commit().setMessage("adding folder").call();
-
-                       recursiveDelete(folder1);
-                       git.rm().addFilepattern("folder1/file1.txt")
-                                       .addFilepattern("folder1/file2.txt").call();
-                       RevCommit other = git.commit()
-                                       .setMessage("removing folders on 'other'").call();
-
-                       git.checkout().setName(base.name()).call();
-
-                       file = new File(db.getWorkTree(), "unrelated.txt");
-                       write(file, "unrelated");
-
-                       git.add().addFilepattern("unrelated.txt").call();
-                       RevCommit head = git.commit().setMessage("Adding another file").call();
-
-                       // Untracked file to cause failing path for delete() of folder1
-                       // but that's ok.
-                       file = new File(folder1, "file3.txt");
-                       write(file, "folder1--file3.txt");
-
-                       ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
-                       merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
-                       merger.setWorkingTreeIterator(new FileTreeIterator(db));
-                       boolean ok = merger.merge(head.getId(), other.getId());
-                       assertTrue(ok);
-                       assertTrue(file.exists());
-               }
-       }
-
-       /**
-        * Merging two conflicting subtrees when the index does not contain any file
-        * in that subtree should lead to a conflicting state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("d/1", "master");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "side");
-               git.commit().setAll(true).setMessage("modified d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two different but mergeable subtrees when the index does not
-        * contain any file in that subtree should lead to a merged state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "1\n2\n3");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("d/1", "1master\n2\n3");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "1\n2\n3side");
-               git.commit().setAll(true).setMessage("modified d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * An existing directory without tracked content should not prevent merging
-        * a tree where that directory exists.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkUntrackedFolderIsNotAConflict(
-                       MergeStrategy strategy) throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "1");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("e/1", "4");
-               git.add().addFilepattern("e/1").call();
-               RevCommit masterCommit = git.commit().setMessage("added e/1").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("f/1", "5");
-               git.add().addFilepattern("f/1").call();
-               git.commit().setAll(true).setMessage("added f/1")
-                               .call();
-
-               // Untracked directory e shall not conflict with merged e/1
-               writeTrashFile("e/2", "d two");
-
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * A tracked file is replaced by a folder in THEIRS.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("sub", "file");
-               git.add().addFilepattern("sub").call();
-               RevCommit first = git.commit().setMessage("initial").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-
-               git.rm().addFilepattern("sub").call();
-               writeTrashFile("sub/file", "subfile");
-               git.add().addFilepattern("sub/file").call();
-               RevCommit masterCommit = git.commit().setMessage("file -> folder")
-                               .call();
-
-               git.checkout().setName("master").call();
-               writeTrashFile("noop", "other");
-               git.add().addFilepattern("noop").call();
-               git.commit().setAll(true).setMessage("noop").call();
-
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * A tracked file is replaced by a folder in OURS.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("sub", "file");
-               git.add().addFilepattern("sub").call();
-               RevCommit first = git.commit().setMessage("initial").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("noop", "other");
-               git.add().addFilepattern("noop").call();
-               RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
-                               .call();
-
-               git.checkout().setName("master").call();
-               git.rm().addFilepattern("sub").call();
-               writeTrashFile("sub/file", "subfile");
-               git.add().addFilepattern("sub/file").call();
-               git.commit().setMessage("file -> folder")
-                               .call();
-
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(sideCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * An existing directory without tracked content should not prevent merging
-        * a file with that name.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
-                       MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "1");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("e", "4");
-               git.add().addFilepattern("e").call();
-               RevCommit masterCommit = git.commit().setMessage("added e").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("f/1", "5");
-               git.add().addFilepattern("f/1").call();
-               git.commit().setAll(true).setMessage("added f/1").call();
-
-               // Untracked empty directory hierarcy e/1 shall not conflict with merged
-               // e/1
-               FileUtils.mkdirs(new File(trash, "e/1"), true);
-
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
-                               indexState(CONTENT));
-       }
-
-       @Theory
-       public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
-                       GitAPIException {
-               Git git = Git.wrap(db);
-               db.getConfig().setString("core", null, "autocrlf", "false");
-               db.getConfig().save();
-               writeTrashFile("crlf.txt", "some\r\ndata\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("base").call();
-
-               git.branchCreate().setName("brancha").call();
-
-               writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("on master").call();
-
-               git.checkout().setName("brancha").call();
-               writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("on brancha").call();
-
-               db.getConfig().setString("core", null, "autocrlf", "input");
-               db.getConfig().save();
-
-               MergeResult mergeResult = git.merge().setStrategy(strategy)
-                               .include(db.resolve("master"))
-                               .call();
-               assertEquals(MergeResult.MergeStatus.MERGED,
-                               mergeResult.getMergeStatus());
-       }
-
-       @Theory
-       public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
-                       throws IOException, GitAPIException {
-               Git git = Git.wrap(db);
-               db.getConfig().setString("core", null, "autocrlf", "true");
-               db.getConfig().save();
-               writeTrashFile("crlf.txt", "a crlf file\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("base").call();
-
-               git.branchCreate().setName("brancha").call();
-
-               writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("on master").call();
-
-               git.checkout().setName("brancha").call();
-               File testFile = writeTrashFile("crlf.txt",
-                               "a first line\r\na crlf file\r\n");
-               git.add().addFilepattern("crlf.txt").call();
-               git.commit().setMessage("on brancha").call();
-
-               MergeResult mergeResult = git.merge().setStrategy(strategy)
-                               .include(db.resolve("master")).call();
-               assertEquals(MergeResult.MergeStatus.MERGED,
-                               mergeResult.getMergeStatus());
-               checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
-               assertEquals(
-                               "[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two equal subtrees when the index does not contain any file in
-        * that subtree should lead to a merged state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("d/1", "modified");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "modified");
-               git.commit().setAll(true).setMessage("modified d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals("[d/1, mode:100644, content:modified]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two equal subtrees with an incore merger should lead to a merged
-        * state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeEqualTreesInCore(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("d/1", "modified");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "modified");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-
-               ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
-                               true);
-               boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
-               assertTrue(noProblems);
-       }
-
-       /**
-        * Merging two equal subtrees with an incore merger should lead to a merged
-        * state, without using a Repository (the 'Gerrit' use case).
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               RevCommit first = git.commit().setMessage("added d/1").call();
-
-               writeTrashFile("d/1", "modified");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "modified");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-
-               try (ObjectInserter ins = db.newObjectInserter()) {
-                       ThreeWayMerger resolveMerger =
-                                       (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
-                       boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
-                       assertTrue(noProblems);
-               }
-       }
-
-       /**
-        * Merging two equal subtrees when the index and HEAD does not contain any
-        * file in that subtree should lead to a merged state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeEqualNewTrees(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("2", "orig");
-               git.add().addFilepattern("2").call();
-               RevCommit first = git.commit().setMessage("added 2").call();
-
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("added d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "orig");
-               git.add().addFilepattern("d/1").call();
-               git.commit().setAll(true).setMessage("added d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two conflicting subtrees when the index and HEAD does not contain
-        * any file in that subtree should lead to a conflicting state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeConflictingNewTrees(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("2", "orig");
-               git.add().addFilepattern("2").call();
-               RevCommit first = git.commit().setMessage("added 2").call();
-
-               writeTrashFile("d/1", "master");
-               git.add().addFilepattern("d/1").call();
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("added d/1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("d/1", "side");
-               git.add().addFilepattern("d/1").call();
-               git.commit().setAll(true).setMessage("added d/1 on side").call();
-
-               git.rm().addFilepattern("d/1").call();
-               git.rm().addFilepattern("d").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
-               assertEquals(
-                               "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two conflicting files when the index contains a tree for that
-        * path should lead to a failed state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("0", "orig");
-               git.add().addFilepattern("0").call();
-               RevCommit first = git.commit().setMessage("added 0").call();
-
-               writeTrashFile("0", "master");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified 0 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("0", "side");
-               git.commit().setAll(true).setMessage("modified 0 on side").call();
-
-               git.rm().addFilepattern("0").call();
-               writeTrashFile("0/0", "side");
-               git.add().addFilepattern("0/0").call();
-               MergeResult mergeRes = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
-       }
-
-       /**
-        * Merging two equal files when the index contains a tree for that path
-        * should lead to a failed state.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("0", "orig");
-               writeTrashFile("1", "1\n2\n3");
-               git.add().addFilepattern("0").addFilepattern("1").call();
-               RevCommit first = git.commit().setMessage("added 0, 1").call();
-
-               writeTrashFile("1", "1master\n2\n3");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified 1 on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("1", "1\n2\n3side");
-               git.commit().setAll(true).setMessage("modified 1 on side").call();
-
-               git.rm().addFilepattern("0").call();
-               writeTrashFile("0/0", "modified");
-               git.add().addFilepattern("0/0").call();
-               try {
-                       git.merge().setStrategy(strategy).include(masterCommit).call();
-                       Assert.fail("Didn't get the expected exception");
-               } catch (CheckoutConflictException e) {
-                       assertEquals(1, e.getConflictingPaths().size());
-                       assertEquals("0/0", e.getConflictingPaths().get(0));
-               }
-       }
-
-       @Theory
-       public void checkContentMergeNoConflict(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("file", "1\n2\n3");
-               git.add().addFilepattern("file").call();
-               RevCommit first = git.commit().setMessage("added file").call();
-
-               writeTrashFile("file", "1master\n2\n3");
-               git.commit().setAll(true).setMessage("modified file on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("file", "1\n2\n3side");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified file on side").call();
-
-               git.checkout().setName("master").call();
-               MergeResult result =
-                               git.merge().setStrategy(strategy).include(sideCommit).call();
-               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-               String expected = "1master\n2\n3side";
-               assertEquals(expected, read("file"));
-       }
-
-       @Theory
-       public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("file", "1\n2\n3");
-               git.add().addFilepattern("file").call();
-               RevCommit first = git.commit().setMessage("added file").call();
-
-               writeTrashFile("file", "1master\n2\n3");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified file on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("file", "1\n2\n3side");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified file on side").call();
-
-               try (ObjectInserter ins = db.newObjectInserter()) {
-                       ResolveMerger merger =
-                                       (ResolveMerger) strategy.newMerger(ins, db.getConfig());
-                       boolean noProblems = merger.merge(masterCommit, sideCommit);
-                       assertTrue(noProblems);
-                       assertEquals("1master\n2\n3side",
-                                       readBlob(merger.getResultTreeId(), "file"));
-               }
-       }
-
-
-       /**
-        * Merging a change involving large binary files should short-circuit reads.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
-               Git git = Git.wrap(db);
-               final int LINELEN = 72;
-
-               // setup a merge that would work correctly if we disconsider the stray '\0'
-               // that the file contains near the start.
-               byte[] binary = new byte[LINELEN * 2000];
-               for (int i = 0; i < binary.length; i++) {
-                       binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
-               }
-               binary[50] = '\0';
-
-               writeTrashFile("file", new String(binary, CHARSET));
-               git.add().addFilepattern("file").call();
-               RevCommit first = git.commit().setMessage("added file").call();
-
-               // Generate an edit in a single line.
-               int idx = LINELEN * 1200 + 1;
-               byte save = binary[idx];
-               binary[idx] = '@';
-               writeTrashFile("file", new String(binary, CHARSET));
-
-               binary[idx] = save;
-               git.add().addFilepattern("file").call();
-               RevCommit masterCommit = git.commit().setAll(true)
-                       .setMessage("modified file l 1200").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
-               binary[LINELEN * 1500 + 1] = '!';
-               writeTrashFile("file", new String(binary, CHARSET));
-               git.add().addFilepattern("file").call();
-               RevCommit sideCommit = git.commit().setAll(true)
-                       .setMessage("modified file l 1500").call();
-
-               try (ObjectInserter ins = db.newObjectInserter()) {
-                       // Check that we don't read the large blobs.
-                       ObjectInserter forbidInserter = new ObjectInserter.Filter() {
-                               @Override
-                               protected ObjectInserter delegate() {
-                                       return ins;
-                               }
-
-                               @Override
-                               public ObjectReader newReader() {
-                                       return new BigReadForbiddenReader(super.newReader(), 8000);
-                               }
-                       };
-
-                       ResolveMerger merger =
-                               (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
-                       boolean noProblems = merger.merge(masterCommit, sideCommit);
-                       assertFalse(noProblems);
-               }
-       }
-
-       /**
-        * Throws an exception if reading beyond limit.
-        */
-       class BigReadForbiddenStream extends ObjectStream.Filter {
-               int limit;
-
-               BigReadForbiddenStream(ObjectStream orig, int limit) {
-                       super(orig.getType(), orig.getSize(), orig);
-                       this.limit = limit;
-               }
-
-               @Override
-               public long skip(long n) throws IOException {
-                       limit -= n;
-                       if (limit < 0) {
-                               throw new IllegalStateException();
-                       }
-
-                       return super.skip(n);
-               }
-
-               @Override
-               public int read() throws IOException {
-                       int r = super.read();
-                       limit--;
-                       if (limit < 0) {
-                               throw new IllegalStateException();
-                       }
-                       return r;
-               }
-
-               @Override
-               public int read(byte[] b, int off, int len) throws IOException {
-                       int n = super.read(b, off, len);
-                       limit -= n;
-                       if (limit < 0) {
-                               throw new IllegalStateException();
-                       }
-                       return n;
-               }
-       }
-
-       class BigReadForbiddenReader extends ObjectReader.Filter {
-               ObjectReader delegate;
-               int limit;
-
-               @Override
-               protected ObjectReader delegate() {
-                       return delegate;
-               }
-
-               BigReadForbiddenReader(ObjectReader delegate, int limit) {
-                       this.delegate = delegate;
-                       this.limit = limit;
-               }
-
-               @Override
-               public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
-                       ObjectLoader orig = super.open(objectId, typeHint);
-                       return new ObjectLoader.Filter() {
-                               @Override
-                               protected ObjectLoader delegate() {
-                                       return orig;
-                               }
-
-                               @Override
-                               public ObjectStream openStream() throws IOException {
-                                       ObjectStream os = orig.openStream();
-                                       return new BigReadForbiddenStream(os, limit);
-                               }
-                       };
-               }
-       }
-
-       @Theory
-       public void checkContentMergeConflict(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("file", "1\n2\n3");
-               git.add().addFilepattern("file").call();
-               RevCommit first = git.commit().setMessage("added file").call();
-
-               writeTrashFile("file", "1master\n2\n3");
-               git.commit().setAll(true).setMessage("modified file on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("file", "1side\n2\n3");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified file on side").call();
-
-               git.checkout().setName("master").call();
-               MergeResult result =
-                               git.merge().setStrategy(strategy).include(sideCommit).call();
-               assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-               String expected = "<<<<<<< HEAD\n"
-                               + "1master\n"
-                               + "=======\n"
-                               + "1side\n"
-                               + ">>>>>>> " + sideCommit.name() + "\n"
-                               + "2\n"
-                               + "3";
-               assertEquals(expected, read("file"));
-       }
-
-       @Theory
-       public void checkContentMergeConflict_noTree(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("file", "1\n2\n3");
-               git.add().addFilepattern("file").call();
-               RevCommit first = git.commit().setMessage("added file").call();
-
-               writeTrashFile("file", "1master\n2\n3");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified file on master").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("file", "1side\n2\n3");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified file on side").call();
-
-               try (ObjectInserter ins = db.newObjectInserter()) {
-                       ResolveMerger merger =
-                                       (ResolveMerger) strategy.newMerger(ins, db.getConfig());
-                       boolean noProblems = merger.merge(masterCommit, sideCommit);
-                       assertFalse(noProblems);
-                       assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
-
-                       MergeFormatter fmt = new MergeFormatter();
-                       merger.getMergeResults().get("file");
-                       try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-                               fmt.formatMerge(out, merger.getMergeResults().get("file"),
-                                               "BASE", "OURS", "THEIRS", CHARSET.name());
-                               String expected = "<<<<<<< OURS\n"
-                                               + "1master\n"
-                                               + "=======\n"
-                                               + "1side\n"
-                                               + ">>>>>>> THEIRS\n"
-                                               + "2\n"
-                                               + "3";
-                               assertEquals(expected, new String(out.toByteArray(), CHARSET));
-                       }
-               }
-       }
-
-       /**
-        * Merging after criss-cross merges. In this case we merge together two
-        * commits which have two equally good common ancestors
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("1", "1\n2\n3");
-               git.add().addFilepattern("1").call();
-               RevCommit first = git.commit().setMessage("added 1").call();
-
-               writeTrashFile("1", "1master\n2\n3");
-               RevCommit masterCommit = git.commit().setAll(true)
-                               .setMessage("modified 1 on master").call();
-
-               writeTrashFile("1", "1master2\n2\n3");
-               git.commit().setAll(true)
-                               .setMessage("modified 1 on master again").call();
-
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("1", "1\n2\na\nb\nc\n3side");
-               RevCommit sideCommit = git.commit().setAll(true)
-                               .setMessage("modified 1 on side").call();
-
-               writeTrashFile("1", "1\n2\n3side2");
-               git.commit().setAll(true)
-                               .setMessage("modified 1 on side again").call();
-
-               MergeResult result = git.merge().setStrategy(strategy)
-                               .include(masterCommit).call();
-               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-               result.getNewHead();
-               git.checkout().setName("master").call();
-               result = git.merge().setStrategy(strategy).include(sideCommit).call();
-               assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-
-               // we have two branches which are criss-cross merged. Try to merge the
-               // tips. This should succeed with RecursiveMerge and fail with
-               // ResolveMerge
-               try {
-                       MergeResult mergeResult = git.merge().setStrategy(strategy)
-                                       .include(git.getRepository().exactRef("refs/heads/side"))
-                                       .call();
-                       assertEquals(MergeStrategy.RECURSIVE, strategy);
-                       assertEquals(MergeResult.MergeStatus.MERGED,
-                                       mergeResult.getMergeStatus());
-                       assertEquals("1master2\n2\n3side2", read("1"));
-               } catch (JGitInternalException e) {
-                       assertEquals(MergeStrategy.RESOLVE, strategy);
-                       assertTrue(e.getCause() instanceof NoMergeBaseException);
-                       assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
-                                       MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
-               }
-       }
-
-       @Theory
-       public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
-                       throws Exception {
-               Git git = Git.wrap(db);
-
-               writeTrashFile("a.txt", "orig");
-               writeTrashFile("b.txt", "orig");
-               git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
-               RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
-
-               // modify and delete files on the master branch
-               writeTrashFile("a.txt", "master");
-               git.rm().addFilepattern("b.txt").call();
-               RevCommit masterCommit = git.commit()
-                               .setMessage("modified a.txt, deleted b.txt").setAll(true)
-                               .call();
-
-               // switch back to a side branch
-               git.checkout().setCreateBranch(true).setStartPoint(first)
-                               .setName("side").call();
-               writeTrashFile("c.txt", "side");
-               git.add().addFilepattern("c.txt").call();
-               git.commit().setMessage("added c.txt").call();
-
-               // Get a handle to the the file so on windows it can't be deleted.
-               try (FileInputStream fis = new FileInputStream(
-                               new File(db.getWorkTree(), "b.txt"))) {
-                       MergeResult mergeRes = git.merge().setStrategy(strategy)
-                                       .include(masterCommit).call();
-                       if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
-                               // probably windows
-                               assertEquals(1, mergeRes.getFailingPaths().size());
-                               assertEquals(MergeFailureReason.COULD_NOT_DELETE,
-                                               mergeRes.getFailingPaths().get("b.txt"));
-                       }
-                       assertEquals(
-                                       "[a.txt, mode:100644, content:master]"
-                                                       + "[c.txt, mode:100644, content:side]",
-                                       indexState(CONTENT));
-               }
-       }
-
-       @Theory
-       public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
-               File f;
-               long lastTs4, lastTsIndex;
-               Git git = Git.wrap(db);
-               File indexFile = db.getIndexFile();
-
-               // Create initial content and remember when the last file was written.
-               f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-               lastTs4 = FS.DETECTED.lastModified(f);
-
-               // add all files, commit and check this doesn't update any working tree
-               // files and that the index is in a new file system timer tick. Make
-               // sure to wait long enough before adding so the index doesn't contain
-               // racily clean entries
-               fsTick(f);
-               git.add().addFilepattern(".").call();
-               RevCommit firstCommit = git.commit().setMessage("initial commit")
-                               .call();
-               checkConsistentLastModified("0", "1", "2", "3", "4");
-               checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
-               assertEquals("Commit should not touch working tree file 4", lastTs4,
-                               FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
-               lastTsIndex = FS.DETECTED.lastModified(indexFile);
-
-               // Do modifications on the master branch. Then add and commit. This
-               // should touch only "0", "2 and "3"
-               fsTick(indexFile);
-               f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
-                               null);
-               fsTick(f);
-               git.add().addFilepattern(".").call();
-               RevCommit masterCommit = git.commit().setMessage("master commit")
-                               .call();
-               checkConsistentLastModified("0", "1", "2", "3", "4");
-               checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
-                               + lastTsIndex, "<0", "2", "3", "<.git/index");
-               lastTsIndex = FS.DETECTED.lastModified(indexFile);
-
-               // Checkout a side branch. This should touch only "0", "2 and "3"
-               fsTick(indexFile);
-               git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
-                               .setName("side").call();
-               checkConsistentLastModified("0", "1", "2", "3", "4");
-               checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
-                               + lastTsIndex, "<0", "2", "3", ".git/index");
-               lastTsIndex = FS.DETECTED.lastModified(indexFile);
-
-               // This checkout may have populated worktree and index so fast that we
-               // may have smudged entries now. Check that we have the right content
-               // and then rewrite the index to get rid of smudged state
-               assertEquals("[0, mode:100644, content:orig]" //
-                               + "[1, mode:100644, content:orig]" //
-                               + "[2, mode:100644, content:1\n2\n3]" //
-                               + "[3, mode:100644, content:orig]" //
-                               + "[4, mode:100644, content:orig]", //
-                               indexState(CONTENT));
-               fsTick(indexFile);
-               f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-               lastTs4 = FS.DETECTED.lastModified(f);
-               fsTick(f);
-               git.add().addFilepattern(".").call();
-               checkConsistentLastModified("0", "1", "2", "3", "4");
-               checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
-                               "4", "<.git/index");
-               lastTsIndex = FS.DETECTED.lastModified(indexFile);
-
-               // Do modifications on the side branch. Touch only "1", "2 and "3"
-               fsTick(indexFile);
-               f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
-               fsTick(f);
-               git.add().addFilepattern(".").call();
-               git.commit().setMessage("side commit").call();
-               checkConsistentLastModified("0", "1", "2", "3", "4");
-               checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
-                               + lastTsIndex, "<1", "2", "3", "<.git/index");
-               lastTsIndex = FS.DETECTED.lastModified(indexFile);
-
-               // merge master and side. Should only touch "0," "2" and "3"
-               fsTick(indexFile);
-               git.merge().setStrategy(strategy).include(masterCommit).call();
-               checkConsistentLastModified("0", "1", "2", "4");
-               checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
-                               + lastTsIndex, "<0", "2", "3", ".git/index");
-               assertEquals(
-                               "[0, mode:100644, content:master]" //
-                                               + "[1, mode:100644, content:side]" //
-                                               + "[2, mode:100644, content:1master\n2\n3side]" //
-                                               + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
-                                               + "[4, mode:100644, content:orig]", //
-                               indexState(CONTENT));
-       }
-
-       /**
-        * Merging two conflicting submodules when the index does not contain any
-        * entry for that submodule.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeConflictingSubmodulesWithoutIndex(
-                       MergeStrategy strategy) throws Exception {
-               Git git = Git.wrap(db);
-               writeTrashFile("initial", "initial");
-               git.add().addFilepattern("initial").call();
-               RevCommit initial = git.commit().setMessage("initial").call();
-
-               writeSubmodule("one", ObjectId
-                               .fromString("1000000000000000000000000000000000000000"));
-               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
-               RevCommit right = git.commit().setMessage("added one").call();
-
-               // a second commit in the submodule
-
-               git.checkout().setStartPoint(initial).setName("left")
-                               .setCreateBranch(true).call();
-               writeSubmodule("one", ObjectId
-                               .fromString("2000000000000000000000000000000000000000"));
-
-               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
-               git.commit().setMessage("a different one").call();
-
-               MergeResult result = git.merge().setStrategy(strategy).include(right)
-                               .call();
-
-               assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-               Map<String, int[][]> conflicts = result.getConflicts();
-               assertEquals(1, conflicts.size());
-               assertNotNull(conflicts.get("one"));
-       }
-
-       /**
-        * Merging two non-conflicting submodules when the index does not contain
-        * any entry for either submodule.
-        *
-        * @param strategy
-        * @throws Exception
-        */
-       @Theory
-       public void checkMergeNonConflictingSubmodulesWithoutIndex(
-                       MergeStrategy strategy) throws Exception {
-               Git git = Git.wrap(db);
-               writeTrashFile("initial", "initial");
-               git.add().addFilepattern("initial").call();
-
-               writeSubmodule("one", ObjectId
-                               .fromString("1000000000000000000000000000000000000000"));
-
-               // Our initial commit should include a .gitmodules with a bunch of
-               // comment lines, so that
-               // we don't have a content merge issue when we add a new submodule at
-               // the top and a different
-               // one at the bottom. This is sort of a hack, but it should allow
-               // add/add submodule merges
-               String existing = read(Constants.DOT_GIT_MODULES);
-               String context = "\n# context\n# more context\n# yet more context\n";
-               write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
-                               existing + context + context + context);
-
-               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
-               RevCommit initial = git.commit().setMessage("initial").call();
-
-               writeSubmodule("two", ObjectId
-                               .fromString("1000000000000000000000000000000000000000"));
-               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
-
-               RevCommit right = git.commit().setMessage("added two").call();
-
-               git.checkout().setStartPoint(initial).setName("left")
-                               .setCreateBranch(true).call();
-
-               // we need to manually create the submodule for three for the
-               // .gitmodules hackery
-               addSubmoduleToIndex("three", ObjectId
-                               .fromString("1000000000000000000000000000000000000000"));
-               new File(db.getWorkTree(), "three").mkdir();
-
-               existing = read(Constants.DOT_GIT_MODULES);
-               String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
-                               + db.getDirectory().toURI() + "\n";
-               write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
-                               three + existing);
-
-               git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
-               git.commit().setMessage("a different one").call();
-
-               MergeResult result = git.merge().setStrategy(strategy).include(right)
-                               .call();
-
-               assertNull(result.getCheckoutConflicts());
-               assertNull(result.getFailingPaths());
-               for (String dir : Arrays.asList("one", "two", "three")) {
-                       assertTrue(new File(db.getWorkTree(), dir).isDirectory());
-               }
-       }
-
-       private void writeSubmodule(String path, ObjectId commit)
-                       throws IOException, ConfigInvalidException {
-               addSubmoduleToIndex(path, commit);
-               new File(db.getWorkTree(), path).mkdir();
-
-               StoredConfig config = db.getConfig();
-               config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-                               ConfigConstants.CONFIG_KEY_URL,
-                               db.getDirectory().toURI().toString());
-               config.save();
-
-               FileBasedConfig modulesConfig = new FileBasedConfig(
-                               new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
-                               db.getFS());
-               modulesConfig.load();
-               modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-                               ConfigConstants.CONFIG_KEY_PATH, path);
-               modulesConfig.save();
-
-       }
-
-       private void addSubmoduleToIndex(String path, ObjectId commit)
-                       throws IOException {
-               DirCache cache = db.lockDirCache();
-               DirCacheEditor editor = cache.editor();
-               editor.add(new DirCacheEditor.PathEdit(path) {
-
-                       @Override
-                       public void apply(DirCacheEntry ent) {
-                               ent.setFileMode(FileMode.GITLINK);
-                               ent.setObjectId(commit);
-                       }
-               });
-               editor.commit();
-       }
-
-       // Assert that every specified index entry has the same last modification
-       // timestamp as the associated file
-       private void checkConsistentLastModified(String... pathes)
-                       throws IOException {
-               DirCache dc = db.readDirCache();
-               File workTree = db.getWorkTree();
-               for (String path : pathes)
-                       assertEquals(
-                                       "IndexEntry with path "
-                                                       + path
-                                                       + " has lastmodified with is different from the worktree file",
-                                       FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
-                                                       .getLastModified());
-       }
-
-       // Assert that modification timestamps of working tree files are as
-       // expected. You may specify n files. It is asserted that every file
-       // i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
-       // then this file must be younger then file i. A path "*<modtime>"
-       // represents a file with a modification time of <modtime>
-       // E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
-       private void checkModificationTimeStampOrder(String... pathes)
-                       throws IOException {
-               long lastMod = Long.MIN_VALUE;
-               for (String p : pathes) {
-                       boolean strong = p.startsWith("<");
-                       boolean fixed = p.charAt(strong ? 1 : 0) == '*';
-                       p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
-                       long curMod = fixed ? Long.valueOf(p).longValue()
-                                       : FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
-                       if (strong)
-                               assertTrue("path " + p + " is not younger than predecesssor",
-                                               curMod > lastMod);
-                       else
-                               assertTrue("path " + p + " is older than predecesssor",
-                                               curMod >= lastMod);
-               }
-       }
-
-       private String readBlob(ObjectId treeish, String path) throws Exception {
-               TestRepository<?> tr = new TestRepository<>(db);
-               RevWalk rw = tr.getRevWalk();
-               RevTree tree = rw.parseTree(treeish);
-               RevObject obj = tr.get(tree, path);
-               if (obj == null) {
-                       return null;
-               }
-               return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), CHARSET);
-       }
-}