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 @Theory
1006 public void fileBecomesDir_noTree(MergeStrategy strategy)
1007 throws Exception {
1008 Git git = Git.wrap(db);
1009
1010 writeTrashFile("file", "1\n2\n3");
1011 writeTrashFile("side", "1\n2\n3");
1012 git.add().addFilepattern("file").addFilepattern("side").call();
1013 RevCommit first = git.commit().setMessage("base").call();
1014
1015 writeTrashFile("side", "our changed");
1016 RevCommit ours = git.commit().setAll(true)
1017 .setMessage("ours").call();
1018
1019 git.checkout().setCreateBranch(true).setStartPoint(first)
1020 .setName("theirs").call();
1021 deleteTrashFile("file");
1022 writeTrashFile("file/file", "in subdir");
1023 git.add().addFilepattern("file/file").call();
1024
1025 RevCommit theirs = git.commit().setAll(true)
1026 .setMessage("theirs").call();
1027
1028
1029 try (ObjectInserter ins = db.newObjectInserter()) {
1030 ResolveMerger merger =
1031 (ResolveMerger) strategy.newMerger(ins, db.getConfig());
1032 boolean success = merger.merge(ours, theirs);
1033 assertTrue(success);
1034 assertTrue(merger.getModifiedFiles().isEmpty());
1035 }
1036 }
1037
1038
1039
1040
1041
1042
1043
1044 @Theory
1045 public void checkMergeDoesntCrashWithSpecialFileNames(
1046 MergeStrategy strategy) throws Exception {
1047 Git git = Git.wrap(db);
1048
1049 writeTrashFile("subtree", "");
1050 writeTrashFile("subtree-0", "");
1051 git.add().addFilepattern("subtree").call();
1052 git.add().addFilepattern("subtree-0").call();
1053 RevCommit toMerge = git.commit().setMessage("commit-1").call();
1054
1055 git.rm().addFilepattern("subtree").call();
1056 writeTrashFile("subtree/file", "");
1057 git.add().addFilepattern("subtree").call();
1058 RevCommit mergeTip = git.commit().setMessage("commit2").call();
1059
1060 ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
1061 assertTrue(merger.merge(mergeTip, toMerge));
1062 }
1063
1064
1065
1066
1067
1068
1069
1070
1071 @Theory
1072 public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
1073 Git git = Git.wrap(db);
1074
1075 writeTrashFile("1", "1\n2\n3");
1076 git.add().addFilepattern("1").call();
1077 RevCommit first = git.commit().setMessage("added 1").call();
1078
1079 writeTrashFile("1", "1master\n2\n3");
1080 RevCommit masterCommit = git.commit().setAll(true)
1081 .setMessage("modified 1 on master").call();
1082
1083 writeTrashFile("1", "1master2\n2\n3");
1084 git.commit().setAll(true)
1085 .setMessage("modified 1 on master again").call();
1086
1087 git.checkout().setCreateBranch(true).setStartPoint(first)
1088 .setName("side").call();
1089 writeTrashFile("1", "1\n2\na\nb\nc\n3side");
1090 RevCommit sideCommit = git.commit().setAll(true)
1091 .setMessage("modified 1 on side").call();
1092
1093 writeTrashFile("1", "1\n2\n3side2");
1094 git.commit().setAll(true)
1095 .setMessage("modified 1 on side again").call();
1096
1097 MergeResult result = git.merge().setStrategy(strategy)
1098 .include(masterCommit).call();
1099 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1100 result.getNewHead();
1101 git.checkout().setName("master").call();
1102 result = git.merge().setStrategy(strategy).include(sideCommit).call();
1103 assertEquals(MergeStatus.MERGED, result.getMergeStatus());
1104
1105
1106
1107
1108 try {
1109 MergeResult mergeResult = git.merge().setStrategy(strategy)
1110 .include(git.getRepository().exactRef("refs/heads/side"))
1111 .call();
1112 assertEquals(MergeStrategy.RECURSIVE, strategy);
1113 assertEquals(MergeResult.MergeStatus.MERGED,
1114 mergeResult.getMergeStatus());
1115 assertEquals("1master2\n2\n3side2", read("1"));
1116 } catch (JGitInternalException e) {
1117 assertEquals(MergeStrategy.RESOLVE, strategy);
1118 assertTrue(e.getCause() instanceof NoMergeBaseException);
1119 assertEquals(((NoMergeBaseException) e.getCause()).getReason(),
1120 MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
1121 }
1122 }
1123
1124 @Theory
1125 public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
1126 throws Exception {
1127 Git git = Git.wrap(db);
1128
1129 writeTrashFile("a.txt", "orig");
1130 writeTrashFile("b.txt", "orig");
1131 git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
1132 RevCommit first = git.commit().setMessage("added a.txt, b.txt").call();
1133
1134
1135 writeTrashFile("a.txt", "master");
1136 git.rm().addFilepattern("b.txt").call();
1137 RevCommit masterCommit = git.commit()
1138 .setMessage("modified a.txt, deleted b.txt").setAll(true)
1139 .call();
1140
1141
1142 git.checkout().setCreateBranch(true).setStartPoint(first)
1143 .setName("side").call();
1144 writeTrashFile("c.txt", "side");
1145 git.add().addFilepattern("c.txt").call();
1146 git.commit().setMessage("added c.txt").call();
1147
1148
1149 try (FileInputStream fis = new FileInputStream(
1150 new File(db.getWorkTree(), "b.txt"))) {
1151 MergeResult mergeRes = git.merge().setStrategy(strategy)
1152 .include(masterCommit).call();
1153 if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) {
1154
1155 assertEquals(1, mergeRes.getFailingPaths().size());
1156 assertEquals(MergeFailureReason.COULD_NOT_DELETE,
1157 mergeRes.getFailingPaths().get("b.txt"));
1158 }
1159 assertEquals(
1160 "[a.txt, mode:100644, content:master]"
1161 + "[c.txt, mode:100644, content:side]",
1162 indexState(CONTENT));
1163 }
1164 }
1165
1166 @Theory
1167 public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
1168 File f;
1169 Instant lastTs4, lastTsIndex;
1170 Git git = Git.wrap(db);
1171 File indexFile = db.getIndexFile();
1172
1173
1174 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1175 lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1176
1177
1178
1179
1180
1181 fsTick(f);
1182 git.add().addFilepattern(".").call();
1183 RevCommit firstCommit = git.commit().setMessage("initial commit")
1184 .call();
1185 checkConsistentLastModified("0", "1", "2", "3", "4");
1186 checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
1187 assertEquals("Commit should not touch working tree file 4", lastTs4,
1188 FS.DETECTED
1189 .lastModifiedInstant(new File(db.getWorkTree(), "4")));
1190 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1191
1192
1193
1194 fsTick(indexFile);
1195 f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master",
1196 null);
1197 fsTick(f);
1198 git.add().addFilepattern(".").call();
1199 RevCommit masterCommit = git.commit().setMessage("master commit")
1200 .call();
1201 checkConsistentLastModified("0", "1", "2", "3", "4");
1202 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1203 + lastTsIndex, "<0", "2", "3", "<.git/index");
1204 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1205
1206
1207 fsTick(indexFile);
1208 git.checkout().setCreateBranch(true).setStartPoint(firstCommit)
1209 .setName("side").call();
1210 checkConsistentLastModified("0", "1", "2", "3", "4");
1211 checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
1212 + lastTsIndex, "<0", "2", "3", ".git/index");
1213 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1214
1215
1216
1217
1218 assertEquals("[0, mode:100644, content:orig]"
1219 + "[1, mode:100644, content:orig]"
1220 + "[2, mode:100644, content:1\n2\n3]"
1221 + "[3, mode:100644, content:orig]"
1222 + "[4, mode:100644, content:orig]",
1223 indexState(CONTENT));
1224 fsTick(indexFile);
1225 f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
1226 lastTs4 = FS.DETECTED.lastModifiedInstant(f);
1227 fsTick(f);
1228 git.add().addFilepattern(".").call();
1229 checkConsistentLastModified("0", "1", "2", "3", "4");
1230 checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
1231 "4", "<.git/index");
1232 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1233
1234
1235 fsTick(indexFile);
1236 f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null);
1237 fsTick(f);
1238 git.add().addFilepattern(".").call();
1239 git.commit().setMessage("side commit").call();
1240 checkConsistentLastModified("0", "1", "2", "3", "4");
1241 checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
1242 + lastTsIndex, "<1", "2", "3", "<.git/index");
1243 lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
1244
1245
1246 fsTick(indexFile);
1247 git.merge().setStrategy(strategy).include(masterCommit).call();
1248 checkConsistentLastModified("0", "1", "2", "4");
1249 checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*"
1250 + lastTsIndex, "<0", "2", "3", ".git/index");
1251 assertEquals(
1252 "[0, mode:100644, content:master]"
1253 + "[1, mode:100644, content:side]"
1254 + "[2, mode:100644, content:1master\n2\n3side]"
1255 + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]"
1256 + "[4, mode:100644, content:orig]",
1257 indexState(CONTENT));
1258 }
1259
1260
1261
1262
1263
1264
1265
1266
1267 @Theory
1268 public void checkMergeConflictingSubmodulesWithoutIndex(
1269 MergeStrategy strategy) throws Exception {
1270 Git git = Git.wrap(db);
1271 writeTrashFile("initial", "initial");
1272 git.add().addFilepattern("initial").call();
1273 RevCommit initial = git.commit().setMessage("initial").call();
1274
1275 writeSubmodule("one", ObjectId
1276 .fromString("1000000000000000000000000000000000000000"));
1277 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1278 RevCommit right = git.commit().setMessage("added one").call();
1279
1280
1281
1282 git.checkout().setStartPoint(initial).setName("left")
1283 .setCreateBranch(true).call();
1284 writeSubmodule("one", ObjectId
1285 .fromString("2000000000000000000000000000000000000000"));
1286
1287 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1288 git.commit().setMessage("a different one").call();
1289
1290 MergeResult result = git.merge().setStrategy(strategy).include(right)
1291 .call();
1292
1293 assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
1294 Map<String, int[][]> conflicts = result.getConflicts();
1295 assertEquals(1, conflicts.size());
1296 assertNotNull(conflicts.get("one"));
1297 }
1298
1299
1300
1301
1302
1303
1304
1305
1306 @Theory
1307 public void checkMergeNonConflictingSubmodulesWithoutIndex(
1308 MergeStrategy strategy) throws Exception {
1309 Git git = Git.wrap(db);
1310 writeTrashFile("initial", "initial");
1311 git.add().addFilepattern("initial").call();
1312
1313 writeSubmodule("one", ObjectId
1314 .fromString("1000000000000000000000000000000000000000"));
1315
1316
1317
1318
1319
1320
1321
1322 String existing = read(Constants.DOT_GIT_MODULES);
1323 String context = "\n# context\n# more context\n# yet more context\n";
1324 write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1325 existing + context + context + context);
1326
1327 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1328 RevCommit initial = git.commit().setMessage("initial").call();
1329
1330 writeSubmodule("two", ObjectId
1331 .fromString("1000000000000000000000000000000000000000"));
1332 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1333
1334 RevCommit right = git.commit().setMessage("added two").call();
1335
1336 git.checkout().setStartPoint(initial).setName("left")
1337 .setCreateBranch(true).call();
1338
1339
1340
1341 addSubmoduleToIndex("three", ObjectId
1342 .fromString("1000000000000000000000000000000000000000"));
1343 new File(db.getWorkTree(), "three").mkdir();
1344
1345 existing = read(Constants.DOT_GIT_MODULES);
1346 String three = "[submodule \"three\"]\n\tpath = three\n\turl = "
1347 + db.getDirectory().toURI() + "\n";
1348 write(new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1349 three + existing);
1350
1351 git.add().addFilepattern(Constants.DOT_GIT_MODULES).call();
1352 git.commit().setMessage("a different one").call();
1353
1354 MergeResult result = git.merge().setStrategy(strategy).include(right)
1355 .call();
1356
1357 assertNull(result.getCheckoutConflicts());
1358 assertNull(result.getFailingPaths());
1359 for (String dir : Arrays.asList("one", "two", "three")) {
1360 assertTrue(new File(db.getWorkTree(), dir).isDirectory());
1361 }
1362 }
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397 @Theory
1398 public void checkMergeConflictInVirtualAncestor(
1399 MergeStrategy strategy) throws Exception {
1400 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1401 return;
1402 }
1403
1404 Git git = Git.wrap(db);
1405
1406
1407 writeTrashFile("a", "aaaaaaaa");
1408 writeTrashFile("b", "bbbbbbbb");
1409 git.add().addFilepattern("a").addFilepattern("b").call();
1410 RevCommit first = git.commit().setMessage("Initial commit").call();
1411
1412 writeTrashFile("a", "aaaaaaaaaaaaaaa");
1413 git.add().addFilepattern("a").call();
1414 RevCommit commitY = git.commit().setMessage("Modify a").call();
1415
1416 git.rm().addFilepattern("a").call();
1417
1418
1419 writeTrashFile("c", "cccccccc");
1420 git.add().addFilepattern("c").call();
1421 git.commit().setMessage("Delete modified a").call();
1422
1423
1424 git.checkout().setCreateBranch(true).setStartPoint(first)
1425 .setName("merge-both-sides").call();
1426 git.rm().addFilepattern("a").call();
1427 RevCommit commitX = git.commit().setMessage("Delete original a").call();
1428
1429
1430 git.checkout().setCreateBranch(true).setStartPoint(commitY)
1431 .setName("second-branch").call();
1432 git.rm().addFilepattern("a").call();
1433 git.commit().setMessage("Delete modified a").call();
1434
1435
1436 MergeResult mergeResult = git.merge().include(commitX)
1437 .setStrategy(strategy)
1438 .call();
1439 ObjectId commitB = mergeResult.getNewHead();
1440
1441
1442 git.checkout().setName("master").call();
1443 mergeResult = git.merge().include(commitX).setStrategy(strategy)
1444 .call();
1445
1446
1447
1448
1449 git.merge().include(commitB).call();
1450 }
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479 @Theory
1480 public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren(
1481 MergeStrategy strategy)
1482 throws Exception {
1483 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1484 return;
1485 }
1486
1487 Git git = Git.wrap(db);
1488
1489
1490 writeTrashFile("a", "initial content");
1491 git.add().addFilepattern("a").call();
1492 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1493
1494 writeTrashFile("a", "content in Ancestor 1");
1495 git.add().addFilepattern("a").call();
1496 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1497
1498 writeTrashFile("a", "content in Child 1 (commited on master)");
1499 git.add().addFilepattern("a").call();
1500
1501 git.commit().setMessage("Child 1 on master").call();
1502
1503 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1504
1505 git.rm().addFilepattern("a").call();
1506 writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1507 git.add().addFilepattern("a/content").call();
1508 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1509
1510
1511 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1512 writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1513 git.add().addFilepattern("a").call();
1514
1515 git.commit().setMessage("Child 2 on second-branch").call();
1516
1517
1518 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1519 assertEquals(mergeResult.getNewHead(), null);
1520 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1521
1522 git.rm().addFilepattern("a").call();
1523 git.rm().addFilepattern("a/content").call();
1524 writeTrashFile("a", "merge conflict resolution");
1525 git.add().addFilepattern("a").call();
1526 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1527 .call();
1528
1529
1530 git.checkout().setName("master").call();
1531 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1532 assertEquals(mergeResult.getNewHead(), null);
1533 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1534
1535
1536 git.rm().addFilepattern("a").call();
1537 git.rm().addFilepattern("a/content").call();
1538 writeTrashFile("a", "merge conflict resolution");
1539 git.add().addFilepattern("a").call();
1540
1541 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1542
1543
1544
1545
1546 mergeResult = git.merge().include(commitC3S).call();
1547 assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1548
1549 }
1550
1551 @Theory
1552 public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy)
1553 throws Exception {
1554 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1555 return;
1556 }
1557
1558 Git git = Git.wrap(db);
1559
1560
1561 writeTrashFile("a", "initial content");
1562 git.add().addFilepattern("a").call();
1563 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1564
1565 writeTrashFile("a", "content in Ancestor 1");
1566 git.add().addFilepattern("a").call();
1567 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1568
1569 writeTrashFile("a", "content in Child 1 (commited on master)");
1570 git.add().addFilepattern("a").call();
1571
1572 git.commit().setMessage("Child 1 on master").call();
1573
1574 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1575
1576
1577 git.rm().addFilepattern("a").call();
1578 writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)");
1579 git.add().addFilepattern("a/content").call();
1580 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1581
1582
1583 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1584 writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1585 git.add().addFilepattern("a").call();
1586
1587 git.commit().setMessage("Child 2 on second-branch").call();
1588
1589
1590 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1591 assertEquals(mergeResult.getNewHead(), null);
1592 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1593
1594 git.rm().addFilepattern("a").call();
1595 git.rm().addFilepattern("a/content").call();
1596 writeTrashFile("a",
1597 "content in Child 3 (commited on second-branch) - merge conflict resolution");
1598 git.add().addFilepattern("a").call();
1599 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict")
1600 .call();
1601
1602
1603 git.checkout().setName("master").call();
1604 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1605 assertEquals(mergeResult.getNewHead(), null);
1606 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1607
1608
1609 git.rm().addFilepattern("a").call();
1610 git.rm().addFilepattern("a/content").call();
1611 writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1612 git.add().addFilepattern("a").call();
1613
1614 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1615
1616
1617
1618 mergeResult = git.merge().include(commitC3S).call();
1619 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1620 String expected =
1621 "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1622 + "=======\n"
1623 + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1624 + ">>>>>>> " + commitC3S.name() + "\n";
1625 assertEquals(expected, read("a"));
1626
1627 assertEquals(
1628 "[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]",
1629 indexState(CONTENT));
1630 }
1631
1632
1633
1634
1635 @Theory
1636 public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy)
1637 throws Exception {
1638 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1639 return;
1640 }
1641
1642 Git git = Git.wrap(db);
1643
1644
1645 writeTrashFile("a/content", "initial content");
1646 git.add().addFilepattern("a/content").call();
1647 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1648
1649 writeTrashFile("a/content", "content in Ancestor 1");
1650 git.add().addFilepattern("a/content").call();
1651 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1652
1653 writeTrashFile("a/content", "content in Child 1 (commited on master)");
1654 git.add().addFilepattern("a/content").call();
1655
1656 git.commit().setMessage("Child 1 on master").call();
1657
1658 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1659
1660
1661 git.rm().addFilepattern("a/content").call();
1662 writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)");
1663 git.add().addFilepattern("a").call();
1664 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1665
1666
1667 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1668 writeTrashFile("a/content", "content in Child 2 (commited on second-branch)");
1669 git.add().addFilepattern("a/content").call();
1670
1671 git.commit().setMessage("Child 2 on second-branch").call();
1672
1673
1674 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1675 assertEquals(mergeResult.getNewHead(), null);
1676 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1677
1678 git.rm().addFilepattern("a").call();
1679 git.rm().addFilepattern("a/content").call();
1680 deleteTrashFile("a/content");
1681 deleteTrashFile("a");
1682 writeTrashFile("a", "content in Child 3 (commited on second-branch) - merge conflict resolution");
1683 git.add().addFilepattern("a").call();
1684 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1685
1686
1687 git.checkout().setName("master").call();
1688 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1689 assertEquals(mergeResult.getNewHead(), null);
1690 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1691
1692
1693 git.rm().addFilepattern("a").call();
1694 git.rm().addFilepattern("a/content").call();
1695 deleteTrashFile("a/content");
1696 deleteTrashFile("a");
1697 writeTrashFile("a", "content in Child 4 (commited on master) - merge conflict resolution");
1698 git.add().addFilepattern("a").call();
1699
1700 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1701
1702
1703
1704 mergeResult = git.merge().include(commitC3S).call();
1705 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1706 String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n"
1707 + "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n"
1708 + ">>>>>>> " + commitC3S.name() + "\n";
1709 assertEquals(expected, read("a"));
1710
1711 assertEquals(
1712 "[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]",
1713 indexState(CONTENT));
1714 }
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725 @Theory
1726 public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
1727 if (!strategy.equals(MergeStrategy.RECURSIVE)) {
1728 return;
1729 }
1730
1731 Git git = Git.wrap(db);
1732
1733
1734 writeTrashFile("c", "initial file");
1735 git.add().addFilepattern("c").call();
1736 RevCommit commitI = git.commit().setMessage("Initial commit").call();
1737
1738 File a = writeTrashFile("a", "content in Ancestor");
1739 git.add().addFilepattern("a").call();
1740 RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
1741
1742 a = writeTrashFile("a", "content in Child 1 (commited on master)");
1743 git.add().addFilepattern("a").call();
1744
1745 git.commit().setMessage("Child 1 on master").call();
1746
1747 git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
1748
1749 a = writeTrashFile("a", "content in Ancestor");
1750 a.setExecutable(true);
1751 git.add().addFilepattern("a").call();
1752 RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
1753
1754
1755 git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
1756 a = writeTrashFile("a", "content in Child 2 (commited on second-branch)");
1757 git.add().addFilepattern("a").call();
1758
1759 git.commit().setMessage("Child 2 on second-branch").call();
1760
1761
1762 MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1763 assertEquals(mergeResult.getNewHead(), null);
1764 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1765
1766 a = writeTrashFile("a", "merge conflict resolution");
1767 a.setExecutable(false);
1768 git.add().addFilepattern("a").call();
1769 RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
1770
1771
1772 git.checkout().setName("master").call();
1773 mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
1774 assertEquals(mergeResult.getNewHead(), null);
1775 assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
1776
1777
1778 a = writeTrashFile("a", "merge conflict resolution");
1779 a.setExecutable(false);
1780 git.add().addFilepattern("a").call();
1781
1782 git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
1783
1784
1785
1786
1787 mergeResult = git.merge().include(commitC3S).call();
1788 assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
1789
1790 }
1791
1792 private void writeSubmodule(String path, ObjectId commit)
1793 throws IOException, ConfigInvalidException {
1794 addSubmoduleToIndex(path, commit);
1795 new File(db.getWorkTree(), path).mkdir();
1796
1797 StoredConfig config = db.getConfig();
1798 config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1799 ConfigConstants.CONFIG_KEY_URL,
1800 db.getDirectory().toURI().toString());
1801 config.save();
1802
1803 FileBasedConfig modulesConfig = new FileBasedConfig(
1804 new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
1805 db.getFS());
1806 modulesConfig.load();
1807 modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
1808 ConfigConstants.CONFIG_KEY_PATH, path);
1809 modulesConfig.save();
1810
1811 }
1812
1813 private void addSubmoduleToIndex(String path, ObjectId commit)
1814 throws IOException {
1815 DirCache cache = db.lockDirCache();
1816 DirCacheEditor editor = cache.editor();
1817 editor.add(new DirCacheEditor.PathEdit(path) {
1818
1819 @Override
1820 public void apply(DirCacheEntry ent) {
1821 ent.setFileMode(FileMode.GITLINK);
1822 ent.setObjectId(commit);
1823 }
1824 });
1825 editor.commit();
1826 }
1827
1828
1829
1830 private void checkConsistentLastModified(String... pathes)
1831 throws IOException {
1832 DirCache dc = db.readDirCache();
1833 File workTree = db.getWorkTree();
1834 for (String path : pathes)
1835 assertEquals(
1836 "IndexEntry with path "
1837 + path
1838 + " has lastmodified which is different from the worktree file",
1839 FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
1840 dc.getEntry(path)
1841 .getLastModifiedInstant());
1842 }
1843
1844
1845
1846
1847
1848
1849
1850 private void checkModificationTimeStampOrder(String... pathes) {
1851 Instant lastMod = EPOCH;
1852 for (String p : pathes) {
1853 boolean strong = p.startsWith("<");
1854 boolean fixed = p.charAt(strong ? 1 : 0) == '*';
1855 p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
1856 Instant curMod = fixed ? Instant.parse(p)
1857 : FS.DETECTED
1858 .lastModifiedInstant(new File(db.getWorkTree(), p));
1859 if (strong) {
1860 assertTrue("path " + p + " is not younger than predecesssor",
1861 curMod.compareTo(lastMod) > 0);
1862 } else {
1863 assertTrue("path " + p + " is older than predecesssor",
1864 curMod.compareTo(lastMod) >= 0);
1865 }
1866 }
1867 }
1868
1869 private String readBlob(ObjectId treeish, String path) throws Exception {
1870 try (TestRepository<?> tr = new TestRepository<>(db);
1871 RevWalk rw = tr.getRevWalk()) {
1872 db.incrementOpen();
1873 RevTree tree = rw.parseTree(treeish);
1874 RevObject obj = tr.get(tree, path);
1875 if (obj == null) {
1876 return null;
1877 }
1878 return new String(
1879 rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
1880 }
1881 }
1882 }