t4301: add more interesting merge-tree testcases

This adds several tests of `merge-tree -z` extended conflict output behavior to the testsuite, including some tests adapted from t6422. These tests mark current behavior, not necessarily optimal behavior. In particular, some path_msg() calls might want to include additional paths. These testcases also make something clear about the <Conflicted file> info section of the output. That section consists of a sequence of lines of the form <mode> <object> <stage> <filename> where <stage> is always greater than 0 (since each line comes from a conflicted file). The lines correspond to conflicts that would be placed in the index if we were doing a merge in a working tree. It is perhaps natural to assume that for any given line, the <object> and <filename> correspond to a single <revision>:<filename> pair from one of the commits being merged (or from the merge base). This is true for simple conflicts. However, these testcases make it clear that this is not the case in general. For example, <object> may be the hash of a three-way content merge of three different files (and with different filenames). The tests no longer pass under TEST_PASSES_SANITIZE_LEAK; it appears that doing a directory rename with "git mv", among other possible problems, triggers issues. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Aug 23, 2022 at 06:48 UTC 3c4dbf556f425d83f3fbb729dcbecdc719ee4099
1 file changed +573 -1
t/t4301-merge-tree-write-tree.sh
+573 -1
@@ -2,7 +2,6 @@
2
3 test_description='git merge-tree --write-tree'
4
5 -TEST_PASSES_SANITIZE_LEAK=true
5 . ./test-lib.sh
6
7 # This test is ort-specific
@@ -138,6 +137,579 @@ test_expect_success 'test conflict notices and such' '
137 test_cmp expect actual
138 '
139
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)
145 +
146 +test_expect_success 'directory rename + content conflict' '
147 + # Setup
148 + git init dir-rename-and-content &&
149 + (
150 + cd dir-rename-and-content &&
151 + test_write_lines 1 2 3 4 5 >foo &&
152 + mkdir olddir &&
153 + for i in a b c; do echo $i >olddir/$i; done
154 + git add foo olddir &&
155 + git commit -m "original" &&
156 +
157 + git branch O &&
158 + git branch A &&
159 + git branch B &&
160 +
161 + git checkout A &&
162 + test_write_lines 1 2 3 4 5 6 >foo &&
163 + git add foo &&
164 + git mv olddir newdir &&
165 + git commit -m "Modify foo, rename olddir to newdir" &&
166 +
167 + git checkout B &&
168 + test_write_lines 1 2 3 4 5 six >foo &&
169 + git add foo &&
170 + git mv foo olddir/bar &&
171 + git commit -m "Modify foo & rename foo -> olddir/bar"
172 + ) &&
173 + # Testing
174 + (
175 + cd dir-rename-and-content &&
176 +
177 + test_expect_code 1 \
178 + git merge-tree -z A^0 B^0 >out &&
179 + printf "\\n" >>out &&
180 + anonymize_hash out >actual &&
181 + q_to_tab <<-\EOF | lf_to_nul >expect &&
182 + HASH
183 + 100644 HASH 1Qnewdir/bar
184 + 100644 HASH 2Qnewdir/bar
185 + 100644 HASH 3Qnewdir/bar
186 + EOF
187 +
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
192 + Q
193 + EOF
194 + test_cmp expect actual
195 + )
196 +'
197 +
198 +# rename/delete + modify/delete handling
199 +# Commit O: foo
200 +# Commit A: modify foo + rename to bar
201 +# Commit B: delete foo
202 +# Expected: CONFLICT(rename/delete) + CONFLICT(modify/delete)
203 +
204 +test_expect_success 'rename/delete handling' '
205 + # Setup
206 + git init rename-delete &&
207 + (
208 + cd rename-delete &&
209 + test_write_lines 1 2 3 4 5 >foo &&
210 + git add foo &&
211 + git commit -m "original" &&
212 +
213 + git branch O &&
214 + git branch A &&
215 + git branch B &&
216 +
217 + git checkout A &&
218 + test_write_lines 1 2 3 4 5 6 >foo &&
219 + git add foo &&
220 + git mv foo bar &&
221 + git commit -m "Modify foo, rename to bar" &&
222 +
223 + git checkout B &&
224 + git rm foo &&
225 + git commit -m "remove foo"
226 + ) &&
227 + # Testing
228 + (
229 + cd rename-delete &&
230 +
231 + test_expect_code 1 \
232 + git merge-tree -z A^0 B^0 >out &&
233 + printf "\\n" >>out &&
234 + anonymize_hash out >actual &&
235 + q_to_tab <<-\EOF | lf_to_nul >expect &&
236 + HASH
237 + 100644 HASH 1Qbar
238 + 100644 HASH 2Qbar
239 + EOF
240 +
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.
244 + Q
245 + EOF
246 + test_cmp expect actual
247 + )
248 +'
249 +
250 +# rename/add handling
251 +# Commit O: foo
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
255 +
256 +test_expect_success 'rename/add handling' '
257 + # Setup
258 + git init rename-add &&
259 + (
260 + cd rename-add &&
261 + test_write_lines original 1 2 3 4 5 >foo &&
262 + git add foo &&
263 + git commit -m "original" &&
264 +
265 + git branch O &&
266 + git branch A &&
267 + git branch B &&
268 +
269 + git checkout A &&
270 + test_write_lines 1 2 3 4 5 >foo &&
271 + echo "different file" >bar &&
272 + git add foo bar &&
273 + git commit -m "Modify foo, add bar" &&
274 +
275 + git checkout B &&
276 + test_write_lines original 1 2 3 4 5 6 >foo &&
277 + git add foo &&
278 + git mv foo bar &&
279 + git commit -m "rename foo to bar"
280 + ) &&
281 + # Testing
282 + (
283 + cd rename-add &&
284 +
285 + test_expect_code 1 \
286 + git merge-tree -z A^0 B^0 >out &&
287 + printf "\\n" >>out &&
288 +
289 + #
290 + # First, check that the bar that appears at stage 3 does not
291 + # correspond to an individual blob anywhere in history
292 + #
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 &&
296 +
297 + #
298 + # Second, check anonymized hash output against expectation
299 + #
300 + anonymize_hash out >actual &&
301 + q_to_tab <<-\EOF | lf_to_nul >expect &&
302 + HASH
303 + 100644 HASH 2Qbar
304 + 100644 HASH 3Qbar
305 + EOF
306 +
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
311 + Q
312 + EOF
313 + test_cmp expect actual
314 + )
315 +'
316 +
317 +# rename/add, where add is a mode conflict
318 +# Commit O: foo
319 +# Commit A: modify foo, add symlink bar
320 +# Commit B: modify & rename foo->bar
321 +# Expected: CONFLICT(distinct modes) for bar
322 +
323 +test_expect_success SYMLINKS 'rename/add, where add is a mode conflict' '
324 + # Setup
325 + git init rename-add-symlink &&
326 + (
327 + cd rename-add-symlink &&
328 + test_write_lines original 1 2 3 4 5 >foo &&
329 + git add foo &&
330 + git commit -m "original" &&
331 +
332 + git branch O &&
333 + git branch A &&
334 + git branch B &&
335 +
336 + git checkout A &&
337 + test_write_lines 1 2 3 4 5 >foo &&
338 + ln -s foo bar &&
339 + git add foo bar &&
340 + git commit -m "Modify foo, add symlink bar" &&
341 +
342 + git checkout B &&
343 + test_write_lines original 1 2 3 4 5 6 >foo &&
344 + git add foo &&
345 + git mv foo bar &&
346 + git commit -m "rename foo to bar"
347 + ) &&
348 + # Testing
349 + (
350 + cd rename-add-symlink &&
351 +
352 + test_expect_code 1 \
353 + git merge-tree -z A^0 B^0 >out &&
354 + printf "\\n" >>out &&
355 +
356 + #
357 + # First, check that the bar that appears at stage 3 does not
358 + # correspond to an individual blob anywhere in history
359 + #
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 &&
363 +
364 + #
365 + # Second, check anonymized hash output against expectation
366 + #
367 + anonymize_hash out >actual &&
368 + q_to_tab <<-\EOF | lf_to_nul >expect &&
369 + HASH
370 + 120000 HASH 2Qbar
371 + 100644 HASH 3Qbar~B^0
372 + EOF
373 +
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
377 + Q
378 + EOF
379 + test_cmp expect actual
380 + )
381 +'
382 +
383 +# rename/rename(1to2) + content conflict handling
384 +# Commit O: foo
385 +# Commit A: modify foo & rename to bar
386 +# Commit B: modify foo & rename to baz
387 +# Expected: CONFLICT(rename/rename)
388 +
389 +test_expect_success 'rename/rename + content conflict' '
390 + # Setup
391 + git init rr-plus-content &&
392 + (
393 + cd rr-plus-content &&
394 + test_write_lines 1 2 3 4 5 >foo &&
395 + git add foo &&
396 + git commit -m "original" &&
397 +
398 + git branch O &&
399 + git branch A &&
400 + git branch B &&
401 +
402 + git checkout A &&
403 + test_write_lines 1 2 3 4 5 six >foo &&
404 + git add foo &&
405 + git mv foo bar &&
406 + git commit -m "Modify foo + rename to bar" &&
407 +
408 + git checkout B &&
409 + test_write_lines 1 2 3 4 5 6 >foo &&
410 + git add foo &&
411 + git mv foo baz &&
412 + git commit -m "Modify foo + rename to baz"
413 + ) &&
414 + # Testing
415 + (
416 + cd rr-plus-content &&
417 +
418 + test_expect_code 1 \
419 + git merge-tree -z A^0 B^0 >out &&
420 + printf "\\n" >>out &&
421 + anonymize_hash out >actual &&
422 + q_to_tab <<-\EOF | lf_to_nul >expect &&
423 + HASH
424 + 100644 HASH 2Qbar
425 + 100644 HASH 3Qbaz
426 + 100644 HASH 1Qfoo
427 + EOF
428 +
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.
432 + Q
433 + EOF
434 + test_cmp expect actual
435 + )
436 +'
437 +
438 +# rename/add/delete
439 +# Commit O: foo
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]
443 +# for bar
444 +
445 +test_expect_success 'rename/add/delete conflict' '
446 + # Setup
447 + git init rad &&
448 + (
449 + cd rad &&
450 + echo "original file" >foo &&
451 + git add foo &&
452 + git commit -m "original" &&
453 +
454 + git branch O &&
455 + git branch A &&
456 + git branch B &&
457 +
458 + git checkout A &&
459 + git rm foo &&
460 + echo "different file" >bar &&
461 + git add bar &&
462 + git commit -m "Remove foo, add bar" &&
463 +
464 + git checkout B &&
465 + git mv foo bar &&
466 + git commit -m "rename foo to bar"
467 + ) &&
468 + # Testing
469 + (
470 + cd rad &&
471 +
472 + test_expect_code 1 \
473 + git merge-tree -z B^0 A^0 >out &&
474 + printf "\\n" >>out &&
475 + anonymize_hash out >actual &&
476 +
477 + q_to_tab <<-\EOF | lf_to_nul >expect &&
478 + HASH
479 + 100644 HASH 2Qbar
480 + 100644 HASH 3Qbar
481 +
482 + EOF
483 +
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
488 + Q
489 + EOF
490 + test_cmp expect actual
491 + )
492 +'
493 +
494 +# rename/rename(2to1)/delete/delete
495 +# Commit O: foo, bar
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
499 +# renames for baz
500 +
501 +test_expect_success 'rename/rename(2to1)/delete/delete conflict' '
502 + # Setup
503 + git init rrdd &&
504 + (
505 + cd rrdd &&
506 + echo foo >foo &&
507 + echo bar >bar &&
508 + git add foo bar &&
509 + git commit -m O &&
510 +
511 + git branch O &&
512 + git branch A &&
513 + git branch B &&
514 +
515 + git checkout A &&
516 + git mv foo baz &&
517 + git rm bar &&
518 + git commit -m "Rename foo, remove bar" &&
519 +
520 + git checkout B &&
521 + git mv bar baz &&
522 + git rm foo &&
523 + git commit -m "Rename bar, remove foo"
524 + ) &&
525 + # Testing
526 + (
527 + cd rrdd &&
528 +
529 + test_expect_code 1 \
530 + git merge-tree -z A^0 B^0 >out &&
531 + printf "\\n" >>out &&
532 + anonymize_hash out >actual &&
533 +
534 + q_to_tab <<-\EOF | lf_to_nul >expect &&
535 + HASH
536 + 100644 HASH 2Qbaz
537 + 100644 HASH 3Qbaz
538 +
539 + EOF
540 +
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
546 + Q
547 + EOF
548 + test_cmp expect actual
549 + )
550 +'
551 +
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
559 +
560 +test_expect_success 'mod6: chains of rename/rename(1to2) and add/add via colliding renames' '
561 + # Setup
562 + git init mod6 &&
563 + (
564 + cd mod6 &&
565 + test_seq 11 19 >one &&
566 + test_seq 31 39 >three &&
567 + test_seq 51 59 >five &&
568 + git add . &&
569 + test_tick &&
570 + git commit -m "O" &&
571 +
572 + git branch O &&
573 + git branch A &&
574 + git branch B &&
575 +
576 + git checkout A &&
577 + test_seq 10 19 >one &&
578 + echo 40 >>three &&
579 + git add one three &&
580 + git mv one two &&
581 + git mv three four &&
582 + git mv five six &&
583 + test_tick &&
584 + git commit -m "A" &&
585 +
586 + git checkout B &&
587 + echo 20 >>one &&
588 + echo forty >>three &&
589 + echo 60 >>five &&
590 + git add one three five &&
591 + git mv one six &&
592 + git mv three two &&
593 + git mv five four &&
594 + test_tick &&
595 + git commit -m "B"
596 + ) &&
597 + # Testing
598 + (
599 + cd mod6 &&
600 +
601 + test_expect_code 1 \
602 + git merge-tree -z A^0 B^0 >out &&
603 + printf "\\n" >>out &&
604 +
605 + #
606 + # First, check that some of the hashes that appear as stage
607 + # conflict entries do not appear as individual blobs anywhere
608 + # in history.
609 + #
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 &&
615 +
616 + #
617 + # Now compare anonymized hash output with expectation
618 + #
619 + anonymize_hash out >actual &&
620 + q_to_tab <<-\EOF | lf_to_nul >expect &&
621 + HASH
622 + 100644 HASH 1Qfive
623 + 100644 HASH 2Qfour
624 + 100644 HASH 3Qfour
625 + 100644 HASH 1Qone
626 + 100644 HASH 2Qsix
627 + 100644 HASH 3Qsix
628 + 100644 HASH 1Qthree
629 + 100644 HASH 2Qtwo
630 + 100644 HASH 3Qtwo
631 +
632 + EOF
633 +
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
646 + Q
647 + EOF
648 + test_cmp expect actual
649 + )
650 +'
651 +
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)
657 +
658 +test_expect_success 'directory rename + rename/delete + modify/delete + directory/file conflict' '
659 + # Setup
660 + git init 4-stacked-conflict &&
661 + (
662 + cd 4-stacked-conflict &&
663 + test_write_lines 1 2 3 4 5 >foo &&
664 + mkdir olddir &&
665 + for i in a b c; do echo $i >olddir/$i; done
666 + git add foo olddir &&
667 + git commit -m "original" &&
668 +
669 + git branch O &&
670 + git branch A &&
671 + git branch B &&
672 +
673 + git checkout A &&
674 + git rm foo &&
675 + git mv olddir newdir &&
676 + mkdir newdir/bar &&
677 + >newdir/bar/file &&
678 + git add newdir/bar/file &&
679 + git commit -m "rm foo, olddir/ -> newdir/, + newdir/bar/file" &&
680 +
681 + git checkout B &&
682 + test_write_lines 1 2 3 4 5 6 >foo &&
683 + git add foo &&
684 + git mv foo olddir/bar &&
685 + git commit -m "Modify foo & rename foo -> olddir/bar"
686 + ) &&
687 + # Testing
688 + (
689 + cd 4-stacked-conflict &&
690 +
691 + test_expect_code 1 \
692 + git merge-tree -z A^0 B^0 >out &&
693 + printf "\\n" >>out &&
694 + anonymize_hash out >actual &&
695 +
696 + q_to_tab <<-\EOF | lf_to_nul >expect &&
697 + HASH
698 + 100644 HASH 1Qnewdir/bar~B^0
699 + 100644 HASH 3Qnewdir/bar~B^0
700 + EOF
701 +
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.
707 + Q
708 + EOF
709 + test_cmp expect actual
710 + )
711 +'
712 +
713 for opt in $(git merge-tree --git-completion-helper-all)
714 do
715 if test $opt = "--trivial-merge" || test $opt = "--write-tree"