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 org.eclipse.jgit.lib.Constants.CHARSET;
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.DataPoints;
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 MergerTest extends RepositoryTestCase {
101 
102 	@DataPoints
103 	public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
104 			MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
105 
106 	@Theory
107 	public void failingDeleteOfDirectoryWithUntrackedContent(
108 			MergeStrategy strategy) throws Exception {
109 		File folder1 = new File(db.getWorkTree(), "folder1");
110 		FileUtils.mkdir(folder1);
111 		File file = new File(folder1, "file1.txt");
112 		write(file, "folder1--file1.txt");
113 		file = new File(folder1, "file2.txt");
114 		write(file, "folder1--file2.txt");
115 
116 		try (Git git = new Git(db)) {
117 			git.add().addFilepattern(folder1.getName()).call();
118 			RevCommit base = git.commit().setMessage("adding folder").call();
119 
120 			recursiveDelete(folder1);
121 			git.rm().addFilepattern("folder1/file1.txt")
122 					.addFilepattern("folder1/file2.txt").call();
123 			RevCommit other = git.commit()
124 					.setMessage("removing folders on 'other'").call();
125 
126 			git.checkout().setName(base.name()).call();
127 
128 			file = new File(db.getWorkTree(), "unrelated.txt");
129 			write(file, "unrelated");
130 
131 			git.add().addFilepattern("unrelated.txt").call();
132 			RevCommit head = git.commit().setMessage("Adding another file").call();
133 
134 			// Untracked file to cause failing path for delete() of folder1
135 			// but that's ok.
136 			file = new File(folder1, "file3.txt");
137 			write(file, "folder1--file3.txt");
138 
139 			ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
140 			merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
141 			merger.setWorkingTreeIterator(new FileTreeIterator(db));
142 			boolean ok = merger.merge(head.getId(), other.getId());
143 			assertTrue(ok);
144 			assertTrue(file.exists());
145 		}
146 	}
147 
148 	/**
149 	 * Merging two conflicting subtrees when the index does not contain any file
150 	 * in that subtree should lead to a conflicting state.
151 	 *
152 	 * @param strategy
153 	 * @throws Exception
154 	 */
155 	@Theory
156 	public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
157 			throws Exception {
158 		Git git = Git.wrap(db);
159 
160 		writeTrashFile("d/1", "orig");
161 		git.add().addFilepattern("d/1").call();
162 		RevCommit first = git.commit().setMessage("added d/1").call();
163 
164 		writeTrashFile("d/1", "master");
165 		RevCommit masterCommit = git.commit().setAll(true)
166 				.setMessage("modified d/1 on master").call();
167 
168 		git.checkout().setCreateBranch(true).setStartPoint(first)
169 				.setName("side").call();
170 		writeTrashFile("d/1", "side");
171 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
172 
173 		git.rm().addFilepattern("d/1").call();
174 		git.rm().addFilepattern("d").call();
175 		MergeResult mergeRes = git.merge().setStrategy(strategy)
176 				.include(masterCommit).call();
177 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
178 		assertEquals(
179 				"[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
180 				indexState(CONTENT));
181 	}
182 
183 	/**
184 	 * Merging two different but mergeable subtrees when the index does not
185 	 * contain any file in that subtree should lead to a merged state.
186 	 *
187 	 * @param strategy
188 	 * @throws Exception
189 	 */
190 	@Theory
191 	public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
192 			throws Exception {
193 		Git git = Git.wrap(db);
194 
195 		writeTrashFile("d/1", "1\n2\n3");
196 		git.add().addFilepattern("d/1").call();
197 		RevCommit first = git.commit().setMessage("added d/1").call();
198 
199 		writeTrashFile("d/1", "1master\n2\n3");
200 		RevCommit masterCommit = git.commit().setAll(true)
201 				.setMessage("modified d/1 on master").call();
202 
203 		git.checkout().setCreateBranch(true).setStartPoint(first)
204 				.setName("side").call();
205 		writeTrashFile("d/1", "1\n2\n3side");
206 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
207 
208 		git.rm().addFilepattern("d/1").call();
209 		git.rm().addFilepattern("d").call();
210 		MergeResult mergeRes = git.merge().setStrategy(strategy)
211 				.include(masterCommit).call();
212 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
213 		assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
214 				indexState(CONTENT));
215 	}
216 
217 	/**
218 	 * An existing directory without tracked content should not prevent merging
219 	 * a tree where that directory exists.
220 	 *
221 	 * @param strategy
222 	 * @throws Exception
223 	 */
224 	@Theory
225 	public void checkUntrackedFolderIsNotAConflict(
226 			MergeStrategy strategy) throws Exception {
227 		Git git = Git.wrap(db);
228 
229 		writeTrashFile("d/1", "1");
230 		git.add().addFilepattern("d/1").call();
231 		RevCommit first = git.commit().setMessage("added d/1").call();
232 
233 		writeTrashFile("e/1", "4");
234 		git.add().addFilepattern("e/1").call();
235 		RevCommit masterCommit = git.commit().setMessage("added e/1").call();
236 
237 		git.checkout().setCreateBranch(true).setStartPoint(first)
238 				.setName("side").call();
239 		writeTrashFile("f/1", "5");
240 		git.add().addFilepattern("f/1").call();
241 		git.commit().setAll(true).setMessage("added f/1")
242 				.call();
243 
244 		// Untracked directory e shall not conflict with merged e/1
245 		writeTrashFile("e/2", "d two");
246 
247 		MergeResult mergeRes = git.merge().setStrategy(strategy)
248 				.include(masterCommit).call();
249 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
250 		assertEquals(
251 				"[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
252 				indexState(CONTENT));
253 	}
254 
255 	/**
256 	 * A tracked file is replaced by a folder in THEIRS.
257 	 *
258 	 * @param strategy
259 	 * @throws Exception
260 	 */
261 	@Theory
262 	public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
263 			throws Exception {
264 		Git git = Git.wrap(db);
265 
266 		writeTrashFile("sub", "file");
267 		git.add().addFilepattern("sub").call();
268 		RevCommit first = git.commit().setMessage("initial").call();
269 
270 		git.checkout().setCreateBranch(true).setStartPoint(first)
271 				.setName("side").call();
272 
273 		git.rm().addFilepattern("sub").call();
274 		writeTrashFile("sub/file", "subfile");
275 		git.add().addFilepattern("sub/file").call();
276 		RevCommit masterCommit = git.commit().setMessage("file -> folder")
277 				.call();
278 
279 		git.checkout().setName("master").call();
280 		writeTrashFile("noop", "other");
281 		git.add().addFilepattern("noop").call();
282 		git.commit().setAll(true).setMessage("noop").call();
283 
284 		MergeResult mergeRes = git.merge().setStrategy(strategy)
285 				.include(masterCommit).call();
286 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
287 		assertEquals(
288 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
289 				indexState(CONTENT));
290 	}
291 
292 	/**
293 	 * A tracked file is replaced by a folder in OURS.
294 	 *
295 	 * @param strategy
296 	 * @throws Exception
297 	 */
298 	@Theory
299 	public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
300 			throws Exception {
301 		Git git = Git.wrap(db);
302 
303 		writeTrashFile("sub", "file");
304 		git.add().addFilepattern("sub").call();
305 		RevCommit first = git.commit().setMessage("initial").call();
306 
307 		git.checkout().setCreateBranch(true).setStartPoint(first)
308 				.setName("side").call();
309 		writeTrashFile("noop", "other");
310 		git.add().addFilepattern("noop").call();
311 		RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
312 				.call();
313 
314 		git.checkout().setName("master").call();
315 		git.rm().addFilepattern("sub").call();
316 		writeTrashFile("sub/file", "subfile");
317 		git.add().addFilepattern("sub/file").call();
318 		git.commit().setMessage("file -> folder")
319 				.call();
320 
321 		MergeResult mergeRes = git.merge().setStrategy(strategy)
322 				.include(sideCommit).call();
323 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
324 		assertEquals(
325 				"[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
326 				indexState(CONTENT));
327 	}
328 
329 	/**
330 	 * An existing directory without tracked content should not prevent merging
331 	 * a file with that name.
332 	 *
333 	 * @param strategy
334 	 * @throws Exception
335 	 */
336 	@Theory
337 	public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
338 			MergeStrategy strategy)
339 			throws Exception {
340 		Git git = Git.wrap(db);
341 
342 		writeTrashFile("d/1", "1");
343 		git.add().addFilepattern("d/1").call();
344 		RevCommit first = git.commit().setMessage("added d/1").call();
345 
346 		writeTrashFile("e", "4");
347 		git.add().addFilepattern("e").call();
348 		RevCommit masterCommit = git.commit().setMessage("added e").call();
349 
350 		git.checkout().setCreateBranch(true).setStartPoint(first)
351 				.setName("side").call();
352 		writeTrashFile("f/1", "5");
353 		git.add().addFilepattern("f/1").call();
354 		git.commit().setAll(true).setMessage("added f/1").call();
355 
356 		// Untracked empty directory hierarcy e/1 shall not conflict with merged
357 		// e/1
358 		FileUtils.mkdirs(new File(trash, "e/1"), true);
359 
360 		MergeResult mergeRes = git.merge().setStrategy(strategy)
361 				.include(masterCommit).call();
362 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
363 		assertEquals(
364 				"[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
365 				indexState(CONTENT));
366 	}
367 
368 	@Theory
369 	public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
370 			GitAPIException {
371 		Git git = Git.wrap(db);
372 		db.getConfig().setString("core", null, "autocrlf", "false");
373 		db.getConfig().save();
374 		writeTrashFile("crlf.txt", "some\r\ndata\r\n");
375 		git.add().addFilepattern("crlf.txt").call();
376 		git.commit().setMessage("base").call();
377 
378 		git.branchCreate().setName("brancha").call();
379 
380 		writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
381 		git.add().addFilepattern("crlf.txt").call();
382 		git.commit().setMessage("on master").call();
383 
384 		git.checkout().setName("brancha").call();
385 		writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
386 		git.add().addFilepattern("crlf.txt").call();
387 		git.commit().setMessage("on brancha").call();
388 
389 		db.getConfig().setString("core", null, "autocrlf", "input");
390 		db.getConfig().save();
391 
392 		MergeResult mergeResult = git.merge().setStrategy(strategy)
393 				.include(db.resolve("master"))
394 				.call();
395 		assertEquals(MergeResult.MergeStatus.MERGED,
396 				mergeResult.getMergeStatus());
397 	}
398 
399 	@Theory
400 	public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
401 			throws IOException, GitAPIException {
402 		Git git = Git.wrap(db);
403 		db.getConfig().setString("core", null, "autocrlf", "true");
404 		db.getConfig().save();
405 		writeTrashFile("crlf.txt", "a crlf file\r\n");
406 		git.add().addFilepattern("crlf.txt").call();
407 		git.commit().setMessage("base").call();
408 
409 		git.branchCreate().setName("brancha").call();
410 
411 		writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
412 		git.add().addFilepattern("crlf.txt").call();
413 		git.commit().setMessage("on master").call();
414 
415 		git.checkout().setName("brancha").call();
416 		File testFile = writeTrashFile("crlf.txt",
417 				"a first line\r\na crlf file\r\n");
418 		git.add().addFilepattern("crlf.txt").call();
419 		git.commit().setMessage("on brancha").call();
420 
421 		MergeResult mergeResult = git.merge().setStrategy(strategy)
422 				.include(db.resolve("master")).call();
423 		assertEquals(MergeResult.MergeStatus.MERGED,
424 				mergeResult.getMergeStatus());
425 		checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
426 		assertEquals(
427 				"[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
428 				indexState(CONTENT));
429 	}
430 
431 	/**
432 	 * Merging two equal subtrees when the index does not contain any file in
433 	 * that subtree should lead to a merged state.
434 	 *
435 	 * @param strategy
436 	 * @throws Exception
437 	 */
438 	@Theory
439 	public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
440 			throws Exception {
441 		Git git = Git.wrap(db);
442 
443 		writeTrashFile("d/1", "orig");
444 		git.add().addFilepattern("d/1").call();
445 		RevCommit first = git.commit().setMessage("added d/1").call();
446 
447 		writeTrashFile("d/1", "modified");
448 		RevCommit masterCommit = git.commit().setAll(true)
449 				.setMessage("modified d/1 on master").call();
450 
451 		git.checkout().setCreateBranch(true).setStartPoint(first)
452 				.setName("side").call();
453 		writeTrashFile("d/1", "modified");
454 		git.commit().setAll(true).setMessage("modified d/1 on side").call();
455 
456 		git.rm().addFilepattern("d/1").call();
457 		git.rm().addFilepattern("d").call();
458 		MergeResult mergeRes = git.merge().setStrategy(strategy)
459 				.include(masterCommit).call();
460 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
461 		assertEquals("[d/1, mode:100644, content:modified]",
462 				indexState(CONTENT));
463 	}
464 
465 	/**
466 	 * Merging two equal subtrees with an incore merger should lead to a merged
467 	 * state.
468 	 *
469 	 * @param strategy
470 	 * @throws Exception
471 	 */
472 	@Theory
473 	public void checkMergeEqualTreesInCore(MergeStrategy strategy)
474 			throws Exception {
475 		Git git = Git.wrap(db);
476 
477 		writeTrashFile("d/1", "orig");
478 		git.add().addFilepattern("d/1").call();
479 		RevCommit first = git.commit().setMessage("added d/1").call();
480 
481 		writeTrashFile("d/1", "modified");
482 		RevCommit masterCommit = git.commit().setAll(true)
483 				.setMessage("modified d/1 on master").call();
484 
485 		git.checkout().setCreateBranch(true).setStartPoint(first)
486 				.setName("side").call();
487 		writeTrashFile("d/1", "modified");
488 		RevCommit sideCommit = git.commit().setAll(true)
489 				.setMessage("modified d/1 on side").call();
490 
491 		git.rm().addFilepattern("d/1").call();
492 		git.rm().addFilepattern("d").call();
493 
494 		ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
495 				true);
496 		boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
497 		assertTrue(noProblems);
498 	}
499 
500 	/**
501 	 * Merging two equal subtrees with an incore merger should lead to a merged
502 	 * state, without using a Repository (the 'Gerrit' use case).
503 	 *
504 	 * @param strategy
505 	 * @throws Exception
506 	 */
507 	@Theory
508 	public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
509 			throws Exception {
510 		Git git = Git.wrap(db);
511 
512 		writeTrashFile("d/1", "orig");
513 		git.add().addFilepattern("d/1").call();
514 		RevCommit first = git.commit().setMessage("added d/1").call();
515 
516 		writeTrashFile("d/1", "modified");
517 		RevCommit masterCommit = git.commit().setAll(true)
518 				.setMessage("modified d/1 on master").call();
519 
520 		git.checkout().setCreateBranch(true).setStartPoint(first)
521 				.setName("side").call();
522 		writeTrashFile("d/1", "modified");
523 		RevCommit sideCommit = git.commit().setAll(true)
524 				.setMessage("modified d/1 on side").call();
525 
526 		git.rm().addFilepattern("d/1").call();
527 		git.rm().addFilepattern("d").call();
528 
529 		try (ObjectInserter ins = db.newObjectInserter()) {
530 			ThreeWayMerger resolveMerger =
531 					(ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
532 			boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
533 			assertTrue(noProblems);
534 		}
535 	}
536 
537 	/**
538 	 * Merging two equal subtrees when the index and HEAD does not contain any
539 	 * file in that subtree should lead to a merged state.
540 	 *
541 	 * @param strategy
542 	 * @throws Exception
543 	 */
544 	@Theory
545 	public void checkMergeEqualNewTrees(MergeStrategy strategy)
546 			throws Exception {
547 		Git git = Git.wrap(db);
548 
549 		writeTrashFile("2", "orig");
550 		git.add().addFilepattern("2").call();
551 		RevCommit first = git.commit().setMessage("added 2").call();
552 
553 		writeTrashFile("d/1", "orig");
554 		git.add().addFilepattern("d/1").call();
555 		RevCommit masterCommit = git.commit().setAll(true)
556 				.setMessage("added d/1 on master").call();
557 
558 		git.checkout().setCreateBranch(true).setStartPoint(first)
559 				.setName("side").call();
560 		writeTrashFile("d/1", "orig");
561 		git.add().addFilepattern("d/1").call();
562 		git.commit().setAll(true).setMessage("added d/1 on side").call();
563 
564 		git.rm().addFilepattern("d/1").call();
565 		git.rm().addFilepattern("d").call();
566 		MergeResult mergeRes = git.merge().setStrategy(strategy)
567 				.include(masterCommit).call();
568 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
569 		assertEquals(
570 				"[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
571 				indexState(CONTENT));
572 	}
573 
574 	/**
575 	 * Merging two conflicting subtrees when the index and HEAD does not contain
576 	 * any file in that subtree should lead to a conflicting state.
577 	 *
578 	 * @param strategy
579 	 * @throws Exception
580 	 */
581 	@Theory
582 	public void checkMergeConflictingNewTrees(MergeStrategy strategy)
583 			throws Exception {
584 		Git git = Git.wrap(db);
585 
586 		writeTrashFile("2", "orig");
587 		git.add().addFilepattern("2").call();
588 		RevCommit first = git.commit().setMessage("added 2").call();
589 
590 		writeTrashFile("d/1", "master");
591 		git.add().addFilepattern("d/1").call();
592 		RevCommit masterCommit = git.commit().setAll(true)
593 				.setMessage("added d/1 on master").call();
594 
595 		git.checkout().setCreateBranch(true).setStartPoint(first)
596 				.setName("side").call();
597 		writeTrashFile("d/1", "side");
598 		git.add().addFilepattern("d/1").call();
599 		git.commit().setAll(true).setMessage("added d/1 on side").call();
600 
601 		git.rm().addFilepattern("d/1").call();
602 		git.rm().addFilepattern("d").call();
603 		MergeResult mergeRes = git.merge().setStrategy(strategy)
604 				.include(masterCommit).call();
605 		assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
606 		assertEquals(
607 				"[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
608 				indexState(CONTENT));
609 	}
610 
611 	/**
612 	 * Merging two conflicting files when the index contains a tree for that
613 	 * path should lead to a failed state.
614 	 *
615 	 * @param strategy
616 	 * @throws Exception
617 	 */
618 	@Theory
619 	public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
620 			throws Exception {
621 		Git git = Git.wrap(db);
622 
623 		writeTrashFile("0", "orig");
624 		git.add().addFilepattern("0").call();
625 		RevCommit first = git.commit().setMessage("added 0").call();
626 
627 		writeTrashFile("0", "master");
628 		RevCommit masterCommit = git.commit().setAll(true)
629 				.setMessage("modified 0 on master").call();
630 
631 		git.checkout().setCreateBranch(true).setStartPoint(first)
632 				.setName("side").call();
633 		writeTrashFile("0", "side");
634 		git.commit().setAll(true).setMessage("modified 0 on side").call();
635 
636 		git.rm().addFilepattern("0").call();
637 		writeTrashFile("0/0", "side");
638 		git.add().addFilepattern("0/0").call();
639 		MergeResult mergeRes = git.merge().setStrategy(strategy)
640 				.include(masterCommit).call();
641 		assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
642 	}
643 
644 	/**
645 	 * Merging two equal files when the index contains a tree for that path
646 	 * should lead to a failed state.
647 	 *
648 	 * @param strategy
649 	 * @throws Exception
650 	 */
651 	@Theory
652 	public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
653 			throws Exception {
654 		Git git = Git.wrap(db);
655 
656 		writeTrashFile("0", "orig");
657 		writeTrashFile("1", "1\n2\n3");
658 		git.add().addFilepattern("0").addFilepattern("1").call();
659 		RevCommit first = git.commit().setMessage("added 0, 1").call();
660 
661 		writeTrashFile("1", "1master\n2\n3");
662 		RevCommit masterCommit = git.commit().setAll(true)
663 				.setMessage("modified 1 on master").call();
664 
665 		git.checkout().setCreateBranch(true).setStartPoint(first)
666 				.setName("side").call();
667 		writeTrashFile("1", "1\n2\n3side");
668 		git.commit().setAll(true).setMessage("modified 1 on side").call();
669 
670 		git.rm().addFilepattern("0").call();
671 		writeTrashFile("0/0", "modified");
672 		git.add().addFilepattern("0/0").call();
673 		try {
674 			git.merge().setStrategy(strategy).include(masterCommit).call();
675 			Assert.fail("Didn't get the expected exception");
676 		} catch (CheckoutConflictException e) {
677 			assertEquals(1, e.getConflictingPaths().size());
678 			assertEquals("0/0", e.getConflictingPaths().get(0));
679 		}
680 	}
681 
682 	@Theory
683 	public void checkContentMergeNoConflict(MergeStrategy strategy)
684 			throws Exception {
685 		Git git = Git.wrap(db);
686 
687 		writeTrashFile("file", "1\n2\n3");
688 		git.add().addFilepattern("file").call();
689 		RevCommit first = git.commit().setMessage("added file").call();
690 
691 		writeTrashFile("file", "1master\n2\n3");
692 		git.commit().setAll(true).setMessage("modified file on master").call();
693 
694 		git.checkout().setCreateBranch(true).setStartPoint(first)
695 				.setName("side").call();
696 		writeTrashFile("file", "1\n2\n3side");
697 		RevCommit sideCommit = git.commit().setAll(true)
698 				.setMessage("modified file on side").call();
699 
700 		git.checkout().setName("master").call();
701 		MergeResult result =
702 				git.merge().setStrategy(strategy).include(sideCommit).call();
703 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
704 		String expected = "1master\n2\n3side";
705 		assertEquals(expected, read("file"));
706 	}
707 
708 	@Theory
709 	public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
710 			throws Exception {
711 		Git git = Git.wrap(db);
712 
713 		writeTrashFile("file", "1\n2\n3");
714 		git.add().addFilepattern("file").call();
715 		RevCommit first = git.commit().setMessage("added file").call();
716 
717 		writeTrashFile("file", "1master\n2\n3");
718 		RevCommit masterCommit = git.commit().setAll(true)
719 				.setMessage("modified file on master").call();
720 
721 		git.checkout().setCreateBranch(true).setStartPoint(first)
722 				.setName("side").call();
723 		writeTrashFile("file", "1\n2\n3side");
724 		RevCommit sideCommit = git.commit().setAll(true)
725 				.setMessage("modified file on side").call();
726 
727 		try (ObjectInserter ins = db.newObjectInserter()) {
728 			ResolveMerger merger =
729 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
730 			boolean noProblems = merger.merge(masterCommit, sideCommit);
731 			assertTrue(noProblems);
732 			assertEquals("1master\n2\n3side",
733 					readBlob(merger.getResultTreeId(), "file"));
734 		}
735 	}
736 
737 
738 	/**
739 	 * Merging a change involving large binary files should short-circuit reads.
740 	 *
741 	 * @param strategy
742 	 * @throws Exception
743 	 */
744 	@Theory
745 	public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
746 		Git git = Git.wrap(db);
747 		final int LINELEN = 72;
748 
749 		// setup a merge that would work correctly if we disconsider the stray '\0'
750 		// that the file contains near the start.
751 		byte[] binary = new byte[LINELEN * 2000];
752 		for (int i = 0; i < binary.length; i++) {
753 			binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
754 		}
755 		binary[50] = '\0';
756 
757 		writeTrashFile("file", new String(binary, CHARSET));
758 		git.add().addFilepattern("file").call();
759 		RevCommit first = git.commit().setMessage("added file").call();
760 
761 		// Generate an edit in a single line.
762 		int idx = LINELEN * 1200 + 1;
763 		byte save = binary[idx];
764 		binary[idx] = '@';
765 		writeTrashFile("file", new String(binary, CHARSET));
766 
767 		binary[idx] = save;
768 		git.add().addFilepattern("file").call();
769 		RevCommit masterCommit = git.commit().setAll(true)
770 			.setMessage("modified file l 1200").call();
771 
772 		git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
773 		binary[LINELEN * 1500 + 1] = '!';
774 		writeTrashFile("file", new String(binary, CHARSET));
775 		git.add().addFilepattern("file").call();
776 		RevCommit sideCommit = git.commit().setAll(true)
777 			.setMessage("modified file l 1500").call();
778 
779 		try (ObjectInserter ins = db.newObjectInserter()) {
780 			// Check that we don't read the large blobs.
781 			ObjectInserter forbidInserter = new ObjectInserter.Filter() {
782 				@Override
783 				protected ObjectInserter delegate() {
784 					return ins;
785 				}
786 
787 				@Override
788 				public ObjectReader newReader() {
789 					return new BigReadForbiddenReader(super.newReader(), 8000);
790 				}
791 			};
792 
793 			ResolveMerger merger =
794 				(ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
795 			boolean noProblems = merger.merge(masterCommit, sideCommit);
796 			assertFalse(noProblems);
797 		}
798 	}
799 
800 	/**
801 	 * Throws an exception if reading beyond limit.
802 	 */
803 	class BigReadForbiddenStream extends ObjectStream.Filter {
804 		int limit;
805 
806 		BigReadForbiddenStream(ObjectStream orig, int limit) {
807 			super(orig.getType(), orig.getSize(), orig);
808 			this.limit = limit;
809 		}
810 
811 		@Override
812 		public long skip(long n) throws IOException {
813 			limit -= n;
814 			if (limit < 0) {
815 				throw new IllegalStateException();
816 			}
817 
818 			return super.skip(n);
819 		}
820 
821 		@Override
822 		public int read() throws IOException {
823 			int r = super.read();
824 			limit--;
825 			if (limit < 0) {
826 				throw new IllegalStateException();
827 			}
828 			return r;
829 		}
830 
831 		@Override
832 		public int read(byte[] b, int off, int len) throws IOException {
833 			int n = super.read(b, off, len);
834 			limit -= n;
835 			if (limit < 0) {
836 				throw new IllegalStateException();
837 			}
838 			return n;
839 		}
840 	}
841 
842 	class BigReadForbiddenReader extends ObjectReader.Filter {
843 		ObjectReader delegate;
844 		int limit;
845 
846 		@Override
847 		protected ObjectReader delegate() {
848 			return delegate;
849 		}
850 
851 		BigReadForbiddenReader(ObjectReader delegate, int limit) {
852 			this.delegate = delegate;
853 			this.limit = limit;
854 		}
855 
856 		@Override
857 		public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
858 			ObjectLoader orig = super.open(objectId, typeHint);
859 			return new ObjectLoader.Filter() {
860 				@Override
861 				protected ObjectLoader delegate() {
862 					return orig;
863 				}
864 
865 				@Override
866 				public ObjectStream openStream() throws IOException {
867 					ObjectStream os = orig.openStream();
868 					return new BigReadForbiddenStream(os, limit);
869 				}
870 			};
871 		}
872 	}
873 
874 	@Theory
875 	public void checkContentMergeConflict(MergeStrategy strategy)
876 			throws Exception {
877 		Git git = Git.wrap(db);
878 
879 		writeTrashFile("file", "1\n2\n3");
880 		git.add().addFilepattern("file").call();
881 		RevCommit first = git.commit().setMessage("added file").call();
882 
883 		writeTrashFile("file", "1master\n2\n3");
884 		git.commit().setAll(true).setMessage("modified file on master").call();
885 
886 		git.checkout().setCreateBranch(true).setStartPoint(first)
887 				.setName("side").call();
888 		writeTrashFile("file", "1side\n2\n3");
889 		RevCommit sideCommit = git.commit().setAll(true)
890 				.setMessage("modified file on side").call();
891 
892 		git.checkout().setName("master").call();
893 		MergeResult result =
894 				git.merge().setStrategy(strategy).include(sideCommit).call();
895 		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
896 		String expected = "<<<<<<< HEAD\n"
897 				+ "1master\n"
898 				+ "=======\n"
899 				+ "1side\n"
900 				+ ">>>>>>> " + sideCommit.name() + "\n"
901 				+ "2\n"
902 				+ "3";
903 		assertEquals(expected, read("file"));
904 	}
905 
906 	@Theory
907 	public void checkContentMergeConflict_noTree(MergeStrategy strategy)
908 			throws Exception {
909 		Git git = Git.wrap(db);
910 
911 		writeTrashFile("file", "1\n2\n3");
912 		git.add().addFilepattern("file").call();
913 		RevCommit first = git.commit().setMessage("added file").call();
914 
915 		writeTrashFile("file", "1master\n2\n3");
916 		RevCommit masterCommit = git.commit().setAll(true)
917 				.setMessage("modified file on master").call();
918 
919 		git.checkout().setCreateBranch(true).setStartPoint(first)
920 				.setName("side").call();
921 		writeTrashFile("file", "1side\n2\n3");
922 		RevCommit sideCommit = git.commit().setAll(true)
923 				.setMessage("modified file on side").call();
924 
925 		try (ObjectInserter ins = db.newObjectInserter()) {
926 			ResolveMerger merger =
927 					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
928 			boolean noProblems = merger.merge(masterCommit, sideCommit);
929 			assertFalse(noProblems);
930 			assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
931 
932 			MergeFormatter fmt = new MergeFormatter();
933 			merger.getMergeResults().get("file");
934 			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
935 				fmt.formatMerge(out, merger.getMergeResults().get("file"),
936 						"BASE", "OURS", "THEIRS", CHARSET.name());
937 				String expected = "<<<<<<< OURS\n"
938 						+ "1master\n"
939 						+ "=======\n"
940 						+ "1side\n"
941 						+ ">>>>>>> THEIRS\n"
942 						+ "2\n"
943 						+ "3";
944 				assertEquals(expected, new String(out.toByteArray(), CHARSET));
945 			}
946 		}
947 	}
948 
949 	/**
950 	 * Merging after criss-cross merges. In this case we merge together two
951 	 * commits which have two equally good common ancestors
952 	 *
953 	 * @param strategy
954 	 * @throws Exception
955 	 */
956 	@Theory
957 	public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
958 		Git git = Git.wrap(db);
959 
960 		writeTrashFile("1", "1\n2\n3");
961 		git.add().addFilepattern("1").call();
962 		RevCommit first = git.commit().setMessage("added 1").call();
963 
964 		writeTrashFile("1", "1master\n2\n3");
965 		RevCommit masterCommit = git.commit().setAll(true)
966 				.setMessage("modified 1 on master").call();
967 
968 		writeTrashFile("1", "1master2\n2\n3");
969 		git.commit().setAll(true)
970 				.setMessage("modified 1 on master again").call();
971 
972 		git.checkout().setCreateBranch(true).setStartPoint(first)
973 				.setName("side").call();
974 		writeTrashFile("1", "1\n2\na\nb\nc\n3side");
975 		RevCommit sideCommit = git.commit().setAll(true)
976 				.setMessage("modified 1 on side").call();
977 
978 		writeTrashFile("1", "1\n2\n3side2");
979 		git.commit().setAll(true)
980 				.setMessage("modified 1 on side again").call();
981 
982 		MergeResult result = git.merge().setStrategy(strategy)
983 				.include(masterCommit).call();
984 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
985 		result.getNewHead();
986 		git.checkout().setName("master").call();
987 		result = git.merge().setStrategy(strategy).include(sideCommit).call();
988 		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
989 
990 		// we have two branches which are criss-cross merged. Try to merge the
991 		// tips. This should succeed with RecursiveMerge and fail with
992 		// ResolveMerge
993 		try {
994 			MergeResult mergeResult = git.merge().setStrategy(strategy)
995 					.include(git.getRepository().exactRef("refs/heads/side"))
996 					.call();
997 			assertEquals(MergeStrategy.RECURSIVE, strategy);
998 			assertEquals(MergeResult.MergeStatus.MERGED,
999 					mergeResult.getMergeStatus());
1000 			assertEquals("1master2\n2\n3side2", read("1"));
1001 		} catch (JGitInternalException e) {
1002 			assertEquals(MergeStrategy.RESOLVE, strategy);
1003 			assertTrue(e.getCause() instanceof NoMergeBaseException);
1004 			assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1005 					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1006 		}
1007 	}
1008 
1009 	@Theory
1010 	public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1011 			throws Exception {
1012 		Git git = Git.wrap(db);
1013 
1014 		writeTrashFile("a.txt", "orig");
1015 		writeTrashFile("b.txt", "orig");
1016 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1017 		RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1018 
1019 		// modify and delete files on the master branch
1020 		writeTrashFile("a.txt", "master");
1021 		git.rm().addFilepattern("b.txt").call();
1022 		RevCommit masterCommit = git.commit()
1023 				.setMessage("modified a.txt, deleted b.txt").setAll(true)
1024 				.call();
1025 
1026 		// switch back to a side branch
1027 		git.checkout().setCreateBranch(true).setStartPoint(first)
1028 				.setName("side").call();
1029 		writeTrashFile("c.txt", "side");
1030 		git.add().addFilepattern("c.txt").call();
1031 		git.commit().setMessage("added c.txt").call();
1032 
1033 		// Get a handle to the the file so on windows it can't be deleted.
1034 		try (FileInputStream fis = new FileInputStream(
1035 				new File(db.getWorkTree(), "b.txt"))) {
1036 			MergeResult mergeRes = git.merge().setStrategy(strategy)
1037 					.include(masterCommit).call();
1038 			if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1039 				// probably windows
1040 				assertEquals(1, mergeRes.getFailingPaths().size());
1041 				assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1042 						mergeRes.getFailingPaths().get("b.txt"));
1043 			}
1044 			assertEquals(
1045 					"[a.txt, mode:100644, content:master]"
1046 							+ "[c.txt, mode:100644, content:side]",
1047 					indexState(CONTENT));
1048 		}
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(), CHARSET);
1332 	}
1333 }