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