View Javadoc
1   /*
2    * Copyright (C) 2012, Robin Stocker <robin@nibor.org>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.merge;
44  
45  import static java.nio.charset.StandardCharsets.UTF_8;
46  import static java.time.Instant.EPOCH;
47  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
48  import static org.junit.Assert.assertEquals;
49  import static org.junit.Assert.assertFalse;
50  import static org.junit.Assert.assertNotNull;
51  import static org.junit.Assert.assertNull;
52  import static org.junit.Assert.assertTrue;
53  
54  import java.io.ByteArrayOutputStream;
55  import java.io.File;
56  import java.io.FileInputStream;
57  import java.io.IOException;
58  import java.time.Instant;
59  import java.util.Arrays;
60  import java.util.Map;
61  
62  import org.eclipse.jgit.api.Git;
63  import org.eclipse.jgit.api.MergeResult;
64  import org.eclipse.jgit.api.MergeResult.MergeStatus;
65  import org.eclipse.jgit.api.RebaseResult;
66  import org.eclipse.jgit.api.errors.CheckoutConflictException;
67  import org.eclipse.jgit.api.errors.GitAPIException;
68  import org.eclipse.jgit.api.errors.JGitInternalException;
69  import org.eclipse.jgit.dircache.DirCache;
70  import org.eclipse.jgit.dircache.DirCacheEditor;
71  import org.eclipse.jgit.dircache.DirCacheEntry;
72  import org.eclipse.jgit.errors.ConfigInvalidException;
73  import org.eclipse.jgit.errors.NoMergeBaseException;
74  import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
75  import org.eclipse.jgit.junit.RepositoryTestCase;
76  import org.eclipse.jgit.junit.TestRepository;
77  import org.eclipse.jgit.lib.AnyObjectId;
78  import org.eclipse.jgit.lib.ConfigConstants;
79  import org.eclipse.jgit.lib.Constants;
80  import org.eclipse.jgit.lib.FileMode;
81  import org.eclipse.jgit.lib.ObjectId;
82  import org.eclipse.jgit.lib.ObjectInserter;
83  import org.eclipse.jgit.lib.ObjectLoader;
84  import org.eclipse.jgit.lib.ObjectReader;
85  import org.eclipse.jgit.lib.ObjectStream;
86  import org.eclipse.jgit.lib.StoredConfig;
87  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
88  import org.eclipse.jgit.revwalk.RevCommit;
89  import org.eclipse.jgit.revwalk.RevObject;
90  import org.eclipse.jgit.revwalk.RevTree;
91  import org.eclipse.jgit.revwalk.RevWalk;
92  import org.eclipse.jgit.storage.file.FileBasedConfig;
93  import org.eclipse.jgit.treewalk.FileTreeIterator;
94  import org.eclipse.jgit.util.FS;
95  import org.eclipse.jgit.util.FileUtils;
96  import org.junit.Assert;
97  import org.junit.experimental.theories.DataPoints;
98  import org.junit.experimental.theories.Theories;
99  import org.junit.experimental.theories.Theory;
100 import org.junit.runner.RunWith;
101 
102 @RunWith(Theories.class)
103 public class MergerTest extends RepositoryTestCase {
104 
105 	@DataPoints
106 	public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
107 			MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
108 
109 	@Theory
110 	public void failingDeleteOfDirectoryWithUntrackedContent(
111 			MergeStrategy strategy) throws Exception {
112 		File folder1 = new File(db.getWorkTree(), "folder1");
113 		FileUtils.mkdir(folder1);
114 		File file = new File(folder1, "file1.txt");
115 		write(file, "folder1--file1.txt");
116 		file = new File(folder1, "file2.txt");
117 		write(file, "folder1--file2.txt");
118 
119 		try (Git git = new Git(db)) {
120 			git.add().addFilepattern(folder1.getName()).call();
121 			RevCommit base = git.commit().setMessage("adding folder").call();
122 
123 			recursiveDelete(folder1);
124 			git.rm().addFilepattern("folder1/file1.txt")
125 					.addFilepattern("folder1/file2.txt").call();
126 			RevCommit other = git.commit()
127 					.setMessage("removing folders on 'other'").call();
128 
129 			git.checkout().setName(base.name()).call();
130 
131 			file = new File(db.getWorkTree(), "unrelated.txt");
132 			write(file, "unrelated");
133 
134 			git.add().addFilepattern("unrelated.txt").call();
135 			RevCommit head = git.commit().setMessage("Adding another file").call();
136 
137 			// Untracked file to cause failing path for delete() of folder1
138 			// but that's ok.
139 			file = new File(folder1, "file3.txt");
140 			write(file, "folder1--file3.txt");
141 
142 			ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
143 			merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
144 			merger.setWorkingTreeIterator(new FileTreeIterator(db));
145 			boolean ok = merger.merge(head.getId(), other.getId());
146 			assertTrue(ok);
147 			assertTrue(file.exists());
148 		}
149 	}
150 
151 	/**
152 	 * Merging two conflicting subtrees when the index does not contain any file
153 	 * in that subtree should lead to a conflicting state.
154 	 *
155 	 * @param strategy
156 	 * @throws Exception
157 	 */
158 	@Theory
159 	public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
160 			throws Exception {
161 		Git git = Git.wrap(db);
162 
163 		writeTrashFile("d/1", "orig");
164 		git.add().addFilepattern("d/1").call();
165 		RevCommit first = git.commit().setMessage("added d/1").call();
166 
167 		writeTrashFile("d/1", "master");
168 		RevCommit masterCommit = git.commit().setAll(true)
169 				.setMessage("modified d/1 on master").call();
170 
171 		git.checkout().setCreateBranch(true).setStartPoint(first)
172 				.setName("side").call();
173 		writeTrashFile("d/1", "side");
174 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
175 
176 		git.rm().addFilepattern("d/1").call();
177 		git.rm().addFilepattern("d").call();
178 		MergeResult mergeRes = git.merge().setStrategy(strategy)
179 				.include(masterCommit).call();
180 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
181 		assertEquals(
182 				"[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
183 				indexState(CONTENT));
184 	}
185 
186 	/**
187 	 * Merging two different but mergeable subtrees when the index does not
188 	 * contain any file in that subtree should lead to a merged state.
189 	 *
190 	 * @param strategy
191 	 * @throws Exception
192 	 */
193 	@Theory
194 	public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
195 			throws Exception {
196 		Git git = Git.wrap(db);
197 
198 		writeTrashFile("d/1", "1\n2\n3");
199 		git.add().addFilepattern("d/1").call();
200 		RevCommit first = git.commit().setMessage("added d/1").call();
201 
202 		writeTrashFile("d/1", "1master\n2\n3");
203 		RevCommit masterCommit = git.commit().setAll(true)
204 				.setMessage("modified d/1 on master").call();
205 
206 		git.checkout().setCreateBranch(true).setStartPoint(first)
207 				.setName("side").call();
208 		writeTrashFile("d/1", "1\n2\n3side");
209 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
210 
211 		git.rm().addFilepattern("d/1").call();
212 		git.rm().addFilepattern("d").call();
213 		MergeResult mergeRes = git.merge().setStrategy(strategy)
214 				.include(masterCommit).call();
215 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
216 		assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
217 				indexState(CONTENT));
218 	}
219 
220 	/**
221 	 * An existing directory without tracked content should not prevent merging
222 	 * a tree where that directory exists.
223 	 *
224 	 * @param strategy
225 	 * @throws Exception
226 	 */
227 	@Theory
228 	public void checkUntrackedFolderIsNotAConflict(
229 			MergeStrategy strategy) throws Exception {
230 		Git git = Git.wrap(db);
231 
232 		writeTrashFile("d/1", "1");
233 		git.add().addFilepattern("d/1").call();
234 		RevCommit first = git.commit().setMessage("added d/1").call();
235 
236 		writeTrashFile("e/1", "4");
237 		git.add().addFilepattern("e/1").call();
238 		RevCommit masterCommit = git.commit().setMessage("added e/1").call();
239 
240 		git.checkout().setCreateBranch(true).setStartPoint(first)
241 				.setName("side").call();
242 		writeTrashFile("f/1", "5");
243 		git.add().addFilepattern("f/1").call();
244 		git.commit().setAll(true).setMessage("added f/1")
245 				.call();
246 
247 		// Untracked directory e shall not conflict with merged e/1
248 		writeTrashFile("e/2", "d two");
249 
250 		MergeResult mergeRes = git.merge().setStrategy(strategy)
251 				.include(masterCommit).call();
252 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
253 		assertEquals(
254 				"[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
255 				indexState(CONTENT));
256 	}
257 
258 	/**
259 	 * A tracked file is replaced by a folder in THEIRS.
260 	 *
261 	 * @param strategy
262 	 * @throws Exception
263 	 */
264 	@Theory
265 	public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
266 			throws Exception {
267 		Git git = Git.wrap(db);
268 
269 		writeTrashFile("sub", "file");
270 		git.add().addFilepattern("sub").call();
271 		RevCommit first = git.commit().setMessage("initial").call();
272 
273 		git.checkout().setCreateBranch(true).setStartPoint(first)
274 				.setName("side").call();
275 
276 		git.rm().addFilepattern("sub").call();
277 		writeTrashFile("sub/file", "subfile");
278 		git.add().addFilepattern("sub/file").call();
279 		RevCommit masterCommit = git.commit().setMessage("file -> folder")
280 				.call();
281 
282 		git.checkout().setName("master").call();
283 		writeTrashFile("noop", "other");
284 		git.add().addFilepattern("noop").call();
285 		git.commit().setAll(true).setMessage("noop").call();
286 
287 		MergeResult mergeRes = git.merge().setStrategy(strategy)
288 				.include(masterCommit).call();
289 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
290 		assertEquals(
291 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
292 				indexState(CONTENT));
293 	}
294 
295 	/**
296 	 * A tracked file is replaced by a folder in OURS.
297 	 *
298 	 * @param strategy
299 	 * @throws Exception
300 	 */
301 	@Theory
302 	public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
303 			throws Exception {
304 		Git git = Git.wrap(db);
305 
306 		writeTrashFile("sub", "file");
307 		git.add().addFilepattern("sub").call();
308 		RevCommit first = git.commit().setMessage("initial").call();
309 
310 		git.checkout().setCreateBranch(true).setStartPoint(first)
311 				.setName("side").call();
312 		writeTrashFile("noop", "other");
313 		git.add().addFilepattern("noop").call();
314 		RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
315 				.call();
316 
317 		git.checkout().setName("master").call();
318 		git.rm().addFilepattern("sub").call();
319 		writeTrashFile("sub/file", "subfile");
320 		git.add().addFilepattern("sub/file").call();
321 		git.commit().setMessage("file -> folder")
322 				.call();
323 
324 		MergeResult mergeRes = git.merge().setStrategy(strategy)
325 				.include(sideCommit).call();
326 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
327 		assertEquals(
328 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
329 				indexState(CONTENT));
330 	}
331 
332 	/**
333 	 * An existing directory without tracked content should not prevent merging
334 	 * a file with that name.
335 	 *
336 	 * @param strategy
337 	 * @throws Exception
338 	 */
339 	@Theory
340 	public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
341 			MergeStrategy strategy)
342 			throws Exception {
343 		Git git = Git.wrap(db);
344 
345 		writeTrashFile("d/1", "1");
346 		git.add().addFilepattern("d/1").call();
347 		RevCommit first = git.commit().setMessage("added d/1").call();
348 
349 		writeTrashFile("e", "4");
350 		git.add().addFilepattern("e").call();
351 		RevCommit masterCommit = git.commit().setMessage("added e").call();
352 
353 		git.checkout().setCreateBranch(true).setStartPoint(first)
354 				.setName("side").call();
355 		writeTrashFile("f/1", "5");
356 		git.add().addFilepattern("f/1").call();
357 		git.commit().setAll(true).setMessage("added f/1").call();
358 
359 		// Untracked empty directory hierarcy e/1 shall not conflict with merged
360 		// e/1
361 		FileUtils.mkdirs(new File(trash, "e/1"), true);
362 
363 		MergeResult mergeRes = git.merge().setStrategy(strategy)
364 				.include(masterCommit).call();
365 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
366 		assertEquals(
367 				"[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
368 				indexState(CONTENT));
369 	}
370 
371 	@Theory
372 	public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
373 			GitAPIException {
374 		Git git = Git.wrap(db);
375 		db.getConfig().setString("core", null, "autocrlf", "false");
376 		db.getConfig().save();
377 		writeTrashFile("crlf.txt", "some\r\ndata\r\n");
378 		git.add().addFilepattern("crlf.txt").call();
379 		git.commit().setMessage("base").call();
380 
381 		git.branchCreate().setName("brancha").call();
382 
383 		writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
384 		git.add().addFilepattern("crlf.txt").call();
385 		git.commit().setMessage("on master").call();
386 
387 		git.checkout().setName("brancha").call();
388 		writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
389 		git.add().addFilepattern("crlf.txt").call();
390 		git.commit().setMessage("on brancha").call();
391 
392 		db.getConfig().setString("core", null, "autocrlf", "input");
393 		db.getConfig().save();
394 
395 		MergeResult mergeResult = git.merge().setStrategy(strategy)
396 				.include(db.resolve("master"))
397 				.call();
398 		assertEquals(MergeResult.MergeStatus.MERGED,
399 				mergeResult.getMergeStatus());
400 	}
401 
402 	@Theory
403 	public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
404 			throws IOException, GitAPIException {
405 		Git git = Git.wrap(db);
406 		db.getConfig().setString("core", null, "autocrlf", "true");
407 		db.getConfig().save();
408 		writeTrashFile("crlf.txt", "a crlf file\r\n");
409 		git.add().addFilepattern("crlf.txt").call();
410 		git.commit().setMessage("base").call();
411 
412 		git.branchCreate().setName("brancha").call();
413 
414 		writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
415 		git.add().addFilepattern("crlf.txt").call();
416 		git.commit().setMessage("on master").call();
417 
418 		git.checkout().setName("brancha").call();
419 		File testFile = writeTrashFile("crlf.txt",
420 				"a first line\r\na crlf file\r\n");
421 		git.add().addFilepattern("crlf.txt").call();
422 		git.commit().setMessage("on brancha").call();
423 
424 		MergeResult mergeResult = git.merge().setStrategy(strategy)
425 				.include(db.resolve("master")).call();
426 		assertEquals(MergeResult.MergeStatus.MERGED,
427 				mergeResult.getMergeStatus());
428 		checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
429 		assertEquals(
430 				"[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
431 				indexState(CONTENT));
432 	}
433 
434 	@Theory
435 	public void rebaseWithCrlfAutoCrlfTrue(MergeStrategy strategy)
436 			throws IOException, GitAPIException {
437 		Git git = Git.wrap(db);
438 		db.getConfig().setString("core", null, "autocrlf", "true");
439 		db.getConfig().save();
440 		writeTrashFile("crlf.txt", "line 1\r\nline 2\r\nline 3\r\n");
441 		git.add().addFilepattern("crlf.txt").call();
442 		RevCommit first = git.commit().setMessage("base").call();
443 
444 		git.checkout().setCreateBranch(true).setStartPoint(first)
445 				.setName("brancha").call();
446 
447 		File testFile = writeTrashFile("crlf.txt",
448 				"line 1\r\nmodified line\r\nline 3\r\n");
449 		git.add().addFilepattern("crlf.txt").call();
450 		git.commit().setMessage("on brancha").call();
451 
452 		git.checkout().setName("master").call();
453 		File otherFile = writeTrashFile("otherfile.txt", "a line\r\n");
454 		git.add().addFilepattern("otherfile.txt").call();
455 		git.commit().setMessage("on master").call();
456 
457 		git.checkout().setName("brancha").call();
458 		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
459 		assertFalse(otherFile.exists());
460 
461 		RebaseResult rebaseResult = git.rebase().setStrategy(strategy)
462 				.setUpstream(db.resolve("master")).call();
463 		assertEquals(RebaseResult.Status.OK, rebaseResult.getStatus());
464 		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
465 		checkFile(otherFile, "a line\r\n");
466 		assertEquals(
467 				"[crlf.txt, mode:100644, content:line 1\nmodified line\nline 3\n]"
468 						+ "[otherfile.txt, mode:100644, content:a line\n]",
469 				indexState(CONTENT));
470 	}
471 
472 	/**
473 	 * Merging two equal subtrees when the index does not contain any file in
474 	 * that subtree should lead to a merged state.
475 	 *
476 	 * @param strategy
477 	 * @throws Exception
478 	 */
479 	@Theory
480 	public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
481 			throws Exception {
482 		Git git = Git.wrap(db);
483 
484 		writeTrashFile("d/1", "orig");
485 		git.add().addFilepattern("d/1").call();
486 		RevCommit first = git.commit().setMessage("added d/1").call();
487 
488 		writeTrashFile("d/1", "modified");
489 		RevCommit masterCommit = git.commit().setAll(true)
490 				.setMessage("modified d/1 on master").call();
491 
492 		git.checkout().setCreateBranch(true).setStartPoint(first)
493 				.setName("side").call();
494 		writeTrashFile("d/1", "modified");
495 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
496 
497 		git.rm().addFilepattern("d/1").call();
498 		git.rm().addFilepattern("d").call();
499 		MergeResult mergeRes = git.merge().setStrategy(strategy)
500 				.include(masterCommit).call();
501 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
502 		assertEquals("[d/1, mode:100644, content:modified]",
503 				indexState(CONTENT));
504 	}
505 
506 	/**
507 	 * Merging two equal subtrees with an incore merger should lead to a merged
508 	 * state.
509 	 *
510 	 * @param strategy
511 	 * @throws Exception
512 	 */
513 	@Theory
514 	public void checkMergeEqualTreesInCore(MergeStrategy strategy)
515 			throws Exception {
516 		Git git = Git.wrap(db);
517 
518 		writeTrashFile("d/1", "orig");
519 		git.add().addFilepattern("d/1").call();
520 		RevCommit first = git.commit().setMessage("added d/1").call();
521 
522 		writeTrashFile("d/1", "modified");
523 		RevCommit masterCommit = git.commit().setAll(true)
524 				.setMessage("modified d/1 on master").call();
525 
526 		git.checkout().setCreateBranch(true).setStartPoint(first)
527 				.setName("side").call();
528 		writeTrashFile("d/1", "modified");
529 		RevCommit sideCommit = git.commit().setAll(true)
530 				.setMessage("modified d/1 on side").call();
531 
532 		git.rm().addFilepattern("d/1").call();
533 		git.rm().addFilepattern("d").call();
534 
535 		ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
536 				true);
537 		boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
538 		assertTrue(noProblems);
539 	}
540 
541 	/**
542 	 * Merging two equal subtrees with an incore merger should lead to a merged
543 	 * state, without using a Repository (the 'Gerrit' use case).
544 	 *
545 	 * @param strategy
546 	 * @throws Exception
547 	 */
548 	@Theory
549 	public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
550 			throws Exception {
551 		Git git = Git.wrap(db);
552 
553 		writeTrashFile("d/1", "orig");
554 		git.add().addFilepattern("d/1").call();
555 		RevCommit first = git.commit().setMessage("added d/1").call();
556 
557 		writeTrashFile("d/1", "modified");
558 		RevCommit masterCommit = git.commit().setAll(true)
559 				.setMessage("modified d/1 on master").call();
560 
561 		git.checkout().setCreateBranch(true).setStartPoint(first)
562 				.setName("side").call();
563 		writeTrashFile("d/1", "modified");
564 		RevCommit sideCommit = git.commit().setAll(true)
565 				.setMessage("modified d/1 on side").call();
566 
567 		git.rm().addFilepattern("d/1").call();
568 		git.rm().addFilepattern("d").call();
569 
570 		try (ObjectInserter ins = db.newObjectInserter()) {
571 			ThreeWayMerger resolveMerger =
572 					(ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
573 			boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
574 			assertTrue(noProblems);
575 		}
576 	}
577 
578 	/**
579 	 * Merging two equal subtrees when the index and HEAD does not contain any
580 	 * file in that subtree should lead to a merged state.
581 	 *
582 	 * @param strategy
583 	 * @throws Exception
584 	 */
585 	@Theory
586 	public void checkMergeEqualNewTrees(MergeStrategy strategy)
587 			throws Exception {
588 		Git git = Git.wrap(db);
589 
590 		writeTrashFile("2", "orig");
591 		git.add().addFilepattern("2").call();
592 		RevCommit first = git.commit().setMessage("added 2").call();
593 
594 		writeTrashFile("d/1", "orig");
595 		git.add().addFilepattern("d/1").call();
596 		RevCommit masterCommit = git.commit().setAll(true)
597 				.setMessage("added d/1 on master").call();
598 
599 		git.checkout().setCreateBranch(true).setStartPoint(first)
600 				.setName("side").call();
601 		writeTrashFile("d/1", "orig");
602 		git.add().addFilepattern("d/1").call();
603 		git.commit().setAll(true).setMessage("added d/1 on side").call();
604 
605 		git.rm().addFilepattern("d/1").call();
606 		git.rm().addFilepattern("d").call();
607 		MergeResult mergeRes = git.merge().setStrategy(strategy)
608 				.include(masterCommit).call();
609 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
610 		assertEquals(
611 				"[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
612 				indexState(CONTENT));
613 	}
614 
615 	/**
616 	 * Merging two conflicting subtrees when the index and HEAD does not contain
617 	 * any file in that subtree should lead to a conflicting state.
618 	 *
619 	 * @param strategy
620 	 * @throws Exception
621 	 */
622 	@Theory
623 	public void checkMergeConflictingNewTrees(MergeStrategy strategy)
624 			throws Exception {
625 		Git git = Git.wrap(db);
626 
627 		writeTrashFile("2", "orig");
628 		git.add().addFilepattern("2").call();
629 		RevCommit first = git.commit().setMessage("added 2").call();
630 
631 		writeTrashFile("d/1", "master");
632 		git.add().addFilepattern("d/1").call();
633 		RevCommit masterCommit = git.commit().setAll(true)
634 				.setMessage("added d/1 on master").call();
635 
636 		git.checkout().setCreateBranch(true).setStartPoint(first)
637 				.setName("side").call();
638 		writeTrashFile("d/1", "side");
639 		git.add().addFilepattern("d/1").call();
640 		git.commit().setAll(true).setMessage("added d/1 on side").call();
641 
642 		git.rm().addFilepattern("d/1").call();
643 		git.rm().addFilepattern("d").call();
644 		MergeResult mergeRes = git.merge().setStrategy(strategy)
645 				.include(masterCommit).call();
646 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
647 		assertEquals(
648 				"[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
649 				indexState(CONTENT));
650 	}
651 
652 	/**
653 	 * Merging two conflicting files when the index contains a tree for that
654 	 * path should lead to a failed state.
655 	 *
656 	 * @param strategy
657 	 * @throws Exception
658 	 */
659 	@Theory
660 	public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
661 			throws Exception {
662 		Git git = Git.wrap(db);
663 
664 		writeTrashFile("0", "orig");
665 		git.add().addFilepattern("0").call();
666 		RevCommit first = git.commit().setMessage("added 0").call();
667 
668 		writeTrashFile("0", "master");
669 		RevCommit masterCommit = git.commit().setAll(true)
670 				.setMessage("modified 0 on master").call();
671 
672 		git.checkout().setCreateBranch(true).setStartPoint(first)
673 				.setName("side").call();
674 		writeTrashFile("0", "side");
675 		git.commit().setAll(true).setMessage("modified 0 on side").call();
676 
677 		git.rm().addFilepattern("0").call();
678 		writeTrashFile("0/0", "side");
679 		git.add().addFilepattern("0/0").call();
680 		MergeResult mergeRes = git.merge().setStrategy(strategy)
681 				.include(masterCommit).call();
682 		assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
683 	}
684 
685 	/**
686 	 * Merging two equal files when the index contains a tree for that path
687 	 * should lead to a failed state.
688 	 *
689 	 * @param strategy
690 	 * @throws Exception
691 	 */
692 	@Theory
693 	public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
694 			throws Exception {
695 		Git git = Git.wrap(db);
696 
697 		writeTrashFile("0", "orig");
698 		writeTrashFile("1", "1\n2\n3");
699 		git.add().addFilepattern("0").addFilepattern("1").call();
700 		RevCommit first = git.commit().setMessage("added 0, 1").call();
701 
702 		writeTrashFile("1", "1master\n2\n3");
703 		RevCommit masterCommit = git.commit().setAll(true)
704 				.setMessage("modified 1 on master").call();
705 
706 		git.checkout().setCreateBranch(true).setStartPoint(first)
707 				.setName("side").call();
708 		writeTrashFile("1", "1\n2\n3side");
709 		git.commit().setAll(true).setMessage("modified 1 on side").call();
710 
711 		git.rm().addFilepattern("0").call();
712 		writeTrashFile("0/0", "modified");
713 		git.add().addFilepattern("0/0").call();
714 		try {
715 			git.merge().setStrategy(strategy).include(masterCommit).call();
716 			Assert.fail("Didn't get the expected exception");
717 		} catch (CheckoutConflictException e) {
718 			assertEquals(1, e.getConflictingPaths().size());
719 			assertEquals("0/0", e.getConflictingPaths().get(0));
720 		}
721 	}
722 
723 	@Theory
724 	public void checkContentMergeNoConflict(MergeStrategy strategy)
725 			throws Exception {
726 		Git git = Git.wrap(db);
727 
728 		writeTrashFile("file", "1\n2\n3");
729 		git.add().addFilepattern("file").call();
730 		RevCommit first = git.commit().setMessage("added file").call();
731 
732 		writeTrashFile("file", "1master\n2\n3");
733 		git.commit().setAll(true).setMessage("modified file on master").call();
734 
735 		git.checkout().setCreateBranch(true).setStartPoint(first)
736 				.setName("side").call();
737 		writeTrashFile("file", "1\n2\n3side");
738 		RevCommit sideCommit = git.commit().setAll(true)
739 				.setMessage("modified file on side").call();
740 
741 		git.checkout().setName("master").call();
742 		MergeResult result =
743 				git.merge().setStrategy(strategy).include(sideCommit).call();
744 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
745 		String expected = "1master\n2\n3side";
746 		assertEquals(expected, read("file"));
747 	}
748 
749 	@Theory
750 	public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
751 			throws Exception {
752 		Git git = Git.wrap(db);
753 
754 		writeTrashFile("file", "1\n2\n3");
755 		git.add().addFilepattern("file").call();
756 		RevCommit first = git.commit().setMessage("added file").call();
757 
758 		writeTrashFile("file", "1master\n2\n3");
759 		RevCommit masterCommit = git.commit().setAll(true)
760 				.setMessage("modified file on master").call();
761 
762 		git.checkout().setCreateBranch(true).setStartPoint(first)
763 				.setName("side").call();
764 		writeTrashFile("file", "1\n2\n3side");
765 		RevCommit sideCommit = git.commit().setAll(true)
766 				.setMessage("modified file on side").call();
767 
768 		try (ObjectInserter ins = db.newObjectInserter()) {
769 			ResolveMerger merger =
770 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
771 			boolean noProblems = merger.merge(masterCommit, sideCommit);
772 			assertTrue(noProblems);
773 			assertEquals("1master\n2\n3side",
774 					readBlob(merger.getResultTreeId(), "file"));
775 		}
776 	}
777 
778 
779 	/**
780 	 * Merging a change involving large binary files should short-circuit reads.
781 	 *
782 	 * @param strategy
783 	 * @throws Exception
784 	 */
785 	@Theory
786 	public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
787 		Git git = Git.wrap(db);
788 		final int LINELEN = 72;
789 
790 		// setup a merge that would work correctly if we disconsider the stray '\0'
791 		// that the file contains near the start.
792 		byte[] binary = new byte[LINELEN * 2000];
793 		for (int i = 0; i < binary.length; i++) {
794 			binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
795 		}
796 		binary[50] = '\0';
797 
798 		writeTrashFile("file", new String(binary, UTF_8));
799 		git.add().addFilepattern("file").call();
800 		RevCommit first = git.commit().setMessage("added file").call();
801 
802 		// Generate an edit in a single line.
803 		int idx = LINELEN * 1200 + 1;
804 		byte save = binary[idx];
805 		binary[idx] = '@';
806 		writeTrashFile("file", new String(binary, UTF_8));
807 
808 		binary[idx] = save;
809 		git.add().addFilepattern("file").call();
810 		RevCommit masterCommit = git.commit().setAll(true)
811 			.setMessage("modified file l 1200").call();
812 
813 		git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
814 		binary[LINELEN * 1500 + 1] = '!';
815 		writeTrashFile("file", new String(binary, UTF_8));
816 		git.add().addFilepattern("file").call();
817 		RevCommit sideCommit = git.commit().setAll(true)
818 			.setMessage("modified file l 1500").call();
819 
820 		try (ObjectInserter ins = db.newObjectInserter()) {
821 			// Check that we don't read the large blobs.
822 			ObjectInserter forbidInserter = new ObjectInserter.Filter() {
823 				@Override
824 				protected ObjectInserter delegate() {
825 					return ins;
826 				}
827 
828 				@Override
829 				public ObjectReader newReader() {
830 					return new BigReadForbiddenReader(super.newReader(), 8000);
831 				}
832 			};
833 
834 			ResolveMerger merger =
835 				(ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
836 			boolean noProblems = merger.merge(masterCommit, sideCommit);
837 			assertFalse(noProblems);
838 		}
839 	}
840 
841 	/**
842 	 * Throws an exception if reading beyond limit.
843 	 */
844 	static class BigReadForbiddenStream extends ObjectStream.Filter {
845 		int limit;
846 
847 		BigReadForbiddenStream(ObjectStream orig, int limit) {
848 			super(orig.getType(), orig.getSize(), orig);
849 			this.limit = limit;
850 		}
851 
852 		@Override
853 		public long skip(long n) throws IOException {
854 			limit -= n;
855 			if (limit < 0) {
856 				throw new IllegalStateException();
857 			}
858 
859 			return super.skip(n);
860 		}
861 
862 		@Override
863 		public int read() throws IOException {
864 			int r = super.read();
865 			limit--;
866 			if (limit < 0) {
867 				throw new IllegalStateException();
868 			}
869 			return r;
870 		}
871 
872 		@Override
873 		public int read(byte[] b, int off, int len) throws IOException {
874 			int n = super.read(b, off, len);
875 			limit -= n;
876 			if (limit < 0) {
877 				throw new IllegalStateException();
878 			}
879 			return n;
880 		}
881 	}
882 
883 	static class BigReadForbiddenReader extends ObjectReader.Filter {
884 		ObjectReader delegate;
885 		int limit;
886 
887 		@Override
888 		protected ObjectReader delegate() {
889 			return delegate;
890 		}
891 
892 		BigReadForbiddenReader(ObjectReader delegate, int limit) {
893 			this.delegate = delegate;
894 			this.limit = limit;
895 		}
896 
897 		@Override
898 		public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
899 			ObjectLoader orig = super.open(objectId, typeHint);
900 			return new ObjectLoader.Filter() {
901 				@Override
902 				protected ObjectLoader delegate() {
903 					return orig;
904 				}
905 
906 				@Override
907 				public ObjectStream openStream() throws IOException {
908 					ObjectStream os = orig.openStream();
909 					return new BigReadForbiddenStream(os, limit);
910 				}
911 			};
912 		}
913 	}
914 
915 	@Theory
916 	public void checkContentMergeConflict(MergeStrategy strategy)
917 			throws Exception {
918 		Git git = Git.wrap(db);
919 
920 		writeTrashFile("file", "1\n2\n3");
921 		git.add().addFilepattern("file").call();
922 		RevCommit first = git.commit().setMessage("added file").call();
923 
924 		writeTrashFile("file", "1master\n2\n3");
925 		git.commit().setAll(true).setMessage("modified file on master").call();
926 
927 		git.checkout().setCreateBranch(true).setStartPoint(first)
928 				.setName("side").call();
929 		writeTrashFile("file", "1side\n2\n3");
930 		RevCommit sideCommit = git.commit().setAll(true)
931 				.setMessage("modified file on side").call();
932 
933 		git.checkout().setName("master").call();
934 		MergeResult result =
935 				git.merge().setStrategy(strategy).include(sideCommit).call();
936 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
937 		String expected = "<<<<<<< HEAD\n"
938 				+ "1master\n"
939 				+ "=======\n"
940 				+ "1side\n"
941 				+ ">>>>>>> " + sideCommit.name() + "\n"
942 				+ "2\n"
943 				+ "3";
944 		assertEquals(expected, read("file"));
945 	}
946 
947 	@Theory
948 	public void checkContentMergeConflict_noTree(MergeStrategy strategy)
949 			throws Exception {
950 		Git git = Git.wrap(db);
951 
952 		writeTrashFile("file", "1\n2\n3");
953 		git.add().addFilepattern("file").call();
954 		RevCommit first = git.commit().setMessage("added file").call();
955 
956 		writeTrashFile("file", "1master\n2\n3");
957 		RevCommit masterCommit = git.commit().setAll(true)
958 				.setMessage("modified file on master").call();
959 
960 		git.checkout().setCreateBranch(true).setStartPoint(first)
961 				.setName("side").call();
962 		writeTrashFile("file", "1side\n2\n3");
963 		RevCommit sideCommit = git.commit().setAll(true)
964 				.setMessage("modified file on side").call();
965 
966 		try (ObjectInserter ins = db.newObjectInserter()) {
967 			ResolveMerger merger =
968 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
969 			boolean noProblems = merger.merge(masterCommit, sideCommit);
970 			assertFalse(noProblems);
971 			assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
972 
973 			MergeFormatter fmt = new MergeFormatter();
974 			merger.getMergeResults().get("file");
975 			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
976 				fmt.formatMerge(out, merger.getMergeResults().get("file"),
977 						"BASE", "OURS", "THEIRS", UTF_8.name());
978 				String expected = "<<<<<<< OURS\n"
979 						+ "1master\n"
980 						+ "=======\n"
981 						+ "1side\n"
982 						+ ">>>>>>> THEIRS\n"
983 						+ "2\n"
984 						+ "3";
985 				assertEquals(expected, new String(out.toByteArray(), UTF_8));
986 			}
987 		}
988 	}
989 
990 	/**
991 	 * Merging after criss-cross merges. In this case we merge together two
992 	 * commits which have two equally good common ancestors
993 	 *
994 	 * @param strategy
995 	 * @throws Exception
996 	 */
997 	@Theory
998 	public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
999 		Git git = Git.wrap(db);
1000 
1001 		writeTrashFile("1", "1\n2\n3");
1002 		git.add().addFilepattern("1").call();
1003 		RevCommit first = git.commit().setMessage("added 1").call();
1004 
1005 		writeTrashFile("1", "1master\n2\n3");
1006 		RevCommit masterCommit = git.commit().setAll(true)
1007 				.setMessage("modified 1 on master").call();
1008 
1009 		writeTrashFile("1", "1master2\n2\n3");
1010 		git.commit().setAll(true)
1011 				.setMessage("modified 1 on master again").call();
1012 
1013 		git.checkout().setCreateBranch(true).setStartPoint(first)
1014 				.setName("side").call();
1015 		writeTrashFile("1", "1\n2\na\nb\nc\n3side");
1016 		RevCommit sideCommit = git.commit().setAll(true)
1017 				.setMessage("modified 1 on side").call();
1018 
1019 		writeTrashFile("1", "1\n2\n3side2");
1020 		git.commit().setAll(true)
1021 				.setMessage("modified 1 on side again").call();
1022 
1023 		MergeResult result = git.merge().setStrategy(strategy)
1024 				.include(masterCommit).call();
1025 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1026 		result.getNewHead();
1027 		git.checkout().setName("master").call();
1028 		result = git.merge().setStrategy(strategy).include(sideCommit).call();
1029 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1030 
1031 		// we have two branches which are criss-cross merged. Try to merge the
1032 		// tips. This should succeed with RecursiveMerge and fail with
1033 		// ResolveMerge
1034 		try {
1035 			MergeResult mergeResult = git.merge().setStrategy(strategy)
1036 					.include(git.getRepository().exactRef("refs/heads/side"))
1037 					.call();
1038 			assertEquals(MergeStrategy.RECURSIVE, strategy);
1039 			assertEquals(MergeResult.MergeStatus.MERGED,
1040 					mergeResult.getMergeStatus());
1041 			assertEquals("1master2\n2\n3side2", read("1"));
1042 		} catch (JGitInternalException e) {
1043 			assertEquals(MergeStrategy.RESOLVE, strategy);
1044 			assertTrue(e.getCause() instanceof NoMergeBaseException);
1045 			assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1046 					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1047 		}
1048 	}
1049 
1050 	@Theory
1051 	public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1052 			throws Exception {
1053 		Git git = Git.wrap(db);
1054 
1055 		writeTrashFile("a.txt", "orig");
1056 		writeTrashFile("b.txt", "orig");
1057 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1058 		RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1059 
1060 		// modify and delete files on the master branch
1061 		writeTrashFile("a.txt", "master");
1062 		git.rm().addFilepattern("b.txt").call();
1063 		RevCommit masterCommit = git.commit()
1064 				.setMessage("modified a.txt, deleted b.txt").setAll(true)
1065 				.call();
1066 
1067 		// switch back to a side branch
1068 		git.checkout().setCreateBranch(true).setStartPoint(first)
1069 				.setName("side").call();
1070 		writeTrashFile("c.txt", "side");
1071 		git.add().addFilepattern("c.txt").call();
1072 		git.commit().setMessage("added c.txt").call();
1073 
1074 		// Get a handle to the the file so on windows it can't be deleted.
1075 		try (FileInputStream fis = new FileInputStream(
1076 				new File(db.getWorkTree(), "b.txt"))) {
1077 			MergeResult mergeRes = git.merge().setStrategy(strategy)
1078 					.include(masterCommit).call();
1079 			if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1080 				// probably windows
1081 				assertEquals(1, mergeRes.getFailingPaths().size());
1082 				assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1083 						mergeRes.getFailingPaths().get("b.txt"));
1084 			}
1085 			assertEquals(
1086 					"[a.txt, mode:100644, content:master]"
1087 							+ "[c.txt, mode:100644, content:side]",
1088 					indexState(CONTENT));
1089 		}
1090 	}
1091 
1092 	@Theory
1093 	public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
1094 		File f;
1095 		Instant lastTs4, lastTsIndex;
1096 		Git git = Git.wrap(db);
1097 		File indexFile = db.getIndexFile();
1098 
1099 		// Create initial content and remember when the last file was written.
1100 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1101 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1102 
1103 		// add all files, commit and check this doesn't update any working tree
1104 		// files and that the index is in a new file system timer tick. Make
1105 		// sure to wait long enough before adding so the index doesn't contain
1106 		// racily clean entries
1107 		fsTick(f);
1108 		git.add().addFilepattern(".").call();
1109 		RevCommit firstCommit = git.commit().setMessage("initial commit")
1110 				.call();
1111 		checkConsistentLastModified("0", "1", "2", "3", "4");
1112 		checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
1113 		assertEquals("Commit should not touch working tree file 4", lastTs4,
1114 				FS.DETECTED
1115 						.lastModifiedInstant(new File(db.getWorkTree(), "4")));
1116 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1117 
1118 		// Do modifications on the master branch. Then add and commit. This
1119 		// should touch only "0", "2 and "3"
1120 		fsTick(indexFile);
1121 		f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
1122 				null);
1123 		fsTick(f);
1124 		git.add().addFilepattern(".").call();
1125 		RevCommit masterCommit = git.commit().setMessage("master commit")
1126 				.call();
1127 		checkConsistentLastModified("0", "1", "2", "3", "4");
1128 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1129 				+ lastTsIndex, "<0", "2", "3", "<.git/index");
1130 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1131 
1132 		// Checkout a side branch. This should touch only "0", "2 and "3"
1133 		fsTick(indexFile);
1134 		git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
1135 				.setName("side").call();
1136 		checkConsistentLastModified("0", "1", "2", "3", "4");
1137 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1138 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1139 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1140 
1141 		// This checkout may have populated worktree and index so fast that we
1142 		// may have smudged entries now. Check that we have the right content
1143 		// and then rewrite the index to get rid of smudged state
1144 		assertEquals("[0, mode:100644, content:orig]" //
1145 				+ "[1, mode:100644, content:orig]" //
1146 				+ "[2, mode:100644, content:1\n2\n3]" //
1147 				+ "[3, mode:100644, content:orig]" //
1148 				+ "[4, mode:100644, content:orig]", //
1149 				indexState(CONTENT));
1150 		fsTick(indexFile);
1151 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1152 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1153 		fsTick(f);
1154 		git.add().addFilepattern(".").call();
1155 		checkConsistentLastModified("0", "1", "2", "3", "4");
1156 		checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
1157 				"4", "<.git/index");
1158 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1159 
1160 		// Do modifications on the side branch. Touch only "1", "2 and "3"
1161 		fsTick(indexFile);
1162 		f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
1163 		fsTick(f);
1164 		git.add().addFilepattern(".").call();
1165 		git.commit().setMessage("side commit").call();
1166 		checkConsistentLastModified("0", "1", "2", "3", "4");
1167 		checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
1168 				+ lastTsIndex, "<1", "2", "3", "<.git/index");
1169 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1170 
1171 		// merge master and side. Should only touch "0," "2" and "3"
1172 		fsTick(indexFile);
1173 		git.merge().setStrategy(strategy).include(masterCommit).call();
1174 		checkConsistentLastModified("0", "1", "2", "4");
1175 		checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
1176 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1177 		assertEquals(
1178 				"[0, mode:100644, content:master]" //
1179 						+ "[1, mode:100644, content:side]" //
1180 						+ "[2, mode:100644, content:1master\n2\n3side]" //
1181 						+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
1182 						+ "[4, mode:100644, content:orig]", //
1183 				indexState(CONTENT));
1184 	}
1185 
1186 	/**
1187 	 * Merging two conflicting submodules when the index does not contain any
1188 	 * entry for that submodule.
1189 	 *
1190 	 * @param strategy
1191 	 * @throws Exception
1192 	 */
1193 	@Theory
1194 	public void checkMergeConflictingSubmodulesWithoutIndex(
1195 			MergeStrategy strategy) throws Exception {
1196 		Git git = Git.wrap(db);
1197 		writeTrashFile("initial", "initial");
1198 		git.add().addFilepattern("initial").call();
1199 		RevCommit initial = git.commit().setMessage("initial").call();
1200 
1201 		writeSubmodule("one", ObjectId
1202 				.fromString("1000000000000000000000000000000000000000"));
1203 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1204 		RevCommit right = git.commit().setMessage("added one").call();
1205 
1206 		// a second commit in the submodule
1207 
1208 		git.checkout().setStartPoint(initial).setName("left")
1209 				.setCreateBranch(true).call();
1210 		writeSubmodule("one", ObjectId
1211 				.fromString("2000000000000000000000000000000000000000"));
1212 
1213 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1214 		git.commit().setMessage("a different one").call();
1215 
1216 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1217 				.call();
1218 
1219 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
1220 		Map<String, int[][]> conflicts = result.getConflicts();
1221 		assertEquals(1, conflicts.size());
1222 		assertNotNull(conflicts.get("one"));
1223 	}
1224 
1225 	/**
1226 	 * Merging two non-conflicting submodules when the index does not contain
1227 	 * any entry for either submodule.
1228 	 *
1229 	 * @param strategy
1230 	 * @throws Exception
1231 	 */
1232 	@Theory
1233 	public void checkMergeNonConflictingSubmodulesWithoutIndex(
1234 			MergeStrategy strategy) throws Exception {
1235 		Git git = Git.wrap(db);
1236 		writeTrashFile("initial", "initial");
1237 		git.add().addFilepattern("initial").call();
1238 
1239 		writeSubmodule("one", ObjectId
1240 				.fromString("1000000000000000000000000000000000000000"));
1241 
1242 		// Our initial commit should include a .gitmodules with a bunch of
1243 		// comment lines, so that
1244 		// we don't have a content merge issue when we add a new submodule at
1245 		// the top and a different
1246 		// one at the bottom. This is sort of a hack, but it should allow
1247 		// add/add submodule merges
1248 		String existing = read(Constants.DOT_GIT_MODULES);
1249 		String context = "\n# context\n# more context\n# yet more context\n";
1250 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1251 				existing + context + context + context);
1252 
1253 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1254 		RevCommit initial = git.commit().setMessage("initial").call();
1255 
1256 		writeSubmodule("two", ObjectId
1257 				.fromString("1000000000000000000000000000000000000000"));
1258 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1259 
1260 		RevCommit right = git.commit().setMessage("added two").call();
1261 
1262 		git.checkout().setStartPoint(initial).setName("left")
1263 				.setCreateBranch(true).call();
1264 
1265 		// we need to manually create the submodule for three for the
1266 		// .gitmodules hackery
1267 		addSubmoduleToIndex("three", ObjectId
1268 				.fromString("1000000000000000000000000000000000000000"));
1269 		new File(db.getWorkTree(), "three").mkdir();
1270 
1271 		existing = read(Constants.DOT_GIT_MODULES);
1272 		String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
1273 				+ db.getDirectory().toURI() + "\n";
1274 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1275 				three + existing);
1276 
1277 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1278 		git.commit().setMessage("a different one").call();
1279 
1280 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1281 				.call();
1282 
1283 		assertNull(result.getCheckoutConflicts());
1284 		assertNull(result.getFailingPaths());
1285 		for (String dir : Arrays.asList("one", "two", "three")) {
1286 			assertTrue(new File(db.getWorkTree(), dir).isDirectory());
1287 		}
1288 	}
1289 
1290 	private void writeSubmodule(String path, ObjectId commit)
1291 			throws IOException, ConfigInvalidException {
1292 		addSubmoduleToIndex(path, commit);
1293 		new File(db.getWorkTree(), path).mkdir();
1294 
1295 		StoredConfig config = db.getConfig();
1296 		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1297 				ConfigConstants.CONFIG_KEY_URL,
1298 				db.getDirectory().toURI().toString());
1299 		config.save();
1300 
1301 		FileBasedConfig modulesConfig = new FileBasedConfig(
1302 				new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1303 				db.getFS());
1304 		modulesConfig.load();
1305 		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1306 				ConfigConstants.CONFIG_KEY_PATH, path);
1307 		modulesConfig.save();
1308 
1309 	}
1310 
1311 	private void addSubmoduleToIndex(String path, ObjectId commit)
1312 			throws IOException {
1313 		DirCache cache = db.lockDirCache();
1314 		DirCacheEditor editor = cache.editor();
1315 		editor.add(new DirCacheEditor.PathEdit(path) {
1316 
1317 			@Override
1318 			public void apply(DirCacheEntry ent) {
1319 				ent.setFileMode(FileMode.GITLINK);
1320 				ent.setObjectId(commit);
1321 			}
1322 		});
1323 		editor.commit();
1324 	}
1325 
1326 	// Assert that every specified index entry has the same last modification
1327 	// timestamp as the associated file
1328 	private void checkConsistentLastModified(String... pathes)
1329 			throws IOException {
1330 		DirCache dc = db.readDirCache();
1331 		File workTree = db.getWorkTree();
1332 		for (String path : pathes)
1333 			assertEquals(
1334 					"IndexEntry with path "
1335 							+ path
1336 							+ " has lastmodified which is different from the worktree file",
1337 					FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
1338 					dc.getEntry(path)
1339 							.getLastModifiedInstant());
1340 	}
1341 
1342 	// Assert that modification timestamps of working tree files are as
1343 	// expected. You may specify n files. It is asserted that every file
1344 	// i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
1345 	// then this file must be younger then file i. A path "*<modtime>"
1346 	// represents a file with a modification time of <modtime>
1347 	// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
1348 	private void checkModificationTimeStampOrder(String... pathes) {
1349 		Instant lastMod = EPOCH;
1350 		for (String p : pathes) {
1351 			boolean strong = p.startsWith("<");
1352 			boolean fixed = p.charAt(strong ? 1 : 0) == '*';
1353 			p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
1354 			Instant curMod = fixed ? Instant.parse(p)
1355 					: FS.DETECTED
1356 							.lastModifiedInstant(new File(db.getWorkTree(), p));
1357 			if (strong) {
1358 				assertTrue("path " + p + " is not younger than predecesssor",
1359 						curMod.compareTo(lastMod) > 0);
1360 			} else {
1361 				assertTrue("path " + p + " is older than predecesssor",
1362 						curMod.compareTo(lastMod) >= 0);
1363 			}
1364 		}
1365 	}
1366 
1367 	private String readBlob(ObjectId treeish, String path) throws Exception {
1368 		TestRepository<?> tr = new TestRepository<>(db);
1369 		RevWalk rw = tr.getRevWalk();
1370 		RevTree tree = rw.parseTree(treeish);
1371 		RevObject obj = tr.get(tree, path);
1372 		if (obj == null) {
1373 			return null;
1374 		}
1375 		return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(),
1376 				UTF_8);
1377 	}
1378 }