3 test_description
='git merge-tree --write-tree'
7 # This test is ort-specific
8 if test "$GIT_TEST_MERGE_ALGORITHM" != "ort"
10 skip_all
="GIT_TEST_MERGE_ALGORITHM != ort"
14 test_expect_success setup
'
15 test_write_lines 1 2 3 4 5 >numbers &&
16 echo hello >greeting &&
18 git add numbers greeting whatever &&
20 git commit -m initial &&
27 test_write_lines 1 2 3 4 5 6 >numbers &&
30 git add numbers greeting whatever &&
32 git commit -m modify-stuff &&
35 test_write_lines 0 1 2 3 4 5 >numbers &&
40 git add numbers greeting whatever/empty &&
42 git commit -m other-modifications &&
45 git mv numbers sequence &&
47 git commit -m rename-numbers &&
49 git switch --orphan unrelated &&
51 git add something-else &&
53 git commit -m first-commit
56 test_expect_success
'Clean merge' '
57 TREE_OID=$(git merge-tree --write-tree side1 side3) &&
58 q_to_tab <<-EOF >expect &&
59 100644 blob $(git rev-parse side1:greeting)Qgreeting
60 100644 blob $(git rev-parse side1:numbers)Qsequence
61 100644 blob $(git rev-parse side1:whatever)Qwhatever
64 git ls-tree $TREE_OID >actual &&
65 test_cmp expect actual
68 test_expect_success
'Content merge and a few conflicts' '
69 git checkout side1^0 &&
70 test_must_fail git merge side2 &&
71 expected_tree=$(git rev-parse AUTO_MERGE) &&
73 # We will redo the merge, while we are still in a conflicted state!
74 git ls-files -u >conflicted-file-info &&
75 test_when_finished "git reset --hard" &&
77 test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
78 actual_tree=$(head -n 1 RESULT) &&
80 # Due to differences of e.g. "HEAD" vs "side1", the results will not
81 # exactly match. Dig into individual files.
83 # Numbers should have three-way merged cleanly
84 test_write_lines 0 1 2 3 4 5 6 >expect &&
85 git show ${actual_tree}:numbers >actual &&
86 test_cmp expect actual &&
88 # whatever and whatever~<branch> should have same HASHES
89 git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&
90 git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&
91 test_cmp expect actual &&
93 # greeting should have a merge conflict
94 git show ${expected_tree}:greeting >tmp &&
95 sed -e s/HEAD/side1/ tmp >expect &&
96 git show ${actual_tree}:greeting >actual &&
97 test_cmp expect actual
100 test_expect_success
'Barf on misspelled option, with exit code other than 0 or 1' '
101 # Mis-spell with single "s" instead of double "s"
102 test_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&
104 grep "error: unknown option.*mesages" expect
107 test_expect_success
'Barf on too many arguments' '
108 test_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&
110 grep "^usage: git merge-tree" expect
114 sed -e "s/[0-9a-f]\{40,\}/HASH/g" "$@"
117 test_expect_success
'test conflict notices and such' '
118 test_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&
119 anonymize_hash out >actual &&
122 # "greeting" should merge with conflicts
123 # "numbers" should merge cleanly
124 # "whatever" has *both* a modify/delete and a file/directory conflict
125 cat <<-EOF >expect &&
130 Auto-merging greeting
131 CONFLICT (content): Merge conflict in greeting
133 CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
134 CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree.
137 test_cmp expect actual
140 # directory rename + content conflict
141 # Commit O: foo, olddir/{a,b,c}
142 # Commit A: modify foo, newdir/{a,b,c}
143 # Commit B: modify foo differently & rename foo -> olddir/bar
144 # Expected: CONFLICT(content) for for newdir/bar (not olddir/bar or foo)
146 test_expect_success
'directory rename + content conflict' '
148 git init dir-rename-and-content &&
150 cd dir-rename-and-content &&
151 test_write_lines 1 2 3 4 5 >foo &&
153 for i in a b c; do echo $i >olddir/$i || exit 1; done &&
154 git add foo olddir &&
155 git commit -m "original" &&
162 test_write_lines 1 2 3 4 5 6 >foo &&
164 git mv olddir newdir &&
165 git commit -m "Modify foo, rename olddir to newdir" &&
168 test_write_lines 1 2 3 4 5 six >foo &&
170 git mv foo olddir/bar &&
171 git commit -m "Modify foo & rename foo -> olddir/bar"
175 cd dir-rename-and-content &&
178 git merge-tree -z A^0 B^0 >out &&
180 anonymize_hash out >actual &&
181 q_to_tab <<-\EOF | lf_to_nul >expect &&
183 100644 HASH 1Qnewdir/bar
184 100644 HASH 2Qnewdir/bar
185 100644 HASH 3Qnewdir/bar
188 q_to_nul <<-EOF >>expect &&
189 Q2Qnewdir/barQolddir/barQCONFLICT (directory rename suggested)QCONFLICT (file location): foo renamed to olddir/bar in B^0, inside a directory that was renamed in A^0, suggesting it should perhaps be moved to newdir/bar.
190 Q1Qnewdir/barQAuto-mergingQAuto-merging newdir/bar
191 Q1Qnewdir/barQCONFLICT (contents)QCONFLICT (content): Merge conflict in newdir/bar
194 test_cmp expect actual
198 # rename/delete + modify/delete handling
200 # Commit A: modify foo + rename to bar
201 # Commit B: delete foo
202 # Expected: CONFLICT(rename/delete) + CONFLICT(modify/delete)
204 test_expect_success
'rename/delete handling' '
206 git init rename-delete &&
209 test_write_lines 1 2 3 4 5 >foo &&
211 git commit -m "original" &&
218 test_write_lines 1 2 3 4 5 6 >foo &&
221 git commit -m "Modify foo, rename to bar" &&
225 git commit -m "remove foo"
232 git merge-tree -z A^0 B^0 >out &&
234 anonymize_hash out >actual &&
235 q_to_tab <<-\EOF | lf_to_nul >expect &&
241 q_to_nul <<-EOF >>expect &&
242 Q2QbarQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to bar in A^0, but deleted in B^0.
243 Q1QbarQCONFLICT (modify/delete)QCONFLICT (modify/delete): bar deleted in B^0 and modified in A^0. Version A^0 of bar left in tree.
246 test_cmp expect actual
250 # rename/add handling
252 # Commit A: modify foo, add different bar
253 # Commit B: modify & rename foo->bar
254 # Expected: CONFLICT(add/add) [via rename collide] for bar
256 test_expect_success
'rename/add handling' '
258 git init rename-add &&
261 test_write_lines original 1 2 3 4 5 >foo &&
263 git commit -m "original" &&
270 test_write_lines 1 2 3 4 5 >foo &&
271 echo "different file" >bar &&
273 git commit -m "Modify foo, add bar" &&
276 test_write_lines original 1 2 3 4 5 6 >foo &&
279 git commit -m "rename foo to bar"
286 git merge-tree -z A^0 B^0 >out &&
290 # First, check that the bar that appears at stage 3 does not
291 # correspond to an individual blob anywhere in history
293 hash=$(cat out | tr "\0" "\n" | head -n 3 | grep 3.bar | cut -f 2 -d " ") &&
294 git rev-list --objects --all >all_blobs &&
295 ! grep $hash all_blobs &&
298 # Second, check anonymized hash output against expectation
300 anonymize_hash out >actual &&
301 q_to_tab <<-\EOF | lf_to_nul >expect &&
307 q_to_nul <<-EOF >>expect &&
308 Q1QbarQAuto-mergingQAuto-merging bar
309 Q1QbarQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in bar
310 Q1QfooQAuto-mergingQAuto-merging foo
313 test_cmp expect actual
317 # rename/add, where add is a mode conflict
319 # Commit A: modify foo, add symlink bar
320 # Commit B: modify & rename foo->bar
321 # Expected: CONFLICT(distinct modes) for bar
323 test_expect_success SYMLINKS
'rename/add, where add is a mode conflict' '
325 git init rename-add-symlink &&
327 cd rename-add-symlink &&
328 test_write_lines original 1 2 3 4 5 >foo &&
330 git commit -m "original" &&
337 test_write_lines 1 2 3 4 5 >foo &&
340 git commit -m "Modify foo, add symlink bar" &&
343 test_write_lines original 1 2 3 4 5 6 >foo &&
346 git commit -m "rename foo to bar"
350 cd rename-add-symlink &&
353 git merge-tree -z A^0 B^0 >out &&
357 # First, check that the bar that appears at stage 3 does not
358 # correspond to an individual blob anywhere in history
360 hash=$(cat out | tr "\0" "\n" | head -n 3 | grep 3.bar | cut -f 2 -d " ") &&
361 git rev-list --objects --all >all_blobs &&
362 ! grep $hash all_blobs &&
365 # Second, check anonymized hash output against expectation
367 anonymize_hash out >actual &&
368 q_to_tab <<-\EOF | lf_to_nul >expect &&
371 100644 HASH 3Qbar~B^0
374 q_to_nul <<-EOF >>expect &&
375 Q2QbarQbar~B^0QCONFLICT (distinct modes)QCONFLICT (distinct types): bar had different types on each side; renamed one of them so each can be recorded somewhere.
376 Q1QfooQAuto-mergingQAuto-merging foo
379 test_cmp expect actual
383 # rename/rename(1to2) + content conflict handling
385 # Commit A: modify foo & rename to bar
386 # Commit B: modify foo & rename to baz
387 # Expected: CONFLICT(rename/rename)
389 test_expect_success
'rename/rename + content conflict' '
391 git init rr-plus-content &&
393 cd rr-plus-content &&
394 test_write_lines 1 2 3 4 5 >foo &&
396 git commit -m "original" &&
403 test_write_lines 1 2 3 4 5 six >foo &&
406 git commit -m "Modify foo + rename to bar" &&
409 test_write_lines 1 2 3 4 5 6 >foo &&
412 git commit -m "Modify foo + rename to baz"
416 cd rr-plus-content &&
419 git merge-tree -z A^0 B^0 >out &&
421 anonymize_hash out >actual &&
422 q_to_tab <<-\EOF | lf_to_nul >expect &&
429 q_to_nul <<-EOF >>expect &&
430 Q1QfooQAuto-mergingQAuto-merging foo
431 Q3QfooQbarQbazQCONFLICT (rename/rename)QCONFLICT (rename/rename): foo renamed to bar in A^0 and to baz in B^0.
434 test_cmp expect actual
440 # Commit A: rm foo, add different bar
441 # Commit B: rename foo->bar
442 # Expected: CONFLICT (rename/delete), CONFLICT(add/add) [via rename collide]
445 test_expect_success
'rename/add/delete conflict' '
450 echo "original file" >foo &&
452 git commit -m "original" &&
460 echo "different file" >bar &&
462 git commit -m "Remove foo, add bar" &&
466 git commit -m "rename foo to bar"
473 git merge-tree -z B^0 A^0 >out &&
475 anonymize_hash out >actual &&
477 q_to_tab <<-\EOF | lf_to_nul >expect &&
484 q_to_nul <<-EOF >>expect &&
485 2QbarQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to bar in B^0, but deleted in A^0.
486 Q1QbarQAuto-mergingQAuto-merging bar
487 Q1QbarQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in bar
490 test_cmp expect actual
494 # rename/rename(2to1)/delete/delete
496 # Commit A: rename foo->baz, rm bar
497 # Commit B: rename bar->baz, rm foo
498 # Expected: 2x CONFLICT (rename/delete), CONFLICT (add/add) via colliding
501 test_expect_success
'rename/rename(2to1)/delete/delete conflict' '
518 git commit -m "Rename foo, remove bar" &&
523 git commit -m "Rename bar, remove foo"
530 git merge-tree -z A^0 B^0 >out &&
532 anonymize_hash out >actual &&
534 q_to_tab <<-\EOF | lf_to_nul >expect &&
541 q_to_nul <<-EOF >>expect &&
542 2QbazQbarQCONFLICT (rename/delete)QCONFLICT (rename/delete): bar renamed to baz in B^0, but deleted in A^0.
543 Q2QbazQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to baz in A^0, but deleted in B^0.
544 Q1QbazQAuto-mergingQAuto-merging baz
545 Q1QbazQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in baz
548 test_cmp expect actual
552 # mod6: chains of rename/rename(1to2) + add/add via colliding renames
553 # Commit O: one, three, five
554 # Commit A: one->two, three->four, five->six
555 # Commit B: one->six, three->two, five->four
556 # Expected: three CONFLICT(rename/rename) messages + three CONFLICT(add/add)
557 # messages; each path in two of the multi-way merged contents
558 # found in two, four, six
560 test_expect_success
'mod6: chains of rename/rename(1to2) and add/add via colliding renames' '
565 test_seq 11 19 >one &&
566 test_seq 31 39 >three &&
567 test_seq 51 59 >five &&
577 test_seq 10 19 >one &&
588 echo forty >>three &&
590 git add one three five &&
602 git merge-tree -z A^0 B^0 >out &&
606 # First, check that some of the hashes that appear as stage
607 # conflict entries do not appear as individual blobs anywhere
610 hash1=$(cat out | tr "\0" "\n" | head | grep 2.four | cut -f 2 -d " ") &&
611 hash2=$(cat out | tr "\0" "\n" | head | grep 3.two | cut -f 2 -d " ") &&
612 git rev-list --objects --all >all_blobs &&
613 ! grep $hash1 all_blobs &&
614 ! grep $hash2 all_blobs &&
617 # Now compare anonymized hash output with expectation
619 anonymize_hash out >actual &&
620 q_to_tab <<-\EOF | lf_to_nul >expect &&
634 q_to_nul <<-EOF >>expect &&
635 3QfiveQsixQfourQCONFLICT (rename/rename)QCONFLICT (rename/rename): five renamed to six in A^0 and to four in B^0.
636 Q1QfourQAuto-mergingQAuto-merging four
637 Q1QfourQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in four
638 Q1QoneQAuto-mergingQAuto-merging one
639 Q3QoneQtwoQsixQCONFLICT (rename/rename)QCONFLICT (rename/rename): one renamed to two in A^0 and to six in B^0.
640 Q1QsixQAuto-mergingQAuto-merging six
641 Q1QsixQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in six
642 Q1QthreeQAuto-mergingQAuto-merging three
643 Q3QthreeQfourQtwoQCONFLICT (rename/rename)QCONFLICT (rename/rename): three renamed to four in A^0 and to two in B^0.
644 Q1QtwoQAuto-mergingQAuto-merging two
645 Q1QtwoQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in two
648 test_cmp expect actual
652 # directory rename + rename/delete + modify/delete + directory/file conflict
653 # Commit O: foo, olddir/{a,b,c}
654 # Commit A: delete foo, rename olddir/ -> newdir/, add newdir/bar/file
655 # Commit B: modify foo & rename foo -> olddir/bar
656 # Expected: CONFLICT(content) for for newdir/bar (not olddir/bar or foo)
658 test_expect_success
'directory rename + rename/delete + modify/delete + directory/file conflict' '
660 git init 4-stacked-conflict &&
662 cd 4-stacked-conflict &&
663 test_write_lines 1 2 3 4 5 >foo &&
665 for i in a b c; do echo $i >olddir/$i || exit 1; done &&
666 git add foo olddir &&
667 git commit -m "original" &&
675 git mv olddir newdir &&
678 git add newdir/bar/file &&
679 git commit -m "rm foo, olddir/ -> newdir/, + newdir/bar/file" &&
682 test_write_lines 1 2 3 4 5 6 >foo &&
684 git mv foo olddir/bar &&
685 git commit -m "Modify foo & rename foo -> olddir/bar"
689 cd 4-stacked-conflict &&
692 git merge-tree -z A^0 B^0 >out &&
694 anonymize_hash out >actual &&
696 q_to_tab <<-\EOF | lf_to_nul >expect &&
698 100644 HASH 1Qnewdir/bar~B^0
699 100644 HASH 3Qnewdir/bar~B^0
702 q_to_nul <<-EOF >>expect &&
703 Q2Qnewdir/barQolddir/barQCONFLICT (directory rename suggested)QCONFLICT (file location): foo renamed to olddir/bar in B^0, inside a directory that was renamed in A^0, suggesting it should perhaps be moved to newdir/bar.
704 Q2Qnewdir/barQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to newdir/bar in B^0, but deleted in A^0.
705 Q2Qnewdir/bar~B^0Qnewdir/barQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of newdir/bar from B^0; moving it to newdir/bar~B^0 instead.
706 Q1Qnewdir/bar~B^0QCONFLICT (modify/delete)QCONFLICT (modify/delete): newdir/bar~B^0 deleted in A^0 and modified in B^0. Version B^0 of newdir/bar~B^0 left in tree.
709 test_cmp expect actual
713 for opt
in $
(git merge-tree
--git-completion-helper-all)
715 if test $opt = "--trivial-merge" ||
test $opt = "--write-tree"
720 test_expect_success
"usage: --trivial-merge is incompatible with $opt" '
721 test_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3
725 test_expect_success
'Just the conflicted files without the messages' '
726 test_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&
727 anonymize_hash out >actual &&
729 test_write_lines HASH greeting whatever~side1 >expect &&
731 test_cmp expect actual
734 test_expect_success
'Check conflicted oids and modes without messages' '
735 test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
736 anonymize_hash out >actual &&
738 # Compare the basic output format
739 q_to_tab >expect <<-\EOF &&
741 100644 HASH 1Qgreeting
742 100644 HASH 2Qgreeting
743 100644 HASH 3Qgreeting
744 100644 HASH 1Qwhatever~side1
745 100644 HASH 2Qwhatever~side1
748 test_cmp expect actual &&
750 # Check the actual hashes against the `ls-files -u` output too
751 tail -n +2 out | sed -e s/side1/HEAD/ >actual &&
752 test_cmp conflicted-file-info actual
755 test_expect_success
'NUL terminated conflicted file "lines"' '
756 git checkout -b tweak1 side1 &&
757 test_write_lines zero 1 2 3 4 5 6 >numbers &&
759 git mv numbers "Αυτά μου φαίνονται κινέζικα" &&
760 git commit -m "Renamed numbers" &&
762 test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&
764 anonymize_hash out >actual &&
767 # "greeting" should merge with conflicts
768 # "whatever" has *both* a modify/delete and a file/directory conflict
769 # "Αυτά μου φαίνονται κινέζικα" should have a conflict
770 echo HASH | lf_to_nul >expect &&
772 q_to_tab <<-EOF | lf_to_nul >>expect &&
773 100644 HASH 1Qgreeting
774 100644 HASH 2Qgreeting
775 100644 HASH 3Qgreeting
776 100644 HASH 1Qwhatever~tweak1
777 100644 HASH 2Qwhatever~tweak1
778 100644 HASH 1QΑυτά μου φαίνονται κινέζικα
779 100644 HASH 2QΑυτά μου φαίνονται κινέζικα
780 100644 HASH 3QΑυτά μου φαίνονται κινέζικα
784 q_to_nul <<-EOF >>expect &&
785 1QgreetingQAuto-mergingQAuto-merging greeting
786 Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
787 Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
788 Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1. Version tweak1 of whatever~tweak1 left in tree.
789 Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα
790 Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
794 test_cmp expect actual
797 test_expect_success
'error out by default for unrelated histories' '
798 test_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&
800 grep "refusing to merge unrelated histories" error
803 test_expect_success
'can override merge of unrelated histories' '
804 git merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&
807 git rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&
808 git rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&
810 test_cmp expect actual
813 test_expect_success SANITY
'merge-ort fails gracefully in a read-only repository' '
814 git init --bare read-only &&
815 git push read-only side1 side2 side3 &&
816 test_when_finished "chmod -R u+w read-only" &&
817 chmod -R a-w read-only &&
818 test_must_fail git -C read-only merge-tree side1 side3 &&
819 test_must_fail git -C read-only merge-tree side1 side2
822 test_expect_success
'--stdin with both a successful and a conflicted merge' '
823 printf "side1 side3\nside1 side2" | git merge-tree --stdin >actual &&
825 git checkout side1^0 &&
828 printf "1\0" >expect &&
829 git rev-parse HEAD^{tree} | lf_to_nul >>expect &&
830 printf "\0" >>expect &&
832 git checkout side1^0 &&
833 test_must_fail git merge side2 &&
834 sed s/HEAD/side1/ greeting >tmp &&
837 git mv whatever~HEAD whatever~side1 &&
839 printf "0\0" >>expect &&
840 git write-tree | lf_to_nul >>expect &&
842 cat <<-EOF | q_to_tab | lf_to_nul >>expect &&
843 100644 $(git rev-parse side1~1:greeting) 1Qgreeting
844 100644 $(git rev-parse side1:greeting) 2Qgreeting
845 100644 $(git rev-parse side2:greeting) 3Qgreeting
846 100644 $(git rev-parse side1~1:whatever) 1Qwhatever~side1
847 100644 $(git rev-parse side1:whatever) 2Qwhatever~side1
850 q_to_nul <<-EOF >>expect &&
851 Q1QgreetingQAuto-mergingQAuto-merging greeting
852 Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
853 Q1QnumbersQAuto-mergingQAuto-merging numbers
854 Q2Qwhatever~side1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
855 Q1Qwhatever~side1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree.
858 printf "\0\0" >>expect &&
860 test_cmp expect actual