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