Code

RelNotes: the first batch of topics graduated to 'master'
[git.git] / t / t6036-recursive-corner-cases.sh
1 #!/bin/sh
3 test_description='recursive merge corner cases involving criss-cross merges'
5 . ./test-lib.sh
7 get_clean_checkout () {
8         git reset --hard &&
9         git clean -fdqx &&
10         git checkout "$1"
11 }
13 #
14 #  L1  L2
15 #   o---o
16 #  / \ / \
17 # o   X   ?
18 #  \ / \ /
19 #   o---o
20 #  R1  R2
21 #
23 test_expect_success 'setup basic criss-cross + rename with no modifications' '
24         ten="0 1 2 3 4 5 6 7 8 9" &&
25         for i in $ten
26         do
27                 echo line $i in a sample file
28         done >one &&
29         for i in $ten
30         do
31                 echo line $i in another sample file
32         done >two &&
33         git add one two &&
34         test_tick && git commit -m initial &&
36         git branch L1 &&
37         git checkout -b R1 &&
38         git mv one three &&
39         test_tick && git commit -m R1 &&
41         git checkout L1 &&
42         git mv two three &&
43         test_tick && git commit -m L1 &&
45         git checkout L1^0 &&
46         test_tick && git merge -s ours R1 &&
47         git tag L2 &&
49         git checkout R1^0 &&
50         test_tick && git merge -s ours L1 &&
51         git tag R2
52 '
54 test_expect_success 'merge simple rename+criss-cross with no modifications' '
55         git reset --hard &&
56         git checkout L2^0 &&
58         test_must_fail git merge -s recursive R2^0 &&
60         test 2 = $(git ls-files -s | wc -l) &&
61         test 2 = $(git ls-files -u | wc -l) &&
62         test 2 = $(git ls-files -o | wc -l) &&
64         test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
65         test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
67         test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
68         test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
69 '
71 #
72 # Same as before, but modify L1 slightly:
73 #
74 #  L1m L2
75 #   o---o
76 #  / \ / \
77 # o   X   ?
78 #  \ / \ /
79 #   o---o
80 #  R1  R2
81 #
83 test_expect_success 'setup criss-cross + rename merges with basic modification' '
84         git rm -rf . &&
85         git clean -fdqx &&
86         rm -rf .git &&
87         git init &&
89         ten="0 1 2 3 4 5 6 7 8 9"
90         for i in $ten
91         do
92                 echo line $i in a sample file
93         done >one &&
94         for i in $ten
95         do
96                 echo line $i in another sample file
97         done >two &&
98         git add one two &&
99         test_tick && git commit -m initial &&
101         git branch L1 &&
102         git checkout -b R1 &&
103         git mv one three &&
104         echo more >>two &&
105         git add two &&
106         test_tick && git commit -m R1 &&
108         git checkout L1 &&
109         git mv two three &&
110         test_tick && git commit -m L1 &&
112         git checkout L1^0 &&
113         test_tick && git merge -s ours R1 &&
114         git tag L2 &&
116         git checkout R1^0 &&
117         test_tick && git merge -s ours L1 &&
118         git tag R2
121 test_expect_success 'merge criss-cross + rename merges with basic modification' '
122         git reset --hard &&
123         git checkout L2^0 &&
125         test_must_fail git merge -s recursive R2^0 &&
127         test 2 = $(git ls-files -s | wc -l) &&
128         test 2 = $(git ls-files -u | wc -l) &&
129         test 2 = $(git ls-files -o | wc -l) &&
131         test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
132         test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
134         test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
135         test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
139 # For the next test, we start with three commits in two lines of development
140 # which setup a rename/add conflict:
141 #   Commit A: File 'a' exists
142 #   Commit B: Rename 'a' -> 'new_a'
143 #   Commit C: Modify 'a', create different 'new_a'
144 # Later, two different people merge and resolve differently:
145 #   Commit D: Merge B & C, ignoring separately created 'new_a'
146 #   Commit E: Merge B & C making use of some piece of secondary 'new_a'
147 # Finally, someone goes to merge D & E.  Does git detect the conflict?
149 #      B   D
150 #      o---o
151 #     / \ / \
152 #  A o   X   ? F
153 #     \ / \ /
154 #      o---o
155 #      C   E
158 test_expect_success 'setup differently handled merges of rename/add conflict' '
159         git rm -rf . &&
160         git clean -fdqx &&
161         rm -rf .git &&
162         git init &&
164         printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a &&
165         git add a &&
166         test_tick && git commit -m A &&
168         git branch B &&
169         git checkout -b C &&
170         echo 10 >>a &&
171         echo "other content" >>new_a &&
172         git add a new_a &&
173         test_tick && git commit -m C &&
175         git checkout B &&
176         git mv a new_a &&
177         test_tick && git commit -m B &&
179         git checkout B^0 &&
180         test_must_fail git merge C &&
181         git clean -f &&
182         test_tick && git commit -m D &&
183         git tag D &&
185         git checkout C^0 &&
186         test_must_fail git merge B &&
187         rm new_a~HEAD new_a &&
188         printf "Incorrectly merged content" >>new_a &&
189         git add -u &&
190         test_tick && git commit -m E &&
191         git tag E
194 test_expect_success 'git detects differently handled merges conflict' '
195         git reset --hard &&
196         git checkout D^0 &&
198         git merge -s recursive E^0 && {
199                 echo "BAD: should have conflicted"
200                 test "Incorrectly merged content" = "$(cat new_a)" &&
201                         echo "BAD: Silently accepted wrong content"
202                 return 1
203         }
205         test 3 = $(git ls-files -s | wc -l) &&
206         test 3 = $(git ls-files -u | wc -l) &&
207         test 0 = $(git ls-files -o | wc -l) &&
209         test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
210         test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
212         git cat-file -p B:new_a >>merged &&
213         git cat-file -p C:new_a >>merge-me &&
214         >empty &&
215         test_must_fail git merge-file \
216                 -L "Temporary merge branch 2" \
217                 -L "" \
218                 -L "Temporary merge branch 1" \
219                 merged empty merge-me &&
220         test $(git rev-parse :1:new_a) = $(git hash-object merged)
224 # criss-cross + modify/delete:
226 #      B   D
227 #      o---o
228 #     / \ / \
229 #  A o   X   ? F
230 #     \ / \ /
231 #      o---o
232 #      C   E
234 #   Commit A: file with contents 'A\n'
235 #   Commit B: file with contents 'B\n'
236 #   Commit C: file not present
237 #   Commit D: file with contents 'B\n'
238 #   Commit E: file not present
240 # Merging commits D & E should result in modify/delete conflict.
242 test_expect_success 'setup criss-cross + modify/delete resolved differently' '
243         git rm -rf . &&
244         git clean -fdqx &&
245         rm -rf .git &&
246         git init &&
248         echo A >file &&
249         git add file &&
250         test_tick &&
251         git commit -m A &&
253         git branch B &&
254         git checkout -b C &&
255         git rm file &&
256         test_tick &&
257         git commit -m C &&
259         git checkout B &&
260         echo B >file &&
261         git add file &&
262         test_tick &&
263         git commit -m B &&
265         git checkout B^0 &&
266         test_must_fail git merge C &&
267         echo B >file &&
268         git add file &&
269         test_tick &&
270         git commit -m D &&
271         git tag D &&
273         git checkout C^0 &&
274         test_must_fail git merge B &&
275         git rm file &&
276         test_tick &&
277         git commit -m E &&
278         git tag E
281 test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
282         git checkout D^0 &&
284         test_must_fail git merge -s recursive E^0 &&
286         test 2 -eq $(git ls-files -s | wc -l) &&
287         test 2 -eq $(git ls-files -u | wc -l) &&
289         test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
290         test $(git rev-parse :2:file) = $(git rev-parse B:file)
293 test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
294         git reset --hard &&
295         git checkout E^0 &&
297         test_must_fail git merge -s recursive D^0 &&
299         test 2 -eq $(git ls-files -s | wc -l) &&
300         test 2 -eq $(git ls-files -u | wc -l) &&
302         test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
303         test $(git rev-parse :3:file) = $(git rev-parse B:file)
307 # criss-cross + modify/modify with very contrived file contents:
309 #      B   D
310 #      o---o
311 #     / \ / \
312 #  A o   X   ? F
313 #     \ / \ /
314 #      o---o
315 #      C   E
317 #   Commit A: file with contents 'A\n'
318 #   Commit B: file with contents 'B\n'
319 #   Commit C: file with contents 'C\n'
320 #   Commit D: file with contents 'D\n'
321 #   Commit E: file with contents:
322 #      <<<<<<< Temporary merge branch 1
323 #      C
324 #      =======
325 #      B
326 #      >>>>>>> Temporary merge branch 2
328 # Now, when we merge commits D & E, does git detect the conflict?
330 test_expect_success 'setup differently handled merges of content conflict' '
331         git clean -fdqx &&
332         rm -rf .git &&
333         git init &&
335         echo A >file &&
336         git add file &&
337         test_tick &&
338         git commit -m A &&
340         git branch B &&
341         git checkout -b C &&
342         echo C >file &&
343         git add file &&
344         test_tick &&
345         git commit -m C &&
347         git checkout B &&
348         echo B >file &&
349         git add file &&
350         test_tick &&
351         git commit -m B &&
353         git checkout B^0 &&
354         test_must_fail git merge C &&
355         echo D >file &&
356         git add file &&
357         test_tick &&
358         git commit -m D &&
359         git tag D &&
361         git checkout C^0 &&
362         test_must_fail git merge B &&
363         cat <<EOF >file &&
364 <<<<<<< Temporary merge branch 1
366 =======
368 >>>>>>> Temporary merge branch 2
369 EOF
370         git add file &&
371         test_tick &&
372         git commit -m E &&
373         git tag E
376 test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
377         git checkout D^0 &&
379         test_must_fail git merge -s recursive E^0 &&
381         test 3 -eq $(git ls-files -s | wc -l) &&
382         test 3 -eq $(git ls-files -u | wc -l) &&
383         test 0 -eq $(git ls-files -o | wc -l) &&
385         test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
386         test $(git rev-parse :3:file) = $(git rev-parse E:file)
390 # criss-cross + d/f conflict via add/add:
391 #   Commit A: Neither file 'a' nor directory 'a/' exist.
392 #   Commit B: Introduce 'a'
393 #   Commit C: Introduce 'a/file'
394 #   Commit D: Merge B & C, keeping 'a' and deleting 'a/'
396 # Two different later cases:
397 #   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
398 #   Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
400 #      B   D
401 #      o---o
402 #     / \ / \
403 #  A o   X   ? F
404 #     \ / \ /
405 #      o---o
406 #      C   E1 or E2
408 # Merging D & E1 requires we first create a virtual merge base X from
409 # merging A & B in memory.  Now, if X could keep both 'a' and 'a/file' in
410 # the index, then the merge of D & E1 could be resolved cleanly with both
411 # 'a' and 'a/file' removed.  Since git does not currently allow creating
412 # such a tree, the best we can do is have X contain both 'a~<unique>' and
413 # 'a/file' resulting in the merge of D and E1 having a rename/delete
414 # conflict for 'a'.  (Although this merge appears to be unsolvable with git
415 # currently, git could do a lot better than it currently does with these
416 # d/f conflicts, which is the purpose of this test.)
418 # Merge of D & E2 has similar issues for path 'a', but should always result
419 # in a modify/delete conflict for path 'a/file'.
421 # We run each merge in both directions, to check for directional issues
422 # with D/F conflict handling.
425 test_expect_success 'setup differently handled merges of directory/file conflict' '
426         git rm -rf . &&
427         git clean -fdqx &&
428         rm -rf .git &&
429         git init &&
431         >ignore-me &&
432         git add ignore-me &&
433         test_tick &&
434         git commit -m A &&
435         git tag A &&
437         git branch B &&
438         git checkout -b C &&
439         mkdir a &&
440         echo 10 >a/file &&
441         git add a/file &&
442         test_tick &&
443         git commit -m C &&
445         git checkout B &&
446         echo 5 >a &&
447         git add a &&
448         test_tick &&
449         git commit -m B &&
451         git checkout B^0 &&
452         test_must_fail git merge C &&
453         git clean -f &&
454         rm -rf a/ &&
455         echo 5 >a &&
456         git add a &&
457         test_tick &&
458         git commit -m D &&
459         git tag D &&
461         git checkout C^0 &&
462         test_must_fail git merge B &&
463         git clean -f &&
464         git rm --cached a &&
465         echo 10 >a/file &&
466         git add a/file &&
467         test_tick &&
468         git commit -m E1 &&
469         git tag E1 &&
471         git checkout C^0 &&
472         test_must_fail git merge B &&
473         git clean -f &&
474         git rm --cached a &&
475         printf "10\n11\n" >a/file &&
476         git add a/file &&
477         test_tick &&
478         git commit -m E2 &&
479         git tag E2
482 test_expect_success 'merge of D & E1 fails but has appropriate contents' '
483         get_clean_checkout D^0 &&
485         test_must_fail git merge -s recursive E1^0 &&
487         test 2 -eq $(git ls-files -s | wc -l) &&
488         test 1 -eq $(git ls-files -u | wc -l) &&
489         test 0 -eq $(git ls-files -o | wc -l) &&
491         test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
492         test $(git rev-parse :2:a) = $(git rev-parse B:a)
495 test_expect_success 'merge of E1 & D fails but has appropriate contents' '
496         get_clean_checkout E1^0 &&
498         test_must_fail git merge -s recursive D^0 &&
500         test 2 -eq $(git ls-files -s | wc -l) &&
501         test 1 -eq $(git ls-files -u | wc -l) &&
502         test 0 -eq $(git ls-files -o | wc -l) &&
504         test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
505         test $(git rev-parse :3:a) = $(git rev-parse B:a)
508 test_expect_success 'merge of D & E2 fails but has appropriate contents' '
509         get_clean_checkout D^0 &&
511         test_must_fail git merge -s recursive E2^0 &&
513         test 4 -eq $(git ls-files -s | wc -l) &&
514         test 3 -eq $(git ls-files -u | wc -l) &&
515         test 1 -eq $(git ls-files -o | wc -l) &&
517         test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
518         test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
519         test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
520         test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
522         test -f a~HEAD
525 test_expect_success 'merge of E2 & D fails but has appropriate contents' '
526         get_clean_checkout E2^0 &&
528         test_must_fail git merge -s recursive D^0 &&
530         test 4 -eq $(git ls-files -s | wc -l) &&
531         test 3 -eq $(git ls-files -u | wc -l) &&
532         test 1 -eq $(git ls-files -o | wc -l) &&
534         test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
535         test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
536         test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)
537         test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
539         test -f a~D^0
543 # criss-cross with rename/rename(1to2)/modify followed by
544 # rename/rename(2to1)/modify:
546 #      B   D
547 #      o---o
548 #     / \ / \
549 #  A o   X   ? F
550 #     \ / \ /
551 #      o---o
552 #      C   E
554 #   Commit A: new file: a
555 #   Commit B: rename a->b, modifying by adding a line
556 #   Commit C: rename a->c
557 #   Commit D: merge B&C, resolving conflict by keeping contents in newname
558 #   Commit E: merge B&C, resolving conflict similar to D but adding another line
560 # There is a conflict merging B & C, but one of filename not of file
561 # content.  Whoever created D and E chose specific resolutions for that
562 # conflict resolution.  Now, since: (1) there is no content conflict
563 # merging B & C, (2) D does not modify that merged content further, and (3)
564 # both D & E resolve the name conflict in the same way, the modification to
565 # newname in E should not cause any conflicts when it is merged with D.
566 # (Note that this can be accomplished by having the virtual merge base have
567 # the merged contents of b and c stored in a file named a, which seems like
568 # the most logical choice anyway.)
570 # Comment from Junio: I do not necessarily agree with the choice "a", but
571 # it feels sound to say "B and C do not agree what the final pathname
572 # should be, but we know this content was derived from the common A:a so we
573 # use one path whose name is arbitrary in the virtual merge base X between
574 # D and E" and then further let the rename detection to notice that that
575 # arbitrary path gets renamed between X-D to "newname" and X-E also to
576 # "newname" to resolve it as both sides renaming it to the same new
577 # name. It is akin to what we do at the content level, i.e. "B and C do not
578 # agree what the final contents should be, so we leave the conflict marker
579 # but that may cancel out at the final merge stage".
581 test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
582         git reset --hard &&
583         git rm -rf . &&
584         git clean -fdqx &&
585         rm -rf .git &&
586         git init &&
588         printf "1\n2\n3\n4\n5\n6\n" >a &&
589         git add a &&
590         git commit -m A &&
591         git tag A &&
593         git checkout -b B A &&
594         git mv a b &&
595         echo 7 >>b &&
596         git add -u &&
597         git commit -m B &&
599         git checkout -b C A &&
600         git mv a c &&
601         git commit -m C &&
603         git checkout -q B^0 &&
604         git merge --no-commit -s ours C^0 &&
605         git mv b newname &&
606         git commit -m "Merge commit C^0 into HEAD" &&
607         git tag D &&
609         git checkout -q C^0 &&
610         git merge --no-commit -s ours B^0 &&
611         git mv c newname &&
612         printf "7\n8\n" >>newname &&
613         git add -u &&
614         git commit -m "Merge commit B^0 into HEAD" &&
615         git tag E
618 test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
619         git checkout D^0 &&
621         git merge -s recursive E^0 &&
623         test 1 -eq $(git ls-files -s | wc -l) &&
624         test 0 -eq $(git ls-files -u | wc -l) &&
625         test 0 -eq $(git ls-files -o | wc -l) &&
627         test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
631 # criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
633 #      B   D
634 #      o---o
635 #     / \ / \
636 #  A o   X   ? F
637 #     \ / \ /
638 #      o---o
639 #      C   E
641 #   Commit A: new file: a
642 #   Commit B: rename a->b
643 #   Commit C: rename a->c, add different a
644 #   Commit D: merge B&C, keeping b&c and (new) a modified at beginning
645 #   Commit E: merge B&C, keeping b&c and (new) a modified at end
647 # Merging commits D & E should result in no conflict; doing so correctly
648 # requires getting the virtual merge base (from merging B&C) right, handling
649 # renaming carefully (both in the virtual merge base and later), and getting
650 # content merge handled.
652 test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '
653         git rm -rf . &&
654         git clean -fdqx &&
655         rm -rf .git &&
656         git init &&
658         printf "lots\nof\nwords\nand\ncontent\n" >a &&
659         git add a &&
660         git commit -m A &&
661         git tag A &&
663         git checkout -b B A &&
664         git mv a b &&
665         git commit -m B &&
667         git checkout -b C A &&
668         git mv a c &&
669         printf "2\n3\n4\n5\n6\n7\n" >a &&
670         git add a &&
671         git commit -m C &&
673         git checkout B^0 &&
674         git merge --no-commit -s ours C^0 &&
675         git checkout C -- a c &&
676         mv a old_a &&
677         echo 1 >a &&
678         cat old_a >>a &&
679         rm old_a &&
680         git add -u &&
681         git commit -m "Merge commit C^0 into HEAD" &&
682         git tag D &&
684         git checkout C^0 &&
685         git merge --no-commit -s ours B^0 &&
686         git checkout B -- b &&
687         echo 8 >>a &&
688         git add -u &&
689         git commit -m "Merge commit B^0 into HEAD" &&
690         git tag E
693 test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
694         git checkout D^0 &&
696         git merge -s recursive E^0 &&
698         test 3 -eq $(git ls-files -s | wc -l) &&
699         test 0 -eq $(git ls-files -u | wc -l) &&
700         test 0 -eq $(git ls-files -o | wc -l) &&
702         test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
703         test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
704         test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
708 # criss-cross with rename/rename(1to2)/add-dest + simple modify:
710 #      B   D
711 #      o---o
712 #     / \ / \
713 #  A o   X   ? F
714 #     \ / \ /
715 #      o---o
716 #      C   E
718 #   Commit A: new file: a
719 #   Commit B: rename a->b, add c
720 #   Commit C: rename a->c
721 #   Commit D: merge B&C, keeping A:a and B:c
722 #   Commit E: merge B&C, keeping A:a and slightly modified c from B
724 # Merging commits D & E should result in no conflict.  The virtual merge
725 # base of B & C needs to not delete B:c for that to work, though...
727 test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
728         git rm -rf . &&
729         git clean -fdqx &&
730         rm -rf .git &&
731         git init &&
733         >a &&
734         git add a &&
735         git commit -m A &&
736         git tag A &&
738         git checkout -b B A &&
739         git mv a b &&
740         printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
741         git add c &&
742         git commit -m B &&
744         git checkout -b C A &&
745         git mv a c &&
746         git commit -m C &&
748         git checkout B^0 &&
749         git merge --no-commit -s ours C^0 &&
750         git mv b a &&
751         git commit -m "D is like B but renames b back to a" &&
752         git tag D &&
754         git checkout B^0 &&
755         git merge --no-commit -s ours C^0 &&
756         git mv b a &&
757         echo 8 >>c &&
758         git add c &&
759         git commit -m "E like D but has mod in c" &&
760         git tag E
763 test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
764         git checkout D^0 &&
766         git merge -s recursive E^0 &&
768         test 2 -eq $(git ls-files -s | wc -l) &&
769         test 0 -eq $(git ls-files -u | wc -l) &&
770         test 0 -eq $(git ls-files -o | wc -l) &&
772         test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
773         test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
776 test_done