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