4a94fa5ba640d9251c318c08ce0f9802b0f25431
1#!/bin/sh
2
3test_description='recursive merge corner cases involving criss-cross merges'
4
5. ./test-lib.sh
6
7#
8# L1 L2
9# o---o
10# / \ / \
11# o X ?
12# \ / \ /
13# o---o
14# R1 R2
15#
16
17test_expect_success 'setup basic criss-cross + rename with no modifications' '
18 test_create_repo basic-rename &&
19 (
20 cd basic-rename &&
21
22 ten="0 1 2 3 4 5 6 7 8 9" &&
23 for i in $ten
24 do
25 echo line $i in a sample file
26 done >one &&
27 for i in $ten
28 do
29 echo line $i in another sample file
30 done >two &&
31 git add one two &&
32 test_tick && git commit -m initial &&
33
34 git branch L1 &&
35 git checkout -b R1 &&
36 git mv one three &&
37 test_tick && git commit -m R1 &&
38
39 git checkout L1 &&
40 git mv two three &&
41 test_tick && git commit -m L1 &&
42
43 git checkout L1^0 &&
44 test_tick && git merge -s ours R1 &&
45 git tag L2 &&
46
47 git checkout R1^0 &&
48 test_tick && git merge -s ours L1 &&
49 git tag R2
50 )
51'
52
53test_expect_success 'merge simple rename+criss-cross with no modifications' '
54 (
55 cd basic-rename &&
56
57 git reset --hard &&
58 git checkout L2^0 &&
59
60 test_must_fail git merge -s recursive R2^0 &&
61
62 git ls-files -s >out &&
63 test_line_count = 2 out &&
64 git ls-files -u >out &&
65 test_line_count = 2 out &&
66 git ls-files -o >out &&
67 test_line_count = 3 out &&
68
69 git rev-parse >expect \
70 L2:three R2:three \
71 L2:three R2:three &&
72 git rev-parse >actual \
73 :2:three :3:three &&
74 git hash-object >>actual \
75 three~HEAD three~R2^0
76 test_cmp expect actual
77 )
78'
79
80#
81# Same as before, but modify L1 slightly:
82#
83# L1m L2
84# o---o
85# / \ / \
86# o X ?
87# \ / \ /
88# o---o
89# R1 R2
90#
91
92test_expect_success 'setup criss-cross + rename merges with basic modification' '
93 test_create_repo rename-modify &&
94 (
95 cd rename-modify &&
96
97 ten="0 1 2 3 4 5 6 7 8 9" &&
98 for i in $ten
99 do
100 echo line $i in a sample file
101 done >one &&
102 for i in $ten
103 do
104 echo line $i in another sample file
105 done >two &&
106 git add one two &&
107 test_tick && git commit -m initial &&
108
109 git branch L1 &&
110 git checkout -b R1 &&
111 git mv one three &&
112 echo more >>two &&
113 git add two &&
114 test_tick && git commit -m R1 &&
115
116 git checkout L1 &&
117 git mv two three &&
118 test_tick && git commit -m L1 &&
119
120 git checkout L1^0 &&
121 test_tick && git merge -s ours R1 &&
122 git tag L2 &&
123
124 git checkout R1^0 &&
125 test_tick && git merge -s ours L1 &&
126 git tag R2
127 )
128'
129
130test_expect_success 'merge criss-cross + rename merges with basic modification' '
131 (
132 cd rename-modify &&
133
134 git checkout L2^0 &&
135
136 test_must_fail git merge -s recursive R2^0 &&
137
138 git ls-files -s >out &&
139 test_line_count = 2 out &&
140 git ls-files -u >out &&
141 test_line_count = 2 out &&
142 git ls-files -o >out &&
143 test_line_count = 3 out &&
144
145 git rev-parse >expect \
146 L2:three R2:three \
147 L2:three R2:three &&
148 git rev-parse >actual \
149 :2:three :3:three &&
150 git hash-object >>actual \
151 three~HEAD three~R2^0
152 test_cmp expect actual
153 )
154'
155
156#
157# For the next test, we start with three commits in two lines of development
158# which setup a rename/add conflict:
159# Commit A: File 'a' exists
160# Commit B: Rename 'a' -> 'new_a'
161# Commit C: Modify 'a', create different 'new_a'
162# Later, two different people merge and resolve differently:
163# Commit D: Merge B & C, ignoring separately created 'new_a'
164# Commit E: Merge B & C making use of some piece of secondary 'new_a'
165# Finally, someone goes to merge D & E. Does git detect the conflict?
166#
167# B D
168# o---o
169# / \ / \
170# A o X ? F
171# \ / \ /
172# o---o
173# C E
174#
175
176test_expect_success 'setup differently handled merges of rename/add conflict' '
177 test_create_repo rename-add &&
178 (
179 cd rename-add &&
180
181 printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a &&
182 git add a &&
183 test_tick && git commit -m A &&
184
185 git branch B &&
186 git checkout -b C &&
187 echo 10 >>a &&
188 echo "other content" >>new_a &&
189 git add a new_a &&
190 test_tick && git commit -m C &&
191
192 git checkout B &&
193 git mv a new_a &&
194 test_tick && git commit -m B &&
195
196 git checkout B^0 &&
197 test_must_fail git merge C &&
198 git clean -f &&
199 test_tick && git commit -m D &&
200 git tag D &&
201
202 git checkout C^0 &&
203 test_must_fail git merge B &&
204 rm new_a~HEAD new_a &&
205 printf "Incorrectly merged content" >>new_a &&
206 git add -u &&
207 test_tick && git commit -m E &&
208 git tag E
209 )
210'
211
212test_expect_success 'git detects differently handled merges conflict' '
213 (
214 cd rename-add &&
215
216 git checkout D^0 &&
217
218 test_must_fail git merge -s recursive E^0 &&
219
220 git ls-files -s >out &&
221 test_line_count = 3 out &&
222 git ls-files -u >out &&
223 test_line_count = 3 out &&
224 git ls-files -o >out &&
225 test_line_count = 1 out &&
226
227 git rev-parse >expect \
228 D:new_a E:new_a &&
229 git rev-parse >actual \
230 :2:new_a :3:new_a &&
231 test_cmp expect actual
232
233 git cat-file -p B:new_a >ours &&
234 git cat-file -p C:new_a >theirs &&
235 >empty &&
236 test_must_fail git merge-file \
237 -L "Temporary merge branch 2" \
238 -L "" \
239 -L "Temporary merge branch 1" \
240 ours empty theirs &&
241 sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect &&
242 git cat-file -p :1:new_a >actual &&
243 test_cmp expect actual
244 )
245'
246
247#
248# criss-cross + modify/delete:
249#
250# B D
251# o---o
252# / \ / \
253# A o X ? F
254# \ / \ /
255# o---o
256# C E
257#
258# Commit A: file with contents 'A\n'
259# Commit B: file with contents 'B\n'
260# Commit C: file not present
261# Commit D: file with contents 'B\n'
262# Commit E: file not present
263#
264# Merging commits D & E should result in modify/delete conflict.
265
266test_expect_success 'setup criss-cross + modify/delete resolved differently' '
267 test_create_repo modify-delete &&
268 (
269 cd modify-delete &&
270
271 echo A >file &&
272 git add file &&
273 test_tick &&
274 git commit -m A &&
275
276 git branch B &&
277 git checkout -b C &&
278 git rm file &&
279 test_tick &&
280 git commit -m C &&
281
282 git checkout B &&
283 echo B >file &&
284 git add file &&
285 test_tick &&
286 git commit -m B &&
287
288 git checkout B^0 &&
289 test_must_fail git merge C &&
290 echo B >file &&
291 git add file &&
292 test_tick &&
293 git commit -m D &&
294 git tag D &&
295
296 git checkout C^0 &&
297 test_must_fail git merge B &&
298 git rm file &&
299 test_tick &&
300 git commit -m E &&
301 git tag E
302 )
303'
304
305test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
306 (
307 cd modify-delete &&
308
309 git checkout D^0 &&
310
311 test_must_fail git merge -s recursive E^0 &&
312
313 git ls-files -s >out &&
314 test_line_count = 2 out &&
315 git ls-files -u >out &&
316 test_line_count = 2 out &&
317
318 git rev-parse >expect \
319 master:file B:file &&
320 git rev-parse >actual \
321 :1:file :2:file &&
322 test_cmp expect actual
323 )
324'
325
326test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
327 (
328 cd modify-delete &&
329
330 git reset --hard &&
331 git checkout E^0 &&
332
333 test_must_fail git merge -s recursive D^0 &&
334
335 git ls-files -s >out &&
336 test_line_count = 2 out &&
337 git ls-files -u >out &&
338 test_line_count = 2 out &&
339
340 git rev-parse >expect \
341 master:file B:file &&
342 git rev-parse >actual \
343 :1:file :3:file &&
344 test_cmp expect actual
345 )
346'
347
348#
349# criss-cross + d/f conflict via add/add:
350# Commit A: Neither file 'a' nor directory 'a/' exists.
351# Commit B: Introduce 'a'
352# Commit C: Introduce 'a/file'
353# Commit D: Merge B & C, keeping 'a' and deleting 'a/'
354#
355# Two different later cases:
356# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
357# Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
358#
359# B D
360# o---o
361# / \ / \
362# A o X ? F
363# \ / \ /
364# o---o
365# C E1 or E2
366#
367# Merging D & E1 requires we first create a virtual merge base X from
368# merging A & B in memory. Now, if X could keep both 'a' and 'a/file' in
369# the index, then the merge of D & E1 could be resolved cleanly with both
370# 'a' and 'a/file' removed. Since git does not currently allow creating
371# such a tree, the best we can do is have X contain both 'a~<unique>' and
372# 'a/file' resulting in the merge of D and E1 having a rename/delete
373# conflict for 'a'. (Although this merge appears to be unsolvable with git
374# currently, git could do a lot better than it currently does with these
375# d/f conflicts, which is the purpose of this test.)
376#
377# Merge of D & E2 has similar issues for path 'a', but should always result
378# in a modify/delete conflict for path 'a/file'.
379#
380# We run each merge in both directions, to check for directional issues
381# with D/F conflict handling.
382#
383
384test_expect_success 'setup differently handled merges of directory/file conflict' '
385 test_create_repo directory-file &&
386 (
387 cd directory-file &&
388
389 >ignore-me &&
390 git add ignore-me &&
391 test_tick &&
392 git commit -m A &&
393 git tag A &&
394
395 git branch B &&
396 git checkout -b C &&
397 mkdir a &&
398 echo 10 >a/file &&
399 git add a/file &&
400 test_tick &&
401 git commit -m C &&
402
403 git checkout B &&
404 echo 5 >a &&
405 git add a &&
406 test_tick &&
407 git commit -m B &&
408
409 git checkout B^0 &&
410 test_must_fail git merge C &&
411 git clean -f &&
412 rm -rf a/ &&
413 echo 5 >a &&
414 git add a &&
415 test_tick &&
416 git commit -m D &&
417 git tag D &&
418
419 git checkout C^0 &&
420 test_must_fail git merge B &&
421 git clean -f &&
422 git rm --cached a &&
423 echo 10 >a/file &&
424 git add a/file &&
425 test_tick &&
426 git commit -m E1 &&
427 git tag E1 &&
428
429 git checkout C^0 &&
430 test_must_fail git merge B &&
431 git clean -f &&
432 git rm --cached a &&
433 printf "10\n11\n" >a/file &&
434 git add a/file &&
435 test_tick &&
436 git commit -m E2 &&
437 git tag E2
438 )
439'
440
441test_expect_success 'merge of D & E1 fails but has appropriate contents' '
442 test_when_finished "git -C directory-file reset --hard" &&
443 test_when_finished "git -C directory-file clean -fdqx" &&
444 (
445 cd directory-file &&
446
447 git checkout D^0 &&
448
449 test_must_fail git merge -s recursive E1^0 &&
450
451 git ls-files -s >out &&
452 test_line_count = 2 out &&
453 git ls-files -u >out &&
454 test_line_count = 1 out &&
455 git ls-files -o >out &&
456 test_line_count = 1 out &&
457
458 git rev-parse >expect \
459 A:ignore-me B:a &&
460 git rev-parse >actual \
461 :0:ignore-me :2:a &&
462 test_cmp expect actual
463 )
464'
465
466test_expect_success 'merge of E1 & D fails but has appropriate contents' '
467 test_when_finished "git -C directory-file reset --hard" &&
468 test_when_finished "git -C directory-file clean -fdqx" &&
469 (
470 cd directory-file &&
471
472 git checkout E1^0 &&
473
474 test_must_fail git merge -s recursive D^0 &&
475
476 git ls-files -s >out &&
477 test_line_count = 2 out &&
478 git ls-files -u >out &&
479 test_line_count = 1 out &&
480 git ls-files -o >out &&
481 test_line_count = 1 out &&
482
483 git rev-parse >expect \
484 A:ignore-me B:a &&
485 git rev-parse >actual \
486 :0:ignore-me :3:a &&
487 test_cmp expect actual
488 )
489'
490
491test_expect_success 'merge of D & E2 fails but has appropriate contents' '
492 test_when_finished "git -C directory-file reset --hard" &&
493 test_when_finished "git -C directory-file clean -fdqx" &&
494 (
495 cd directory-file &&
496
497 git checkout D^0 &&
498
499 test_must_fail git merge -s recursive E2^0 &&
500
501 git ls-files -s >out &&
502 test_line_count = 4 out &&
503 git ls-files -u >out &&
504 test_line_count = 3 out &&
505 git ls-files -o >out &&
506 test_line_count = 2 out &&
507
508 git rev-parse >expect \
509 B:a E2:a/file c:a/file A:ignore-me &&
510 git rev-parse >actual \
511 :2:a :3:a/file :1:a/file :0:ignore-me &&
512 test_cmp expect actual
513
514 test_path_is_file a~HEAD
515 )
516'
517
518test_expect_success 'merge of E2 & D fails but has appropriate contents' '
519 test_when_finished "git -C directory-file reset --hard" &&
520 test_when_finished "git -C directory-file clean -fdqx" &&
521 (
522 cd directory-file &&
523
524 git checkout E2^0 &&
525
526 test_must_fail git merge -s recursive D^0 &&
527
528 git ls-files -s >out &&
529 test_line_count = 4 out &&
530 git ls-files -u >out &&
531 test_line_count = 3 out &&
532 git ls-files -o >out &&
533 test_line_count = 2 out &&
534
535 git rev-parse >expect \
536 B:a E2:a/file c:a/file A:ignore-me &&
537 git rev-parse >actual \
538 :3:a :2:a/file :1:a/file :0:ignore-me &&
539 test_cmp expect actual
540
541 test_path_is_file a~D^0
542 )
543'
544
545#
546# criss-cross with rename/rename(1to2)/modify followed by
547# rename/rename(2to1)/modify:
548#
549# B D
550# o---o
551# / \ / \
552# A o X ? F
553# \ / \ /
554# o---o
555# C E
556#
557# Commit A: new file: a
558# Commit B: rename a->b, modifying by adding a line
559# Commit C: rename a->c
560# Commit D: merge B&C, resolving conflict by keeping contents in newname
561# Commit E: merge B&C, resolving conflict similar to D but adding another line
562#
563# There is a conflict merging B & C, but one of filename not of file
564# content. Whoever created D and E chose specific resolutions for that
565# conflict resolution. Now, since: (1) there is no content conflict
566# merging B & C, (2) D does not modify that merged content further, and (3)
567# both D & E resolve the name conflict in the same way, the modification to
568# newname in E should not cause any conflicts when it is merged with D.
569# (Note that this can be accomplished by having the virtual merge base have
570# the merged contents of b and c stored in a file named a, which seems like
571# the most logical choice anyway.)
572#
573# Comment from Junio: I do not necessarily agree with the choice "a", but
574# it feels sound to say "B and C do not agree what the final pathname
575# should be, but we know this content was derived from the common A:a so we
576# use one path whose name is arbitrary in the virtual merge base X between
577# D and E" and then further let the rename detection to notice that that
578# arbitrary path gets renamed between X-D to "newname" and X-E also to
579# "newname" to resolve it as both sides renaming it to the same new
580# name. It is akin to what we do at the content level, i.e. "B and C do not
581# agree what the final contents should be, so we leave the conflict marker
582# but that may cancel out at the final merge stage".
583
584test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
585 test_create_repo rename-squared-squared &&
586 (
587 cd rename-squared-squared &&
588
589 printf "1\n2\n3\n4\n5\n6\n" >a &&
590 git add a &&
591 git commit -m A &&
592 git tag A &&
593
594 git checkout -b B A &&
595 git mv a b &&
596 echo 7 >>b &&
597 git add -u &&
598 git commit -m B &&
599
600 git checkout -b C A &&
601 git mv a c &&
602 git commit -m C &&
603
604 git checkout -q B^0 &&
605 git merge --no-commit -s ours C^0 &&
606 git mv b newname &&
607 git commit -m "Merge commit C^0 into HEAD" &&
608 git tag D &&
609
610 git checkout -q C^0 &&
611 git merge --no-commit -s ours B^0 &&
612 git mv c newname &&
613 printf "7\n8\n" >>newname &&
614 git add -u &&
615 git commit -m "Merge commit B^0 into HEAD" &&
616 git tag E
617 )
618'
619
620test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
621 (
622 cd rename-squared-squared &&
623
624 git checkout D^0 &&
625
626 git merge -s recursive E^0 &&
627
628 git ls-files -s >out &&
629 test_line_count = 1 out &&
630 git ls-files -u >out &&
631 test_line_count = 0 out &&
632 git ls-files -o >out &&
633 test_line_count = 1 out &&
634
635 test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
636 )
637'
638
639#
640# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
641#
642# B D
643# o---o
644# / \ / \
645# A o X ? F
646# \ / \ /
647# o---o
648# C E
649#
650# Commit A: new file: a
651# Commit B: rename a->b
652# Commit C: rename a->c, add different a
653# Commit D: merge B&C, keeping b&c and (new) a modified at beginning
654# Commit E: merge B&C, keeping b&c and (new) a modified at end
655#
656# Merging commits D & E should result in no conflict; doing so correctly
657# requires getting the virtual merge base (from merging B&C) right, handling
658# renaming carefully (both in the virtual merge base and later), and getting
659# content merge handled.
660
661test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' '
662 test_create_repo rename-rename-add-source &&
663 (
664 cd rename-rename-add-source &&
665
666 printf "lots\nof\nwords\nand\ncontent\n" >a &&
667 git add a &&
668 git commit -m A &&
669 git tag A &&
670
671 git checkout -b B A &&
672 git mv a b &&
673 git commit -m B &&
674
675 git checkout -b C A &&
676 git mv a c &&
677 printf "2\n3\n4\n5\n6\n7\n" >a &&
678 git add a &&
679 git commit -m C &&
680
681 git checkout B^0 &&
682 git merge --no-commit -s ours C^0 &&
683 git checkout C -- a c &&
684 mv a old_a &&
685 echo 1 >a &&
686 cat old_a >>a &&
687 rm old_a &&
688 git add -u &&
689 git commit -m "Merge commit C^0 into HEAD" &&
690 git tag D &&
691
692 git checkout C^0 &&
693 git merge --no-commit -s ours B^0 &&
694 git checkout B -- b &&
695 echo 8 >>a &&
696 git add -u &&
697 git commit -m "Merge commit B^0 into HEAD" &&
698 git tag E
699 )
700'
701
702test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
703 (
704 cd rename-rename-add-source &&
705
706 git checkout D^0 &&
707
708 git merge -s recursive E^0 &&
709
710 git ls-files -s >out &&
711 test_line_count = 3 out &&
712 git ls-files -u >out &&
713 test_line_count = 0 out &&
714 git ls-files -o >out &&
715 test_line_count = 1 out &&
716
717 printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct &&
718 git rev-parse >expect \
719 A:a A:a \
720 correct &&
721 git rev-parse >actual \
722 :0:b :0:c &&
723 git hash-object >>actual \
724 a &&
725 test_cmp expect actual
726 )
727'
728
729#
730# criss-cross with rename/rename(1to2)/add-dest + simple modify:
731#
732# B D
733# o---o
734# / \ / \
735# A o X ? F
736# \ / \ /
737# o---o
738# C E
739#
740# Commit A: new file: a
741# Commit B: rename a->b, add c
742# Commit C: rename a->c
743# Commit D: merge B&C, keeping A:a and B:c
744# Commit E: merge B&C, keeping A:a and slightly modified c from B
745#
746# Merging commits D & E should result in no conflict. The virtual merge
747# base of B & C needs to not delete B:c for that to work, though...
748
749test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
750 test_create_repo rename-rename-add-dest &&
751 (
752 cd rename-rename-add-dest &&
753
754 >a &&
755 git add a &&
756 git commit -m A &&
757 git tag A &&
758
759 git checkout -b B A &&
760 git mv a b &&
761 printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
762 git add c &&
763 git commit -m B &&
764
765 git checkout -b C A &&
766 git mv a c &&
767 git commit -m C &&
768
769 git checkout B^0 &&
770 git merge --no-commit -s ours C^0 &&
771 git mv b a &&
772 git commit -m "D is like B but renames b back to a" &&
773 git tag D &&
774
775 git checkout B^0 &&
776 git merge --no-commit -s ours C^0 &&
777 git mv b a &&
778 echo 8 >>c &&
779 git add c &&
780 git commit -m "E like D but has mod in c" &&
781 git tag E
782 )
783'
784
785test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
786 (
787 cd rename-rename-add-dest &&
788
789 git checkout D^0 &&
790
791 git merge -s recursive E^0 &&
792
793 git ls-files -s >out &&
794 test_line_count = 2 out &&
795 git ls-files -u >out &&
796 test_line_count = 0 out &&
797 git ls-files -o >out &&
798 test_line_count = 1 out &&
799
800 git rev-parse >expect \
801 A:a E:c &&
802 git rev-parse >actual \
803 :0:a :0:c &&
804 test_cmp expect actual
805 )
806'
807
808#
809# criss-cross with modify/modify on a symlink:
810#
811# B D
812# o---o
813# / \ / \
814# A o X ? F
815# \ / \ /
816# o---o
817# C E
818#
819# Commit A: simple simlink fickle->lagoon
820# Commit B: redirect fickle->disneyland
821# Commit C: redirect fickle->home
822# Commit D: merge B&C, resolving in favor of B
823# Commit E: merge B&C, resolving in favor of C
824#
825# This is an obvious modify/modify conflict for the symlink 'fickle'. Can
826# git detect it?
827
828test_expect_success 'setup symlink modify/modify' '
829 test_create_repo symlink-modify-modify &&
830 (
831 cd symlink-modify-modify &&
832
833 test_ln_s_add lagoon fickle &&
834 git commit -m A &&
835 git tag A &&
836
837 git checkout -b B A &&
838 git rm fickle &&
839 test_ln_s_add disneyland fickle &&
840 git commit -m B &&
841
842 git checkout -b C A &&
843 git rm fickle &&
844 test_ln_s_add home fickle &&
845 git add fickle &&
846 git commit -m C &&
847
848 git checkout -q B^0 &&
849 git merge -s ours -m D C^0 &&
850 git tag D &&
851
852 git checkout -q C^0 &&
853 git merge -s ours -m E B^0 &&
854 git tag E
855 )
856'
857
858test_expect_failure 'check symlink modify/modify' '
859 (
860 cd symlink-modify-modify &&
861
862 git checkout D^0 &&
863
864 test_must_fail git merge -s recursive E^0 &&
865
866 git ls-files -s >out &&
867 test_line_count = 3 out &&
868 git ls-files -u >out &&
869 test_line_count = 3 out &&
870 git ls-files -o >out &&
871 test_line_count = 1 out
872 )
873'
874
875test_done