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