directory rename detection: miscellaneous testcases to complete coverage

I came up with the testcases in the first eight sections before coding up the implementation. The testcases in this section were mostly ones I thought of while coding/debugging, and which I was too lazy to insert into the previous sections because I didn't want to re-label with all the testcase references. :-) Reviewed-by: Stefan Beller <sbeller@google.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Apr 19, 2018 at 10:57 UTC 792e1371d9d10100a7b05bb96efcee96dd03bc43
1 file changed +564 -1
t/t6043-merge-rename-directories.sh
+564 -1
@@ -305,6 +305,7 @@ test_expect_failure '1d-check: Directory renames cause a rename/rename(2to1) con
305 '
306
307 # Testcase 1e, Renamed directory, with all filenames being renamed too
308 +# (Related to testcases 9f & 9g)
309 # Commit O: z/{oldb,oldc}
310 # Commit A: y/{newb,newc}
311 # Commit B: z/{oldb,oldc,d}
@@ -593,7 +594,7 @@ test_expect_success '2b-check: Directory split into two on one side, with equal
594 ###########################################################################
595
596 # Testcase 3a, Avoid implicit rename if involved as source on other side
596 -# (Related to testcases 1c and 1f)
597 +# (Related to testcases 1c, 1f, and 9h)
598 # Commit O: z/{b,c,d}
599 # Commit A: z/{b,c,d} (no change)
600 # Commit B: y/{b,c}, x/d
@@ -2316,4 +2317,566 @@ test_expect_failure '8e-check: Both sides rename, one side adds to original dire
2317 )
2318 '
2319
2320 +###########################################################################
2321 +# SECTION 9: Other testcases
2322 +#
2323 +# This section consists of miscellaneous testcases I thought of during
2324 +# the implementation which round out the testing.
2325 +###########################################################################
2326 +
2327 +# Testcase 9a, Inner renamed directory within outer renamed directory
2328 +# (Related to testcase 1f)
2329 +# Commit O: z/{b,c,d/{e,f,g}}
2330 +# Commit A: y/{b,c}, x/w/{e,f,g}
2331 +# Commit B: z/{b,c,d/{e,f,g,h},i}
2332 +# Expected: y/{b,c,i}, x/w/{e,f,g,h}
2333 +# NOTE: The only reason this one is interesting is because when a directory
2334 +# is split into multiple other directories, we determine by the weight
2335 +# of which one had the most paths going to it. A naive implementation
2336 +# of that could take the new file in commit B at z/i to x/w/i or x/i.
2337 +
2338 +test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' '
2339 + test_create_repo 9a &&
2340 + (
2341 + cd 9a &&
2342 +
2343 + mkdir -p z/d &&
2344 + echo b >z/b &&
2345 + echo c >z/c &&
2346 + echo e >z/d/e &&
2347 + echo f >z/d/f &&
2348 + echo g >z/d/g &&
2349 + git add z &&
2350 + test_tick &&
2351 + git commit -m "O" &&
2352 +
2353 + git branch O &&
2354 + git branch A &&
2355 + git branch B &&
2356 +
2357 + git checkout A &&
2358 + mkdir x &&
2359 + git mv z/d x/w &&
2360 + git mv z y &&
2361 + test_tick &&
2362 + git commit -m "A" &&
2363 +
2364 + git checkout B &&
2365 + echo h >z/d/h &&
2366 + echo i >z/i &&
2367 + git add z &&
2368 + test_tick &&
2369 + git commit -m "B"
2370 + )
2371 +'
2372 +
2373 +test_expect_failure '9a-check: Inner renamed directory within outer renamed directory' '
2374 + (
2375 + cd 9a &&
2376 +
2377 + git checkout A^0 &&
2378 +
2379 + git merge -s recursive B^0 &&
2380 +
2381 + git ls-files -s >out &&
2382 + test_line_count = 7 out &&
2383 + git ls-files -u >out &&
2384 + test_line_count = 0 out &&
2385 + git ls-files -o >out &&
2386 + test_line_count = 1 out &&
2387 +
2388 + git rev-parse >actual \
2389 + HEAD:y/b HEAD:y/c HEAD:y/i &&
2390 + git rev-parse >expect \
2391 + O:z/b O:z/c B:z/i &&
2392 + test_cmp expect actual &&
2393 +
2394 + git rev-parse >actual \
2395 + HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h &&
2396 + git rev-parse >expect \
2397 + O:z/d/e O:z/d/f O:z/d/g B:z/d/h &&
2398 + test_cmp expect actual
2399 + )
2400 +'
2401 +
2402 +# Testcase 9b, Transitive rename with content merge
2403 +# (Related to testcase 1c)
2404 +# Commit O: z/{b,c}, x/d_1
2405 +# Commit A: y/{b,c}, x/d_2
2406 +# Commit B: z/{b,c,d_3}
2407 +# Expected: y/{b,c,d_merged}
2408 +
2409 +test_expect_success '9b-setup: Transitive rename with content merge' '
2410 + test_create_repo 9b &&
2411 + (
2412 + cd 9b &&
2413 +
2414 + mkdir z &&
2415 + echo b >z/b &&
2416 + echo c >z/c &&
2417 + mkdir x &&
2418 + test_seq 1 10 >x/d &&
2419 + git add z x &&
2420 + test_tick &&
2421 + git commit -m "O" &&
2422 +
2423 + git branch O &&
2424 + git branch A &&
2425 + git branch B &&
2426 +
2427 + git checkout A &&
2428 + git mv z y &&
2429 + test_seq 1 11 >x/d &&
2430 + git add x/d &&
2431 + test_tick &&
2432 + git commit -m "A" &&
2433 +
2434 + git checkout B &&
2435 + test_seq 0 10 >x/d &&
2436 + git mv x/d z/d &&
2437 + git add z/d &&
2438 + test_tick &&
2439 + git commit -m "B"
2440 + )
2441 +'
2442 +
2443 +test_expect_failure '9b-check: Transitive rename with content merge' '
2444 + (
2445 + cd 9b &&
2446 +
2447 + git checkout A^0 &&
2448 +
2449 + git merge -s recursive B^0 &&
2450 +
2451 + git ls-files -s >out &&
2452 + test_line_count = 3 out &&
2453 +
2454 + test_seq 0 11 >expected &&
2455 + test_cmp expected y/d &&
2456 + git add expected &&
2457 + git rev-parse >actual \
2458 + HEAD:y/b HEAD:y/c HEAD:y/d &&
2459 + git rev-parse >expect \
2460 + O:z/b O:z/c :0:expected &&
2461 + test_cmp expect actual &&
2462 + test_must_fail git rev-parse HEAD:x/d &&
2463 + test_must_fail git rev-parse HEAD:z/d &&
2464 + test_path_is_missing z/d &&
2465 +
2466 + test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) &&
2467 + test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) &&
2468 + test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d)
2469 + )
2470 +'
2471 +
2472 +# Testcase 9c, Doubly transitive rename?
2473 +# (Related to testcase 1c, 7e, and 9d)
2474 +# Commit O: z/{b,c}, x/{d,e}, w/f
2475 +# Commit A: y/{b,c}, x/{d,e,f,g}
2476 +# Commit B: z/{b,c,d,e}, w/f
2477 +# Expected: y/{b,c,d,e}, x/{f,g}
2478 +#
2479 +# NOTE: x/f and x/g may be slightly confusing here. The rename from w/f to
2480 +# x/f is clear. Let's look beyond that. Here's the logic:
2481 +# Commit B renamed x/ -> z/
2482 +# Commit A renamed z/ -> y/
2483 +# So, we could possibly further rename x/f to z/f to y/f, a doubly
2484 +# transient rename. However, where does it end? We can chain these
2485 +# indefinitely (see testcase 9d). What if there is a D/F conflict
2486 +# at z/f/ or y/f/? Or just another file conflict at one of those
2487 +# paths? In the case of an N-long chain of transient renamings,
2488 +# where do we "abort" the rename at? Can the user make sense of
2489 +# the resulting conflict and resolve it?
2490 +#
2491 +# To avoid this confusion I use the simple rule that if the other side
2492 +# of history did a directory rename to a path that your side renamed
2493 +# away, then ignore that particular rename from the other side of
2494 +# history for any implicit directory renames.
2495 +
2496 +test_expect_success '9c-setup: Doubly transitive rename?' '
2497 + test_create_repo 9c &&
2498 + (
2499 + cd 9c &&
2500 +
2501 + mkdir z &&
2502 + echo b >z/b &&
2503 + echo c >z/c &&
2504 + mkdir x &&
2505 + echo d >x/d &&
2506 + echo e >x/e &&
2507 + mkdir w &&
2508 + echo f >w/f &&
2509 + git add z x w &&
2510 + test_tick &&
2511 + git commit -m "O" &&
2512 +
2513 + git branch O &&
2514 + git branch A &&
2515 + git branch B &&
2516 +
2517 + git checkout A &&
2518 + git mv z y &&
2519 + git mv w/f x/ &&
2520 + echo g >x/g &&
2521 + git add x/g &&
2522 + test_tick &&
2523 + git commit -m "A" &&
2524 +
2525 + git checkout B &&
2526 + git mv x/d z/d &&
2527 + git mv x/e z/e &&
2528 + test_tick &&
2529 + git commit -m "B"
2530 + )
2531 +'
2532 +
2533 +test_expect_failure '9c-check: Doubly transitive rename?' '
2534 + (
2535 + cd 9c &&
2536 +
2537 + git checkout A^0 &&
2538 +
2539 + git merge -s recursive B^0 >out &&
2540 + test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out &&
2541 +
2542 + git ls-files -s >out &&
2543 + test_line_count = 6 out &&
2544 + git ls-files -o >out &&
2545 + test_line_count = 1 out &&
2546 +
2547 + git rev-parse >actual \
2548 + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g &&
2549 + git rev-parse >expect \
2550 + O:z/b O:z/c O:x/d O:x/e O:w/f A:x/g &&
2551 + test_cmp expect actual
2552 + )
2553 +'
2554 +
2555 +# Testcase 9d, N-fold transitive rename?
2556 +# (Related to testcase 9c...and 1c and 7e)
2557 +# Commit O: z/a, y/b, x/c, w/d, v/e, u/f
2558 +# Commit A: y/{a,b}, w/{c,d}, u/{e,f}
2559 +# Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f
2560 +# Expected: <see NOTE first>
2561 +#
2562 +# NOTE: z/ -> y/ (in commit A)
2563 +# y/ -> x/ (in commit B)
2564 +# x/ -> w/ (in commit A)
2565 +# w/ -> v/ (in commit B)
2566 +# v/ -> u/ (in commit A)
2567 +# So, if we add a file to z, say z/t, where should it end up? In u?
2568 +# What if there's another file or directory named 't' in one of the
2569 +# intervening directories and/or in u itself? Also, shouldn't the
2570 +# same logic that places 't' in u/ also move ALL other files to u/?
2571 +# What if there are file or directory conflicts in any of them? If
2572 +# we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames
2573 +# like this, would the user have any hope of understanding any
2574 +# conflicts or how their working tree ended up? I think not, so I'm
2575 +# ruling out N-ary transitive renames for N>1.
2576 +#
2577 +# Therefore our expected result is:
2578 +# z/t, y/a, x/b, w/c, u/d, u/e, u/f
2579 +# The reason that v/d DOES get transitively renamed to u/d is that u/ isn't
2580 +# renamed somewhere. A slightly sub-optimal result, but it uses fairly
2581 +# simple rules that are consistent with what we need for all the other
2582 +# testcases and simplifies things for the user.
2583 +
2584 +test_expect_success '9d-setup: N-way transitive rename?' '
2585 + test_create_repo 9d &&
2586 + (
2587 + cd 9d &&
2588 +
2589 + mkdir z y x w v u &&
2590 + echo a >z/a &&
2591 + echo b >y/b &&
2592 + echo c >x/c &&
2593 + echo d >w/d &&
2594 + echo e >v/e &&
2595 + echo f >u/f &&
2596 + git add z y x w v u &&
2597 + test_tick &&
2598 + git commit -m "O" &&
2599 +
2600 + git branch O &&
2601 + git branch A &&
2602 + git branch B &&
2603 +
2604 + git checkout A &&
2605 + git mv z/a y/ &&
2606 + git mv x/c w/ &&
2607 + git mv v/e u/ &&
2608 + test_tick &&
2609 + git commit -m "A" &&
2610 +
2611 + git checkout B &&
2612 + echo t >z/t &&
2613 + git mv y/b x/ &&
2614 + git mv w/d v/ &&
2615 + git add z/t &&
2616 + test_tick &&
2617 + git commit -m "B"
2618 + )
2619 +'
2620 +
2621 +test_expect_failure '9d-check: N-way transitive rename?' '
2622 + (
2623 + cd 9d &&
2624 +
2625 + git checkout A^0 &&
2626 +
2627 + git merge -s recursive B^0 >out &&
2628 + test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out &&
2629 + test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out &&
2630 + test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out &&
2631 + test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out &&
2632 +
2633 + git ls-files -s >out &&
2634 + test_line_count = 7 out &&
2635 + git ls-files -o >out &&
2636 + test_line_count = 1 out &&
2637 +
2638 + git rev-parse >actual \
2639 + HEAD:z/t \
2640 + HEAD:y/a HEAD:x/b HEAD:w/c \
2641 + HEAD:u/d HEAD:u/e HEAD:u/f &&
2642 + git rev-parse >expect \
2643 + B:z/t \
2644 + O:z/a O:y/b O:x/c \
2645 + O:w/d O:v/e A:u/f &&
2646 + test_cmp expect actual
2647 + )
2648 +'
2649 +
2650 +# Testcase 9e, N-to-1 whammo
2651 +# (Related to testcase 9c...and 1c and 7e)
2652 +# Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k}
2653 +# Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo}
2654 +# Commit B: combined/{a,b,d,e,g,h,j,k}
2655 +# Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings,
2656 +# dir1/yo, dir2/yo, dir3/yo, dirN/yo
2657 +
2658 +test_expect_success '9e-setup: N-to-1 whammo' '
2659 + test_create_repo 9e &&
2660 + (
2661 + cd 9e &&
2662 +
2663 + mkdir dir1 dir2 dir3 dirN &&
2664 + echo a >dir1/a &&
2665 + echo b >dir1/b &&
2666 + echo d >dir2/d &&
2667 + echo e >dir2/e &&
2668 + echo g >dir3/g &&
2669 + echo h >dir3/h &&
2670 + echo j >dirN/j &&
2671 + echo k >dirN/k &&
2672 + git add dir* &&
2673 + test_tick &&
2674 + git commit -m "O" &&
2675 +
2676 + git branch O &&
2677 + git branch A &&
2678 + git branch B &&
2679 +
2680 + git checkout A &&
2681 + echo c >dir1/c &&
2682 + echo yo >dir1/yo &&
2683 + echo f >dir2/f &&
2684 + echo yo >dir2/yo &&
2685 + echo i >dir3/i &&
2686 + echo yo >dir3/yo &&
2687 + echo l >dirN/l &&
2688 + echo yo >dirN/yo &&
2689 + git add dir* &&
2690 + test_tick &&
2691 + git commit -m "A" &&
2692 +
2693 + git checkout B &&
2694 + git mv dir1 combined &&
2695 + git mv dir2/* combined/ &&
2696 + git mv dir3/* combined/ &&
2697 + git mv dirN/* combined/ &&
2698 + test_tick &&
2699 + git commit -m "B"
2700 + )
2701 +'
2702 +
2703 +test_expect_failure C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
2704 + (
2705 + cd 9e &&
2706 +
2707 + git checkout A^0 &&
2708 +
2709 + test_must_fail git merge -s recursive B^0 >out &&
2710 + grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line &&
2711 + grep -q dir1/yo error_line &&
2712 + grep -q dir2/yo error_line &&
2713 + grep -q dir3/yo error_line &&
2714 + grep -q dirN/yo error_line &&
2715 +
2716 + git ls-files -s >out &&
2717 + test_line_count = 16 out &&
2718 + git ls-files -u >out &&
2719 + test_line_count = 0 out &&
2720 + git ls-files -o >out &&
2721 + test_line_count = 2 out &&
2722 +
2723 + git rev-parse >actual \
2724 + :0:combined/a :0:combined/b :0:combined/c \
2725 + :0:combined/d :0:combined/e :0:combined/f \
2726 + :0:combined/g :0:combined/h :0:combined/i \
2727 + :0:combined/j :0:combined/k :0:combined/l &&
2728 + git rev-parse >expect \
2729 + O:dir1/a O:dir1/b A:dir1/c \
2730 + O:dir2/d O:dir2/e A:dir2/f \
2731 + O:dir3/g O:dir3/h A:dir3/i \
2732 + O:dirN/j O:dirN/k A:dirN/l &&
2733 + test_cmp expect actual &&
2734 +
2735 + git rev-parse >actual \
2736 + :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo &&
2737 + git rev-parse >expect \
2738 + A:dir1/yo A:dir2/yo A:dir3/yo A:dirN/yo &&
2739 + test_cmp expect actual
2740 + )
2741 +'
2742 +
2743 +# Testcase 9f, Renamed directory that only contained immediate subdirs
2744 +# (Related to testcases 1e & 9g)
2745 +# Commit O: goal/{a,b}/$more_files
2746 +# Commit A: priority/{a,b}/$more_files
2747 +# Commit B: goal/{a,b}/$more_files, goal/c
2748 +# Expected: priority/{a,b}/$more_files, priority/c
2749 +
2750 +test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' '
2751 + test_create_repo 9f &&
2752 + (
2753 + cd 9f &&
2754 +
2755 + mkdir -p goal/a &&
2756 + mkdir -p goal/b &&
2757 + echo foo >goal/a/foo &&
2758 + echo bar >goal/b/bar &&
2759 + echo baz >goal/b/baz &&
2760 + git add goal &&
2761 + test_tick &&
2762 + git commit -m "O" &&
2763 +
2764 + git branch O &&
2765 + git branch A &&
2766 + git branch B &&
2767 +
2768 + git checkout A &&
2769 + git mv goal/ priority &&
2770 + test_tick &&
2771 + git commit -m "A" &&
2772 +
2773 + git checkout B &&
2774 + echo c >goal/c &&
2775 + git add goal/c &&
2776 + test_tick &&
2777 + git commit -m "B"
2778 + )
2779 +'
2780 +
2781 +test_expect_failure '9f-check: Renamed directory that only contained immediate subdirs' '
2782 + (
2783 + cd 9f &&
2784 +
2785 + git checkout A^0 &&
2786 +
2787 + git merge -s recursive B^0 &&
2788 +
2789 + git ls-files -s >out &&
2790 + test_line_count = 4 out &&
2791 +
2792 + git rev-parse >actual \
2793 + HEAD:priority/a/foo \
2794 + HEAD:priority/b/bar \
2795 + HEAD:priority/b/baz \
2796 + HEAD:priority/c &&
2797 + git rev-parse >expect \
2798 + O:goal/a/foo \
2799 + O:goal/b/bar \
2800 + O:goal/b/baz \
2801 + B:goal/c &&
2802 + test_cmp expect actual &&
2803 + test_must_fail git rev-parse HEAD:goal/c
2804 + )
2805 +'
2806 +
2807 +# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed
2808 +# (Related to testcases 1e & 9f)
2809 +# Commit O: goal/{a,b}/$more_files
2810 +# Commit A: priority/{alpha,bravo}/$more_files
2811 +# Commit B: goal/{a,b}/$more_files, goal/c
2812 +# Expected: priority/{alpha,bravo}/$more_files, priority/c
2813 +
2814 +test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
2815 + test_create_repo 9g &&
2816 + (
2817 + cd 9g &&
2818 +
2819 + mkdir -p goal/a &&
2820 + mkdir -p goal/b &&
2821 + echo foo >goal/a/foo &&
2822 + echo bar >goal/b/bar &&
2823 + echo baz >goal/b/baz &&
2824 + git add goal &&
2825 + test_tick &&
2826 + git commit -m "O" &&
2827 +
2828 + git branch O &&
2829 + git branch A &&
2830 + git branch B &&
2831 +
2832 + git checkout A &&
2833 + mkdir priority &&
2834 + git mv goal/a/ priority/alpha &&
2835 + git mv goal/b/ priority/beta &&
2836 + rmdir goal/ &&
2837 + test_tick &&
2838 + git commit -m "A" &&
2839 +
2840 + git checkout B &&
2841 + echo c >goal/c &&
2842 + git add goal/c &&
2843 + test_tick &&
2844 + git commit -m "B"
2845 + )
2846 +'
2847 +
2848 +test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
2849 + (
2850 + cd 9g &&
2851 +
2852 + git checkout A^0 &&
2853 +
2854 + git merge -s recursive B^0 &&
2855 +
2856 + git ls-files -s >out &&
2857 + test_line_count = 4 out &&
2858 +
2859 + git rev-parse >actual \
2860 + HEAD:priority/alpha/foo \
2861 + HEAD:priority/beta/bar \
2862 + HEAD:priority/beta/baz \
2863 + HEAD:priority/c &&
2864 + git rev-parse >expect \
2865 + O:goal/a/foo \
2866 + O:goal/b/bar \
2867 + O:goal/b/baz \
2868 + B:goal/c &&
2869 + test_cmp expect actual &&
2870 + test_must_fail git rev-parse HEAD:goal/c
2871 + )
2872 +'
2873 +
2874 +###########################################################################
2875 +# Rules suggested by section 9:
2876 +#
2877 +# If the other side of history did a directory rename to a path that your
2878 +# side renamed away, then ignore that particular rename from the other
2879 +# side of history for any implicit directory renames.
2880 +###########################################################################
2881 +
2882 test_done