View Javadoc
1   /*
2    * Copyright (C) 2012, 2020 Robin Stocker <robin@nibor.org> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.merge;
11  
12  import static java.nio.charset.StandardCharsets.UTF_8;
13  import static java.time.Instant.EPOCH;
14  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
15  import static org.junit.Assert.assertEquals;
16  import static org.junit.Assert.assertFalse;
17  import static org.junit.Assert.assertNotNull;
18  import static org.junit.Assert.assertNull;
19  import static org.junit.Assert.assertTrue;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.time.Instant;
26  import java.util.Arrays;
27  import java.util.Map;
28  
29  import org.eclipse.jgit.api.Git;
30  import org.eclipse.jgit.api.MergeResult;
31  import org.eclipse.jgit.api.MergeResult.MergeStatus;
32  import org.eclipse.jgit.api.RebaseResult;
33  import org.eclipse.jgit.api.errors.CheckoutConflictException;
34  import org.eclipse.jgit.api.errors.GitAPIException;
35  import org.eclipse.jgit.api.errors.JGitInternalException;
36  import org.eclipse.jgit.diff.RawText;
37  import org.eclipse.jgit.dircache.DirCache;
38  import org.eclipse.jgit.dircache.DirCacheEditor;
39  import org.eclipse.jgit.dircache.DirCacheEntry;
40  import org.eclipse.jgit.errors.ConfigInvalidException;
41  import org.eclipse.jgit.errors.NoMergeBaseException;
42  import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
43  import org.eclipse.jgit.junit.RepositoryTestCase;
44  import org.eclipse.jgit.junit.TestRepository;
45  import org.eclipse.jgit.lib.AnyObjectId;
46  import org.eclipse.jgit.lib.ConfigConstants;
47  import org.eclipse.jgit.lib.Constants;
48  import org.eclipse.jgit.lib.FileMode;
49  import org.eclipse.jgit.lib.ObjectId;
50  import org.eclipse.jgit.lib.ObjectInserter;
51  import org.eclipse.jgit.lib.ObjectLoader;
52  import org.eclipse.jgit.lib.ObjectReader;
53  import org.eclipse.jgit.lib.ObjectStream;
54  import org.eclipse.jgit.lib.StoredConfig;
55  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
56  import org.eclipse.jgit.revwalk.RevCommit;
57  import org.eclipse.jgit.revwalk.RevObject;
58  import org.eclipse.jgit.revwalk.RevTree;
59  import org.eclipse.jgit.revwalk.RevWalk;
60  import org.eclipse.jgit.storage.file.FileBasedConfig;
61  import org.eclipse.jgit.treewalk.FileTreeIterator;
62  import org.eclipse.jgit.util.FS;
63  import org.eclipse.jgit.util.FileUtils;
64  import org.junit.Assert;
65  import org.junit.experimental.theories.DataPoints;
66  import org.junit.experimental.theories.Theories;
67  import org.junit.experimental.theories.Theory;
68  import org.junit.runner.RunWith;
69  
70  @RunWith(Theories.class)
71  public class MergerTest extends RepositoryTestCase {
72  
73  	@DataPoints
74  	public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
75  			MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
76  
77  	@Theory
78  	public void failingDeleteOfDirectoryWithUntrackedContent(
79  			MergeStrategy strategy) throws Exception {
80  		File folder1 = new File(db.getWorkTree(), "folder1");
81  		FileUtils.mkdir(folder1);
82  		File file = new File(folder1, "file1.txt");
83  		write(file, "folder1--file1.txt");
84  		file = new File(folder1, "file2.txt");
85  		write(file, "folder1--file2.txt");
86  
87  		try (Git git = new Git(db)) {
88  			git.add().addFilepattern(folder1.getName()).call();
89  			RevCommit base = git.commit().setMessage("adding folder").call();
90  
91  			recursiveDelete(folder1);
92  			git.rm().addFilepattern("folder1/file1.txt")
93  					.addFilepattern("folder1/file2.txt").call();
94  			RevCommit other = git.commit()
95  					.setMessage("removing folders on 'other'").call();
96  
97  			git.checkout().setName(base.name()).call();
98  
99  			file = new File(db.getWorkTree(), "unrelated.txt");
100 			write(file, "unrelated");
101 
102 			git.add().addFilepattern("unrelated.txt").call();
103 			RevCommit head = git.commit().setMessage("Adding another file").call();
104 
105 			// Untracked file to cause failing path for delete() of folder1
106 			// but that's ok.
107 			file = new File(folder1, "file3.txt");
108 			write(file, "folder1--file3.txt");
109 
110 			ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
111 			merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
112 			merger.setWorkingTreeIterator(new FileTreeIterator(db));
113 			boolean ok = merger.merge(head.getId(), other.getId());
114 			assertTrue(ok);
115 			assertTrue(file.exists());
116 		}
117 	}
118 
119 	/**
120 	 * Merging two conflicting subtrees when the index does not contain any file
121 	 * in that subtree should lead to a conflicting state.
122 	 *
123 	 * @param strategy
124 	 * @throws Exception
125 	 */
126 	@Theory
127 	public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
128 			throws Exception {
129 		Git git = Git.wrap(db);
130 
131 		writeTrashFile("d/1", "orig");
132 		git.add().addFilepattern("d/1").call();
133 		RevCommit first = git.commit().setMessage("added d/1").call();
134 
135 		writeTrashFile("d/1", "master");
136 		RevCommit masterCommit = git.commit().setAll(true)
137 				.setMessage("modified d/1 on master").call();
138 
139 		git.checkout().setCreateBranch(true).setStartPoint(first)
140 				.setName("side").call();
141 		writeTrashFile("d/1", "side");
142 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
143 
144 		git.rm().addFilepattern("d/1").call();
145 		git.rm().addFilepattern("d").call();
146 		MergeResult mergeRes = git.merge().setStrategy(strategy)
147 				.include(masterCommit).call();
148 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
149 		assertEquals(
150 				"[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
151 				indexState(CONTENT));
152 	}
153 
154 	/**
155 	 * Merging two different but mergeable subtrees when the index does not
156 	 * contain any file in that subtree should lead to a merged state.
157 	 *
158 	 * @param strategy
159 	 * @throws Exception
160 	 */
161 	@Theory
162 	public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
163 			throws Exception {
164 		Git git = Git.wrap(db);
165 
166 		writeTrashFile("d/1", "1\n2\n3");
167 		git.add().addFilepattern("d/1").call();
168 		RevCommit first = git.commit().setMessage("added d/1").call();
169 
170 		writeTrashFile("d/1", "1master\n2\n3");
171 		RevCommit masterCommit = git.commit().setAll(true)
172 				.setMessage("modified d/1 on master").call();
173 
174 		git.checkout().setCreateBranch(true).setStartPoint(first)
175 				.setName("side").call();
176 		writeTrashFile("d/1", "1\n2\n3side");
177 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
178 
179 		git.rm().addFilepattern("d/1").call();
180 		git.rm().addFilepattern("d").call();
181 		MergeResult mergeRes = git.merge().setStrategy(strategy)
182 				.include(masterCommit).call();
183 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
184 		assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
185 				indexState(CONTENT));
186 	}
187 
188 	/**
189 	 * An existing directory without tracked content should not prevent merging
190 	 * a tree where that directory exists.
191 	 *
192 	 * @param strategy
193 	 * @throws Exception
194 	 */
195 	@Theory
196 	public void checkUntrackedFolderIsNotAConflict(
197 			MergeStrategy strategy) throws Exception {
198 		Git git = Git.wrap(db);
199 
200 		writeTrashFile("d/1", "1");
201 		git.add().addFilepattern("d/1").call();
202 		RevCommit first = git.commit().setMessage("added d/1").call();
203 
204 		writeTrashFile("e/1", "4");
205 		git.add().addFilepattern("e/1").call();
206 		RevCommit masterCommit = git.commit().setMessage("added e/1").call();
207 
208 		git.checkout().setCreateBranch(true).setStartPoint(first)
209 				.setName("side").call();
210 		writeTrashFile("f/1", "5");
211 		git.add().addFilepattern("f/1").call();
212 		git.commit().setAll(true).setMessage("added f/1")
213 				.call();
214 
215 		// Untracked directory e shall not conflict with merged e/1
216 		writeTrashFile("e/2", "d two");
217 
218 		MergeResult mergeRes = git.merge().setStrategy(strategy)
219 				.include(masterCommit).call();
220 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
221 		assertEquals(
222 				"[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
223 				indexState(CONTENT));
224 	}
225 
226 	/**
227 	 * A tracked file is replaced by a folder in THEIRS.
228 	 *
229 	 * @param strategy
230 	 * @throws Exception
231 	 */
232 	@Theory
233 	public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
234 			throws Exception {
235 		Git git = Git.wrap(db);
236 
237 		writeTrashFile("sub", "file");
238 		git.add().addFilepattern("sub").call();
239 		RevCommit first = git.commit().setMessage("initial").call();
240 
241 		git.checkout().setCreateBranch(true).setStartPoint(first)
242 				.setName("side").call();
243 
244 		git.rm().addFilepattern("sub").call();
245 		writeTrashFile("sub/file", "subfile");
246 		git.add().addFilepattern("sub/file").call();
247 		RevCommit masterCommit = git.commit().setMessage("file -> folder")
248 				.call();
249 
250 		git.checkout().setName("master").call();
251 		writeTrashFile("noop", "other");
252 		git.add().addFilepattern("noop").call();
253 		git.commit().setAll(true).setMessage("noop").call();
254 
255 		MergeResult mergeRes = git.merge().setStrategy(strategy)
256 				.include(masterCommit).call();
257 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
258 		assertEquals(
259 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
260 				indexState(CONTENT));
261 	}
262 
263 	/**
264 	 * A tracked file is replaced by a folder in OURS.
265 	 *
266 	 * @param strategy
267 	 * @throws Exception
268 	 */
269 	@Theory
270 	public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
271 			throws Exception {
272 		Git git = Git.wrap(db);
273 
274 		writeTrashFile("sub", "file");
275 		git.add().addFilepattern("sub").call();
276 		RevCommit first = git.commit().setMessage("initial").call();
277 
278 		git.checkout().setCreateBranch(true).setStartPoint(first)
279 				.setName("side").call();
280 		writeTrashFile("noop", "other");
281 		git.add().addFilepattern("noop").call();
282 		RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
283 				.call();
284 
285 		git.checkout().setName("master").call();
286 		git.rm().addFilepattern("sub").call();
287 		writeTrashFile("sub/file", "subfile");
288 		git.add().addFilepattern("sub/file").call();
289 		git.commit().setMessage("file -> folder")
290 				.call();
291 
292 		MergeResult mergeRes = git.merge().setStrategy(strategy)
293 				.include(sideCommit).call();
294 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
295 		assertEquals(
296 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
297 				indexState(CONTENT));
298 	}
299 
300 	/**
301 	 * An existing directory without tracked content should not prevent merging
302 	 * a file with that name.
303 	 *
304 	 * @param strategy
305 	 * @throws Exception
306 	 */
307 	@Theory
308 	public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
309 			MergeStrategy strategy)
310 			throws Exception {
311 		Git git = Git.wrap(db);
312 
313 		writeTrashFile("d/1", "1");
314 		git.add().addFilepattern("d/1").call();
315 		RevCommit first = git.commit().setMessage("added d/1").call();
316 
317 		writeTrashFile("e", "4");
318 		git.add().addFilepattern("e").call();
319 		RevCommit masterCommit = git.commit().setMessage("added e").call();
320 
321 		git.checkout().setCreateBranch(true).setStartPoint(first)
322 				.setName("side").call();
323 		writeTrashFile("f/1", "5");
324 		git.add().addFilepattern("f/1").call();
325 		git.commit().setAll(true).setMessage("added f/1").call();
326 
327 		// Untracked empty directory hierarcy e/1 shall not conflict with merged
328 		// e/1
329 		FileUtils.mkdirs(new File(trash, "e/1"), true);
330 
331 		MergeResult mergeRes = git.merge().setStrategy(strategy)
332 				.include(masterCommit).call();
333 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
334 		assertEquals(
335 				"[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
336 				indexState(CONTENT));
337 	}
338 
339 	@Theory
340 	public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
341 			GitAPIException {
342 		Git git = Git.wrap(db);
343 		db.getConfig().setString("core", null, "autocrlf", "false");
344 		db.getConfig().save();
345 		writeTrashFile("crlf.txt", "some\r\ndata\r\n");
346 		git.add().addFilepattern("crlf.txt").call();
347 		git.commit().setMessage("base").call();
348 
349 		git.branchCreate().setName("brancha").call();
350 
351 		writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
352 		git.add().addFilepattern("crlf.txt").call();
353 		git.commit().setMessage("on master").call();
354 
355 		git.checkout().setName("brancha").call();
356 		writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
357 		git.add().addFilepattern("crlf.txt").call();
358 		git.commit().setMessage("on brancha").call();
359 
360 		db.getConfig().setString("core", null, "autocrlf", "input");
361 		db.getConfig().save();
362 
363 		MergeResult mergeResult = git.merge().setStrategy(strategy)
364 				.include(db.resolve("master"))
365 				.call();
366 		assertEquals(MergeResult.MergeStatus.MERGED,
367 				mergeResult.getMergeStatus());
368 	}
369 
370 	@Theory
371 	public void mergeConflictWithCrLfTextAuto(MergeStrategy strategy)
372 			throws IOException, GitAPIException {
373 		Git git = Git.wrap(db);
374 		writeTrashFile("crlf.txt", "a crlf file\r\n");
375 		git.add().addFilepattern("crlf.txt").call();
376 		git.commit().setMessage("base").call();
377 		assertEquals("[crlf.txt, mode:100644, content:a crlf file\r\n]",
378 				indexState(CONTENT));
379 		writeTrashFile(".gitattributes", "crlf.txt text=auto");
380 		git.add().addFilepattern(".gitattributes").call();
381 		git.commit().setMessage("attributes").call();
382 
383 		git.branchCreate().setName("brancha").call();
384 
385 		writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
386 		git.add().addFilepattern("crlf.txt").call();
387 		git.commit().setMessage("on master").call();
388 		assertEquals(
389 				"[.gitattributes, mode:100644, content:crlf.txt text=auto]"
390 						+ "[crlf.txt, mode:100644, content:a crlf file\r\na second line\r\n]",
391 				indexState(CONTENT));
392 
393 		git.checkout().setName("brancha").call();
394 		File testFile = writeTrashFile("crlf.txt",
395 				"a crlf file\r\nanother line\r\n");
396 		git.add().addFilepattern("crlf.txt").call();
397 		git.commit().setMessage("on brancha").call();
398 
399 		MergeResult mergeResult = git.merge().setStrategy(strategy)
400 				.include(db.resolve("master")).call();
401 		assertEquals(MergeResult.MergeStatus.CONFLICTING,
402 				mergeResult.getMergeStatus());
403 		checkFile(testFile,
404 				"a crlf file\r\n" //
405 						+ "<<<<<<< HEAD\n" //
406 						+ "another line\r\n" //
407 						+ "=======\n" //
408 						+ "a second line\r\n" //
409 						+ ">>>>>>> 8e9e704742f1bc8a41eac88aac4aeefd338b7384\n");
410 	}
411 
412 	@Theory
413 	public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
414 			throws IOException, GitAPIException {
415 		Git git = Git.wrap(db);
416 		db.getConfig().setString("core", null, "autocrlf", "true");
417 		db.getConfig().save();
418 		writeTrashFile("crlf.txt", "a crlf file\r\n");
419 		git.add().addFilepattern("crlf.txt").call();
420 		git.commit().setMessage("base").call();
421 
422 		git.branchCreate().setName("brancha").call();
423 
424 		writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
425 		git.add().addFilepattern("crlf.txt").call();
426 		git.commit().setMessage("on master").call();
427 
428 		git.checkout().setName("brancha").call();
429 		File testFile = writeTrashFile("crlf.txt",
430 				"a first line\r\na crlf file\r\n");
431 		git.add().addFilepattern("crlf.txt").call();
432 		git.commit().setMessage("on brancha").call();
433 
434 		MergeResult mergeResult = git.merge().setStrategy(strategy)
435 				.include(db.resolve("master")).call();
436 		assertEquals(MergeResult.MergeStatus.MERGED,
437 				mergeResult.getMergeStatus());
438 		checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
439 		assertEquals(
440 				"[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
441 				indexState(CONTENT));
442 	}
443 
444 	@Theory
445 	public void rebaseWithCrlfAutoCrlfTrue(MergeStrategy strategy)
446 			throws IOException, GitAPIException {
447 		Git git = Git.wrap(db);
448 		db.getConfig().setString("core", null, "autocrlf", "true");
449 		db.getConfig().save();
450 		writeTrashFile("crlf.txt", "line 1\r\nline 2\r\nline 3\r\n");
451 		git.add().addFilepattern("crlf.txt").call();
452 		RevCommit first = git.commit().setMessage("base").call();
453 
454 		git.checkout().setCreateBranch(true).setStartPoint(first)
455 				.setName("brancha").call();
456 
457 		File testFile = writeTrashFile("crlf.txt",
458 				"line 1\r\nmodified line\r\nline 3\r\n");
459 		git.add().addFilepattern("crlf.txt").call();
460 		git.commit().setMessage("on brancha").call();
461 
462 		git.checkout().setName("master").call();
463 		File otherFile = writeTrashFile("otherfile.txt", "a line\r\n");
464 		git.add().addFilepattern("otherfile.txt").call();
465 		git.commit().setMessage("on master").call();
466 
467 		git.checkout().setName("brancha").call();
468 		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
469 		assertFalse(otherFile.exists());
470 
471 		RebaseResult rebaseResult = git.rebase().setStrategy(strategy)
472 				.setUpstream(db.resolve("master")).call();
473 		assertEquals(RebaseResult.Status.OK, rebaseResult.getStatus());
474 		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
475 		checkFile(otherFile, "a line\r\n");
476 		assertEquals(
477 				"[crlf.txt, mode:100644, content:line 1\nmodified line\nline 3\n]"
478 						+ "[otherfile.txt, mode:100644, content:a line\n]",
479 				indexState(CONTENT));
480 	}
481 
482 	/**
483 	 * Merging two equal subtrees when the index does not contain any file in
484 	 * that subtree should lead to a merged state.
485 	 *
486 	 * @param strategy
487 	 * @throws Exception
488 	 */
489 	@Theory
490 	public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
491 			throws Exception {
492 		Git git = Git.wrap(db);
493 
494 		writeTrashFile("d/1", "orig");
495 		git.add().addFilepattern("d/1").call();
496 		RevCommit first = git.commit().setMessage("added d/1").call();
497 
498 		writeTrashFile("d/1", "modified");
499 		RevCommit masterCommit = git.commit().setAll(true)
500 				.setMessage("modified d/1 on master").call();
501 
502 		git.checkout().setCreateBranch(true).setStartPoint(first)
503 				.setName("side").call();
504 		writeTrashFile("d/1", "modified");
505 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
506 
507 		git.rm().addFilepattern("d/1").call();
508 		git.rm().addFilepattern("d").call();
509 		MergeResult mergeRes = git.merge().setStrategy(strategy)
510 				.include(masterCommit).call();
511 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
512 		assertEquals("[d/1, mode:100644, content:modified]",
513 				indexState(CONTENT));
514 	}
515 
516 	/**
517 	 * Merging two equal subtrees with an incore merger should lead to a merged
518 	 * state.
519 	 *
520 	 * @param strategy
521 	 * @throws Exception
522 	 */
523 	@Theory
524 	public void checkMergeEqualTreesInCore(MergeStrategy strategy)
525 			throws Exception {
526 		Git git = Git.wrap(db);
527 
528 		writeTrashFile("d/1", "orig");
529 		git.add().addFilepattern("d/1").call();
530 		RevCommit first = git.commit().setMessage("added d/1").call();
531 
532 		writeTrashFile("d/1", "modified");
533 		RevCommit masterCommit = git.commit().setAll(true)
534 				.setMessage("modified d/1 on master").call();
535 
536 		git.checkout().setCreateBranch(true).setStartPoint(first)
537 				.setName("side").call();
538 		writeTrashFile("d/1", "modified");
539 		RevCommit sideCommit = git.commit().setAll(true)
540 				.setMessage("modified d/1 on side").call();
541 
542 		git.rm().addFilepattern("d/1").call();
543 		git.rm().addFilepattern("d").call();
544 
545 		ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
546 				true);
547 		boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
548 		assertTrue(noProblems);
549 	}
550 
551 	/**
552 	 * Merging two equal subtrees with an incore merger should lead to a merged
553 	 * state, without using a Repository (the 'Gerrit' use case).
554 	 *
555 	 * @param strategy
556 	 * @throws Exception
557 	 */
558 	@Theory
559 	public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
560 			throws Exception {
561 		Git git = Git.wrap(db);
562 
563 		writeTrashFile("d/1", "orig");
564 		git.add().addFilepattern("d/1").call();
565 		RevCommit first = git.commit().setMessage("added d/1").call();
566 
567 		writeTrashFile("d/1", "modified");
568 		RevCommit masterCommit = git.commit().setAll(true)
569 				.setMessage("modified d/1 on master").call();
570 
571 		git.checkout().setCreateBranch(true).setStartPoint(first)
572 				.setName("side").call();
573 		writeTrashFile("d/1", "modified");
574 		RevCommit sideCommit = git.commit().setAll(true)
575 				.setMessage("modified d/1 on side").call();
576 
577 		git.rm().addFilepattern("d/1").call();
578 		git.rm().addFilepattern("d").call();
579 
580 		try (ObjectInserter ins = db.newObjectInserter()) {
581 			ThreeWayMerger resolveMerger =
582 					(ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
583 			boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
584 			assertTrue(noProblems);
585 		}
586 	}
587 
588 	/**
589 	 * Merging two equal subtrees when the index and HEAD does not contain any
590 	 * file in that subtree should lead to a merged state.
591 	 *
592 	 * @param strategy
593 	 * @throws Exception
594 	 */
595 	@Theory
596 	public void checkMergeEqualNewTrees(MergeStrategy strategy)
597 			throws Exception {
598 		Git git = Git.wrap(db);
599 
600 		writeTrashFile("2", "orig");
601 		git.add().addFilepattern("2").call();
602 		RevCommit first = git.commit().setMessage("added 2").call();
603 
604 		writeTrashFile("d/1", "orig");
605 		git.add().addFilepattern("d/1").call();
606 		RevCommit masterCommit = git.commit().setAll(true)
607 				.setMessage("added d/1 on master").call();
608 
609 		git.checkout().setCreateBranch(true).setStartPoint(first)
610 				.setName("side").call();
611 		writeTrashFile("d/1", "orig");
612 		git.add().addFilepattern("d/1").call();
613 		git.commit().setAll(true).setMessage("added d/1 on side").call();
614 
615 		git.rm().addFilepattern("d/1").call();
616 		git.rm().addFilepattern("d").call();
617 		MergeResult mergeRes = git.merge().setStrategy(strategy)
618 				.include(masterCommit).call();
619 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
620 		assertEquals(
621 				"[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
622 				indexState(CONTENT));
623 	}
624 
625 	/**
626 	 * Merging two conflicting subtrees when the index and HEAD does not contain
627 	 * any file in that subtree should lead to a conflicting state.
628 	 *
629 	 * @param strategy
630 	 * @throws Exception
631 	 */
632 	@Theory
633 	public void checkMergeConflictingNewTrees(MergeStrategy strategy)
634 			throws Exception {
635 		Git git = Git.wrap(db);
636 
637 		writeTrashFile("2", "orig");
638 		git.add().addFilepattern("2").call();
639 		RevCommit first = git.commit().setMessage("added 2").call();
640 
641 		writeTrashFile("d/1", "master");
642 		git.add().addFilepattern("d/1").call();
643 		RevCommit masterCommit = git.commit().setAll(true)
644 				.setMessage("added d/1 on master").call();
645 
646 		git.checkout().setCreateBranch(true).setStartPoint(first)
647 				.setName("side").call();
648 		writeTrashFile("d/1", "side");
649 		git.add().addFilepattern("d/1").call();
650 		git.commit().setAll(true).setMessage("added d/1 on side").call();
651 
652 		git.rm().addFilepattern("d/1").call();
653 		git.rm().addFilepattern("d").call();
654 		MergeResult mergeRes = git.merge().setStrategy(strategy)
655 				.include(masterCommit).call();
656 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
657 		assertEquals(
658 				"[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
659 				indexState(CONTENT));
660 	}
661 
662 	/**
663 	 * Merging two conflicting files when the index contains a tree for that
664 	 * path should lead to a failed state.
665 	 *
666 	 * @param strategy
667 	 * @throws Exception
668 	 */
669 	@Theory
670 	public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
671 			throws Exception {
672 		Git git = Git.wrap(db);
673 
674 		writeTrashFile("0", "orig");
675 		git.add().addFilepattern("0").call();
676 		RevCommit first = git.commit().setMessage("added 0").call();
677 
678 		writeTrashFile("0", "master");
679 		RevCommit masterCommit = git.commit().setAll(true)
680 				.setMessage("modified 0 on master").call();
681 
682 		git.checkout().setCreateBranch(true).setStartPoint(first)
683 				.setName("side").call();
684 		writeTrashFile("0", "side");
685 		git.commit().setAll(true).setMessage("modified 0 on side").call();
686 
687 		git.rm().addFilepattern("0").call();
688 		writeTrashFile("0/0", "side");
689 		git.add().addFilepattern("0/0").call();
690 		MergeResult mergeRes = git.merge().setStrategy(strategy)
691 				.include(masterCommit).call();
692 		assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
693 	}
694 
695 	/**
696 	 * Merging two equal files when the index contains a tree for that path
697 	 * should lead to a failed state.
698 	 *
699 	 * @param strategy
700 	 * @throws Exception
701 	 */
702 	@Theory
703 	public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
704 			throws Exception {
705 		Git git = Git.wrap(db);
706 
707 		writeTrashFile("0", "orig");
708 		writeTrashFile("1", "1\n2\n3");
709 		git.add().addFilepattern("0").addFilepattern("1").call();
710 		RevCommit first = git.commit().setMessage("added 0, 1").call();
711 
712 		writeTrashFile("1", "1master\n2\n3");
713 		RevCommit masterCommit = git.commit().setAll(true)
714 				.setMessage("modified 1 on master").call();
715 
716 		git.checkout().setCreateBranch(true).setStartPoint(first)
717 				.setName("side").call();
718 		writeTrashFile("1", "1\n2\n3side");
719 		git.commit().setAll(true).setMessage("modified 1 on side").call();
720 
721 		git.rm().addFilepattern("0").call();
722 		writeTrashFile("0/0", "modified");
723 		git.add().addFilepattern("0/0").call();
724 		try {
725 			git.merge().setStrategy(strategy).include(masterCommit).call();
726 			Assert.fail("Didn't get the expected exception");
727 		} catch (CheckoutConflictException e) {
728 			assertEquals(1, e.getConflictingPaths().size());
729 			assertEquals("0/0", e.getConflictingPaths().get(0));
730 		}
731 	}
732 
733 	@Theory
734 	public void checkContentMergeNoConflict(MergeStrategy strategy)
735 			throws Exception {
736 		Git git = Git.wrap(db);
737 
738 		writeTrashFile("file", "1\n2\n3");
739 		git.add().addFilepattern("file").call();
740 		RevCommit first = git.commit().setMessage("added file").call();
741 
742 		writeTrashFile("file", "1master\n2\n3");
743 		git.commit().setAll(true).setMessage("modified file on master").call();
744 
745 		git.checkout().setCreateBranch(true).setStartPoint(first)
746 				.setName("side").call();
747 		writeTrashFile("file", "1\n2\n3side");
748 		RevCommit sideCommit = git.commit().setAll(true)
749 				.setMessage("modified file on side").call();
750 
751 		git.checkout().setName("master").call();
752 		MergeResult result =
753 				git.merge().setStrategy(strategy).include(sideCommit).call();
754 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
755 		String expected = "1master\n2\n3side";
756 		assertEquals(expected, read("file"));
757 	}
758 
759 	@Theory
760 	public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
761 			throws Exception {
762 		Git git = Git.wrap(db);
763 
764 		writeTrashFile("file", "1\n2\n3");
765 		git.add().addFilepattern("file").call();
766 		RevCommit first = git.commit().setMessage("added file").call();
767 
768 		writeTrashFile("file", "1master\n2\n3");
769 		RevCommit masterCommit = git.commit().setAll(true)
770 				.setMessage("modified file on master").call();
771 
772 		git.checkout().setCreateBranch(true).setStartPoint(first)
773 				.setName("side").call();
774 		writeTrashFile("file", "1\n2\n3side");
775 		RevCommit sideCommit = git.commit().setAll(true)
776 				.setMessage("modified file on side").call();
777 
778 		try (ObjectInserter ins = db.newObjectInserter()) {
779 			ResolveMerger merger =
780 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
781 			boolean noProblems = merger.merge(masterCommit, sideCommit);
782 			assertTrue(noProblems);
783 			assertEquals("1master\n2\n3side",
784 					readBlob(merger.getResultTreeId(), "file"));
785 		}
786 	}
787 
788 
789 	/**
790 	 * Merging a change involving large binary files should short-circuit reads.
791 	 *
792 	 * @param strategy
793 	 * @throws Exception
794 	 */
795 	@Theory
796 	public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
797 		Git git = Git.wrap(db);
798 		final int LINELEN = 72;
799 
800 		// setup a merge that would work correctly if we disconsider the stray '\0'
801 		// that the file contains near the start.
802 		byte[] binary = new byte[LINELEN * 2000];
803 		for (int i = 0; i < binary.length; i++) {
804 			binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
805 		}
806 		binary[50] = '\0';
807 
808 		writeTrashFile("file", new String(binary, UTF_8));
809 		git.add().addFilepattern("file").call();
810 		RevCommit first = git.commit().setMessage("added file").call();
811 
812 		// Generate an edit in a single line.
813 		int idx = LINELEN * 1200 + 1;
814 		byte save = binary[idx];
815 		binary[idx] = '@';
816 		writeTrashFile("file", new String(binary, UTF_8));
817 
818 		binary[idx] = save;
819 		git.add().addFilepattern("file").call();
820 		RevCommit masterCommit = git.commit().setAll(true)
821 			.setMessage("modified file l 1200").call();
822 
823 		git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
824 		binary[LINELEN * 1500 + 1] = '!';
825 		writeTrashFile("file", new String(binary, UTF_8));
826 		git.add().addFilepattern("file").call();
827 		RevCommit sideCommit = git.commit().setAll(true)
828 			.setMessage("modified file l 1500").call();
829 
830 		int originalBufferSize = RawText.getBufferSize();
831 		int smallBufferSize = RawText.setBufferSize(8000);
832 		try (ObjectInserter ins = db.newObjectInserter()) {
833 			// Check that we don't read the large blobs.
834 			ObjectInserter forbidInserter = new ObjectInserter.Filter() {
835 				@Override
836 				protected ObjectInserter delegate() {
837 					return ins;
838 				}
839 
840 				@Override
841 				public ObjectReader newReader() {
842 					return new BigReadForbiddenReader(super.newReader(),
843 							smallBufferSize);
844 				}
845 			};
846 
847 			ResolveMerger merger =
848 				(ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
849 			boolean noProblems = merger.merge(masterCommit, sideCommit);
850 			assertFalse(noProblems);
851 		} finally {
852 			RawText.setBufferSize(originalBufferSize);
853 		}
854 	}
855 
856 	/**
857 	 * Throws an exception if reading beyond limit.
858 	 */
859 	static class BigReadForbiddenStream extends ObjectStream.Filter {
860 		long limit;
861 
862 		BigReadForbiddenStream(ObjectStream orig, long limit) {
863 			super(orig.getType(), orig.getSize(), orig);
864 			this.limit = limit;
865 		}
866 
867 		@Override
868 		public long skip(long n) throws IOException {
869 			limit -= n;
870 			if (limit < 0) {
871 				throw new IllegalStateException();
872 			}
873 
874 			return super.skip(n);
875 		}
876 
877 		@Override
878 		public int read() throws IOException {
879 			int r = super.read();
880 			limit--;
881 			if (limit < 0) {
882 				throw new IllegalStateException();
883 			}
884 			return r;
885 		}
886 
887 		@Override
888 		public int read(byte[] b, int off, int len) throws IOException {
889 			int n = super.read(b, off, len);
890 			limit -= n;
891 			if (limit < 0) {
892 				throw new IllegalStateException();
893 			}
894 			return n;
895 		}
896 	}
897 
898 	static class BigReadForbiddenReader extends ObjectReader.Filter {
899 		ObjectReader delegate;
900 		int limit;
901 
902 		@Override
903 		protected ObjectReader delegate() {
904 			return delegate;
905 		}
906 
907 		BigReadForbiddenReader(ObjectReader delegate, int limit) {
908 			this.delegate = delegate;
909 			this.limit = limit;
910 		}
911 
912 		@Override
913 		public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
914 			ObjectLoader orig = super.open(objectId, typeHint);
915 			return new ObjectLoader.Filter() {
916 				@Override
917 				protected ObjectLoader delegate() {
918 					return orig;
919 				}
920 
921 				@Override
922 				public ObjectStream openStream() throws IOException {
923 					ObjectStream os = orig.openStream();
924 					return new BigReadForbiddenStream(os, limit);
925 				}
926 			};
927 		}
928 	}
929 
930 	@Theory
931 	public void checkContentMergeConflict(MergeStrategy strategy)
932 			throws Exception {
933 		Git git = Git.wrap(db);
934 
935 		writeTrashFile("file", "1\n2\n3");
936 		git.add().addFilepattern("file").call();
937 		RevCommit first = git.commit().setMessage("added file").call();
938 
939 		writeTrashFile("file", "1master\n2\n3");
940 		git.commit().setAll(true).setMessage("modified file on master").call();
941 
942 		git.checkout().setCreateBranch(true).setStartPoint(first)
943 				.setName("side").call();
944 		writeTrashFile("file", "1side\n2\n3");
945 		RevCommit sideCommit = git.commit().setAll(true)
946 				.setMessage("modified file on side").call();
947 
948 		git.checkout().setName("master").call();
949 		MergeResult result =
950 				git.merge().setStrategy(strategy).include(sideCommit).call();
951 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
952 		String expected = "<<<<<<< HEAD\n"
953 				+ "1master\n"
954 				+ "=======\n"
955 				+ "1side\n"
956 				+ ">>>>>>> " + sideCommit.name() + "\n"
957 				+ "2\n"
958 				+ "3";
959 		assertEquals(expected, read("file"));
960 	}
961 
962 	@Theory
963 	public void checkContentMergeConflict_noTree(MergeStrategy strategy)
964 			throws Exception {
965 		Git git = Git.wrap(db);
966 
967 		writeTrashFile("file", "1\n2\n3");
968 		git.add().addFilepattern("file").call();
969 		RevCommit first = git.commit().setMessage("added file").call();
970 
971 		writeTrashFile("file", "1master\n2\n3");
972 		RevCommit masterCommit = git.commit().setAll(true)
973 				.setMessage("modified file on master").call();
974 
975 		git.checkout().setCreateBranch(true).setStartPoint(first)
976 				.setName("side").call();
977 		writeTrashFile("file", "1side\n2\n3");
978 		RevCommit sideCommit = git.commit().setAll(true)
979 				.setMessage("modified file on side").call();
980 
981 		try (ObjectInserter ins = db.newObjectInserter()) {
982 			ResolveMerger merger =
983 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
984 			boolean noProblems = merger.merge(masterCommit, sideCommit);
985 			assertFalse(noProblems);
986 			assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
987 
988 			MergeFormatter fmt = new MergeFormatter();
989 			merger.getMergeResults().get("file");
990 			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
991 				fmt.formatMerge(out, merger.getMergeResults().get("file"),
992 						"BASE", "OURS", "THEIRS", UTF_8);
993 				String expected = "<<<<<<< OURS\n"
994 						+ "1master\n"
995 						+ "=======\n"
996 						+ "1side\n"
997 						+ ">>>>>>> THEIRS\n"
998 						+ "2\n"
999 						+ "3";
1000 				assertEquals(expected, new String(out.toByteArray(), UTF_8));
1001 			}
1002 		}
1003 	}
1004 
1005 	@Theory
1006 	public void fileBecomesDir_noTree(MergeStrategy strategy)
1007 			throws Exception {
1008 		Git git = Git.wrap(db);
1009 
1010 		writeTrashFile("file", "1\n2\n3");
1011 		writeTrashFile("side", "1\n2\n3");
1012 		git.add().addFilepattern("file").addFilepattern("side").call();
1013 		RevCommit first = git.commit().setMessage("base").call();
1014 
1015 		writeTrashFile("side", "our changed");
1016 		RevCommit ours = git.commit().setAll(true)
1017 				.setMessage("ours").call();
1018 
1019 		git.checkout().setCreateBranch(true).setStartPoint(first)
1020 				.setName("theirs").call();
1021 		deleteTrashFile("file");
1022 		writeTrashFile("file/file", "in subdir");
1023 		git.add().addFilepattern("file/file").call();
1024 
1025 		RevCommit theirs = git.commit().setAll(true)
1026 				.setMessage("theirs").call();
1027 
1028 		// Exercise inCore flavor of the merge.
1029 		try (ObjectInserter ins = db.newObjectInserter()) {
1030 			ResolveMerger merger =
1031 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
1032 			boolean success = merger.merge(ours, theirs);
1033 			assertTrue(success);
1034 			assertTrue(merger.getModifiedFiles().isEmpty());
1035 		}
1036 	}
1037 
1038 	/**
1039 	 * Merging after criss-cross merges. In this case we merge together two
1040 	 * commits which have two equally good common ancestors
1041 	 *
1042 	 * @param strategy
1043 	 * @throws Exception
1044 	 */
1045 	@Theory
1046 	public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
1047 		Git git = Git.wrap(db);
1048 
1049 		writeTrashFile("1", "1\n2\n3");
1050 		git.add().addFilepattern("1").call();
1051 		RevCommit first = git.commit().setMessage("added 1").call();
1052 
1053 		writeTrashFile("1", "1master\n2\n3");
1054 		RevCommit masterCommit = git.commit().setAll(true)
1055 				.setMessage("modified 1 on master").call();
1056 
1057 		writeTrashFile("1", "1master2\n2\n3");
1058 		git.commit().setAll(true)
1059 				.setMessage("modified 1 on master again").call();
1060 
1061 		git.checkout().setCreateBranch(true).setStartPoint(first)
1062 				.setName("side").call();
1063 		writeTrashFile("1", "1\n2\na\nb\nc\n3side");
1064 		RevCommit sideCommit = git.commit().setAll(true)
1065 				.setMessage("modified 1 on side").call();
1066 
1067 		writeTrashFile("1", "1\n2\n3side2");
1068 		git.commit().setAll(true)
1069 				.setMessage("modified 1 on side again").call();
1070 
1071 		MergeResult result = git.merge().setStrategy(strategy)
1072 				.include(masterCommit).call();
1073 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1074 		result.getNewHead();
1075 		git.checkout().setName("master").call();
1076 		result = git.merge().setStrategy(strategy).include(sideCommit).call();
1077 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1078 
1079 		// we have two branches which are criss-cross merged. Try to merge the
1080 		// tips. This should succeed with RecursiveMerge and fail with
1081 		// ResolveMerge
1082 		try {
1083 			MergeResult mergeResult = git.merge().setStrategy(strategy)
1084 					.include(git.getRepository().exactRef("refs/heads/side"))
1085 					.call();
1086 			assertEquals(MergeStrategy.RECURSIVE, strategy);
1087 			assertEquals(MergeResult.MergeStatus.MERGED,
1088 					mergeResult.getMergeStatus());
1089 			assertEquals("1master2\n2\n3side2", read("1"));
1090 		} catch (JGitInternalException e) {
1091 			assertEquals(MergeStrategy.RESOLVE, strategy);
1092 			assertTrue(e.getCause() instanceof NoMergeBaseException);
1093 			assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1094 					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1095 		}
1096 	}
1097 
1098 	@Theory
1099 	public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1100 			throws Exception {
1101 		Git git = Git.wrap(db);
1102 
1103 		writeTrashFile("a.txt", "orig");
1104 		writeTrashFile("b.txt", "orig");
1105 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1106 		RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1107 
1108 		// modify and delete files on the master branch
1109 		writeTrashFile("a.txt", "master");
1110 		git.rm().addFilepattern("b.txt").call();
1111 		RevCommit masterCommit = git.commit()
1112 				.setMessage("modified a.txt, deleted b.txt").setAll(true)
1113 				.call();
1114 
1115 		// switch back to a side branch
1116 		git.checkout().setCreateBranch(true).setStartPoint(first)
1117 				.setName("side").call();
1118 		writeTrashFile("c.txt", "side");
1119 		git.add().addFilepattern("c.txt").call();
1120 		git.commit().setMessage("added c.txt").call();
1121 
1122 		// Get a handle to the file so on windows it can't be deleted.
1123 		try (FileInputStream fis = new FileInputStream(
1124 				new File(db.getWorkTree(), "b.txt"))) {
1125 			MergeResult mergeRes = git.merge().setStrategy(strategy)
1126 					.include(masterCommit).call();
1127 			if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1128 				// probably windows
1129 				assertEquals(1, mergeRes.getFailingPaths().size());
1130 				assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1131 						mergeRes.getFailingPaths().get("b.txt"));
1132 			}
1133 			assertEquals(
1134 					"[a.txt, mode:100644, content:master]"
1135 							+ "[c.txt, mode:100644, content:side]",
1136 					indexState(CONTENT));
1137 		}
1138 	}
1139 
1140 	@Theory
1141 	public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
1142 		File f;
1143 		Instant lastTs4, lastTsIndex;
1144 		Git git = Git.wrap(db);
1145 		File indexFile = db.getIndexFile();
1146 
1147 		// Create initial content and remember when the last file was written.
1148 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1149 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1150 
1151 		// add all files, commit and check this doesn't update any working tree
1152 		// files and that the index is in a new file system timer tick. Make
1153 		// sure to wait long enough before adding so the index doesn't contain
1154 		// racily clean entries
1155 		fsTick(f);
1156 		git.add().addFilepattern(".").call();
1157 		RevCommit firstCommit = git.commit().setMessage("initial commit")
1158 				.call();
1159 		checkConsistentLastModified("0", "1", "2", "3", "4");
1160 		checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
1161 		assertEquals("Commit should not touch working tree file 4", lastTs4,
1162 				FS.DETECTED
1163 						.lastModifiedInstant(new File(db.getWorkTree(), "4")));
1164 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1165 
1166 		// Do modifications on the master branch. Then add and commit. This
1167 		// should touch only "0", "2 and "3"
1168 		fsTick(indexFile);
1169 		f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
1170 				null);
1171 		fsTick(f);
1172 		git.add().addFilepattern(".").call();
1173 		RevCommit masterCommit = git.commit().setMessage("master commit")
1174 				.call();
1175 		checkConsistentLastModified("0", "1", "2", "3", "4");
1176 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1177 				+ lastTsIndex, "<0", "2", "3", "<.git/index");
1178 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1179 
1180 		// Checkout a side branch. This should touch only "0", "2 and "3"
1181 		fsTick(indexFile);
1182 		git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
1183 				.setName("side").call();
1184 		checkConsistentLastModified("0", "1", "2", "3", "4");
1185 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1186 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1187 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1188 
1189 		// This checkout may have populated worktree and index so fast that we
1190 		// may have smudged entries now. Check that we have the right content
1191 		// and then rewrite the index to get rid of smudged state
1192 		assertEquals("[0, mode:100644, content:orig]" //
1193 				+ "[1, mode:100644, content:orig]" //
1194 				+ "[2, mode:100644, content:1\n2\n3]" //
1195 				+ "[3, mode:100644, content:orig]" //
1196 				+ "[4, mode:100644, content:orig]", //
1197 				indexState(CONTENT));
1198 		fsTick(indexFile);
1199 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1200 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1201 		fsTick(f);
1202 		git.add().addFilepattern(".").call();
1203 		checkConsistentLastModified("0", "1", "2", "3", "4");
1204 		checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
1205 				"4", "<.git/index");
1206 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1207 
1208 		// Do modifications on the side branch. Touch only "1", "2 and "3"
1209 		fsTick(indexFile);
1210 		f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
1211 		fsTick(f);
1212 		git.add().addFilepattern(".").call();
1213 		git.commit().setMessage("side commit").call();
1214 		checkConsistentLastModified("0", "1", "2", "3", "4");
1215 		checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
1216 				+ lastTsIndex, "<1", "2", "3", "<.git/index");
1217 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1218 
1219 		// merge master and side. Should only touch "0," "2" and "3"
1220 		fsTick(indexFile);
1221 		git.merge().setStrategy(strategy).include(masterCommit).call();
1222 		checkConsistentLastModified("0", "1", "2", "4");
1223 		checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
1224 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1225 		assertEquals(
1226 				"[0, mode:100644, content:master]" //
1227 						+ "[1, mode:100644, content:side]" //
1228 						+ "[2, mode:100644, content:1master\n2\n3side]" //
1229 						+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
1230 						+ "[4, mode:100644, content:orig]", //
1231 				indexState(CONTENT));
1232 	}
1233 
1234 	/**
1235 	 * Merging two conflicting submodules when the index does not contain any
1236 	 * entry for that submodule.
1237 	 *
1238 	 * @param strategy
1239 	 * @throws Exception
1240 	 */
1241 	@Theory
1242 	public void checkMergeConflictingSubmodulesWithoutIndex(
1243 			MergeStrategy strategy) throws Exception {
1244 		Git git = Git.wrap(db);
1245 		writeTrashFile("initial", "initial");
1246 		git.add().addFilepattern("initial").call();
1247 		RevCommit initial = git.commit().setMessage("initial").call();
1248 
1249 		writeSubmodule("one", ObjectId
1250 				.fromString("1000000000000000000000000000000000000000"));
1251 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1252 		RevCommit right = git.commit().setMessage("added one").call();
1253 
1254 		// a second commit in the submodule
1255 
1256 		git.checkout().setStartPoint(initial).setName("left")
1257 				.setCreateBranch(true).call();
1258 		writeSubmodule("one", ObjectId
1259 				.fromString("2000000000000000000000000000000000000000"));
1260 
1261 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1262 		git.commit().setMessage("a different one").call();
1263 
1264 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1265 				.call();
1266 
1267 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
1268 		Map<String, int[][]> conflicts = result.getConflicts();
1269 		assertEquals(1, conflicts.size());
1270 		assertNotNull(conflicts.get("one"));
1271 	}
1272 
1273 	/**
1274 	 * Merging two non-conflicting submodules when the index does not contain
1275 	 * any entry for either submodule.
1276 	 *
1277 	 * @param strategy
1278 	 * @throws Exception
1279 	 */
1280 	@Theory
1281 	public void checkMergeNonConflictingSubmodulesWithoutIndex(
1282 			MergeStrategy strategy) throws Exception {
1283 		Git git = Git.wrap(db);
1284 		writeTrashFile("initial", "initial");
1285 		git.add().addFilepattern("initial").call();
1286 
1287 		writeSubmodule("one", ObjectId
1288 				.fromString("1000000000000000000000000000000000000000"));
1289 
1290 		// Our initial commit should include a .gitmodules with a bunch of
1291 		// comment lines, so that
1292 		// we don't have a content merge issue when we add a new submodule at
1293 		// the top and a different
1294 		// one at the bottom. This is sort of a hack, but it should allow
1295 		// add/add submodule merges
1296 		String existing = read(Constants.DOT_GIT_MODULES);
1297 		String context = "\n# context\n# more context\n# yet more context\n";
1298 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1299 				existing + context + context + context);
1300 
1301 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1302 		RevCommit initial = git.commit().setMessage("initial").call();
1303 
1304 		writeSubmodule("two", ObjectId
1305 				.fromString("1000000000000000000000000000000000000000"));
1306 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1307 
1308 		RevCommit right = git.commit().setMessage("added two").call();
1309 
1310 		git.checkout().setStartPoint(initial).setName("left")
1311 				.setCreateBranch(true).call();
1312 
1313 		// we need to manually create the submodule for three for the
1314 		// .gitmodules hackery
1315 		addSubmoduleToIndex("three", ObjectId
1316 				.fromString("1000000000000000000000000000000000000000"));
1317 		new File(db.getWorkTree(), "three").mkdir();
1318 
1319 		existing = read(Constants.DOT_GIT_MODULES);
1320 		String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
1321 				+ db.getDirectory().toURI() + "\n";
1322 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1323 				three + existing);
1324 
1325 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1326 		git.commit().setMessage("a different one").call();
1327 
1328 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1329 				.call();
1330 
1331 		assertNull(result.getCheckoutConflicts());
1332 		assertNull(result.getFailingPaths());
1333 		for (String dir : Arrays.asList("one", "two", "three")) {
1334 			assertTrue(new File(db.getWorkTree(), dir).isDirectory());
1335 		}
1336 	}
1337 
1338 	/**
1339 	 * Merging two commits with a conflict in the virtual ancestor.
1340 	 *
1341 	 * Content conflicts while merging the virtual ancestor must be ignored.
1342 	 *
1343 	 * In the following tree, while merging A and B, the recursive algorithm
1344 	 * finds as base commits X and Y and tries to merge them: X deletes file "a"
1345 	 * and Y modifies it.
1346 	 *
1347 	 * Note: we delete "a" in (master) and (second-branch) to make avoid manual
1348 	 * merges. The situation is the same without those deletions and fixing
1349 	 * manually the merge of (merge-both-sides) on both branches.
1350 	 *
1351 	 * <pre>
1352 	 * A  (second-branch) Merge branch 'merge-both-sides' into second-branch
1353 	 * |\
1354 	 * o | Delete modified a
1355 	 * | |
1356 	 * | | B (master) Merge branch 'merge-both-sides' (into master)
1357 	 * | |/|
1358 	 * | X | (merge-both-sides) Delete original a
1359 	 * | | |
1360 	 * | | o Delete modified a
1361 	 * | |/
1362 	 * |/|
1363 	 * Y | Modify a
1364 	 * |/
1365 	 * o Initial commit
1366 	 * </pre>
1367 	 *
1368 	 * @param strategy
1369 	 * @throws Exception
1370 	 */
1371 	@Theory
1372 	public void checkMergeConflictInVirtualAncestor(
1373 			MergeStrategy strategy) throws Exception {
1374 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1375 			return;
1376 		}
1377 
1378 		Git git = Git.wrap(db);
1379 
1380 		// master
1381 		writeTrashFile("a", "aaaaaaaa");
1382 		writeTrashFile("b", "bbbbbbbb");
1383 		git.add().addFilepattern("a").addFilepattern("b").call();
1384 		RevCommit first = git.commit().setMessage("Initial commit").call();
1385 
1386 		writeTrashFile("a", "aaaaaaaaaaaaaaa");
1387 		git.add().addFilepattern("a").call();
1388 		RevCommit commitY = git.commit().setMessage("Modify a").call();
1389 
1390 		git.rm().addFilepattern("a").call();
1391 		// Do more in this commits, so it is not identical to the deletion in
1392 		// second-branch
1393 		writeTrashFile("c", "cccccccc");
1394 		git.add().addFilepattern("c").call();
1395 		git.commit().setMessage("Delete modified a").call();
1396 
1397 		// merge-both-sides: starts before "a" is modified and deletes it
1398 		git.checkout().setCreateBranch(true).setStartPoint(first)
1399 				.setName("merge-both-sides").call();
1400 		git.rm().addFilepattern("a").call();
1401 		RevCommit commitX = git.commit().setMessage("Delete original a").call();
1402 
1403 		// second branch
1404 		git.checkout().setCreateBranch(true).setStartPoint(commitY)
1405 				.setName("second-branch").call();
1406 		git.rm().addFilepattern("a").call();
1407 		git.commit().setMessage("Delete modified a").call();
1408 
1409 		// Merge merge-both-sides into second-branch
1410 		MergeResult mergeResult = git.merge().include(commitX)
1411 				.setStrategy(strategy)
1412 				.call();
1413 		ObjectId commitB = mergeResult.getNewHead();
1414 
1415 		// Merge merge-both-sides into master
1416 		git.checkout().setName("master").call();
1417 		mergeResult = git.merge().include(commitX).setStrategy(strategy)
1418 				.call();
1419 
1420 		// Now, merge commit A and B (i.e. "master" and "second-branch").
1421 		// None of them have the file "a", so there is no conflict, BUT while
1422 		// building the virtual ancestor it will find a conflict between Y and X
1423 		git.merge().include(commitB).call();
1424 	}
1425 
1426 	/**
1427 	 * Merging two commits with a file/dir conflict in the virtual ancestor.
1428 	 *
1429 	 * <p>
1430 	 * Those conflicts should be ignored, otherwise the found base can not be used by the
1431 	 * RecursiveMerger.
1432 	 * <pre>
1433 	 *  --------------
1434 	 * |              \
1435 	 * |         C1 - C4 --- ?     master
1436 	 * |        /          /
1437 	 * |  I - A1 - C2 - C3         second-branch
1438 	 * |   \            /
1439 	 * \    \          /
1440 	 *  ----A2--------             branch-to-merge
1441 	 *  </pre>
1442 	 * <p>
1443 	 * <p>
1444 	 * Path "a" is initially a file in I and A1. It is changed to a directory in A2
1445 	 * ("branch-to-merge").
1446 	 * <p>
1447 	 * A2 is merged into "master" and "second-branch". The dir/file merge conflict is resolved
1448 	 * manually, results in C4 and C3.
1449 	 * <p>
1450 	 * While merging C3 and C4, A1 and A2 are the base commits found by the recursive merge that
1451 	 * have the dir/file conflict.
1452 	 */
1453 	@Theory
1454 	public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren(
1455 			MergeStrategy strategy)
1456 			throws Exception {
1457 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1458 			return;
1459 		}
1460 
1461 		Git git = Git.wrap(db);
1462 
1463 		// master
1464 		writeTrashFile("a", "initial content");
1465 		git.add().addFilepattern("a").call();
1466 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1467 
1468 		writeTrashFile("a", "content in Ancestor 1");
1469 		git.add().addFilepattern("a").call();
1470 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1471 
1472 		writeTrashFile("a", "content in Child 1 (commited on master)");
1473 		git.add().addFilepattern("a").call();
1474 		// commit C1M
1475 		git.commit().setMessage("Child 1 on master").call();
1476 
1477 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1478 		// "a" becomes a directory in A2
1479 		git.rm().addFilepattern("a").call();
1480 		writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1481 		git.add().addFilepattern("a/content").call();
1482 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1483 
1484 		// second branch
1485 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1486 		writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1487 		git.add().addFilepattern("a").call();
1488 		// commit C2S
1489 		git.commit().setMessage("Child 2 on second-branch").call();
1490 
1491 		// Merge branch-to-merge into second-branch
1492 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1493 		assertEquals(mergeResult.getNewHead(), null);
1494 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1495 		// Resolve the conflict manually, merge "a" as a file
1496 		git.rm().addFilepattern("a").call();
1497 		git.rm().addFilepattern("a/content").call();
1498 		writeTrashFile("a", "merge conflict resolution");
1499 		git.add().addFilepattern("a").call();
1500 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1501 				.call();
1502 
1503 		// Merge branch-to-merge into master
1504 		git.checkout().setName("master").call();
1505 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1506 		assertEquals(mergeResult.getNewHead(), null);
1507 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1508 
1509 		// Resolve the conflict manually - merge "a" as a file
1510 		git.rm().addFilepattern("a").call();
1511 		git.rm().addFilepattern("a/content").call();
1512 		writeTrashFile("a", "merge conflict resolution");
1513 		git.add().addFilepattern("a").call();
1514 		// commit C4M
1515 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1516 
1517 		// Merge C4M (second-branch) into master (C3S)
1518 		// Conflict in virtual base should be here, but there are no conflicts in
1519 		// children
1520 		mergeResult = git.merge().include(commitC3S).call();
1521 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1522 
1523 	}
1524 
1525 	@Theory
1526 	public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy)
1527 			throws Exception {
1528 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1529 			return;
1530 		}
1531 
1532 		Git git = Git.wrap(db);
1533 
1534 		// master
1535 		writeTrashFile("a", "initial content");
1536 		git.add().addFilepattern("a").call();
1537 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1538 
1539 		writeTrashFile("a", "content in Ancestor 1");
1540 		git.add().addFilepattern("a").call();
1541 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1542 
1543 		writeTrashFile("a", "content in Child 1 (commited on master)");
1544 		git.add().addFilepattern("a").call();
1545 		// commit C1M
1546 		git.commit().setMessage("Child 1 on master").call();
1547 
1548 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1549 
1550 		// "a" becomes a directory in A2
1551 		git.rm().addFilepattern("a").call();
1552 		writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1553 		git.add().addFilepattern("a/content").call();
1554 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1555 
1556 		// second branch
1557 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1558 		writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1559 		git.add().addFilepattern("a").call();
1560 		// commit C2S
1561 		git.commit().setMessage("Child 2 on second-branch").call();
1562 
1563 		// Merge branch-to-merge into second-branch
1564 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1565 		assertEquals(mergeResult.getNewHead(), null);
1566 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1567 		// Resolve the conflict manually - write a file
1568 		git.rm().addFilepattern("a").call();
1569 		git.rm().addFilepattern("a/content").call();
1570 		writeTrashFile("a",
1571 				"content in Child 3 (commited on second-branch) - merge conflict resolution");
1572 		git.add().addFilepattern("a").call();
1573 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1574 				.call();
1575 
1576 		// Merge branch-to-merge into master
1577 		git.checkout().setName("master").call();
1578 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1579 		assertEquals(mergeResult.getNewHead(), null);
1580 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1581 
1582 		// Resolve the conflict manually - write a file
1583 		git.rm().addFilepattern("a").call();
1584 		git.rm().addFilepattern("a/content").call();
1585 		writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1586 		git.add().addFilepattern("a").call();
1587 		// commit C4M
1588 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1589 
1590 		// Merge C4M (second-branch) into master (C3S)
1591 		// Conflict in virtual base should be here
1592 		mergeResult = git.merge().include(commitC3S).call();
1593 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1594 		String expected =
1595 				"<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1596 						+ "=======\n"
1597 						+ "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1598 						+ ">>>>>>> " + commitC3S.name() + "\n";
1599 		assertEquals(expected, read("a"));
1600 		// Nothing was populated from the ancestors.
1601 		assertEquals(
1602 				"[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
1603 				indexState(CONTENT));
1604 	}
1605 
1606 	/**
1607 	 * Same test as above, but "a" is a dir in A1 and a file in A2
1608 	 */
1609 	@Theory
1610 	public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy)
1611 			throws Exception {
1612 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1613 			return;
1614 		}
1615 
1616 		Git git = Git.wrap(db);
1617 
1618 		// master
1619 		writeTrashFile("a/content", "initial content");
1620 		git.add().addFilepattern("a/content").call();
1621 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1622 
1623 		writeTrashFile("a/content", "content in Ancestor 1");
1624 		git.add().addFilepattern("a/content").call();
1625 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1626 
1627 		writeTrashFile("a/content", "content in Child 1 (commited on master)");
1628 		git.add().addFilepattern("a/content").call();
1629 		// commit C1M
1630 		git.commit().setMessage("Child 1 on master").call();
1631 
1632 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1633 
1634 		// "a" becomes a file in A2
1635 		git.rm().addFilepattern("a/content").call();
1636 		writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)");
1637 		git.add().addFilepattern("a").call();
1638 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1639 
1640 		// second branch
1641 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1642 		writeTrashFile("a/content", "content in Child 2 (commited on second-branch)");
1643 		git.add().addFilepattern("a/content").call();
1644 		// commit C2S
1645 		git.commit().setMessage("Child 2 on second-branch").call();
1646 
1647 		// Merge branch-to-merge into second-branch
1648 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1649 		assertEquals(mergeResult.getNewHead(), null);
1650 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1651 		// Resolve the conflict manually - write a file
1652 		git.rm().addFilepattern("a").call();
1653 		git.rm().addFilepattern("a/content").call();
1654 		deleteTrashFile("a/content");
1655 		deleteTrashFile("a");
1656 		writeTrashFile("a", "content in Child 3 (commited on second-branch) - merge conflict resolution");
1657 		git.add().addFilepattern("a").call();
1658 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1659 
1660 		// Merge branch-to-merge into master
1661 		git.checkout().setName("master").call();
1662 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1663 		assertEquals(mergeResult.getNewHead(), null);
1664 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1665 
1666 		// Resolve the conflict manually - write a file
1667 		git.rm().addFilepattern("a").call();
1668 		git.rm().addFilepattern("a/content").call();
1669 		deleteTrashFile("a/content");
1670 		deleteTrashFile("a");
1671 		writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1672 		git.add().addFilepattern("a").call();
1673 		// commit C4M
1674 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1675 
1676 		// Merge C4M (second-branch) into master (C3S)
1677 		// Conflict in virtual base should be here
1678 		mergeResult = git.merge().include(commitC3S).call();
1679 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1680 		String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1681 				+ "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1682 				+ ">>>>>>> " + commitC3S.name() + "\n";
1683 		assertEquals(expected, read("a"));
1684 		// Nothing was populated from the ancestors.
1685 		assertEquals(
1686 				"[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
1687 				indexState(CONTENT));
1688 	}
1689 
1690 	/**
1691 	 * Merging two commits when files have equal content, but conflicting file mode
1692 	 * in the virtual ancestor.
1693 	 *
1694 	 * <p>
1695 	 * This test has the same set up as
1696 	 * {@code checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren}, only
1697 	 * with the mode conflict in A1 and A2.
1698 	 */
1699 	@Theory
1700 	public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
1701 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1702 			return;
1703 		}
1704 
1705 		Git git = Git.wrap(db);
1706 
1707 		// master
1708 		writeTrashFile("c", "initial file");
1709 		git.add().addFilepattern("c").call();
1710 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1711 
1712 		File a = writeTrashFile("a", "content in Ancestor");
1713 		git.add().addFilepattern("a").call();
1714 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1715 
1716 		a = writeTrashFile("a", "content in Child 1 (commited on master)");
1717 		git.add().addFilepattern("a").call();
1718 		// commit C1M
1719 		git.commit().setMessage("Child 1 on master").call();
1720 
1721 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1722 		// "a" becomes executable in A2
1723 		a = writeTrashFile("a", "content in Ancestor");
1724 		a.setExecutable(true);
1725 		git.add().addFilepattern("a").call();
1726 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1727 
1728 		// second branch
1729 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1730 		a = writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1731 		git.add().addFilepattern("a").call();
1732 		// commit C2S
1733 		git.commit().setMessage("Child 2 on second-branch").call();
1734 
1735 		// Merge branch-to-merge into second-branch
1736 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1737 		assertEquals(mergeResult.getNewHead(), null);
1738 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1739 		// Resolve the conflict manually, merge "a" as non-executable
1740 		a = writeTrashFile("a", "merge conflict resolution");
1741 		a.setExecutable(false);
1742 		git.add().addFilepattern("a").call();
1743 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1744 
1745 		// Merge branch-to-merge into master
1746 		git.checkout().setName("master").call();
1747 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1748 		assertEquals(mergeResult.getNewHead(), null);
1749 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1750 
1751 		// Resolve the conflict manually - merge "a" as non-executable
1752 		a = writeTrashFile("a", "merge conflict resolution");
1753 		a.setExecutable(false);
1754 		git.add().addFilepattern("a").call();
1755 		// commit C4M
1756 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1757 
1758 		// Merge C4M (second-branch) into master (C3S)
1759 		// Conflict in virtual base should be here, but there are no conflicts in
1760 		// children
1761 		mergeResult = git.merge().include(commitC3S).call();
1762 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1763 
1764 	}
1765 
1766 	private void writeSubmodule(String path, ObjectId commit)
1767 			throws IOException, ConfigInvalidException {
1768 		addSubmoduleToIndex(path, commit);
1769 		new File(db.getWorkTree(), path).mkdir();
1770 
1771 		StoredConfig config = db.getConfig();
1772 		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1773 				ConfigConstants.CONFIG_KEY_URL,
1774 				db.getDirectory().toURI().toString());
1775 		config.save();
1776 
1777 		FileBasedConfig modulesConfig = new FileBasedConfig(
1778 				new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1779 				db.getFS());
1780 		modulesConfig.load();
1781 		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1782 				ConfigConstants.CONFIG_KEY_PATH, path);
1783 		modulesConfig.save();
1784 
1785 	}
1786 
1787 	private void addSubmoduleToIndex(String path, ObjectId commit)
1788 			throws IOException {
1789 		DirCache cache = db.lockDirCache();
1790 		DirCacheEditor editor = cache.editor();
1791 		editor.add(new DirCacheEditor.PathEdit(path) {
1792 
1793 			@Override
1794 			public void apply(DirCacheEntry ent) {
1795 				ent.setFileMode(FileMode.GITLINK);
1796 				ent.setObjectId(commit);
1797 			}
1798 		});
1799 		editor.commit();
1800 	}
1801 
1802 	// Assert that every specified index entry has the same last modification
1803 	// timestamp as the associated file
1804 	private void checkConsistentLastModified(String... pathes)
1805 			throws IOException {
1806 		DirCache dc = db.readDirCache();
1807 		File workTree = db.getWorkTree();
1808 		for (String path : pathes)
1809 			assertEquals(
1810 					"IndexEntry with path "
1811 							+ path
1812 							+ " has lastmodified which is different from the worktree file",
1813 					FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
1814 					dc.getEntry(path)
1815 							.getLastModifiedInstant());
1816 	}
1817 
1818 	// Assert that modification timestamps of working tree files are as
1819 	// expected. You may specify n files. It is asserted that every file
1820 	// i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
1821 	// then this file must be younger then file i. A path "*<modtime>"
1822 	// represents a file with a modification time of <modtime>
1823 	// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
1824 	private void checkModificationTimeStampOrder(String... pathes) {
1825 		Instant lastMod = EPOCH;
1826 		for (String p : pathes) {
1827 			boolean strong = p.startsWith("<");
1828 			boolean fixed = p.charAt(strong ? 1 : 0) == '*';
1829 			p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
1830 			Instant curMod = fixed ? Instant.parse(p)
1831 					: FS.DETECTED
1832 							.lastModifiedInstant(new File(db.getWorkTree(), p));
1833 			if (strong) {
1834 				assertTrue("path " + p + " is not younger than predecesssor",
1835 						curMod.compareTo(lastMod) > 0);
1836 			} else {
1837 				assertTrue("path " + p + " is older than predecesssor",
1838 						curMod.compareTo(lastMod) >= 0);
1839 			}
1840 		}
1841 	}
1842 
1843 	private String readBlob(ObjectId treeish, String path) throws Exception {
1844 		try (TestRepository<?> tr = new TestRepository<>(db);
1845 				RevWalk rw = tr.getRevWalk()) {
1846 			db.incrementOpen();
1847 			RevTree tree = rw.parseTree(treeish);
1848 			RevObject obj = tr.get(tree, path);
1849 			if (obj == null) {
1850 				return null;
1851 			}
1852 			return new String(
1853 					rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
1854 		}
1855 	}
1856 }