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.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
137
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
152
153
154
155
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
187
188
189
190
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
221
222
223
224
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
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
259
260
261
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
296
297
298
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
333
334
335
336
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
359
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
435
436
437
438
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
469
470
471
472
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
504
505
506
507
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
541
542
543
544
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
578
579
580
581
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
615
616
617
618
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
648
649
650
651
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
742
743
744
745
746 @Theory
747 public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
748 Git git = Git.wrap(db);
749 final int LINELEN = 72;
750
751
752
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
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
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
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
953
954
955
956
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
993
994
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
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
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
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
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
1059 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1060 lastTs4 = FS.DETECTED.lastModified(f);
1061
1062
1063
1064
1065
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
1077
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
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
1100
1101
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
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
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
1146
1147
1148
1149
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
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
1185
1186
1187
1188
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
1201
1202
1203
1204
1205
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
1224
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
1285
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
1300
1301
1302
1303
1304
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 }