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 	 * This is a high-level test for https://bugs.eclipse.org/bugs/show_bug.cgi?id=535919
1040 	 *
1041 	 * The actual fix was made in {@link org.eclipse.jgit.treewalk.NameConflictTreeWalk}
1042 	 * and tested in {@link org.eclipse.jgit.treewalk.NameConflictTreeWalkTest#tesdDF_LastItemsInTreeHasDFConflictAndSpecialNames}.
1043 	 */
1044 	@Theory
1045 	public void checkMergeDoesntCrashWithSpecialFileNames(
1046 			MergeStrategy strategy) throws Exception {
1047 		Git git = Git.wrap(db);
1048 
1049 		writeTrashFile("subtree", "");
1050 		writeTrashFile("subtree-0", "");
1051 		git.add().addFilepattern("subtree").call();
1052 		git.add().addFilepattern("subtree-0").call();
1053 		RevCommit toMerge = git.commit().setMessage("commit-1").call();
1054 
1055 		git.rm().addFilepattern("subtree").call();
1056 		writeTrashFile("subtree/file", "");
1057 		git.add().addFilepattern("subtree").call();
1058 		RevCommit mergeTip = git.commit().setMessage("commit2").call();
1059 
1060 		ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
1061 		assertTrue(merger.merge(mergeTip, toMerge));
1062 	}
1063 
1064 	/**
1065 	 * Merging after criss-cross merges. In this case we merge together two
1066 	 * commits which have two equally good common ancestors
1067 	 *
1068 	 * @param strategy
1069 	 * @throws Exception
1070 	 */
1071 	@Theory
1072 	public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
1073 		Git git = Git.wrap(db);
1074 
1075 		writeTrashFile("1", "1\n2\n3");
1076 		git.add().addFilepattern("1").call();
1077 		RevCommit first = git.commit().setMessage("added 1").call();
1078 
1079 		writeTrashFile("1", "1master\n2\n3");
1080 		RevCommit masterCommit = git.commit().setAll(true)
1081 				.setMessage("modified 1 on master").call();
1082 
1083 		writeTrashFile("1", "1master2\n2\n3");
1084 		git.commit().setAll(true)
1085 				.setMessage("modified 1 on master again").call();
1086 
1087 		git.checkout().setCreateBranch(true).setStartPoint(first)
1088 				.setName("side").call();
1089 		writeTrashFile("1", "1\n2\na\nb\nc\n3side");
1090 		RevCommit sideCommit = git.commit().setAll(true)
1091 				.setMessage("modified 1 on side").call();
1092 
1093 		writeTrashFile("1", "1\n2\n3side2");
1094 		git.commit().setAll(true)
1095 				.setMessage("modified 1 on side again").call();
1096 
1097 		MergeResult result = git.merge().setStrategy(strategy)
1098 				.include(masterCommit).call();
1099 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1100 		result.getNewHead();
1101 		git.checkout().setName("master").call();
1102 		result = git.merge().setStrategy(strategy).include(sideCommit).call();
1103 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1104 
1105 		// we have two branches which are criss-cross merged. Try to merge the
1106 		// tips. This should succeed with RecursiveMerge and fail with
1107 		// ResolveMerge
1108 		try {
1109 			MergeResult mergeResult = git.merge().setStrategy(strategy)
1110 					.include(git.getRepository().exactRef("refs/heads/side"))
1111 					.call();
1112 			assertEquals(MergeStrategy.RECURSIVE, strategy);
1113 			assertEquals(MergeResult.MergeStatus.MERGED,
1114 					mergeResult.getMergeStatus());
1115 			assertEquals("1master2\n2\n3side2", read("1"));
1116 		} catch (JGitInternalException e) {
1117 			assertEquals(MergeStrategy.RESOLVE, strategy);
1118 			assertTrue(e.getCause() instanceof NoMergeBaseException);
1119 			assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1120 					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1121 		}
1122 	}
1123 
1124 	@Theory
1125 	public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1126 			throws Exception {
1127 		Git git = Git.wrap(db);
1128 
1129 		writeTrashFile("a.txt", "orig");
1130 		writeTrashFile("b.txt", "orig");
1131 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1132 		RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1133 
1134 		// modify and delete files on the master branch
1135 		writeTrashFile("a.txt", "master");
1136 		git.rm().addFilepattern("b.txt").call();
1137 		RevCommit masterCommit = git.commit()
1138 				.setMessage("modified a.txt, deleted b.txt").setAll(true)
1139 				.call();
1140 
1141 		// switch back to a side branch
1142 		git.checkout().setCreateBranch(true).setStartPoint(first)
1143 				.setName("side").call();
1144 		writeTrashFile("c.txt", "side");
1145 		git.add().addFilepattern("c.txt").call();
1146 		git.commit().setMessage("added c.txt").call();
1147 
1148 		// Get a handle to the file so on windows it can't be deleted.
1149 		try (FileInputStream fis = new FileInputStream(
1150 				new File(db.getWorkTree(), "b.txt"))) {
1151 			MergeResult mergeRes = git.merge().setStrategy(strategy)
1152 					.include(masterCommit).call();
1153 			if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1154 				// probably windows
1155 				assertEquals(1, mergeRes.getFailingPaths().size());
1156 				assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1157 						mergeRes.getFailingPaths().get("b.txt"));
1158 			}
1159 			assertEquals(
1160 					"[a.txt, mode:100644, content:master]"
1161 							+ "[c.txt, mode:100644, content:side]",
1162 					indexState(CONTENT));
1163 		}
1164 	}
1165 
1166 	@Theory
1167 	public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
1168 		File f;
1169 		Instant lastTs4, lastTsIndex;
1170 		Git git = Git.wrap(db);
1171 		File indexFile = db.getIndexFile();
1172 
1173 		// Create initial content and remember when the last file was written.
1174 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1175 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1176 
1177 		// add all files, commit and check this doesn't update any working tree
1178 		// files and that the index is in a new file system timer tick. Make
1179 		// sure to wait long enough before adding so the index doesn't contain
1180 		// racily clean entries
1181 		fsTick(f);
1182 		git.add().addFilepattern(".").call();
1183 		RevCommit firstCommit = git.commit().setMessage("initial commit")
1184 				.call();
1185 		checkConsistentLastModified("0", "1", "2", "3", "4");
1186 		checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
1187 		assertEquals("Commit should not touch working tree file 4", lastTs4,
1188 				FS.DETECTED
1189 						.lastModifiedInstant(new File(db.getWorkTree(), "4")));
1190 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1191 
1192 		// Do modifications on the master branch. Then add and commit. This
1193 		// should touch only "0", "2 and "3"
1194 		fsTick(indexFile);
1195 		f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
1196 				null);
1197 		fsTick(f);
1198 		git.add().addFilepattern(".").call();
1199 		RevCommit masterCommit = git.commit().setMessage("master commit")
1200 				.call();
1201 		checkConsistentLastModified("0", "1", "2", "3", "4");
1202 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1203 				+ lastTsIndex, "<0", "2", "3", "<.git/index");
1204 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1205 
1206 		// Checkout a side branch. This should touch only "0", "2 and "3"
1207 		fsTick(indexFile);
1208 		git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
1209 				.setName("side").call();
1210 		checkConsistentLastModified("0", "1", "2", "3", "4");
1211 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1212 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1213 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1214 
1215 		// This checkout may have populated worktree and index so fast that we
1216 		// may have smudged entries now. Check that we have the right content
1217 		// and then rewrite the index to get rid of smudged state
1218 		assertEquals("[0, mode:100644, content:orig]" //
1219 				+ "[1, mode:100644, content:orig]" //
1220 				+ "[2, mode:100644, content:1\n2\n3]" //
1221 				+ "[3, mode:100644, content:orig]" //
1222 				+ "[4, mode:100644, content:orig]", //
1223 				indexState(CONTENT));
1224 		fsTick(indexFile);
1225 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1226 		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1227 		fsTick(f);
1228 		git.add().addFilepattern(".").call();
1229 		checkConsistentLastModified("0", "1", "2", "3", "4");
1230 		checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
1231 				"4", "<.git/index");
1232 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1233 
1234 		// Do modifications on the side branch. Touch only "1", "2 and "3"
1235 		fsTick(indexFile);
1236 		f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
1237 		fsTick(f);
1238 		git.add().addFilepattern(".").call();
1239 		git.commit().setMessage("side commit").call();
1240 		checkConsistentLastModified("0", "1", "2", "3", "4");
1241 		checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
1242 				+ lastTsIndex, "<1", "2", "3", "<.git/index");
1243 		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1244 
1245 		// merge master and side. Should only touch "0," "2" and "3"
1246 		fsTick(indexFile);
1247 		git.merge().setStrategy(strategy).include(masterCommit).call();
1248 		checkConsistentLastModified("0", "1", "2", "4");
1249 		checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
1250 				+ lastTsIndex, "<0", "2", "3", ".git/index");
1251 		assertEquals(
1252 				"[0, mode:100644, content:master]" //
1253 						+ "[1, mode:100644, content:side]" //
1254 						+ "[2, mode:100644, content:1master\n2\n3side]" //
1255 						+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
1256 						+ "[4, mode:100644, content:orig]", //
1257 				indexState(CONTENT));
1258 	}
1259 
1260 	/**
1261 	 * Merging two conflicting submodules when the index does not contain any
1262 	 * entry for that submodule.
1263 	 *
1264 	 * @param strategy
1265 	 * @throws Exception
1266 	 */
1267 	@Theory
1268 	public void checkMergeConflictingSubmodulesWithoutIndex(
1269 			MergeStrategy strategy) throws Exception {
1270 		Git git = Git.wrap(db);
1271 		writeTrashFile("initial", "initial");
1272 		git.add().addFilepattern("initial").call();
1273 		RevCommit initial = git.commit().setMessage("initial").call();
1274 
1275 		writeSubmodule("one", ObjectId
1276 				.fromString("1000000000000000000000000000000000000000"));
1277 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1278 		RevCommit right = git.commit().setMessage("added one").call();
1279 
1280 		// a second commit in the submodule
1281 
1282 		git.checkout().setStartPoint(initial).setName("left")
1283 				.setCreateBranch(true).call();
1284 		writeSubmodule("one", ObjectId
1285 				.fromString("2000000000000000000000000000000000000000"));
1286 
1287 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1288 		git.commit().setMessage("a different one").call();
1289 
1290 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1291 				.call();
1292 
1293 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
1294 		Map<String, int[][]> conflicts = result.getConflicts();
1295 		assertEquals(1, conflicts.size());
1296 		assertNotNull(conflicts.get("one"));
1297 	}
1298 
1299 	/**
1300 	 * Merging two non-conflicting submodules when the index does not contain
1301 	 * any entry for either submodule.
1302 	 *
1303 	 * @param strategy
1304 	 * @throws Exception
1305 	 */
1306 	@Theory
1307 	public void checkMergeNonConflictingSubmodulesWithoutIndex(
1308 			MergeStrategy strategy) throws Exception {
1309 		Git git = Git.wrap(db);
1310 		writeTrashFile("initial", "initial");
1311 		git.add().addFilepattern("initial").call();
1312 
1313 		writeSubmodule("one", ObjectId
1314 				.fromString("1000000000000000000000000000000000000000"));
1315 
1316 		// Our initial commit should include a .gitmodules with a bunch of
1317 		// comment lines, so that
1318 		// we don't have a content merge issue when we add a new submodule at
1319 		// the top and a different
1320 		// one at the bottom. This is sort of a hack, but it should allow
1321 		// add/add submodule merges
1322 		String existing = read(Constants.DOT_GIT_MODULES);
1323 		String context = "\n# context\n# more context\n# yet more context\n";
1324 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1325 				existing + context + context + context);
1326 
1327 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1328 		RevCommit initial = git.commit().setMessage("initial").call();
1329 
1330 		writeSubmodule("two", ObjectId
1331 				.fromString("1000000000000000000000000000000000000000"));
1332 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1333 
1334 		RevCommit right = git.commit().setMessage("added two").call();
1335 
1336 		git.checkout().setStartPoint(initial).setName("left")
1337 				.setCreateBranch(true).call();
1338 
1339 		// we need to manually create the submodule for three for the
1340 		// .gitmodules hackery
1341 		addSubmoduleToIndex("three", ObjectId
1342 				.fromString("1000000000000000000000000000000000000000"));
1343 		new File(db.getWorkTree(), "three").mkdir();
1344 
1345 		existing = read(Constants.DOT_GIT_MODULES);
1346 		String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
1347 				+ db.getDirectory().toURI() + "\n";
1348 		write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1349 				three + existing);
1350 
1351 		git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1352 		git.commit().setMessage("a different one").call();
1353 
1354 		MergeResult result = git.merge().setStrategy(strategy).include(right)
1355 				.call();
1356 
1357 		assertNull(result.getCheckoutConflicts());
1358 		assertNull(result.getFailingPaths());
1359 		for (String dir : Arrays.asList("one", "two", "three")) {
1360 			assertTrue(new File(db.getWorkTree(), dir).isDirectory());
1361 		}
1362 	}
1363 
1364 	/**
1365 	 * Merging two commits with a conflict in the virtual ancestor.
1366 	 *
1367 	 * Content conflicts while merging the virtual ancestor must be ignored.
1368 	 *
1369 	 * In the following tree, while merging A and B, the recursive algorithm
1370 	 * finds as base commits X and Y and tries to merge them: X deletes file "a"
1371 	 * and Y modifies it.
1372 	 *
1373 	 * Note: we delete "a" in (master) and (second-branch) to make avoid manual
1374 	 * merges. The situation is the same without those deletions and fixing
1375 	 * manually the merge of (merge-both-sides) on both branches.
1376 	 *
1377 	 * <pre>
1378 	 * A  (second-branch) Merge branch 'merge-both-sides' into second-branch
1379 	 * |\
1380 	 * o | Delete modified a
1381 	 * | |
1382 	 * | | B (master) Merge branch 'merge-both-sides' (into master)
1383 	 * | |/|
1384 	 * | X | (merge-both-sides) Delete original a
1385 	 * | | |
1386 	 * | | o Delete modified a
1387 	 * | |/
1388 	 * |/|
1389 	 * Y | Modify a
1390 	 * |/
1391 	 * o Initial commit
1392 	 * </pre>
1393 	 *
1394 	 * @param strategy
1395 	 * @throws Exception
1396 	 */
1397 	@Theory
1398 	public void checkMergeConflictInVirtualAncestor(
1399 			MergeStrategy strategy) throws Exception {
1400 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1401 			return;
1402 		}
1403 
1404 		Git git = Git.wrap(db);
1405 
1406 		// master
1407 		writeTrashFile("a", "aaaaaaaa");
1408 		writeTrashFile("b", "bbbbbbbb");
1409 		git.add().addFilepattern("a").addFilepattern("b").call();
1410 		RevCommit first = git.commit().setMessage("Initial commit").call();
1411 
1412 		writeTrashFile("a", "aaaaaaaaaaaaaaa");
1413 		git.add().addFilepattern("a").call();
1414 		RevCommit commitY = git.commit().setMessage("Modify a").call();
1415 
1416 		git.rm().addFilepattern("a").call();
1417 		// Do more in this commits, so it is not identical to the deletion in
1418 		// second-branch
1419 		writeTrashFile("c", "cccccccc");
1420 		git.add().addFilepattern("c").call();
1421 		git.commit().setMessage("Delete modified a").call();
1422 
1423 		// merge-both-sides: starts before "a" is modified and deletes it
1424 		git.checkout().setCreateBranch(true).setStartPoint(first)
1425 				.setName("merge-both-sides").call();
1426 		git.rm().addFilepattern("a").call();
1427 		RevCommit commitX = git.commit().setMessage("Delete original a").call();
1428 
1429 		// second branch
1430 		git.checkout().setCreateBranch(true).setStartPoint(commitY)
1431 				.setName("second-branch").call();
1432 		git.rm().addFilepattern("a").call();
1433 		git.commit().setMessage("Delete modified a").call();
1434 
1435 		// Merge merge-both-sides into second-branch
1436 		MergeResult mergeResult = git.merge().include(commitX)
1437 				.setStrategy(strategy)
1438 				.call();
1439 		ObjectId commitB = mergeResult.getNewHead();
1440 
1441 		// Merge merge-both-sides into master
1442 		git.checkout().setName("master").call();
1443 		mergeResult = git.merge().include(commitX).setStrategy(strategy)
1444 				.call();
1445 
1446 		// Now, merge commit A and B (i.e. "master" and "second-branch").
1447 		// None of them have the file "a", so there is no conflict, BUT while
1448 		// building the virtual ancestor it will find a conflict between Y and X
1449 		git.merge().include(commitB).call();
1450 	}
1451 
1452 	/**
1453 	 * Merging two commits with a file/dir conflict in the virtual ancestor.
1454 	 *
1455 	 * <p>
1456 	 * Those conflicts should be ignored, otherwise the found base can not be used by the
1457 	 * RecursiveMerger.
1458 	 * <pre>
1459 	 *  --------------
1460 	 * |              \
1461 	 * |         C1 - C4 --- ?     master
1462 	 * |        /          /
1463 	 * |  I - A1 - C2 - C3         second-branch
1464 	 * |   \            /
1465 	 * \    \          /
1466 	 *  ----A2--------             branch-to-merge
1467 	 *  </pre>
1468 	 * <p>
1469 	 * <p>
1470 	 * Path "a" is initially a file in I and A1. It is changed to a directory in A2
1471 	 * ("branch-to-merge").
1472 	 * <p>
1473 	 * A2 is merged into "master" and "second-branch". The dir/file merge conflict is resolved
1474 	 * manually, results in C4 and C3.
1475 	 * <p>
1476 	 * While merging C3 and C4, A1 and A2 are the base commits found by the recursive merge that
1477 	 * have the dir/file conflict.
1478 	 */
1479 	@Theory
1480 	public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren(
1481 			MergeStrategy strategy)
1482 			throws Exception {
1483 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1484 			return;
1485 		}
1486 
1487 		Git git = Git.wrap(db);
1488 
1489 		// master
1490 		writeTrashFile("a", "initial content");
1491 		git.add().addFilepattern("a").call();
1492 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1493 
1494 		writeTrashFile("a", "content in Ancestor 1");
1495 		git.add().addFilepattern("a").call();
1496 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1497 
1498 		writeTrashFile("a", "content in Child 1 (commited on master)");
1499 		git.add().addFilepattern("a").call();
1500 		// commit C1M
1501 		git.commit().setMessage("Child 1 on master").call();
1502 
1503 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1504 		// "a" becomes a directory in A2
1505 		git.rm().addFilepattern("a").call();
1506 		writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1507 		git.add().addFilepattern("a/content").call();
1508 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1509 
1510 		// second branch
1511 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1512 		writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1513 		git.add().addFilepattern("a").call();
1514 		// commit C2S
1515 		git.commit().setMessage("Child 2 on second-branch").call();
1516 
1517 		// Merge branch-to-merge into second-branch
1518 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1519 		assertEquals(mergeResult.getNewHead(), null);
1520 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1521 		// Resolve the conflict manually, merge "a" as a file
1522 		git.rm().addFilepattern("a").call();
1523 		git.rm().addFilepattern("a/content").call();
1524 		writeTrashFile("a", "merge conflict resolution");
1525 		git.add().addFilepattern("a").call();
1526 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1527 				.call();
1528 
1529 		// Merge branch-to-merge into master
1530 		git.checkout().setName("master").call();
1531 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1532 		assertEquals(mergeResult.getNewHead(), null);
1533 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1534 
1535 		// Resolve the conflict manually - merge "a" as a file
1536 		git.rm().addFilepattern("a").call();
1537 		git.rm().addFilepattern("a/content").call();
1538 		writeTrashFile("a", "merge conflict resolution");
1539 		git.add().addFilepattern("a").call();
1540 		// commit C4M
1541 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1542 
1543 		// Merge C4M (second-branch) into master (C3S)
1544 		// Conflict in virtual base should be here, but there are no conflicts in
1545 		// children
1546 		mergeResult = git.merge().include(commitC3S).call();
1547 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1548 
1549 	}
1550 
1551 	@Theory
1552 	public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy)
1553 			throws Exception {
1554 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1555 			return;
1556 		}
1557 
1558 		Git git = Git.wrap(db);
1559 
1560 		// master
1561 		writeTrashFile("a", "initial content");
1562 		git.add().addFilepattern("a").call();
1563 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1564 
1565 		writeTrashFile("a", "content in Ancestor 1");
1566 		git.add().addFilepattern("a").call();
1567 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1568 
1569 		writeTrashFile("a", "content in Child 1 (commited on master)");
1570 		git.add().addFilepattern("a").call();
1571 		// commit C1M
1572 		git.commit().setMessage("Child 1 on master").call();
1573 
1574 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1575 
1576 		// "a" becomes a directory in A2
1577 		git.rm().addFilepattern("a").call();
1578 		writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1579 		git.add().addFilepattern("a/content").call();
1580 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1581 
1582 		// second branch
1583 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1584 		writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1585 		git.add().addFilepattern("a").call();
1586 		// commit C2S
1587 		git.commit().setMessage("Child 2 on second-branch").call();
1588 
1589 		// Merge branch-to-merge into second-branch
1590 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1591 		assertEquals(mergeResult.getNewHead(), null);
1592 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1593 		// Resolve the conflict manually - write a file
1594 		git.rm().addFilepattern("a").call();
1595 		git.rm().addFilepattern("a/content").call();
1596 		writeTrashFile("a",
1597 				"content in Child 3 (commited on second-branch) - merge conflict resolution");
1598 		git.add().addFilepattern("a").call();
1599 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1600 				.call();
1601 
1602 		// Merge branch-to-merge into master
1603 		git.checkout().setName("master").call();
1604 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1605 		assertEquals(mergeResult.getNewHead(), null);
1606 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1607 
1608 		// Resolve the conflict manually - write a file
1609 		git.rm().addFilepattern("a").call();
1610 		git.rm().addFilepattern("a/content").call();
1611 		writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1612 		git.add().addFilepattern("a").call();
1613 		// commit C4M
1614 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1615 
1616 		// Merge C4M (second-branch) into master (C3S)
1617 		// Conflict in virtual base should be here
1618 		mergeResult = git.merge().include(commitC3S).call();
1619 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1620 		String expected =
1621 				"<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1622 						+ "=======\n"
1623 						+ "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1624 						+ ">>>>>>> " + commitC3S.name() + "\n";
1625 		assertEquals(expected, read("a"));
1626 		// Nothing was populated from the ancestors.
1627 		assertEquals(
1628 				"[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]",
1629 				indexState(CONTENT));
1630 	}
1631 
1632 	/**
1633 	 * Same test as above, but "a" is a dir in A1 and a file in A2
1634 	 */
1635 	@Theory
1636 	public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy)
1637 			throws Exception {
1638 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1639 			return;
1640 		}
1641 
1642 		Git git = Git.wrap(db);
1643 
1644 		// master
1645 		writeTrashFile("a/content", "initial content");
1646 		git.add().addFilepattern("a/content").call();
1647 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1648 
1649 		writeTrashFile("a/content", "content in Ancestor 1");
1650 		git.add().addFilepattern("a/content").call();
1651 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1652 
1653 		writeTrashFile("a/content", "content in Child 1 (commited on master)");
1654 		git.add().addFilepattern("a/content").call();
1655 		// commit C1M
1656 		git.commit().setMessage("Child 1 on master").call();
1657 
1658 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1659 
1660 		// "a" becomes a file in A2
1661 		git.rm().addFilepattern("a/content").call();
1662 		writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)");
1663 		git.add().addFilepattern("a").call();
1664 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1665 
1666 		// second branch
1667 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1668 		writeTrashFile("a/content", "content in Child 2 (commited on second-branch)");
1669 		git.add().addFilepattern("a/content").call();
1670 		// commit C2S
1671 		git.commit().setMessage("Child 2 on second-branch").call();
1672 
1673 		// Merge branch-to-merge into second-branch
1674 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1675 		assertEquals(mergeResult.getNewHead(), null);
1676 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1677 		// Resolve the conflict manually - write a file
1678 		git.rm().addFilepattern("a").call();
1679 		git.rm().addFilepattern("a/content").call();
1680 		deleteTrashFile("a/content");
1681 		deleteTrashFile("a");
1682 		writeTrashFile("a", "content in Child 3 (commited on second-branch) - merge conflict resolution");
1683 		git.add().addFilepattern("a").call();
1684 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1685 
1686 		// Merge branch-to-merge into master
1687 		git.checkout().setName("master").call();
1688 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1689 		assertEquals(mergeResult.getNewHead(), null);
1690 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1691 
1692 		// Resolve the conflict manually - write a file
1693 		git.rm().addFilepattern("a").call();
1694 		git.rm().addFilepattern("a/content").call();
1695 		deleteTrashFile("a/content");
1696 		deleteTrashFile("a");
1697 		writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1698 		git.add().addFilepattern("a").call();
1699 		// commit C4M
1700 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1701 
1702 		// Merge C4M (second-branch) into master (C3S)
1703 		// Conflict in virtual base should be here
1704 		mergeResult = git.merge().include(commitC3S).call();
1705 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1706 		String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1707 				+ "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1708 				+ ">>>>>>> " + commitC3S.name() + "\n";
1709 		assertEquals(expected, read("a"));
1710 		// Nothing was populated from the ancestors.
1711 		assertEquals(
1712 				"[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]",
1713 				indexState(CONTENT));
1714 	}
1715 
1716 	/**
1717 	 * Merging two commits when files have equal content, but conflicting file mode
1718 	 * in the virtual ancestor.
1719 	 *
1720 	 * <p>
1721 	 * This test has the same set up as
1722 	 * {@code checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren}, only
1723 	 * with the mode conflict in A1 and A2.
1724 	 */
1725 	@Theory
1726 	public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
1727 		if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1728 			return;
1729 		}
1730 
1731 		Git git = Git.wrap(db);
1732 
1733 		// master
1734 		writeTrashFile("c", "initial file");
1735 		git.add().addFilepattern("c").call();
1736 		RevCommit commitI = git.commit().setMessage("Initial commit").call();
1737 
1738 		File a = writeTrashFile("a", "content in Ancestor");
1739 		git.add().addFilepattern("a").call();
1740 		RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1741 
1742 		a = writeTrashFile("a", "content in Child 1 (commited on master)");
1743 		git.add().addFilepattern("a").call();
1744 		// commit C1M
1745 		git.commit().setMessage("Child 1 on master").call();
1746 
1747 		git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1748 		// "a" becomes executable in A2
1749 		a = writeTrashFile("a", "content in Ancestor");
1750 		a.setExecutable(true);
1751 		git.add().addFilepattern("a").call();
1752 		RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1753 
1754 		// second branch
1755 		git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1756 		a = writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1757 		git.add().addFilepattern("a").call();
1758 		// commit C2S
1759 		git.commit().setMessage("Child 2 on second-branch").call();
1760 
1761 		// Merge branch-to-merge into second-branch
1762 		MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1763 		assertEquals(mergeResult.getNewHead(), null);
1764 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1765 		// Resolve the conflict manually, merge "a" as non-executable
1766 		a = writeTrashFile("a", "merge conflict resolution");
1767 		a.setExecutable(false);
1768 		git.add().addFilepattern("a").call();
1769 		RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1770 
1771 		// Merge branch-to-merge into master
1772 		git.checkout().setName("master").call();
1773 		mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1774 		assertEquals(mergeResult.getNewHead(), null);
1775 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1776 
1777 		// Resolve the conflict manually - merge "a" as non-executable
1778 		a = writeTrashFile("a", "merge conflict resolution");
1779 		a.setExecutable(false);
1780 		git.add().addFilepattern("a").call();
1781 		// commit C4M
1782 		git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1783 
1784 		// Merge C4M (second-branch) into master (C3S)
1785 		// Conflict in virtual base should be here, but there are no conflicts in
1786 		// children
1787 		mergeResult = git.merge().include(commitC3S).call();
1788 		assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1789 
1790 	}
1791 
1792 	private void writeSubmodule(String path, ObjectId commit)
1793 			throws IOException, ConfigInvalidException {
1794 		addSubmoduleToIndex(path, commit);
1795 		new File(db.getWorkTree(), path).mkdir();
1796 
1797 		StoredConfig config = db.getConfig();
1798 		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1799 				ConfigConstants.CONFIG_KEY_URL,
1800 				db.getDirectory().toURI().toString());
1801 		config.save();
1802 
1803 		FileBasedConfig modulesConfig = new FileBasedConfig(
1804 				new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1805 				db.getFS());
1806 		modulesConfig.load();
1807 		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1808 				ConfigConstants.CONFIG_KEY_PATH, path);
1809 		modulesConfig.save();
1810 
1811 	}
1812 
1813 	private void addSubmoduleToIndex(String path, ObjectId commit)
1814 			throws IOException {
1815 		DirCache cache = db.lockDirCache();
1816 		DirCacheEditor editor = cache.editor();
1817 		editor.add(new DirCacheEditor.PathEdit(path) {
1818 
1819 			@Override
1820 			public void apply(DirCacheEntry ent) {
1821 				ent.setFileMode(FileMode.GITLINK);
1822 				ent.setObjectId(commit);
1823 			}
1824 		});
1825 		editor.commit();
1826 	}
1827 
1828 	// Assert that every specified index entry has the same last modification
1829 	// timestamp as the associated file
1830 	private void checkConsistentLastModified(String... pathes)
1831 			throws IOException {
1832 		DirCache dc = db.readDirCache();
1833 		File workTree = db.getWorkTree();
1834 		for (String path : pathes)
1835 			assertEquals(
1836 					"IndexEntry with path "
1837 							+ path
1838 							+ " has lastmodified which is different from the worktree file",
1839 					FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
1840 					dc.getEntry(path)
1841 							.getLastModifiedInstant());
1842 	}
1843 
1844 	// Assert that modification timestamps of working tree files are as
1845 	// expected. You may specify n files. It is asserted that every file
1846 	// i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
1847 	// then this file must be younger then file i. A path "*<modtime>"
1848 	// represents a file with a modification time of <modtime>
1849 	// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
1850 	private void checkModificationTimeStampOrder(String... pathes) {
1851 		Instant lastMod = EPOCH;
1852 		for (String p : pathes) {
1853 			boolean strong = p.startsWith("<");
1854 			boolean fixed = p.charAt(strong ? 1 : 0) == '*';
1855 			p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
1856 			Instant curMod = fixed ? Instant.parse(p)
1857 					: FS.DETECTED
1858 							.lastModifiedInstant(new File(db.getWorkTree(), p));
1859 			if (strong) {
1860 				assertTrue("path " + p + " is not younger than predecesssor",
1861 						curMod.compareTo(lastMod) > 0);
1862 			} else {
1863 				assertTrue("path " + p + " is older than predecesssor",
1864 						curMod.compareTo(lastMod) >= 0);
1865 			}
1866 		}
1867 	}
1868 
1869 	private String readBlob(ObjectId treeish, String path) throws Exception {
1870 		try (TestRepository<?> tr = new TestRepository<>(db);
1871 				RevWalk rw = tr.getRevWalk()) {
1872 			db.incrementOpen();
1873 			RevTree tree = rw.parseTree(treeish);
1874 			RevObject obj = tr.get(tree, path);
1875 			if (obj == null) {
1876 				return null;
1877 			}
1878 			return new String(
1879 					rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
1880 		}
1881 	}
1882 }