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