]> git.ipfire.org Git - thirdparty/git.git/blobdiff - t/t6423-merge-rename-directories.sh
t6416: correct expectation for rename/rename(1to2) + directory/file
[thirdparty/git.git] / t / t6423-merge-rename-directories.sh
index f7ecbb886d6191c4bb2e8c1da8b23e0a1e22d47d..5ea77564d778c0788cf07ae3ece4d5c5ce346a9d 100755 (executable)
@@ -26,6 +26,7 @@ test_description="recursive merge with directory renames"
 #                     files that might be renamed into each other's paths.)
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
 
 
 ###########################################################################
@@ -1176,10 +1177,18 @@ test_expect_success '5d: Directory/file/file conflict due to directory rename' '
                git ls-files -u >out &&
                test_line_count = 1 out &&
                git ls-files -o >out &&
-               test_line_count = 2 out &&
-
-               git rev-parse >actual \
-                       :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e &&
+               if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+               then
+                       test_line_count = 1 out &&
+
+                       git rev-parse >actual \
+                           :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d~HEAD :0:y/d/e
+               else
+                       test_line_count = 2 out &&
+
+                       git rev-parse >actual \
+                           :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d      :0:y/d/e
+               fi &&
                git rev-parse >expect \
                         O:z/b  O:z/c  B:z/d  B:z/f  A:y/d  B:y/d/e &&
                test_cmp expect actual &&
@@ -1277,20 +1286,114 @@ test_expect_success '6a: Tricky rename/delete' '
        )
 '
 
-# Testcase 6b, Same rename done on both sides
+# Testcase 6b1, Same rename done on both sides
+#   (Related to testcase 6b2 and 8e)
+#   Commit O: z/{b,c,d,e}
+#   Commit A: y/{b,c,d}, x/e
+#   Commit B: y/{b,c,d}, z/{e,f}
+#   Expected: y/{b,c,d,f}, x/e
+#   Note: Directory rename detection says A renamed z/ -> y/ (3 paths renamed
+#         to y/ and only 1 renamed to x/), therefore the new file 'z/f' in B
+#         should be moved to 'y/f'.
+#
+#         This is a bit of an edge case where any behavior might surprise users,
+#         whether that is treating A as renaming z/ -> y/, treating A as renaming
+#         z/ -> x/, or treating A as not doing any directory rename.  However, I
+#         think this answer is the least confusing and most consistent with the
+#         rules elsewhere.
+#
+#         A note about z/ -> x/, since it may not be clear how that could come
+#         about: If we were to ignore files renamed by both sides
+#         (i.e. z/{b,c,d}), as directory rename detection did in git-2.18 thru
+#         at least git-2.28, then we would note there are no renames from z/ to
+#         y/ and one rename from z/ to x/ and thus come to the conclusion that
+#         A renamed z/ -> x/.  This seems more confusing for end users than a
+#         rename of z/ to y/, it makes directory rename detection behavior
+#         harder for them to predict.  As such, we modified the rule, changed
+#         the behavior on testcases 6b2 and 8e, and introduced this 6b1 testcase.
+
+test_setup_6b1 () {
+       test_create_repo 6b1 &&
+       (
+               cd 6b1 &&
+
+               mkdir z &&
+               echo b >z/b &&
+               echo c >z/c &&
+               echo d >z/d &&
+               echo e >z/e &&
+               git add z &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv z y &&
+               mkdir x &&
+               git mv y/e x/e &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv z y &&
+               mkdir z &&
+               git mv y/e z/e &&
+               echo f >z/f &&
+               git add z/f &&
+               test_tick &&
+               git commit -m "B"
+       )
+}
+
+test_expect_merge_algorithm failure success '6b1: Same renames done on both sides, plus another rename' '
+       test_setup_6b1 &&
+       (
+               cd 6b1 &&
+
+               git checkout A^0 &&
+
+               git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 5 out &&
+               git ls-files -u >out &&
+               test_line_count = 0 out &&
+               git ls-files -o >out &&
+               test_line_count = 1 out &&
+
+               git rev-parse >actual \
+                       HEAD:y/b HEAD:y/c HEAD:y/d HEAD:x/e HEAD:y/f &&
+               git rev-parse >expect \
+                       O:z/b    O:z/c    O:z/d    O:z/e    B:z/f &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 6b2, Same rename done on both sides
 #   (Related to testcases 6c and 8e)
 #   Commit O: z/{b,c}
 #   Commit A: y/{b,c}
 #   Commit B: y/{b,c}, z/d
-#   Expected: y/{b,c}, z/d
-#   Note: If we did directory rename detection here, we'd move z/d into y/,
-#         but B did that rename and still decided to put the file into z/,
-#         so we probably shouldn't apply directory rename detection for it.
-
-test_setup_6b () {
-       test_create_repo 6b &&
+#   Expected: y/{b,c,d}
+#   Alternate: y/{b,c}, z/d
+#   Note: Directory rename detection says A renamed z/ -> y/, therefore the new
+#         file 'z/d' in B should be moved to 'y/d'.
+#
+#         We could potentially ignore the renames of z/{b,c} on side A since
+#         those were renamed on both sides.  However, it's a bit of a corner
+#         case because what if there was also a z/e that side A moved to x/e
+#         and side B left alone?  If we used the "ignore renames done on both
+#         sides" logic, then we'd compute that A renamed z/ -> x/, and move
+#         z/d to x/d.  That seems more surprising and uglier than allowing
+#         the z/ -> y/ rename.
+
+test_setup_6b2 () {
+       test_create_repo 6b2 &&
        (
-               cd 6b &&
+               cd 6b2 &&
 
                mkdir z &&
                echo b >z/b &&
@@ -1318,10 +1421,10 @@ test_setup_6b () {
        )
 }
 
-test_expect_success '6b: Same rename done on both sides' '
-       test_setup_6b &&
+test_expect_merge_algorithm failure success '6b2: Same rename done on both sides' '
+       test_setup_6b2 &&
        (
-               cd 6b &&
+               cd 6b2 &&
 
                git checkout A^0 &&
 
@@ -1335,7 +1438,7 @@ test_expect_success '6b: Same rename done on both sides' '
                test_line_count = 1 out &&
 
                git rev-parse >actual \
-                       HEAD:y/b HEAD:y/c HEAD:z/d &&
+                       HEAD:y/b HEAD:y/c HEAD:y/d &&
                git rev-parse >expect \
                        O:z/b    O:z/c    B:z/d &&
                test_cmp expect actual
@@ -1343,7 +1446,7 @@ test_expect_success '6b: Same rename done on both sides' '
 '
 
 # Testcase 6c, Rename only done on same side
-#   (Related to testcases 6b and 8e)
+#   (Related to testcases 6b1, 6b2, and 8e)
 #   Commit O: z/{b,c}
 #   Commit A: z/{b,c} (no change)
 #   Commit B: y/{b,c}, z/d
@@ -1922,17 +2025,32 @@ test_expect_success '7e: transitive rename in rename/delete AND dirs in the way'
                test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
                test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
 
-               git ls-files -s >out &&
-               test_line_count = 5 out &&
-               git ls-files -u >out &&
-               test_line_count = 1 out &&
-               git ls-files -o >out &&
-               test_line_count = 2 out &&
-
-               git rev-parse >actual \
-                       :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d &&
-               git rev-parse >expect \
-                        A:x/d/f  A:y/d/g  O:z/b  O:z/c  O:x/d &&
+               if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+               then
+                       git ls-files -s >out &&
+                       test_line_count = 6 out &&
+                       git ls-files -u >out &&
+                       test_line_count = 2 out &&
+                       git ls-files -o >out &&
+                       test_line_count = 1 out &&
+
+                       git rev-parse >actual \
+                               :0:x/d/f :0:y/d/g :0:y/b :0:y/c :1:y/d~B^0 :3:y/d~B^0 &&
+                       git rev-parse >expect \
+                                A:x/d/f  A:y/d/g  O:z/b  O:z/c  O:x/d      O:x/d
+               else
+                       git ls-files -s >out &&
+                       test_line_count = 5 out &&
+                       git ls-files -u >out &&
+                       test_line_count = 1 out &&
+                       git ls-files -o >out &&
+                       test_line_count = 2 out &&
+
+                       git rev-parse >actual \
+                               :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d &&
+                       git rev-parse >expect \
+                                A:x/d/f  A:y/d/g  O:z/b  O:z/c  O:x/d
+               fi &&
                test_cmp expect actual &&
 
                git hash-object y/d~B^0 >actual &&
@@ -2269,14 +2387,22 @@ test_expect_success '8d: rename/delete...or not?' '
 # Notes: In commit A, directory z got renamed to y.  In commit B, directory z
 #        did NOT get renamed; the directory is still present; instead it is
 #        considered to have just renamed a subset of paths in directory z
-#        elsewhere.  However, this is much like testcase 6b (where commit B
-#        moves all the original paths out of z/ but opted to keep d
-#        within z/).  This makes it hard to judge where d should end up.
+#        elsewhere.  This is much like testcase 6b2 (where commit B moves all
+#        the original paths out of z/ but opted to keep d within z/).
+#
+#        It was not clear in the past what should be done with this testcase;
+#        in fact, I noted that I "just picked one" previously.  However,
+#        following the new logic for testcase 6b2, we should take the rename
+#        and move z/d to y/d.
 #
-#        It's possible that users would get confused about this, but what
-#        should we do instead?  It's not at all clear to me whether z/d or
-#        y/d or something else is a better resolution here, and other cases
-#        start getting really tricky, so I just picked one.
+#        6b1, 6b2, and this case are definitely somewhat fuzzy in terms of
+#        whether they are optimal for end users, but (a) the default for
+#        directory rename detection is to mark these all as conflicts
+#        anyway, (b) it feels like this is less prone to higher order corner
+#        case confusion, and (c) the current algorithm requires less global
+#        knowledge (i.e. less coupling in the algorithm between renames done
+#        on both sides) which thus means users are better able to predict
+#        the behavior, and predict it without computing as many details.
 
 test_setup_8e () {
        test_create_repo 8e &&
@@ -3369,7 +3495,7 @@ test_setup_10e () {
        )
 }
 
-test_expect_failure '10e: Does git complain about untracked file that is not really in the way?' '
+test_expect_merge_algorithm failure success '10e: Does git complain about untracked file that is not really in the way?' '
        test_setup_10e &&
        (
                cd 10e &&
@@ -3947,31 +4073,124 @@ test_expect_success '12a: Moving one directory hierarchy into another' '
        )
 '
 
-# Testcase 12b, Moving two directory hierarchies into each other
+# Testcase 12b1, Moving two directory hierarchies into each other
 #   (Related to testcases 1c and 12c)
 #   Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
 #   Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}}
 #   Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}}
-#   Expected: node1/node2/node1/{leaf1, leaf2},
+#   Expected: node1/node2/{leaf3, leaf4}
+#             node2/node1/{leaf1, leaf2}
+#   NOTE: If there were new files added to the old node1/ or node2/ directories,
+#         then we would need to detect renames for those directories and would
+#         find that:
+#             commit A renames node2/ -> node1/node2/
+#             commit B renames node1/ -> node2/node1/
+#         Applying those directory renames to the initial result (making all
+#         four paths experience a transitive renaming), yields
+#             node1/node2/node1/{leaf1, leaf2}
 #             node2/node1/node2/{leaf3, leaf4}
+#         as the result.  It may be really weird to have two directories
+#         rename each other, but simple rules give weird results when given
+#         weird inputs.  HOWEVER, the "If" at the beginning of those NOTE was
+#         false; there were no new files added and thus there is no directory
+#         rename detection to perform.  As such, we just have simple renames
+#         and the expected answer is:
+#             node1/node2/{leaf3, leaf4}
+#             node2/node1/{leaf1, leaf2}
+
+test_setup_12b1 () {
+       test_create_repo 12b1 &&
+       (
+               cd 12b1 &&
+
+               mkdir -p node1 node2 &&
+               echo leaf1 >node1/leaf1 &&
+               echo leaf2 >node1/leaf2 &&
+               echo leaf3 >node2/leaf3 &&
+               echo leaf4 >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               test_tick &&
+               git commit -m "B"
+       )
+}
+
+test_expect_merge_algorithm failure success '12b1: Moving two directory hierarchies into each other' '
+       test_setup_12b1 &&
+       (
+               cd 12b1 &&
+
+               git checkout A^0 &&
+
+               git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+               git ls-files -s >out &&
+               test_line_count = 4 out &&
+
+               git rev-parse >actual \
+                       HEAD:node2/node1/leaf1 \
+                       HEAD:node2/node1/leaf2 \
+                       HEAD:node1/node2/leaf3 \
+                       HEAD:node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12b2, Moving two directory hierarchies into each other
+#   (Related to testcases 1c and 12c)
+#   Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
+#   Commit A: node1/{leaf1, leaf2, leaf5, node2/{leaf3, leaf4}}
+#   Commit B: node2/{leaf3, leaf4, leaf6, node1/{leaf1, leaf2}}
+#   Expected: node1/node2/{node1/{leaf1, leaf2}, leaf6}
+#             node2/node1/{node2/{leaf3, leaf4}, leaf5}
 #   NOTE: Without directory renames, we would expect
-#                   node2/node1/{leaf1, leaf2},
-#                   node1/node2/{leaf3, leaf4}
+#             A: node2/leaf3 -> node1/node2/leaf3
+#             A: node2/leaf1 -> node1/node2/leaf4
+#             A: Adds           node1/leaf5
+#             B: node1/leaf1 -> node2/node1/leaf1
+#             B: node1/leaf2 -> node2/node1/leaf2
+#             B: Adds           node2/leaf6
 #         with directory rename detection, we note that
 #             commit A renames node2/ -> node1/node2/
 #             commit B renames node1/ -> node2/node1/
-#         therefore, applying those directory renames to the initial result
-#         (making all four paths experience a transitive renaming), yields
-#         the expected result.
+#         therefore, applying A's directory rename to the paths added in B gives:
+#             B: node1/leaf1 -> node1/node2/node1/leaf1
+#             B: node1/leaf2 -> node1/node2/node1/leaf2
+#             B: Adds           node1/node2/leaf6
+#         and applying B's directory rename to the paths added in A gives:
+#             A: node2/leaf3 -> node2/node1/node2/leaf3
+#             A: node2/leaf1 -> node2/node1/node2/leaf4
+#             A: Adds           node2/node1/leaf5
+#         resulting in the expected
+#             node1/node2/{node1/{leaf1, leaf2}, leaf6}
+#             node2/node1/{node2/{leaf3, leaf4}, leaf5}
 #
 #         You may ask, is it weird to have two directories rename each other?
 #         To which, I can do no more than shrug my shoulders and say that
 #         even simple rules give weird results when given weird inputs.
 
-test_setup_12b () {
-       test_create_repo 12b &&
+test_setup_12b2 () {
+       test_create_repo 12b2 &&
        (
-               cd 12b &&
+               cd 12b2 &&
 
                mkdir -p node1 node2 &&
                echo leaf1 >node1/leaf1 &&
@@ -3988,43 +4207,51 @@ test_setup_12b () {
 
                git checkout A &&
                git mv node2/ node1/ &&
+               echo leaf5 >node1/leaf5 &&
+               git add node1/leaf5 &&
                test_tick &&
                git commit -m "A" &&
 
                git checkout B &&
                git mv node1/ node2/ &&
+               echo leaf6 >node2/leaf6 &&
+               git add node2/leaf6 &&
                test_tick &&
                git commit -m "B"
        )
 }
 
-test_expect_success '12b: Moving two directory hierarchies into each other' '
-       test_setup_12b &&
+test_expect_success '12b2: Moving two directory hierarchies into each other' '
+       test_setup_12b2 &&
        (
-               cd 12b &&
+               cd 12b2 &&
 
                git checkout A^0 &&
 
                git -c merge.directoryRenames=true merge -s recursive B^0 &&
 
                git ls-files -s >out &&
-               test_line_count = 4 out &&
+               test_line_count = 6 out &&
 
                git rev-parse >actual \
                        HEAD:node1/node2/node1/leaf1 \
                        HEAD:node1/node2/node1/leaf2 \
                        HEAD:node2/node1/node2/leaf3 \
-                       HEAD:node2/node1/node2/leaf4 &&
+                       HEAD:node2/node1/node2/leaf4 \
+                       HEAD:node2/node1/leaf5       \
+                       HEAD:node1/node2/leaf6       &&
                git rev-parse >expect \
                        O:node1/leaf1 \
                        O:node1/leaf2 \
                        O:node2/leaf3 \
-                       O:node2/leaf4 &&
+                       O:node2/leaf4 \
+                       A:node1/leaf5 \
+                       B:node2/leaf6 &&
                test_cmp expect actual
        )
 '
 
-# Testcase 12c, Moving two directory hierarchies into each other w/ content merge
+# Testcase 12c1, Moving two directory hierarchies into each other w/ content merge
 #   (Related to testcase 12b)
 #   Commit O: node1/{       leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
 #   Commit A: node1/{       leaf1_2, leaf2_2,  node2/{leaf3_2, leaf4_2}}
@@ -4032,13 +4259,103 @@ test_expect_success '12b: Moving two directory hierarchies into each other' '
 #   Expected: Content merge conflicts for each of:
 #               node1/node2/node1/{leaf1, leaf2},
 #               node2/node1/node2/{leaf3, leaf4}
-#   NOTE: This is *exactly* like 12c, except that every path is modified on
+#   NOTE: This is *exactly* like 12b1, except that every path is modified on
 #         each side of the merge.
 
-test_setup_12c () {
-       test_create_repo 12c &&
+test_setup_12c1 () {
+       test_create_repo 12c1 &&
+       (
+               cd 12c1 &&
+
+               mkdir -p node1 node2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 &&
+               printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 &&
+               git add node1 node2 &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               git mv node2/ node1/ &&
+               for i in `git ls-files`; do echo side A >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               git mv node1/ node2/ &&
+               for i in `git ls-files`; do echo side B >>$i; done &&
+               git add -u &&
+               test_tick &&
+               git commit -m "B"
+       )
+}
+
+test_expect_merge_algorithm failure success '12c1: Moving one directory hierarchy into another w/ content merge' '
+       test_setup_12c1 &&
+       (
+               cd 12c1 &&
+
+               git checkout A^0 &&
+
+               test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+               git ls-files -u >out &&
+               test_line_count = 12 out &&
+
+               git rev-parse >actual \
+                       :1:node2/node1/leaf1 \
+                       :1:node2/node1/leaf2 \
+                       :1:node1/node2/leaf3 \
+                       :1:node1/node2/leaf4 \
+                       :2:node2/node1/leaf1 \
+                       :2:node2/node1/leaf2 \
+                       :2:node1/node2/leaf3 \
+                       :2:node1/node2/leaf4 \
+                       :3:node2/node1/leaf1 \
+                       :3:node2/node1/leaf2 \
+                       :3:node1/node2/leaf3 \
+                       :3:node1/node2/leaf4 &&
+               git rev-parse >expect \
+                       O:node1/leaf1 \
+                       O:node1/leaf2 \
+                       O:node2/leaf3 \
+                       O:node2/leaf4 \
+                       A:node1/leaf1 \
+                       A:node1/leaf2 \
+                       A:node1/node2/leaf3 \
+                       A:node1/node2/leaf4 \
+                       B:node2/node1/leaf1 \
+                       B:node2/node1/leaf2 \
+                       B:node2/leaf3 \
+                       B:node2/leaf4 &&
+               test_cmp expect actual
+       )
+'
+
+# Testcase 12c2, Moving two directory hierarchies into each other w/ content merge
+#   (Related to testcase 12b)
+#   Commit O: node1/{       leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
+#   Commit A: node1/{       leaf1_2, leaf2_2,  node2/{leaf3_2, leaf4_2}, leaf5}
+#   Commit B: node2/{node1/{leaf1_3, leaf2_3},        leaf3_3, leaf4_3,  leaf6}
+#   Expected: Content merge conflicts for each of:
+#               node1/node2/node1/{leaf1, leaf2}
+#               node2/node1/node2/{leaf3, leaf4}
+#             plus
+#               node2/node1/leaf5
+#               node1/node2/leaf6
+#   NOTE: This is *exactly* like 12b2, except that every path from O is modified
+#         on each side of the merge.
+
+test_setup_12c2 () {
+       test_create_repo 12c2 &&
        (
-               cd 12c &&
+               cd 12c2 &&
 
                mkdir -p node1 node2 &&
                printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
@@ -4057,6 +4374,8 @@ test_setup_12c () {
                git mv node2/ node1/ &&
                for i in `git ls-files`; do echo side A >>$i; done &&
                git add -u &&
+               echo leaf5 >node1/leaf5 &&
+               git add node1/leaf5 &&
                test_tick &&
                git commit -m "A" &&
 
@@ -4064,20 +4383,24 @@ test_setup_12c () {
                git mv node1/ node2/ &&
                for i in `git ls-files`; do echo side B >>$i; done &&
                git add -u &&
+               echo leaf6 >node2/leaf6 &&
+               git add node2/leaf6 &&
                test_tick &&
                git commit -m "B"
        )
 }
 
-test_expect_success '12c: Moving one directory hierarchy into another w/ content merge' '
-       test_setup_12c &&
+test_expect_success '12c2: Moving one directory hierarchy into another w/ content merge' '
+       test_setup_12c2 &&
        (
-               cd 12c &&
+               cd 12c2 &&
 
                git checkout A^0 &&
 
                test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 &&
 
+               git ls-files -s >out &&
+               test_line_count = 14 out &&
                git ls-files -u >out &&
                test_line_count = 12 out &&
 
@@ -4093,7 +4416,9 @@ test_expect_success '12c: Moving one directory hierarchy into another w/ content
                        :3:node1/node2/node1/leaf1 \
                        :3:node1/node2/node1/leaf2 \
                        :3:node2/node1/node2/leaf3 \
-                       :3:node2/node1/node2/leaf4 &&
+                       :3:node2/node1/node2/leaf4 \
+                       :0:node2/node1/leaf5       \
+                       :0:node1/node2/leaf6       &&
                git rev-parse >expect \
                        O:node1/leaf1 \
                        O:node1/leaf2 \
@@ -4106,7 +4431,9 @@ test_expect_success '12c: Moving one directory hierarchy into another w/ content
                        B:node2/node1/leaf1 \
                        B:node2/node1/leaf2 \
                        B:node2/leaf3 \
-                       B:node2/leaf4 &&
+                       B:node2/leaf4 \
+                       A:node1/leaf5 \
+                       B:node2/leaf6 &&
                test_cmp expect actual
        )
 '
@@ -4227,6 +4554,201 @@ test_expect_success '12e: Rename/merge subdir into the root, variant 2' '
        )
 '
 
+# Testcase 12f, Rebase of patches with big directory rename
+#   Commit O:
+#              dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O}
+#              dir/subdir/tweaked/{f,g,h,Makefile_SUB_O}
+#              dir/unchanged/<LOTS OF FILES>
+#   Commit A:
+#     (Remove f & g, move e into newsubdir, rename dir/->folder/, modify files)
+#              folder/subdir/{a,b,c,d,Makefile_TOP_A}
+#              folder/subdir/newsubdir/e_A
+#              folder/subdir/tweaked/{h,Makefile_SUB_A}
+#              folder/unchanged/<LOTS OF FILES>
+#   Commit B1:
+#     (add newfile.{c,py}, modify underscored files)
+#              dir/{a,b,c,d,e_B1,Makefile_TOP_B1,newfile.c}
+#              dir/tweaked/{f,g,h,Makefile_SUB_B1,newfile.py}
+#              dir/unchanged/<LOTS OF FILES>
+#   Commit B2:
+#     (Modify e further, add newfile.rs)
+#              dir/{a,b,c,d,e_B2,Makefile_TOP_B1,newfile.c,newfile.rs}
+#              dir/tweaked/{f,g,h,Makefile_SUB_B1,newfile.py}
+#              dir/unchanged/<LOTS OF FILES>
+#   Expected:
+#          B1-picked:
+#              folder/subdir/{a,b,c,d,Makefile_TOP_Merge1,newfile.c}
+#              folder/subdir/newsubdir/e_Merge1
+#              folder/subdir/tweaked/{h,Makefile_SUB_Merge1,newfile.py}
+#              folder/unchanged/<LOTS OF FILES>
+#          B2-picked:
+#              folder/subdir/{a,b,c,d,Makefile_TOP_Merge1,newfile.c,newfile.rs}
+#              folder/subdir/newsubdir/e_Merge2
+#              folder/subdir/tweaked/{h,Makefile_SUB_Merge1,newfile.py}
+#              folder/unchanged/<LOTS OF FILES>
+#
+# Notes: This testcase happens to exercise lots of the
+#        optimization-specific codepaths in merge-ort, and also
+#        demonstrated a failing of the directory rename detection algorithm
+#        in merge-recursive; newfile.c should not get pushed into
+#        folder/subdir/newsubdir/, yet merge-recursive put it there because
+#        the rename of dir/subdir/{a,b,c,d} -> folder/subdir/{a,b,c,d}
+#        looks like
+#            dir/ -> folder/,
+#        whereas the rename of dir/subdir/e -> folder/subdir/newsubdir/e
+#        looks like
+#            dir/subdir/ -> folder/subdir/newsubdir/
+#        and if we note that newfile.c is found in dir/subdir/, we might
+#        overlook the dir/ -> folder/ rule that has more weight.
+
+test_setup_12f () {
+       test_create_repo 12f &&
+       (
+               cd 12f &&
+
+               mkdir -p dir/unchanged &&
+               mkdir -p dir/subdir/tweaked &&
+               echo a >dir/subdir/a &&
+               echo b >dir/subdir/b &&
+               echo c >dir/subdir/c &&
+               echo d >dir/subdir/d &&
+               test_seq 1 10 >dir/subdir/e &&
+               test_seq 10 20 >dir/subdir/Makefile &&
+               echo f >dir/subdir/tweaked/f &&
+               echo g >dir/subdir/tweaked/g &&
+               echo h >dir/subdir/tweaked/h &&
+               test_seq 20 30 >dir/subdir/tweaked/Makefile &&
+               for i in `test_seq 1 88`; do
+                       echo content $i >dir/unchanged/file_$i
+               done &&
+               git add . &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git switch A &&
+               git rm dir/subdir/tweaked/f dir/subdir/tweaked/g &&
+               test_seq 2 10 >dir/subdir/e &&
+               test_seq 11 20 >dir/subdir/Makefile &&
+               test_seq 21 30 >dir/subdir/tweaked/Makefile &&
+               mkdir dir/subdir/newsubdir &&
+               git mv dir/subdir/e dir/subdir/newsubdir/ &&
+               git mv dir folder &&
+               git add . &&
+               git commit -m "A" &&
+
+               git switch B &&
+               mkdir dir/subdir/newsubdir/ &&
+               echo c code >dir/subdir/newfile.c &&
+               echo python code >dir/subdir/newsubdir/newfile.py &&
+               test_seq 1 11 >dir/subdir/e &&
+               test_seq 10 21 >dir/subdir/Makefile &&
+               test_seq 20 31 >dir/subdir/tweaked/Makefile &&
+               git add . &&
+               git commit -m "B1" &&
+
+               echo rust code >dir/subdir/newfile.rs &&
+               test_seq 1 12 >dir/subdir/e &&
+               git add . &&
+               git commit -m "B2"
+       )
+}
+
+test_expect_merge_algorithm failure success '12f: Trivial directory resolve, caching, all kinds of fun' '
+       test_setup_12f &&
+       (
+               cd 12f &&
+
+               git checkout A^0 &&
+               git branch Bmod B &&
+
+               git -c merge.directoryRenames=true rebase A Bmod &&
+
+               echo Checking the pick of B1... &&
+
+               test_must_fail git rev-parse Bmod~1:dir &&
+
+               git ls-tree -r Bmod~1 >out &&
+               test_line_count = 98 out &&
+
+               git diff --name-status A Bmod~1 >actual &&
+               q_to_tab >expect <<-\EOF &&
+               MQfolder/subdir/Makefile
+               AQfolder/subdir/newfile.c
+               MQfolder/subdir/newsubdir/e
+               AQfolder/subdir/newsubdir/newfile.py
+               MQfolder/subdir/tweaked/Makefile
+               EOF
+               test_cmp expect actual &&
+
+               # Three-way merged files
+               test_seq  2 11 >e_Merge1 &&
+               test_seq 11 21 >Makefile_TOP &&
+               test_seq 21 31 >Makefile_SUB &&
+               git hash-object >expect      \
+                       e_Merge1             \
+                       Makefile_TOP         \
+                       Makefile_SUB         &&
+               git rev-parse >actual              \
+                       Bmod~1:folder/subdir/newsubdir/e     \
+                       Bmod~1:folder/subdir/Makefile        \
+                       Bmod~1:folder/subdir/tweaked/Makefile &&
+               test_cmp expect actual &&
+
+               # New files showed up at the right location with right contents
+               git rev-parse >expect                \
+                       B~1:dir/subdir/newfile.c            \
+                       B~1:dir/subdir/newsubdir/newfile.py &&
+               git rev-parse >actual                      \
+                       Bmod~1:folder/subdir/newfile.c            \
+                       Bmod~1:folder/subdir/newsubdir/newfile.py &&
+               test_cmp expect actual &&
+
+               # Removed files
+               test_path_is_missing folder/subdir/tweaked/f &&
+               test_path_is_missing folder/subdir/tweaked/g &&
+
+               # Unchanged files or directories
+               git rev-parse >actual        \
+                       Bmod~1:folder/subdir/a          \
+                       Bmod~1:folder/subdir/b          \
+                       Bmod~1:folder/subdir/c          \
+                       Bmod~1:folder/subdir/d          \
+                       Bmod~1:folder/unchanged         \
+                       Bmod~1:folder/subdir/tweaked/h &&
+               git rev-parse >expect          \
+                       O:dir/subdir/a         \
+                       O:dir/subdir/b         \
+                       O:dir/subdir/c         \
+                       O:dir/subdir/d         \
+                       O:dir/unchanged        \
+                       O:dir/subdir/tweaked/h &&
+               test_cmp expect actual &&
+
+               echo Checking the pick of B2... &&
+
+               test_must_fail git rev-parse Bmod:dir &&
+
+               git ls-tree -r Bmod >out &&
+               test_line_count = 99 out &&
+
+               git diff --name-status Bmod~1 Bmod >actual &&
+               q_to_tab >expect <<-\EOF &&
+               AQfolder/subdir/newfile.rs
+               MQfolder/subdir/newsubdir/e
+               EOF
+               test_cmp expect actual &&
+
+               # Three-way merged file
+               test_seq  2 12 >e_Merge2 &&
+               git hash-object e_Merge2 >expect &&
+               git rev-parse Bmod:folder/subdir/newsubdir/e >actual &&
+               test_cmp expect actual
+       )
+'
+
 ###########################################################################
 # SECTION 13: Checking informational and conflict messages
 #