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 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
135
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
150
151
152
153
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
185
186
187
188
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
219
220
221
222
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
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
257
258
259
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
294
295
296
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
331
332
333
334
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
357
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
433
434
435
436
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
467
468
469
470
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
502
503
504
505
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
539
540
541
542
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
576
577
578
579
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
613
614
615
616
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
646
647
648
649
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
740
741
742
743
744 @Theory
745 public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
746 Git git = Git.wrap(db);
747 final int LINELEN = 72;
748
749
750
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
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
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
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
951
952
953
954
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
991
992
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
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
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
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
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
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(), CHARSET);
1332 }
1333 }