1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.merge;
11
12 import static java.nio.charset.StandardCharsets.UTF_8;
13 import static java.time.Instant.EPOCH;
14 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.assertFalse;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertNull;
19 import static org.junit.Assert.assertTrue;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.time.Instant;
26 import java.util.Arrays;
27 import java.util.Map;
28
29 import org.eclipse.jgit.api.Git;
30 import org.eclipse.jgit.api.MergeResult;
31 import org.eclipse.jgit.api.MergeResult.MergeStatus;
32 import org.eclipse.jgit.api.RebaseResult;
33 import org.eclipse.jgit.api.errors.CheckoutConflictException;
34 import org.eclipse.jgit.api.errors.GitAPIException;
35 import org.eclipse.jgit.api.errors.JGitInternalException;
36 import org.eclipse.jgit.diff.RawText;
37 import org.eclipse.jgit.dircache.DirCache;
38 import org.eclipse.jgit.dircache.DirCacheEditor;
39 import org.eclipse.jgit.dircache.DirCacheEntry;
40 import org.eclipse.jgit.errors.ConfigInvalidException;
41 import org.eclipse.jgit.errors.NoMergeBaseException;
42 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
43 import org.eclipse.jgit.junit.RepositoryTestCase;
44 import org.eclipse.jgit.junit.TestRepository;
45 import org.eclipse.jgit.lib.AnyObjectId;
46 import org.eclipse.jgit.lib.ConfigConstants;
47 import org.eclipse.jgit.lib.Constants;
48 import org.eclipse.jgit.lib.FileMode;
49 import org.eclipse.jgit.lib.ObjectId;
50 import org.eclipse.jgit.lib.ObjectInserter;
51 import org.eclipse.jgit.lib.ObjectLoader;
52 import org.eclipse.jgit.lib.ObjectReader;
53 import org.eclipse.jgit.lib.ObjectStream;
54 import org.eclipse.jgit.lib.StoredConfig;
55 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
56 import org.eclipse.jgit.revwalk.RevCommit;
57 import org.eclipse.jgit.revwalk.RevObject;
58 import org.eclipse.jgit.revwalk.RevTree;
59 import org.eclipse.jgit.revwalk.RevWalk;
60 import org.eclipse.jgit.storage.file.FileBasedConfig;
61 import org.eclipse.jgit.treewalk.FileTreeIterator;
62 import org.eclipse.jgit.util.FS;
63 import org.eclipse.jgit.util.FileUtils;
64 import org.junit.Assert;
65 import org.junit.experimental.theories.DataPoints;
66 import org.junit.experimental.theories.Theories;
67 import org.junit.experimental.theories.Theory;
68 import org.junit.runner.RunWith;
69
70 @RunWith(Theories.class)
71 public class MergerTest extends RepositoryTestCase {
72
73 @DataPoints
74 public static MergeStrategy[] strategiesUnderTest = new MergeStrategy[] {
75 MergeStrategy.RECURSIVE, MergeStrategy.RESOLVE };
76
77 @Theory
78 public void failingDeleteOfDirectoryWithUntrackedContent(
79 MergeStrategy strategy) throws Exception {
80 File folder1 = new File(db.getWorkTree(), "folder1");
81 FileUtils.mkdir(folder1);
82 File file = new File(folder1, "file1.txt");
83 write(file, "folder1--file1.txt");
84 file = new File(folder1, "file2.txt");
85 write(file, "folder1--file2.txt");
86
87 try (Git git = new Git(db)) {
88 git.add().addFilepattern(folder1.getName()).call();
89 RevCommit base = git.commit().setMessage("adding folder").call();
90
91 recursiveDelete(folder1);
92 git.rm().addFilepattern("folder1/file1.txt")
93 .addFilepattern("folder1/file2.txt").call();
94 RevCommit other = git.commit()
95 .setMessage("removing folders on 'other'").call();
96
97 git.checkout().setName(base.name()).call();
98
99 file = new File(db.getWorkTree(), "unrelated.txt");
100 write(file, "unrelated");
101
102 git.add().addFilepattern("unrelated.txt").call();
103 RevCommit head = git.commit().setMessage("Adding another file").call();
104
105
106
107 file = new File(folder1, "file3.txt");
108 write(file, "folder1--file3.txt");
109
110 ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
111 merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
112 merger.setWorkingTreeIterator(new FileTreeIterator(db));
113 boolean ok = merger.merge(head.getId(), other.getId());
114 assertTrue(ok);
115 assertTrue(file.exists());
116 }
117 }
118
119
120
121
122
123
124
125
126 @Theory
127 public void checkMergeConflictingTreesWithoutIndex(MergeStrategy strategy)
128 throws Exception {
129 Git git = Git.wrap(db);
130
131 writeTrashFile("d/1", "orig");
132 git.add().addFilepattern("d/1").call();
133 RevCommit first = git.commit().setMessage("added d/1").call();
134
135 writeTrashFile("d/1", "master");
136 RevCommit masterCommit = git.commit().setAll(true)
137 .setMessage("modified d/1 on master").call();
138
139 git.checkout().setCreateBranch(true).setStartPoint(first)
140 .setName("side").call();
141 writeTrashFile("d/1", "side");
142 git.commit().setAll(true).setMessage("modified d/1 on side").call();
143
144 git.rm().addFilepattern("d/1").call();
145 git.rm().addFilepattern("d").call();
146 MergeResult mergeRes = git.merge().setStrategy(strategy)
147 .include(masterCommit).call();
148 assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
149 assertEquals(
150 "[d/1, mode:100644, stage:1, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
151 indexState(CONTENT));
152 }
153
154
155
156
157
158
159
160
161 @Theory
162 public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
163 throws Exception {
164 Git git = Git.wrap(db);
165
166 writeTrashFile("d/1", "1\n2\n3");
167 git.add().addFilepattern("d/1").call();
168 RevCommit first = git.commit().setMessage("added d/1").call();
169
170 writeTrashFile("d/1", "1master\n2\n3");
171 RevCommit masterCommit = git.commit().setAll(true)
172 .setMessage("modified d/1 on master").call();
173
174 git.checkout().setCreateBranch(true).setStartPoint(first)
175 .setName("side").call();
176 writeTrashFile("d/1", "1\n2\n3side");
177 git.commit().setAll(true).setMessage("modified d/1 on side").call();
178
179 git.rm().addFilepattern("d/1").call();
180 git.rm().addFilepattern("d").call();
181 MergeResult mergeRes = git.merge().setStrategy(strategy)
182 .include(masterCommit).call();
183 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
184 assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
185 indexState(CONTENT));
186 }
187
188
189
190
191
192
193
194
195 @Theory
196 public void checkUntrackedFolderIsNotAConflict(
197 MergeStrategy strategy) throws Exception {
198 Git git = Git.wrap(db);
199
200 writeTrashFile("d/1", "1");
201 git.add().addFilepattern("d/1").call();
202 RevCommit first = git.commit().setMessage("added d/1").call();
203
204 writeTrashFile("e/1", "4");
205 git.add().addFilepattern("e/1").call();
206 RevCommit masterCommit = git.commit().setMessage("added e/1").call();
207
208 git.checkout().setCreateBranch(true).setStartPoint(first)
209 .setName("side").call();
210 writeTrashFile("f/1", "5");
211 git.add().addFilepattern("f/1").call();
212 git.commit().setAll(true).setMessage("added f/1")
213 .call();
214
215
216 writeTrashFile("e/2", "d two");
217
218 MergeResult mergeRes = git.merge().setStrategy(strategy)
219 .include(masterCommit).call();
220 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
221 assertEquals(
222 "[d/1, mode:100644, content:1][e/1, mode:100644, content:4][f/1, mode:100644, content:5]",
223 indexState(CONTENT));
224 }
225
226
227
228
229
230
231
232 @Theory
233 public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy)
234 throws Exception {
235 Git git = Git.wrap(db);
236
237 writeTrashFile("sub", "file");
238 git.add().addFilepattern("sub").call();
239 RevCommit first = git.commit().setMessage("initial").call();
240
241 git.checkout().setCreateBranch(true).setStartPoint(first)
242 .setName("side").call();
243
244 git.rm().addFilepattern("sub").call();
245 writeTrashFile("sub/file", "subfile");
246 git.add().addFilepattern("sub/file").call();
247 RevCommit masterCommit = git.commit().setMessage("file -> folder")
248 .call();
249
250 git.checkout().setName("master").call();
251 writeTrashFile("noop", "other");
252 git.add().addFilepattern("noop").call();
253 git.commit().setAll(true).setMessage("noop").call();
254
255 MergeResult mergeRes = git.merge().setStrategy(strategy)
256 .include(masterCommit).call();
257 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
258 assertEquals(
259 "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
260 indexState(CONTENT));
261 }
262
263
264
265
266
267
268
269 @Theory
270 public void checkFileReplacedByFolderInOurs(MergeStrategy strategy)
271 throws Exception {
272 Git git = Git.wrap(db);
273
274 writeTrashFile("sub", "file");
275 git.add().addFilepattern("sub").call();
276 RevCommit first = git.commit().setMessage("initial").call();
277
278 git.checkout().setCreateBranch(true).setStartPoint(first)
279 .setName("side").call();
280 writeTrashFile("noop", "other");
281 git.add().addFilepattern("noop").call();
282 RevCommit sideCommit = git.commit().setAll(true).setMessage("noop")
283 .call();
284
285 git.checkout().setName("master").call();
286 git.rm().addFilepattern("sub").call();
287 writeTrashFile("sub/file", "subfile");
288 git.add().addFilepattern("sub/file").call();
289 git.commit().setMessage("file -> folder")
290 .call();
291
292 MergeResult mergeRes = git.merge().setStrategy(strategy)
293 .include(sideCommit).call();
294 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
295 assertEquals(
296 "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]",
297 indexState(CONTENT));
298 }
299
300
301
302
303
304
305
306
307 @Theory
308 public void checkUntrackedEmpytFolderIsNotAConflictWithFile(
309 MergeStrategy strategy)
310 throws Exception {
311 Git git = Git.wrap(db);
312
313 writeTrashFile("d/1", "1");
314 git.add().addFilepattern("d/1").call();
315 RevCommit first = git.commit().setMessage("added d/1").call();
316
317 writeTrashFile("e", "4");
318 git.add().addFilepattern("e").call();
319 RevCommit masterCommit = git.commit().setMessage("added e").call();
320
321 git.checkout().setCreateBranch(true).setStartPoint(first)
322 .setName("side").call();
323 writeTrashFile("f/1", "5");
324 git.add().addFilepattern("f/1").call();
325 git.commit().setAll(true).setMessage("added f/1").call();
326
327
328
329 FileUtils.mkdirs(new File(trash, "e/1"), true);
330
331 MergeResult mergeRes = git.merge().setStrategy(strategy)
332 .include(masterCommit).call();
333 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
334 assertEquals(
335 "[d/1, mode:100644, content:1][e, mode:100644, content:4][f/1, mode:100644, content:5]",
336 indexState(CONTENT));
337 }
338
339 @Theory
340 public void mergeWithCrlfInWT(MergeStrategy strategy) throws IOException,
341 GitAPIException {
342 Git git = Git.wrap(db);
343 db.getConfig().setString("core", null, "autocrlf", "false");
344 db.getConfig().save();
345 writeTrashFile("crlf.txt", "some\r\ndata\r\n");
346 git.add().addFilepattern("crlf.txt").call();
347 git.commit().setMessage("base").call();
348
349 git.branchCreate().setName("brancha").call();
350
351 writeTrashFile("crlf.txt", "some\r\nmore\r\ndata\r\n");
352 git.add().addFilepattern("crlf.txt").call();
353 git.commit().setMessage("on master").call();
354
355 git.checkout().setName("brancha").call();
356 writeTrashFile("crlf.txt", "some\r\ndata\r\ntoo\r\n");
357 git.add().addFilepattern("crlf.txt").call();
358 git.commit().setMessage("on brancha").call();
359
360 db.getConfig().setString("core", null, "autocrlf", "input");
361 db.getConfig().save();
362
363 MergeResult mergeResult = git.merge().setStrategy(strategy)
364 .include(db.resolve("master"))
365 .call();
366 assertEquals(MergeResult.MergeStatus.MERGED,
367 mergeResult.getMergeStatus());
368 }
369
370 @Theory
371 public void mergeConflictWithCrLfTextAuto(MergeStrategy strategy)
372 throws IOException, GitAPIException {
373 Git git = Git.wrap(db);
374 writeTrashFile("crlf.txt", "a crlf file\r\n");
375 git.add().addFilepattern("crlf.txt").call();
376 git.commit().setMessage("base").call();
377 assertEquals("[crlf.txt, mode:100644, content:a crlf file\r\n]",
378 indexState(CONTENT));
379 writeTrashFile(".gitattributes", "crlf.txt text=auto");
380 git.add().addFilepattern(".gitattributes").call();
381 git.commit().setMessage("attributes").call();
382
383 git.branchCreate().setName("brancha").call();
384
385 writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
386 git.add().addFilepattern("crlf.txt").call();
387 git.commit().setMessage("on master").call();
388 assertEquals(
389 "[.gitattributes, mode:100644, content:crlf.txt text=auto]"
390 + "[crlf.txt, mode:100644, content:a crlf file\r\na second line\r\n]",
391 indexState(CONTENT));
392
393 git.checkout().setName("brancha").call();
394 File testFile = writeTrashFile("crlf.txt",
395 "a crlf file\r\nanother line\r\n");
396 git.add().addFilepattern("crlf.txt").call();
397 git.commit().setMessage("on brancha").call();
398
399 MergeResult mergeResult = git.merge().setStrategy(strategy)
400 .include(db.resolve("master")).call();
401 assertEquals(MergeResult.MergeStatus.CONFLICTING,
402 mergeResult.getMergeStatus());
403 checkFile(testFile,
404 "a crlf file\r\n"
405 + "<<<<<<< HEAD\n"
406 + "another line\r\n"
407 + "=======\n"
408 + "a second line\r\n"
409 + ">>>>>>> 8e9e704742f1bc8a41eac88aac4aeefd338b7384\n");
410 }
411
412 @Theory
413 public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
414 throws IOException, GitAPIException {
415 Git git = Git.wrap(db);
416 db.getConfig().setString("core", null, "autocrlf", "true");
417 db.getConfig().save();
418 writeTrashFile("crlf.txt", "a crlf file\r\n");
419 git.add().addFilepattern("crlf.txt").call();
420 git.commit().setMessage("base").call();
421
422 git.branchCreate().setName("brancha").call();
423
424 writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
425 git.add().addFilepattern("crlf.txt").call();
426 git.commit().setMessage("on master").call();
427
428 git.checkout().setName("brancha").call();
429 File testFile = writeTrashFile("crlf.txt",
430 "a first line\r\na crlf file\r\n");
431 git.add().addFilepattern("crlf.txt").call();
432 git.commit().setMessage("on brancha").call();
433
434 MergeResult mergeResult = git.merge().setStrategy(strategy)
435 .include(db.resolve("master")).call();
436 assertEquals(MergeResult.MergeStatus.MERGED,
437 mergeResult.getMergeStatus());
438 checkFile(testFile, "a first line\r\na crlf file\r\na second line\r\n");
439 assertEquals(
440 "[crlf.txt, mode:100644, content:a first line\na crlf file\na second line\n]",
441 indexState(CONTENT));
442 }
443
444 @Theory
445 public void rebaseWithCrlfAutoCrlfTrue(MergeStrategy strategy)
446 throws IOException, GitAPIException {
447 Git git = Git.wrap(db);
448 db.getConfig().setString("core", null, "autocrlf", "true");
449 db.getConfig().save();
450 writeTrashFile("crlf.txt", "line 1\r\nline 2\r\nline 3\r\n");
451 git.add().addFilepattern("crlf.txt").call();
452 RevCommit first = git.commit().setMessage("base").call();
453
454 git.checkout().setCreateBranch(true).setStartPoint(first)
455 .setName("brancha").call();
456
457 File testFile = writeTrashFile("crlf.txt",
458 "line 1\r\nmodified line\r\nline 3\r\n");
459 git.add().addFilepattern("crlf.txt").call();
460 git.commit().setMessage("on brancha").call();
461
462 git.checkout().setName("master").call();
463 File otherFile = writeTrashFile("otherfile.txt", "a line\r\n");
464 git.add().addFilepattern("otherfile.txt").call();
465 git.commit().setMessage("on master").call();
466
467 git.checkout().setName("brancha").call();
468 checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
469 assertFalse(otherFile.exists());
470
471 RebaseResult rebaseResult = git.rebase().setStrategy(strategy)
472 .setUpstream(db.resolve("master")).call();
473 assertEquals(RebaseResult.Status.OK, rebaseResult.getStatus());
474 checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
475 checkFile(otherFile, "a line\r\n");
476 assertEquals(
477 "[crlf.txt, mode:100644, content:line 1\nmodified line\nline 3\n]"
478 + "[otherfile.txt, mode:100644, content:a line\n]",
479 indexState(CONTENT));
480 }
481
482
483
484
485
486
487
488
489 @Theory
490 public void checkMergeEqualTreesWithoutIndex(MergeStrategy strategy)
491 throws Exception {
492 Git git = Git.wrap(db);
493
494 writeTrashFile("d/1", "orig");
495 git.add().addFilepattern("d/1").call();
496 RevCommit first = git.commit().setMessage("added d/1").call();
497
498 writeTrashFile("d/1", "modified");
499 RevCommit masterCommit = git.commit().setAll(true)
500 .setMessage("modified d/1 on master").call();
501
502 git.checkout().setCreateBranch(true).setStartPoint(first)
503 .setName("side").call();
504 writeTrashFile("d/1", "modified");
505 git.commit().setAll(true).setMessage("modified d/1 on side").call();
506
507 git.rm().addFilepattern("d/1").call();
508 git.rm().addFilepattern("d").call();
509 MergeResult mergeRes = git.merge().setStrategy(strategy)
510 .include(masterCommit).call();
511 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
512 assertEquals("[d/1, mode:100644, content:modified]",
513 indexState(CONTENT));
514 }
515
516
517
518
519
520
521
522
523 @Theory
524 public void checkMergeEqualTreesInCore(MergeStrategy strategy)
525 throws Exception {
526 Git git = Git.wrap(db);
527
528 writeTrashFile("d/1", "orig");
529 git.add().addFilepattern("d/1").call();
530 RevCommit first = git.commit().setMessage("added d/1").call();
531
532 writeTrashFile("d/1", "modified");
533 RevCommit masterCommit = git.commit().setAll(true)
534 .setMessage("modified d/1 on master").call();
535
536 git.checkout().setCreateBranch(true).setStartPoint(first)
537 .setName("side").call();
538 writeTrashFile("d/1", "modified");
539 RevCommit sideCommit = git.commit().setAll(true)
540 .setMessage("modified d/1 on side").call();
541
542 git.rm().addFilepattern("d/1").call();
543 git.rm().addFilepattern("d").call();
544
545 ThreeWayMerger resolveMerger = (ThreeWayMerger) strategy.newMerger(db,
546 true);
547 boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
548 assertTrue(noProblems);
549 }
550
551
552
553
554
555
556
557
558 @Theory
559 public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
560 throws Exception {
561 Git git = Git.wrap(db);
562
563 writeTrashFile("d/1", "orig");
564 git.add().addFilepattern("d/1").call();
565 RevCommit first = git.commit().setMessage("added d/1").call();
566
567 writeTrashFile("d/1", "modified");
568 RevCommit masterCommit = git.commit().setAll(true)
569 .setMessage("modified d/1 on master").call();
570
571 git.checkout().setCreateBranch(true).setStartPoint(first)
572 .setName("side").call();
573 writeTrashFile("d/1", "modified");
574 RevCommit sideCommit = git.commit().setAll(true)
575 .setMessage("modified d/1 on side").call();
576
577 git.rm().addFilepattern("d/1").call();
578 git.rm().addFilepattern("d").call();
579
580 try (ObjectInserter ins = db.newObjectInserter()) {
581 ThreeWayMerger resolveMerger =
582 (ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
583 boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
584 assertTrue(noProblems);
585 }
586 }
587
588
589
590
591
592
593
594
595 @Theory
596 public void checkMergeEqualNewTrees(MergeStrategy strategy)
597 throws Exception {
598 Git git = Git.wrap(db);
599
600 writeTrashFile("2", "orig");
601 git.add().addFilepattern("2").call();
602 RevCommit first = git.commit().setMessage("added 2").call();
603
604 writeTrashFile("d/1", "orig");
605 git.add().addFilepattern("d/1").call();
606 RevCommit masterCommit = git.commit().setAll(true)
607 .setMessage("added d/1 on master").call();
608
609 git.checkout().setCreateBranch(true).setStartPoint(first)
610 .setName("side").call();
611 writeTrashFile("d/1", "orig");
612 git.add().addFilepattern("d/1").call();
613 git.commit().setAll(true).setMessage("added d/1 on side").call();
614
615 git.rm().addFilepattern("d/1").call();
616 git.rm().addFilepattern("d").call();
617 MergeResult mergeRes = git.merge().setStrategy(strategy)
618 .include(masterCommit).call();
619 assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
620 assertEquals(
621 "[2, mode:100644, content:orig][d/1, mode:100644, content:orig]",
622 indexState(CONTENT));
623 }
624
625
626
627
628
629
630
631
632 @Theory
633 public void checkMergeConflictingNewTrees(MergeStrategy strategy)
634 throws Exception {
635 Git git = Git.wrap(db);
636
637 writeTrashFile("2", "orig");
638 git.add().addFilepattern("2").call();
639 RevCommit first = git.commit().setMessage("added 2").call();
640
641 writeTrashFile("d/1", "master");
642 git.add().addFilepattern("d/1").call();
643 RevCommit masterCommit = git.commit().setAll(true)
644 .setMessage("added d/1 on master").call();
645
646 git.checkout().setCreateBranch(true).setStartPoint(first)
647 .setName("side").call();
648 writeTrashFile("d/1", "side");
649 git.add().addFilepattern("d/1").call();
650 git.commit().setAll(true).setMessage("added d/1 on side").call();
651
652 git.rm().addFilepattern("d/1").call();
653 git.rm().addFilepattern("d").call();
654 MergeResult mergeRes = git.merge().setStrategy(strategy)
655 .include(masterCommit).call();
656 assertEquals(MergeStatus.CONFLICTING, mergeRes.getMergeStatus());
657 assertEquals(
658 "[2, mode:100644, content:orig][d/1, mode:100644, stage:2, content:side][d/1, mode:100644, stage:3, content:master]",
659 indexState(CONTENT));
660 }
661
662
663
664
665
666
667
668
669 @Theory
670 public void checkMergeConflictingFilesWithTreeInIndex(MergeStrategy strategy)
671 throws Exception {
672 Git git = Git.wrap(db);
673
674 writeTrashFile("0", "orig");
675 git.add().addFilepattern("0").call();
676 RevCommit first = git.commit().setMessage("added 0").call();
677
678 writeTrashFile("0", "master");
679 RevCommit masterCommit = git.commit().setAll(true)
680 .setMessage("modified 0 on master").call();
681
682 git.checkout().setCreateBranch(true).setStartPoint(first)
683 .setName("side").call();
684 writeTrashFile("0", "side");
685 git.commit().setAll(true).setMessage("modified 0 on side").call();
686
687 git.rm().addFilepattern("0").call();
688 writeTrashFile("0/0", "side");
689 git.add().addFilepattern("0/0").call();
690 MergeResult mergeRes = git.merge().setStrategy(strategy)
691 .include(masterCommit).call();
692 assertEquals(MergeStatus.FAILED, mergeRes.getMergeStatus());
693 }
694
695
696
697
698
699
700
701
702 @Theory
703 public void checkMergeMergeableFilesWithTreeInIndex(MergeStrategy strategy)
704 throws Exception {
705 Git git = Git.wrap(db);
706
707 writeTrashFile("0", "orig");
708 writeTrashFile("1", "1\n2\n3");
709 git.add().addFilepattern("0").addFilepattern("1").call();
710 RevCommit first = git.commit().setMessage("added 0, 1").call();
711
712 writeTrashFile("1", "1master\n2\n3");
713 RevCommit masterCommit = git.commit().setAll(true)
714 .setMessage("modified 1 on master").call();
715
716 git.checkout().setCreateBranch(true).setStartPoint(first)
717 .setName("side").call();
718 writeTrashFile("1", "1\n2\n3side");
719 git.commit().setAll(true).setMessage("modified 1 on side").call();
720
721 git.rm().addFilepattern("0").call();
722 writeTrashFile("0/0", "modified");
723 git.add().addFilepattern("0/0").call();
724 try {
725 git.merge().setStrategy(strategy).include(masterCommit).call();
726 Assert.fail("Didn't get the expected exception");
727 } catch (CheckoutConflictException e) {
728 assertEquals(1, e.getConflictingPaths().size());
729 assertEquals("0/0", e.getConflictingPaths().get(0));
730 }
731 }
732
733 @Theory
734 public void checkContentMergeNoConflict(MergeStrategy strategy)
735 throws Exception {
736 Git git = Git.wrap(db);
737
738 writeTrashFile("file", "1\n2\n3");
739 git.add().addFilepattern("file").call();
740 RevCommit first = git.commit().setMessage("added file").call();
741
742 writeTrashFile("file", "1master\n2\n3");
743 git.commit().setAll(true).setMessage("modified file on master").call();
744
745 git.checkout().setCreateBranch(true).setStartPoint(first)
746 .setName("side").call();
747 writeTrashFile("file", "1\n2\n3side");
748 RevCommit sideCommit = git.commit().setAll(true)
749 .setMessage("modified file on side").call();
750
751 git.checkout().setName("master").call();
752 MergeResult result =
753 git.merge().setStrategy(strategy).include(sideCommit).call();
754 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
755 String expected = "1master\n2\n3side";
756 assertEquals(expected, read("file"));
757 }
758
759 @Theory
760 public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
761 throws Exception {
762 Git git = Git.wrap(db);
763
764 writeTrashFile("file", "1\n2\n3");
765 git.add().addFilepattern("file").call();
766 RevCommit first = git.commit().setMessage("added file").call();
767
768 writeTrashFile("file", "1master\n2\n3");
769 RevCommit masterCommit = git.commit().setAll(true)
770 .setMessage("modified file on master").call();
771
772 git.checkout().setCreateBranch(true).setStartPoint(first)
773 .setName("side").call();
774 writeTrashFile("file", "1\n2\n3side");
775 RevCommit sideCommit = git.commit().setAll(true)
776 .setMessage("modified file on side").call();
777
778 try (ObjectInserter ins = db.newObjectInserter()) {
779 ResolveMerger merger =
780 (ResolveMerger) strategy.newMerger(ins, db.getConfig());
781 boolean noProblems = merger.merge(masterCommit, sideCommit);
782 assertTrue(noProblems);
783 assertEquals("1master\n2\n3side",
784 readBlob(merger.getResultTreeId(), "file"));
785 }
786 }
787
788
789
790
791
792
793
794
795 @Theory
796 public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Exception {
797 Git git = Git.wrap(db);
798 final int LINELEN = 72;
799
800
801
802 byte[] binary = new byte[LINELEN * 2000];
803 for (int i = 0; i < binary.length; i++) {
804 binary[i] = (byte)((i % LINELEN) == 0 ? '\n' : 'x');
805 }
806 binary[50] = '\0';
807
808 writeTrashFile("file", new String(binary, UTF_8));
809 git.add().addFilepattern("file").call();
810 RevCommit first = git.commit().setMessage("added file").call();
811
812
813 int idx = LINELEN * 1200 + 1;
814 byte save = binary[idx];
815 binary[idx] = '@';
816 writeTrashFile("file", new String(binary, UTF_8));
817
818 binary[idx] = save;
819 git.add().addFilepattern("file").call();
820 RevCommit masterCommit = git.commit().setAll(true)
821 .setMessage("modified file l 1200").call();
822
823 git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
824 binary[LINELEN * 1500 + 1] = '!';
825 writeTrashFile("file", new String(binary, UTF_8));
826 git.add().addFilepattern("file").call();
827 RevCommit sideCommit = git.commit().setAll(true)
828 .setMessage("modified file l 1500").call();
829
830 int originalBufferSize = RawText.getBufferSize();
831 int smallBufferSize = RawText.setBufferSize(8000);
832 try (ObjectInserter ins = db.newObjectInserter()) {
833
834 ObjectInserter forbidInserter = new ObjectInserter.Filter() {
835 @Override
836 protected ObjectInserter delegate() {
837 return ins;
838 }
839
840 @Override
841 public ObjectReader newReader() {
842 return new BigReadForbiddenReader(super.newReader(),
843 smallBufferSize);
844 }
845 };
846
847 ResolveMerger merger =
848 (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig());
849 boolean noProblems = merger.merge(masterCommit, sideCommit);
850 assertFalse(noProblems);
851 } finally {
852 RawText.setBufferSize(originalBufferSize);
853 }
854 }
855
856
857
858
859 static class BigReadForbiddenStream extends ObjectStream.Filter {
860 long limit;
861
862 BigReadForbiddenStream(ObjectStream orig, long limit) {
863 super(orig.getType(), orig.getSize(), orig);
864 this.limit = limit;
865 }
866
867 @Override
868 public long skip(long n) throws IOException {
869 limit -= n;
870 if (limit < 0) {
871 throw new IllegalStateException();
872 }
873
874 return super.skip(n);
875 }
876
877 @Override
878 public int read() throws IOException {
879 int r = super.read();
880 limit--;
881 if (limit < 0) {
882 throw new IllegalStateException();
883 }
884 return r;
885 }
886
887 @Override
888 public int read(byte[] b, int off, int len) throws IOException {
889 int n = super.read(b, off, len);
890 limit -= n;
891 if (limit < 0) {
892 throw new IllegalStateException();
893 }
894 return n;
895 }
896 }
897
898 static class BigReadForbiddenReader extends ObjectReader.Filter {
899 ObjectReader delegate;
900 int limit;
901
902 @Override
903 protected ObjectReader delegate() {
904 return delegate;
905 }
906
907 BigReadForbiddenReader(ObjectReader delegate, int limit) {
908 this.delegate = delegate;
909 this.limit = limit;
910 }
911
912 @Override
913 public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
914 ObjectLoader orig = super.open(objectId, typeHint);
915 return new ObjectLoader.Filter() {
916 @Override
917 protected ObjectLoader delegate() {
918 return orig;
919 }
920
921 @Override
922 public ObjectStream openStream() throws IOException {
923 ObjectStream os = orig.openStream();
924 return new BigReadForbiddenStream(os, limit);
925 }
926 };
927 }
928 }
929
930 @Theory
931 public void checkContentMergeConflict(MergeStrategy strategy)
932 throws Exception {
933 Git git = Git.wrap(db);
934
935 writeTrashFile("file", "1\n2\n3");
936 git.add().addFilepattern("file").call();
937 RevCommit first = git.commit().setMessage("added file").call();
938
939 writeTrashFile("file", "1master\n2\n3");
940 git.commit().setAll(true).setMessage("modified file on master").call();
941
942 git.checkout().setCreateBranch(true).setStartPoint(first)
943 .setName("side").call();
944 writeTrashFile("file", "1side\n2\n3");
945 RevCommit sideCommit = git.commit().setAll(true)
946 .setMessage("modified file on side").call();
947
948 git.checkout().setName("master").call();
949 MergeResult result =
950 git.merge().setStrategy(strategy).include(sideCommit).call();
951 assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
952 String expected = "<<<<<<< HEAD\n"
953 + "1master\n"
954 + "=======\n"
955 + "1side\n"
956 + ">>>>>>> " + sideCommit.name() + "\n"
957 + "2\n"
958 + "3";
959 assertEquals(expected, read("file"));
960 }
961
962 @Theory
963 public void checkContentMergeConflict_noTree(MergeStrategy strategy)
964 throws Exception {
965 Git git = Git.wrap(db);
966
967 writeTrashFile("file", "1\n2\n3");
968 git.add().addFilepattern("file").call();
969 RevCommit first = git.commit().setMessage("added file").call();
970
971 writeTrashFile("file", "1master\n2\n3");
972 RevCommit masterCommit = git.commit().setAll(true)
973 .setMessage("modified file on master").call();
974
975 git.checkout().setCreateBranch(true).setStartPoint(first)
976 .setName("side").call();
977 writeTrashFile("file", "1side\n2\n3");
978 RevCommit sideCommit = git.commit().setAll(true)
979 .setMessage("modified file on side").call();
980
981 try (ObjectInserter ins = db.newObjectInserter()) {
982 ResolveMerger merger =
983 (ResolveMerger) strategy.newMerger(ins, db.getConfig());
984 boolean noProblems = merger.merge(masterCommit, sideCommit);
985 assertFalse(noProblems);
986 assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
987
988 MergeFormatter fmt = new MergeFormatter();
989 merger.getMergeResults().get("file");
990 try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
991 fmt.formatMerge(out, merger.getMergeResults().get("file"),
992 "BASE", "OURS", "THEIRS", UTF_8);
993 String expected = "<<<<<<< OURS\n"
994 + "1master\n"
995 + "=======\n"
996 + "1side\n"
997 + ">>>>>>> THEIRS\n"
998 + "2\n"
999 + "3";
1000 assertEquals(expected, new String(out.toByteArray(), UTF_8));
1001 }
1002 }
1003 }
1004
1005
1006
1007
1008
1009
1010
1011
1012 @Theory
1013 public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
1014 Git git = Git.wrap(db);
1015
1016 writeTrashFile("1", "1\n2\n3");
1017 git.add().addFilepattern("1").call();
1018 RevCommit first = git.commit().setMessage("added 1").call();
1019
1020 writeTrashFile("1", "1master\n2\n3");
1021 RevCommit masterCommit = git.commit().setAll(true)
1022 .setMessage("modified 1 on master").call();
1023
1024 writeTrashFile("1", "1master2\n2\n3");
1025 git.commit().setAll(true)
1026 .setMessage("modified 1 on master again").call();
1027
1028 git.checkout().setCreateBranch(true).setStartPoint(first)
1029 .setName("side").call();
1030 writeTrashFile("1", "1\n2\na\nb\nc\n3side");
1031 RevCommit sideCommit = git.commit().setAll(true)
1032 .setMessage("modified 1 on side").call();
1033
1034 writeTrashFile("1", "1\n2\n3side2");
1035 git.commit().setAll(true)
1036 .setMessage("modified 1 on side again").call();
1037
1038 MergeResult result = git.merge().setStrategy(strategy)
1039 .include(masterCommit).call();
1040 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1041 result.getNewHead();
1042 git.checkout().setName("master").call();
1043 result = git.merge().setStrategy(strategy).include(sideCommit).call();
1044 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1045
1046
1047
1048
1049 try {
1050 MergeResult mergeResult = git.merge().setStrategy(strategy)
1051 .include(git.getRepository().exactRef("refs/heads/side"))
1052 .call();
1053 assertEquals(MergeStrategy.RECURSIVE, strategy);
1054 assertEquals(MergeResult.MergeStatus.MERGED,
1055 mergeResult.getMergeStatus());
1056 assertEquals("1master2\n2\n3side2", read("1"));
1057 } catch (JGitInternalException e) {
1058 assertEquals(MergeStrategy.RESOLVE, strategy);
1059 assertTrue(e.getCause() instanceof NoMergeBaseException);
1060 assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1061 MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1062 }
1063 }
1064
1065 @Theory
1066 public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1067 throws Exception {
1068 Git git = Git.wrap(db);
1069
1070 writeTrashFile("a.txt", "orig");
1071 writeTrashFile("b.txt", "orig");
1072 git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1073 RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1074
1075
1076 writeTrashFile("a.txt", "master");
1077 git.rm().addFilepattern("b.txt").call();
1078 RevCommit masterCommit = git.commit()
1079 .setMessage("modified a.txt, deleted b.txt").setAll(true)
1080 .call();
1081
1082
1083 git.checkout().setCreateBranch(true).setStartPoint(first)
1084 .setName("side").call();
1085 writeTrashFile("c.txt", "side");
1086 git.add().addFilepattern("c.txt").call();
1087 git.commit().setMessage("added c.txt").call();
1088
1089
1090 try (FileInputStream fis = new FileInputStream(
1091 new File(db.getWorkTree(), "b.txt"))) {
1092 MergeResult mergeRes = git.merge().setStrategy(strategy)
1093 .include(masterCommit).call();
1094 if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1095
1096 assertEquals(1, mergeRes.getFailingPaths().size());
1097 assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1098 mergeRes.getFailingPaths().get("b.txt"));
1099 }
1100 assertEquals(
1101 "[a.txt, mode:100644, content:master]"
1102 + "[c.txt, mode:100644, content:side]",
1103 indexState(CONTENT));
1104 }
1105 }
1106
1107 @Theory
1108 public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
1109 File f;
1110 Instant lastTs4, lastTsIndex;
1111 Git git = Git.wrap(db);
1112 File indexFile = db.getIndexFile();
1113
1114
1115 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1116 lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1117
1118
1119
1120
1121
1122 fsTick(f);
1123 git.add().addFilepattern(".").call();
1124 RevCommit firstCommit = git.commit().setMessage("initial commit")
1125 .call();
1126 checkConsistentLastModified("0", "1", "2", "3", "4");
1127 checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
1128 assertEquals("Commit should not touch working tree file 4", lastTs4,
1129 FS.DETECTED
1130 .lastModifiedInstant(new File(db.getWorkTree(), "4")));
1131 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1132
1133
1134
1135 fsTick(indexFile);
1136 f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
1137 null);
1138 fsTick(f);
1139 git.add().addFilepattern(".").call();
1140 RevCommit masterCommit = git.commit().setMessage("master commit")
1141 .call();
1142 checkConsistentLastModified("0", "1", "2", "3", "4");
1143 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1144 + lastTsIndex, "<0", "2", "3", "<.git/index");
1145 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1146
1147
1148 fsTick(indexFile);
1149 git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
1150 .setName("side").call();
1151 checkConsistentLastModified("0", "1", "2", "3", "4");
1152 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1153 + lastTsIndex, "<0", "2", "3", ".git/index");
1154 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1155
1156
1157
1158
1159 assertEquals("[0, mode:100644, content:orig]"
1160 + "[1, mode:100644, content:orig]"
1161 + "[2, mode:100644, content:1\n2\n3]"
1162 + "[3, mode:100644, content:orig]"
1163 + "[4, mode:100644, content:orig]",
1164 indexState(CONTENT));
1165 fsTick(indexFile);
1166 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1167 lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1168 fsTick(f);
1169 git.add().addFilepattern(".").call();
1170 checkConsistentLastModified("0", "1", "2", "3", "4");
1171 checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
1172 "4", "<.git/index");
1173 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1174
1175
1176 fsTick(indexFile);
1177 f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
1178 fsTick(f);
1179 git.add().addFilepattern(".").call();
1180 git.commit().setMessage("side commit").call();
1181 checkConsistentLastModified("0", "1", "2", "3", "4");
1182 checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
1183 + lastTsIndex, "<1", "2", "3", "<.git/index");
1184 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1185
1186
1187 fsTick(indexFile);
1188 git.merge().setStrategy(strategy).include(masterCommit).call();
1189 checkConsistentLastModified("0", "1", "2", "4");
1190 checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
1191 + lastTsIndex, "<0", "2", "3", ".git/index");
1192 assertEquals(
1193 "[0, mode:100644, content:master]"
1194 + "[1, mode:100644, content:side]"
1195 + "[2, mode:100644, content:1master\n2\n3side]"
1196 + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]"
1197 + "[4, mode:100644, content:orig]",
1198 indexState(CONTENT));
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208 @Theory
1209 public void checkMergeConflictingSubmodulesWithoutIndex(
1210 MergeStrategy strategy) throws Exception {
1211 Git git = Git.wrap(db);
1212 writeTrashFile("initial", "initial");
1213 git.add().addFilepattern("initial").call();
1214 RevCommit initial = git.commit().setMessage("initial").call();
1215
1216 writeSubmodule("one", ObjectId
1217 .fromString("1000000000000000000000000000000000000000"));
1218 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1219 RevCommit right = git.commit().setMessage("added one").call();
1220
1221
1222
1223 git.checkout().setStartPoint(initial).setName("left")
1224 .setCreateBranch(true).call();
1225 writeSubmodule("one", ObjectId
1226 .fromString("2000000000000000000000000000000000000000"));
1227
1228 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1229 git.commit().setMessage("a different one").call();
1230
1231 MergeResult result = git.merge().setStrategy(strategy).include(right)
1232 .call();
1233
1234 assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
1235 Map<String, int[][]> conflicts = result.getConflicts();
1236 assertEquals(1, conflicts.size());
1237 assertNotNull(conflicts.get("one"));
1238 }
1239
1240
1241
1242
1243
1244
1245
1246
1247 @Theory
1248 public void checkMergeNonConflictingSubmodulesWithoutIndex(
1249 MergeStrategy strategy) throws Exception {
1250 Git git = Git.wrap(db);
1251 writeTrashFile("initial", "initial");
1252 git.add().addFilepattern("initial").call();
1253
1254 writeSubmodule("one", ObjectId
1255 .fromString("1000000000000000000000000000000000000000"));
1256
1257
1258
1259
1260
1261
1262
1263 String existing = read(Constants.DOT_GIT_MODULES);
1264 String context = "\n# context\n# more context\n# yet more context\n";
1265 write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1266 existing + context + context + context);
1267
1268 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1269 RevCommit initial = git.commit().setMessage("initial").call();
1270
1271 writeSubmodule("two", ObjectId
1272 .fromString("1000000000000000000000000000000000000000"));
1273 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1274
1275 RevCommit right = git.commit().setMessage("added two").call();
1276
1277 git.checkout().setStartPoint(initial).setName("left")
1278 .setCreateBranch(true).call();
1279
1280
1281
1282 addSubmoduleToIndex("three", ObjectId
1283 .fromString("1000000000000000000000000000000000000000"));
1284 new File(db.getWorkTree(), "three").mkdir();
1285
1286 existing = read(Constants.DOT_GIT_MODULES);
1287 String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
1288 + db.getDirectory().toURI() + "\n";
1289 write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1290 three + existing);
1291
1292 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1293 git.commit().setMessage("a different one").call();
1294
1295 MergeResult result = git.merge().setStrategy(strategy).include(right)
1296 .call();
1297
1298 assertNull(result.getCheckoutConflicts());
1299 assertNull(result.getFailingPaths());
1300 for (String dir : Arrays.asList("one", "two", "three")) {
1301 assertTrue(new File(db.getWorkTree(), dir).isDirectory());
1302 }
1303 }
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338 @Theory
1339 public void checkMergeConflictInVirtualAncestor(
1340 MergeStrategy strategy) throws Exception {
1341 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1342 return;
1343 }
1344
1345 Git git = Git.wrap(db);
1346
1347
1348 writeTrashFile("a", "aaaaaaaa");
1349 writeTrashFile("b", "bbbbbbbb");
1350 git.add().addFilepattern("a").addFilepattern("b").call();
1351 RevCommit first = git.commit().setMessage("Initial commit").call();
1352
1353 writeTrashFile("a", "aaaaaaaaaaaaaaa");
1354 git.add().addFilepattern("a").call();
1355 RevCommit commitY = git.commit().setMessage("Modify a").call();
1356
1357 git.rm().addFilepattern("a").call();
1358
1359
1360 writeTrashFile("c", "cccccccc");
1361 git.add().addFilepattern("c").call();
1362 git.commit().setMessage("Delete modified a").call();
1363
1364
1365 git.checkout().setCreateBranch(true).setStartPoint(first)
1366 .setName("merge-both-sides").call();
1367 git.rm().addFilepattern("a").call();
1368 RevCommit commitX = git.commit().setMessage("Delete original a").call();
1369
1370
1371 git.checkout().setCreateBranch(true).setStartPoint(commitY)
1372 .setName("second-branch").call();
1373 git.rm().addFilepattern("a").call();
1374 git.commit().setMessage("Delete modified a").call();
1375
1376
1377 MergeResult mergeResult = git.merge().include(commitX)
1378 .setStrategy(strategy)
1379 .call();
1380 ObjectId commitB = mergeResult.getNewHead();
1381
1382
1383 git.checkout().setName("master").call();
1384 mergeResult = git.merge().include(commitX).setStrategy(strategy)
1385 .call();
1386
1387
1388
1389
1390 git.merge().include(commitB).call();
1391 }
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420 @Theory
1421 public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren(
1422 MergeStrategy strategy)
1423 throws Exception {
1424 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1425 return;
1426 }
1427
1428 Git git = Git.wrap(db);
1429
1430
1431 writeTrashFile("a", "initial content");
1432 git.add().addFilepattern("a").call();
1433 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1434
1435 writeTrashFile("a", "content in Ancestor 1");
1436 git.add().addFilepattern("a").call();
1437 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1438
1439 writeTrashFile("a", "content in Child 1 (commited on master)");
1440 git.add().addFilepattern("a").call();
1441
1442 git.commit().setMessage("Child 1 on master").call();
1443
1444 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1445
1446 git.rm().addFilepattern("a").call();
1447 writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1448 git.add().addFilepattern("a/content").call();
1449 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1450
1451
1452 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1453 writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1454 git.add().addFilepattern("a").call();
1455
1456 git.commit().setMessage("Child 2 on second-branch").call();
1457
1458
1459 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1460 assertEquals(mergeResult.getNewHead(), null);
1461 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1462
1463 git.rm().addFilepattern("a").call();
1464 git.rm().addFilepattern("a/content").call();
1465 writeTrashFile("a", "merge conflict resolution");
1466 git.add().addFilepattern("a").call();
1467 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1468 .call();
1469
1470
1471 git.checkout().setName("master").call();
1472 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1473 assertEquals(mergeResult.getNewHead(), null);
1474 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1475
1476
1477 git.rm().addFilepattern("a").call();
1478 git.rm().addFilepattern("a/content").call();
1479 writeTrashFile("a", "merge conflict resolution");
1480 git.add().addFilepattern("a").call();
1481
1482 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1483
1484
1485
1486
1487 mergeResult = git.merge().include(commitC3S).call();
1488 assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1489
1490 }
1491
1492 @Theory
1493 public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy)
1494 throws Exception {
1495 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1496 return;
1497 }
1498
1499 Git git = Git.wrap(db);
1500
1501
1502 writeTrashFile("a", "initial content");
1503 git.add().addFilepattern("a").call();
1504 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1505
1506 writeTrashFile("a", "content in Ancestor 1");
1507 git.add().addFilepattern("a").call();
1508 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1509
1510 writeTrashFile("a", "content in Child 1 (commited on master)");
1511 git.add().addFilepattern("a").call();
1512
1513 git.commit().setMessage("Child 1 on master").call();
1514
1515 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1516
1517
1518 git.rm().addFilepattern("a").call();
1519 writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1520 git.add().addFilepattern("a/content").call();
1521 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1522
1523
1524 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1525 writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1526 git.add().addFilepattern("a").call();
1527
1528 git.commit().setMessage("Child 2 on second-branch").call();
1529
1530
1531 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1532 assertEquals(mergeResult.getNewHead(), null);
1533 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1534
1535 git.rm().addFilepattern("a").call();
1536 git.rm().addFilepattern("a/content").call();
1537 writeTrashFile("a",
1538 "content in Child 3 (commited on second-branch) - merge conflict resolution");
1539 git.add().addFilepattern("a").call();
1540 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1541 .call();
1542
1543
1544 git.checkout().setName("master").call();
1545 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1546 assertEquals(mergeResult.getNewHead(), null);
1547 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1548
1549
1550 git.rm().addFilepattern("a").call();
1551 git.rm().addFilepattern("a/content").call();
1552 writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1553 git.add().addFilepattern("a").call();
1554
1555 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1556
1557
1558
1559 mergeResult = git.merge().include(commitC3S).call();
1560 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1561 String expected =
1562 "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1563 + "=======\n"
1564 + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1565 + ">>>>>>> " + commitC3S.name() + "\n";
1566 assertEquals(expected, read("a"));
1567
1568 assertEquals(
1569 "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
1570 indexState(CONTENT));
1571 }
1572
1573
1574
1575
1576 @Theory
1577 public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy)
1578 throws Exception {
1579 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1580 return;
1581 }
1582
1583 Git git = Git.wrap(db);
1584
1585
1586 writeTrashFile("a/content", "initial content");
1587 git.add().addFilepattern("a/content").call();
1588 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1589
1590 writeTrashFile("a/content", "content in Ancestor 1");
1591 git.add().addFilepattern("a/content").call();
1592 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1593
1594 writeTrashFile("a/content", "content in Child 1 (commited on master)");
1595 git.add().addFilepattern("a/content").call();
1596
1597 git.commit().setMessage("Child 1 on master").call();
1598
1599 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1600
1601
1602 git.rm().addFilepattern("a/content").call();
1603 writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)");
1604 git.add().addFilepattern("a").call();
1605 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1606
1607
1608 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1609 writeTrashFile("a/content", "content in Child 2 (commited on second-branch)");
1610 git.add().addFilepattern("a/content").call();
1611
1612 git.commit().setMessage("Child 2 on second-branch").call();
1613
1614
1615 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1616 assertEquals(mergeResult.getNewHead(), null);
1617 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1618
1619 git.rm().addFilepattern("a").call();
1620 git.rm().addFilepattern("a/content").call();
1621 deleteTrashFile("a/content");
1622 deleteTrashFile("a");
1623 writeTrashFile("a", "content in Child 3 (commited on second-branch) - merge conflict resolution");
1624 git.add().addFilepattern("a").call();
1625 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1626
1627
1628 git.checkout().setName("master").call();
1629 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1630 assertEquals(mergeResult.getNewHead(), null);
1631 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1632
1633
1634 git.rm().addFilepattern("a").call();
1635 git.rm().addFilepattern("a/content").call();
1636 deleteTrashFile("a/content");
1637 deleteTrashFile("a");
1638 writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1639 git.add().addFilepattern("a").call();
1640
1641 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1642
1643
1644
1645 mergeResult = git.merge().include(commitC3S).call();
1646 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1647 String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1648 + "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1649 + ">>>>>>> " + commitC3S.name() + "\n";
1650 assertEquals(expected, read("a"));
1651
1652 assertEquals(
1653 "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]",
1654 indexState(CONTENT));
1655 }
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666 @Theory
1667 public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
1668 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1669 return;
1670 }
1671
1672 Git git = Git.wrap(db);
1673
1674
1675 writeTrashFile("c", "initial file");
1676 git.add().addFilepattern("c").call();
1677 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1678
1679 File a = writeTrashFile("a", "content in Ancestor");
1680 git.add().addFilepattern("a").call();
1681 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1682
1683 a = writeTrashFile("a", "content in Child 1 (commited on master)");
1684 git.add().addFilepattern("a").call();
1685
1686 git.commit().setMessage("Child 1 on master").call();
1687
1688 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1689
1690 a = writeTrashFile("a", "content in Ancestor");
1691 a.setExecutable(true);
1692 git.add().addFilepattern("a").call();
1693 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1694
1695
1696 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1697 a = writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1698 git.add().addFilepattern("a").call();
1699
1700 git.commit().setMessage("Child 2 on second-branch").call();
1701
1702
1703 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1704 assertEquals(mergeResult.getNewHead(), null);
1705 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1706
1707 a = writeTrashFile("a", "merge conflict resolution");
1708 a.setExecutable(false);
1709 git.add().addFilepattern("a").call();
1710 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1711
1712
1713 git.checkout().setName("master").call();
1714 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1715 assertEquals(mergeResult.getNewHead(), null);
1716 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1717
1718
1719 a = writeTrashFile("a", "merge conflict resolution");
1720 a.setExecutable(false);
1721 git.add().addFilepattern("a").call();
1722
1723 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1724
1725
1726
1727
1728 mergeResult = git.merge().include(commitC3S).call();
1729 assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1730
1731 }
1732
1733 private void writeSubmodule(String path, ObjectId commit)
1734 throws IOException, ConfigInvalidException {
1735 addSubmoduleToIndex(path, commit);
1736 new File(db.getWorkTree(), path).mkdir();
1737
1738 StoredConfig config = db.getConfig();
1739 config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1740 ConfigConstants.CONFIG_KEY_URL,
1741 db.getDirectory().toURI().toString());
1742 config.save();
1743
1744 FileBasedConfig modulesConfig = new FileBasedConfig(
1745 new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1746 db.getFS());
1747 modulesConfig.load();
1748 modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1749 ConfigConstants.CONFIG_KEY_PATH, path);
1750 modulesConfig.save();
1751
1752 }
1753
1754 private void addSubmoduleToIndex(String path, ObjectId commit)
1755 throws IOException {
1756 DirCache cache = db.lockDirCache();
1757 DirCacheEditor editor = cache.editor();
1758 editor.add(new DirCacheEditor.PathEdit(path) {
1759
1760 @Override
1761 public void apply(DirCacheEntry ent) {
1762 ent.setFileMode(FileMode.GITLINK);
1763 ent.setObjectId(commit);
1764 }
1765 });
1766 editor.commit();
1767 }
1768
1769
1770
1771 private void checkConsistentLastModified(String... pathes)
1772 throws IOException {
1773 DirCache dc = db.readDirCache();
1774 File workTree = db.getWorkTree();
1775 for (String path : pathes)
1776 assertEquals(
1777 "IndexEntry with path "
1778 + path
1779 + " has lastmodified which is different from the worktree file",
1780 FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
1781 dc.getEntry(path)
1782 .getLastModifiedInstant());
1783 }
1784
1785
1786
1787
1788
1789
1790
1791 private void checkModificationTimeStampOrder(String... pathes) {
1792 Instant lastMod = EPOCH;
1793 for (String p : pathes) {
1794 boolean strong = p.startsWith("<");
1795 boolean fixed = p.charAt(strong ? 1 : 0) == '*';
1796 p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
1797 Instant curMod = fixed ? Instant.parse(p)
1798 : FS.DETECTED
1799 .lastModifiedInstant(new File(db.getWorkTree(), p));
1800 if (strong) {
1801 assertTrue("path " + p + " is not younger than predecesssor",
1802 curMod.compareTo(lastMod) > 0);
1803 } else {
1804 assertTrue("path " + p + " is older than predecesssor",
1805 curMod.compareTo(lastMod) >= 0);
1806 }
1807 }
1808 }
1809
1810 private String readBlob(ObjectId treeish, String path) throws Exception {
1811 try (TestRepository<?> tr = new TestRepository<>(db);
1812 RevWalk rw = tr.getRevWalk()) {
1813 RevTree tree = rw.parseTree(treeish);
1814 RevObject obj = tr.get(tree, path);
1815 if (obj == null) {
1816 return null;
1817 }
1818 return new String(
1819 rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
1820 }
1821 }
1822 }