1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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.assertTrue;
50
51 import java.io.ByteArrayOutputStream;
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.util.Arrays;
56
57 import org.eclipse.jgit.api.Git;
58 import org.eclipse.jgit.api.MergeResult;
59 import org.eclipse.jgit.api.MergeResult.MergeStatus;
60 import org.eclipse.jgit.api.errors.CheckoutConflictException;
61 import org.eclipse.jgit.api.errors.GitAPIException;
62 import org.eclipse.jgit.api.errors.JGitInternalException;
63 import org.eclipse.jgit.dircache.DirCache;
64 import org.eclipse.jgit.errors.NoMergeBaseException;
65 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
66 import org.eclipse.jgit.junit.RepositoryTestCase;
67 import org.eclipse.jgit.junit.TestRepository;
68 import org.eclipse.jgit.lib.ObjectId;
69 import org.eclipse.jgit.lib.ObjectInserter;
70 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
71 import org.eclipse.jgit.revwalk.RevCommit;
72 import org.eclipse.jgit.revwalk.RevObject;
73 import org.eclipse.jgit.revwalk.RevTree;
74 import org.eclipse.jgit.revwalk.RevWalk;
75 import org.eclipse.jgit.treewalk.FileTreeIterator;
76 import org.eclipse.jgit.util.FS;
77 import org.eclipse.jgit.util.FileUtils;
78 import org.junit.Assert;
79 import org.junit.experimental.theories.DataPoint;
80 import org.junit.experimental.theories.Theories;
81 import org.junit.experimental.theories.Theory;
82 import org.junit.runner.RunWith;
83
84 @RunWith(Theories.class)
85 public class ResolveMergerTest extends RepositoryTestCase {
86
87 @DataPoint
88 public static MergeStrategy resolve = MergeStrategy.RESOLVE;
89
90 @DataPoint
91 public static MergeStrategy recursive = MergeStrategy.RECURSIVE;
92
93 @Theory
94 public void failingDeleteOfDirectoryWithUntrackedContent(
95 MergeStrategy strategy) throws Exception {
96 File folder1 = new File(db.getWorkTree(), "folder1");
97 FileUtils.mkdir(folder1);
98 File file = new File(folder1, "file1.txt");
99 write(file, "folder1--file1.txt");
100 file = new File(folder1, "file2.txt");
101 write(file, "folder1--file2.txt");
102
103 try (Git git = new Git(db)) {
104 git.add().addFilepattern(folder1.getName()).call();
105 RevCommit base = git.commit().setMessage("adding folder").call();
106
107 recursiveDelete(folder1);
108 git.rm().addFilepattern("folder1/file1.txt")
109 .addFilepattern("folder1/file2.txt").call();
110 RevCommit other = git.commit()
111 .setMessage("removing folders on 'other'").call();
112
113 git.checkout().setName(base.name()).call();
114
115 file = new File(db.getWorkTree(), "unrelated.txt");
116 write(file, "unrelated");
117
118 git.add().addFilepattern("unrelated.txt").call();
119 RevCommit head = git.commit().setMessage("Adding another file").call();
120
121
122
123 file = new File(folder1, "file3.txt");
124 write(file, "folder1--file3.txt");
125
126 ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
127 merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
128 merger.setWorkingTreeIterator(new FileTreeIterator(db));
129 boolean ok = merger.merge(head.getId(), other.getId());
130 assertTrue(ok);
131 assertTrue(file.exists());
132 }
133 }
134
135
136
137
138
139
140
141
142 @Theory
143 public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
144 throws Exception {
145 Git git = Git.wrap(db);
146
147 writeTrashFile("d/1", "orig");
148 git.add().addFilepattern("d/1").call();
149 RevCommit first = git.commit().setMessage("added d/1").call();
150
151 writeTrashFile("d/1", "master");
152 RevCommit masterCommit = git.commit().setAll(true)
153 .setMessage("modified d/1 on master").call();
154
155 git.checkout().setCreateBranch(true).setStartPoint(first)
156 .setName("side").call();
157 writeTrashFile("d/1", "side");
158 git.commit().setAll(true).setMessage("modified d/1 on side").call();
159
160 git.rm().addFilepattern("d/1").call();
161 git.rm().addFilepattern("d").call();
162 MergeResult mergeRes = git.merge().setStrategy(strategy)
163 .include(masterCommit).call();
164 assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
165 assertEquals(
166 "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
167 indexState(CONTENT));
168 }
169
170
171
172
173
174
175
176
177 @Theory
178 public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
179 throws Exception {
180 Git git = Git.wrap(db);
181
182 writeTrashFile("d/1", "1\n2\n3");
183 git.add().addFilepattern("d/1").call();
184 RevCommit first = git.commit().setMessage("added d/1").call();
185
186 writeTrashFile("d/1", "1master\n2\n3");
187 RevCommit masterCommit = git.commit().setAll(true)
188 .setMessage("modified d/1 on master").call();
189
190 git.checkout().setCreateBranch(true).setStartPoint(first)
191 .setName("side").call();
192 writeTrashFile("d/1", "1\n2\n3side");
193 git.commit().setAll(true).setMessage("modified d/1 on side").call();
194
195 git.rm().addFilepattern("d/1").call();
196 git.rm().addFilepattern("d").call();
197 MergeResult mergeRes = git.merge().setStrategy(strategy)
198 .include(masterCommit).call();
199 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
200 assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
201 indexState(CONTENT));
202 }
203
204
205
206
207
208
209
210
211 @Theory
212 public void checkUntrackedFolderIsNotAConflict(
213 MergeStrategy strategy) throws Exception {
214 Git git = Git.wrap(db);
215
216 writeTrashFile("d/1", "1");
217 git.add().addFilepattern("d/1").call();
218 RevCommit first = git.commit().setMessage("added d/1").call();
219
220 writeTrashFile("e/1", "4");
221 git.add().addFilepattern("e/1").call();
222 RevCommit masterCommit = git.commit().setMessage("added e/1").call();
223
224 git.checkout().setCreateBranch(true).setStartPoint(first)
225 .setName("side").call();
226 writeTrashFile("f/1", "5");
227 git.add().addFilepattern("f/1").call();
228 git.commit().setAll(true).setMessage("added f/1")
229 .call();
230
231
232 writeTrashFile("e/2", "d two");
233
234 MergeResult mergeRes = git.merge().setStrategy(strategy)
235 .include(masterCommit).call();
236 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
237 assertEquals(
238 "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
239 indexState(CONTENT));
240 }
241
242
243
244
245
246
247
248 @Theory
249 public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
250 throws Exception {
251 Git git = Git.wrap(db);
252
253 writeTrashFile("sub", "file");
254 git.add().addFilepattern("sub").call();
255 RevCommit first = git.commit().setMessage("initial").call();
256
257 git.checkout().setCreateBranch(true).setStartPoint(first)
258 .setName("side").call();
259
260 git.rm().addFilepattern("sub").call();
261 writeTrashFile("sub/file", "subfile");
262 git.add().addFilepattern("sub/file").call();
263 RevCommit masterCommit = git.commit().setMessage("file -> folder")
264 .call();
265
266 git.checkout().setName("master").call();
267 writeTrashFile("noop", "other");
268 git.add().addFilepattern("noop").call();
269 git.commit().setAll(true).setMessage("noop").call();
270
271 MergeResult mergeRes = git.merge().setStrategy(strategy)
272 .include(masterCommit).call();
273 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
274 assertEquals(
275 "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
276 indexState(CONTENT));
277 }
278
279
280
281
282
283
284
285 @Theory
286 public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
287 throws Exception {
288 Git git = Git.wrap(db);
289
290 writeTrashFile("sub", "file");
291 git.add().addFilepattern("sub").call();
292 RevCommit first = git.commit().setMessage("initial").call();
293
294 git.checkout().setCreateBranch(true).setStartPoint(first)
295 .setName("side").call();
296 writeTrashFile("noop", "other");
297 git.add().addFilepattern("noop").call();
298 RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
299 .call();
300
301 git.checkout().setName("master").call();
302 git.rm().addFilepattern("sub").call();
303 writeTrashFile("sub/file", "subfile");
304 git.add().addFilepattern("sub/file").call();
305 git.commit().setMessage("file -> folder")
306 .call();
307
308 MergeResult mergeRes = git.merge().setStrategy(strategy)
309 .include(sideCommit).call();
310 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
311 assertEquals(
312 "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
313 indexState(CONTENT));
314 }
315
316
317
318
319
320
321
322
323 @Theory
324 public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
325 MergeStrategy strategy)
326 throws Exception {
327 Git git = Git.wrap(db);
328
329 writeTrashFile("d/1", "1");
330 git.add().addFilepattern("d/1").call();
331 RevCommit first = git.commit().setMessage("added d/1").call();
332
333 writeTrashFile("e", "4");
334 git.add().addFilepattern("e").call();
335 RevCommit masterCommit = git.commit().setMessage("added e").call();
336
337 git.checkout().setCreateBranch(true).setStartPoint(first)
338 .setName("side").call();
339 writeTrashFile("f/1", "5");
340 git.add().addFilepattern("f/1").call();
341 git.commit().setAll(true).setMessage("added f/1").call();
342
343
344
345 FileUtils.mkdirs(new File(trash, "e/1"), true);
346
347 MergeResult mergeRes = git.merge().setStrategy(strategy)
348 .include(masterCommit).call();
349 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
350 assertEquals(
351 "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
352 indexState(CONTENT));
353 }
354
355 @Theory
356 public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
357 GitAPIException {
358 Git git = Git.wrap(db);
359 db.getConfig().setString("core", null, "autocrlf", "false");
360 db.getConfig().save();
361 writeTrashFile("crlf.txt", "some\r\ndata\r\n");
362 git.add().addFilepattern("crlf.txt").call();
363 git.commit().setMessage("base").call();
364
365 git.branchCreate().setName("brancha").call();
366
367 writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
368 git.add().addFilepattern("crlf.txt").call();
369 git.commit().setMessage("on master").call();
370
371 git.checkout().setName("brancha").call();
372 writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
373 git.add().addFilepattern("crlf.txt").call();
374 git.commit().setMessage("on brancha").call();
375
376 db.getConfig().setString("core", null, "autocrlf", "input");
377 db.getConfig().save();
378
379 MergeResult mergeResult = git.merge().setStrategy(strategy)
380 .include(db.resolve("master"))
381 .call();
382 assertEquals(MergeResult.MergeStatus.MERGED,
383 mergeResult.getMergeStatus());
384 }
385
386
387
388
389
390
391
392
393 @Theory
394 public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
395 throws Exception {
396 Git git = Git.wrap(db);
397
398 writeTrashFile("d/1", "orig");
399 git.add().addFilepattern("d/1").call();
400 RevCommit first = git.commit().setMessage("added d/1").call();
401
402 writeTrashFile("d/1", "modified");
403 RevCommit masterCommit = git.commit().setAll(true)
404 .setMessage("modified d/1 on master").call();
405
406 git.checkout().setCreateBranch(true).setStartPoint(first)
407 .setName("side").call();
408 writeTrashFile("d/1", "modified");
409 git.commit().setAll(true).setMessage("modified d/1 on side").call();
410
411 git.rm().addFilepattern("d/1").call();
412 git.rm().addFilepattern("d").call();
413 MergeResult mergeRes = git.merge().setStrategy(strategy)
414 .include(masterCommit).call();
415 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
416 assertEquals("[d/1, mode:100644, content:modified]",
417 indexState(CONTENT));
418 }
419
420
421
422
423
424
425
426
427 @Theory
428 public void checkMergeEqualTreesInCore(MergeStrategy strategy)
429 throws Exception {
430 Git git = Git.wrap(db);
431
432 writeTrashFile("d/1", "orig");
433 git.add().addFilepattern("d/1").call();
434 RevCommit first = git.commit().setMessage("added d/1").call();
435
436 writeTrashFile("d/1", "modified");
437 RevCommit masterCommit = git.commit().setAll(true)
438 .setMessage("modified d/1 on master").call();
439
440 git.checkout().setCreateBranch(true).setStartPoint(first)
441 .setName("side").call();
442 writeTrashFile("d/1", "modified");
443 RevCommit sideCommit = git.commit().setAll(true)
444 .setMessage("modified d/1 on side").call();
445
446 git.rm().addFilepattern("d/1").call();
447 git.rm().addFilepattern("d").call();
448
449 ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
450 true);
451 boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
452 assertTrue(noProblems);
453 }
454
455
456
457
458
459
460
461
462 @Theory
463 public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
464 throws Exception {
465 Git git = Git.wrap(db);
466
467 writeTrashFile("d/1", "orig");
468 git.add().addFilepattern("d/1").call();
469 RevCommit first = git.commit().setMessage("added d/1").call();
470
471 writeTrashFile("d/1", "modified");
472 RevCommit masterCommit = git.commit().setAll(true)
473 .setMessage("modified d/1 on master").call();
474
475 git.checkout().setCreateBranch(true).setStartPoint(first)
476 .setName("side").call();
477 writeTrashFile("d/1", "modified");
478 RevCommit sideCommit = git.commit().setAll(true)
479 .setMessage("modified d/1 on side").call();
480
481 git.rm().addFilepattern("d/1").call();
482 git.rm().addFilepattern("d").call();
483
484 try (ObjectInserter ins = db.newObjectInserter()) {
485 ThreeWayMerger resolveMerger =
486 (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
487 boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
488 assertTrue(noProblems);
489 }
490 }
491
492
493
494
495
496
497
498
499 @Theory
500 public void checkMergeEqualNewTrees(MergeStrategy strategy)
501 throws Exception {
502 Git git = Git.wrap(db);
503
504 writeTrashFile("2", "orig");
505 git.add().addFilepattern("2").call();
506 RevCommit first = git.commit().setMessage("added 2").call();
507
508 writeTrashFile("d/1", "orig");
509 git.add().addFilepattern("d/1").call();
510 RevCommit masterCommit = git.commit().setAll(true)
511 .setMessage("added d/1 on master").call();
512
513 git.checkout().setCreateBranch(true).setStartPoint(first)
514 .setName("side").call();
515 writeTrashFile("d/1", "orig");
516 git.add().addFilepattern("d/1").call();
517 git.commit().setAll(true).setMessage("added d/1 on side").call();
518
519 git.rm().addFilepattern("d/1").call();
520 git.rm().addFilepattern("d").call();
521 MergeResult mergeRes = git.merge().setStrategy(strategy)
522 .include(masterCommit).call();
523 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
524 assertEquals(
525 "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
526 indexState(CONTENT));
527 }
528
529
530
531
532
533
534
535
536 @Theory
537 public void checkMergeConflictingNewTrees(MergeStrategy strategy)
538 throws Exception {
539 Git git = Git.wrap(db);
540
541 writeTrashFile("2", "orig");
542 git.add().addFilepattern("2").call();
543 RevCommit first = git.commit().setMessage("added 2").call();
544
545 writeTrashFile("d/1", "master");
546 git.add().addFilepattern("d/1").call();
547 RevCommit masterCommit = git.commit().setAll(true)
548 .setMessage("added d/1 on master").call();
549
550 git.checkout().setCreateBranch(true).setStartPoint(first)
551 .setName("side").call();
552 writeTrashFile("d/1", "side");
553 git.add().addFilepattern("d/1").call();
554 git.commit().setAll(true).setMessage("added d/1 on side").call();
555
556 git.rm().addFilepattern("d/1").call();
557 git.rm().addFilepattern("d").call();
558 MergeResult mergeRes = git.merge().setStrategy(strategy)
559 .include(masterCommit).call();
560 assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
561 assertEquals(
562 "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
563 indexState(CONTENT));
564 }
565
566
567
568
569
570
571
572
573 @Theory
574 public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
575 throws Exception {
576 Git git = Git.wrap(db);
577
578 writeTrashFile("0", "orig");
579 git.add().addFilepattern("0").call();
580 RevCommit first = git.commit().setMessage("added 0").call();
581
582 writeTrashFile("0", "master");
583 RevCommit masterCommit = git.commit().setAll(true)
584 .setMessage("modified 0 on master").call();
585
586 git.checkout().setCreateBranch(true).setStartPoint(first)
587 .setName("side").call();
588 writeTrashFile("0", "side");
589 git.commit().setAll(true).setMessage("modified 0 on side").call();
590
591 git.rm().addFilepattern("0").call();
592 writeTrashFile("0/0", "side");
593 git.add().addFilepattern("0/0").call();
594 MergeResult mergeRes = git.merge().setStrategy(strategy)
595 .include(masterCommit).call();
596 assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
597 }
598
599
600
601
602
603
604
605
606 @Theory
607 public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
608 throws Exception {
609 Git git = Git.wrap(db);
610
611 writeTrashFile("0", "orig");
612 writeTrashFile("1", "1\n2\n3");
613 git.add().addFilepattern("0").addFilepattern("1").call();
614 RevCommit first = git.commit().setMessage("added 0, 1").call();
615
616 writeTrashFile("1", "1master\n2\n3");
617 RevCommit masterCommit = git.commit().setAll(true)
618 .setMessage("modified 1 on master").call();
619
620 git.checkout().setCreateBranch(true).setStartPoint(first)
621 .setName("side").call();
622 writeTrashFile("1", "1\n2\n3side");
623 git.commit().setAll(true).setMessage("modified 1 on side").call();
624
625 git.rm().addFilepattern("0").call();
626 writeTrashFile("0/0", "modified");
627 git.add().addFilepattern("0/0").call();
628 try {
629 git.merge().setStrategy(strategy).include(masterCommit).call();
630 Assert.fail("Didn't get the expected exception");
631 } catch (CheckoutConflictException e) {
632 assertEquals(1, e.getConflictingPaths().size());
633 assertEquals("0/0", e.getConflictingPaths().get(0));
634 }
635 }
636
637 @Theory
638 public void checkContentMergeNoConflict(MergeStrategy strategy)
639 throws Exception {
640 Git git = Git.wrap(db);
641
642 writeTrashFile("file", "1\n2\n3");
643 git.add().addFilepattern("file").call();
644 RevCommit first = git.commit().setMessage("added file").call();
645
646 writeTrashFile("file", "1master\n2\n3");
647 git.commit().setAll(true).setMessage("modified file on master").call();
648
649 git.checkout().setCreateBranch(true).setStartPoint(first)
650 .setName("side").call();
651 writeTrashFile("file", "1\n2\n3side");
652 RevCommit sideCommit = git.commit().setAll(true)
653 .setMessage("modified file on side").call();
654
655 git.checkout().setName("master").call();
656 MergeResult result =
657 git.merge().setStrategy(strategy).include(sideCommit).call();
658 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
659 String expected = "1master\n2\n3side";
660 assertEquals(expected, read("file"));
661 }
662
663 @Theory
664 public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
665 throws Exception {
666 Git git = Git.wrap(db);
667
668 writeTrashFile("file", "1\n2\n3");
669 git.add().addFilepattern("file").call();
670 RevCommit first = git.commit().setMessage("added file").call();
671
672 writeTrashFile("file", "1master\n2\n3");
673 RevCommit masterCommit = git.commit().setAll(true)
674 .setMessage("modified file on master").call();
675
676 git.checkout().setCreateBranch(true).setStartPoint(first)
677 .setName("side").call();
678 writeTrashFile("file", "1\n2\n3side");
679 RevCommit sideCommit = git.commit().setAll(true)
680 .setMessage("modified file on side").call();
681
682 try (ObjectInserter ins = db.newObjectInserter()) {
683 ResolveMerger merger =
684 (ResolveMerger) strategy.newMerger(ins, db.getConfig());
685 boolean noProblems = merger.merge(masterCommit, sideCommit);
686 assertTrue(noProblems);
687 assertEquals("1master\n2\n3side",
688 readBlob(merger.getResultTreeId(), "file"));
689 }
690 }
691
692 @Theory
693 public void checkContentMergeConflict(MergeStrategy strategy)
694 throws Exception {
695 Git git = Git.wrap(db);
696
697 writeTrashFile("file", "1\n2\n3");
698 git.add().addFilepattern("file").call();
699 RevCommit first = git.commit().setMessage("added file").call();
700
701 writeTrashFile("file", "1master\n2\n3");
702 git.commit().setAll(true).setMessage("modified file on master").call();
703
704 git.checkout().setCreateBranch(true).setStartPoint(first)
705 .setName("side").call();
706 writeTrashFile("file", "1side\n2\n3");
707 RevCommit sideCommit = git.commit().setAll(true)
708 .setMessage("modified file on side").call();
709
710 git.checkout().setName("master").call();
711 MergeResult result =
712 git.merge().setStrategy(strategy).include(sideCommit).call();
713 assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
714 String expected = "<<<<<<< HEAD\n"
715 + "1master\n"
716 + "=======\n"
717 + "1side\n"
718 + ">>>>>>> " + sideCommit.name() + "\n"
719 + "2\n"
720 + "3";
721 assertEquals(expected, read("file"));
722 }
723
724 @Theory
725 public void checkContentMergeConflict_noTree(MergeStrategy strategy)
726 throws Exception {
727 Git git = Git.wrap(db);
728
729 writeTrashFile("file", "1\n2\n3");
730 git.add().addFilepattern("file").call();
731 RevCommit first = git.commit().setMessage("added file").call();
732
733 writeTrashFile("file", "1master\n2\n3");
734 RevCommit masterCommit = git.commit().setAll(true)
735 .setMessage("modified file on master").call();
736
737 git.checkout().setCreateBranch(true).setStartPoint(first)
738 .setName("side").call();
739 writeTrashFile("file", "1side\n2\n3");
740 RevCommit sideCommit = git.commit().setAll(true)
741 .setMessage("modified file on side").call();
742
743 try (ObjectInserter ins = db.newObjectInserter()) {
744 ResolveMerger merger =
745 (ResolveMerger) strategy.newMerger(ins, db.getConfig());
746 boolean noProblems = merger.merge(masterCommit, sideCommit);
747 assertFalse(noProblems);
748 assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
749
750 MergeFormatter fmt = new MergeFormatter();
751 merger.getMergeResults().get("file");
752 try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
753 fmt.formatMerge(out, merger.getMergeResults().get("file"),
754 "BASE", "OURS", "THEIRS", UTF_8.name());
755 String expected = "<<<<<<< OURS\n"
756 + "1master\n"
757 + "=======\n"
758 + "1side\n"
759 + ">>>>>>> THEIRS\n"
760 + "2\n"
761 + "3";
762 assertEquals(expected, new String(out.toByteArray(), UTF_8));
763 }
764 }
765 }
766
767
768
769
770
771
772
773
774 @Theory
775 public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
776 Git git = Git.wrap(db);
777
778 writeTrashFile("1", "1\n2\n3");
779 git.add().addFilepattern("1").call();
780 RevCommit first = git.commit().setMessage("added 1").call();
781
782 writeTrashFile("1", "1master\n2\n3");
783 RevCommit masterCommit = git.commit().setAll(true)
784 .setMessage("modified 1 on master").call();
785
786 writeTrashFile("1", "1master2\n2\n3");
787 git.commit().setAll(true)
788 .setMessage("modified 1 on master again").call();
789
790 git.checkout().setCreateBranch(true).setStartPoint(first)
791 .setName("side").call();
792 writeTrashFile("1", "1\n2\na\nb\nc\n3side");
793 RevCommit sideCommit = git.commit().setAll(true)
794 .setMessage("modified 1 on side").call();
795
796 writeTrashFile("1", "1\n2\n3side2");
797 git.commit().setAll(true)
798 .setMessage("modified 1 on side again").call();
799
800 MergeResult result = git.merge().setStrategy(strategy)
801 .include(masterCommit).call();
802 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
803 result.getNewHead();
804 git.checkout().setName("master").call();
805 result = git.merge().setStrategy(strategy).include(sideCommit).call();
806 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
807
808
809
810
811 try {
812 MergeResult mergeResult = git.merge().setStrategy(strategy)
813 .include(git.getRepository().exactRef("refs/heads/side"))
814 .call();
815 assertEquals(MergeStrategy.RECURSIVE, strategy);
816 assertEquals(MergeResult.MergeStatus.MERGED,
817 mergeResult.getMergeStatus());
818 assertEquals("1master2\n2\n3side2", read("1"));
819 } catch (JGitInternalException e) {
820 assertEquals(MergeStrategy.RESOLVE, strategy);
821 assertTrue(e.getCause() instanceof NoMergeBaseException);
822 assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
823 MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
824 }
825 }
826
827 @Theory
828 public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
829 throws Exception {
830 Git git = Git.wrap(db);
831
832 writeTrashFile("a.txt", "orig");
833 writeTrashFile("b.txt", "orig");
834 git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
835 RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
836
837
838 writeTrashFile("a.txt", "master");
839 git.rm().addFilepattern("b.txt").call();
840 RevCommit masterCommit = git.commit()
841 .setMessage("modified a.txt, deleted b.txt").setAll(true)
842 .call();
843
844
845 git.checkout().setCreateBranch(true).setStartPoint(first)
846 .setName("side").call();
847 writeTrashFile("c.txt", "side");
848 git.add().addFilepattern("c.txt").call();
849 git.commit().setMessage("added c.txt").call();
850
851
852 FileInputStream fis = new FileInputStream(new File(db.getWorkTree(),
853 "b.txt"));
854 MergeResult mergeRes = git.merge().setStrategy(strategy)
855 .include(masterCommit).call();
856 if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
857
858 assertEquals(1, mergeRes.getFailingPaths().size());
859 assertEquals(MergeFailureReason.COULD_NOT_DELETE, mergeRes
860 .getFailingPaths().get("b.txt"));
861 }
862 assertEquals("[a.txt, mode:100644, content:master]"
863 + "[c.txt, mode:100644, content:side]", indexState(CONTENT));
864 fis.close();
865 }
866
867 @Theory
868 public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
869 File f;
870 long lastTs4, lastTsIndex;
871 Git git = Git.wrap(db);
872 File indexFile = db.getIndexFile();
873
874
875 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
876 lastTs4 = FS.DETECTED.lastModified(f);
877
878
879
880
881
882 fsTick(f);
883 git.add().addFilepattern(".").call();
884 RevCommit firstCommit = git.commit().setMessage("initial commit")
885 .call();
886 checkConsistentLastModified("0", "1", "2", "3", "4");
887 checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
888 assertEquals("Commit should not touch working tree file 4", lastTs4,
889 FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
890 lastTsIndex = FS.DETECTED.lastModified(indexFile);
891
892
893
894 fsTick(indexFile);
895 f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
896 null);
897 fsTick(f);
898 git.add().addFilepattern(".").call();
899 RevCommit masterCommit = git.commit().setMessage("master commit")
900 .call();
901 checkConsistentLastModified("0", "1", "2", "3", "4");
902 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
903 + lastTsIndex, "<0", "2", "3", "<.git/index");
904 lastTsIndex = FS.DETECTED.lastModified(indexFile);
905
906
907 fsTick(indexFile);
908 git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
909 .setName("side").call();
910 checkConsistentLastModified("0", "1", "2", "3", "4");
911 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
912 + lastTsIndex, "<0", "2", "3", ".git/index");
913 lastTsIndex = FS.DETECTED.lastModified(indexFile);
914
915
916
917
918 assertEquals("[0, mode:100644, content:orig]"
919 + "[1, mode:100644, content:orig]"
920 + "[2, mode:100644, content:1\n2\n3]"
921 + "[3, mode:100644, content:orig]"
922 + "[4, mode:100644, content:orig]",
923 indexState(CONTENT));
924 fsTick(indexFile);
925 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
926 lastTs4 = FS.DETECTED.lastModified(f);
927 fsTick(f);
928 git.add().addFilepattern(".").call();
929 checkConsistentLastModified("0", "1", "2", "3", "4");
930 checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
931 "4", "<.git/index");
932 lastTsIndex = FS.DETECTED.lastModified(indexFile);
933
934
935 fsTick(indexFile);
936 f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
937 fsTick(f);
938 git.add().addFilepattern(".").call();
939 git.commit().setMessage("side commit").call();
940 checkConsistentLastModified("0", "1", "2", "3", "4");
941 checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
942 + lastTsIndex, "<1", "2", "3", "<.git/index");
943 lastTsIndex = FS.DETECTED.lastModified(indexFile);
944
945
946 fsTick(indexFile);
947 git.merge().setStrategy(strategy).include(masterCommit).call();
948 checkConsistentLastModified("0", "1", "2", "4");
949 checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
950 + lastTsIndex, "<0", "2", "3", ".git/index");
951 assertEquals(
952 "[0, mode:100644, content:master]"
953 + "[1, mode:100644, content:side]"
954 + "[2, mode:100644, content:1master\n2\n3side]"
955 + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]"
956 + "[4, mode:100644, content:orig]",
957 indexState(CONTENT));
958 }
959
960
961
962 private void checkConsistentLastModified(String... pathes)
963 throws IOException {
964 DirCache dc = db.readDirCache();
965 File workTree = db.getWorkTree();
966 for (String path : pathes)
967 assertEquals(
968 "IndexEntry with path "
969 + path
970 + " has lastmodified with is different from the worktree file",
971 FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
972 .getLastModified());
973 }
974
975
976
977
978
979
980
981 private void checkModificationTimeStampOrder(String... pathes)
982 throws IOException {
983 long lastMod = Long.MIN_VALUE;
984 for (String p : pathes) {
985 boolean strong = p.startsWith("<");
986 boolean fixed = p.charAt(strong ? 1 : 0) == '*';
987 p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
988 long curMod = fixed ? Long.valueOf(p).longValue()
989 : FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
990 if (strong)
991 assertTrue("path " + p + " is not younger than predecesssor",
992 curMod > lastMod);
993 else
994 assertTrue("path " + p + " is older than predecesssor",
995 curMod >= lastMod);
996 }
997 }
998
999 private String readBlob(ObjectId treeish, String path) throws Exception {
1000 TestRepository<?> tr = new TestRepository<>(db);
1001 RevWalk rw = tr.getRevWalk();
1002 RevTree tree = rw.parseTree(treeish);
1003 RevObject obj = tr.get(tree, path);
1004 if (obj == null) {
1005 return null;
1006 }
1007 return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
1008 }
1009 }