t6423: more involved rules for renaming directories into each other

Testcases 12b and 12c were both slightly weird; they were marked as having a weird resolution, but with the note that even straightforward simple rules can give weird results when the input is bizarre. However, during optimization work for merge-ort, I discovered a significant speedup that is possible if we add one more fairly straightforward rule: we don't bother doing directory rename detection if there are no new files added to the directory on the other side of the history to be affected by the directory rename. This seems like an obvious and straightforward rule, but there was one funny corner case where directory rename detection could affect only existing files: the funny corner case where two directories are renamed into each other on opposite sides of history. In other words, it only results in a different output for testcases 12b and 12c. Since we already thought testcases 12b and 12c were weird anyway, and because the optimization often has a significant effect on common cases (but is entirely prevented if we can't change how 12b and 12c function), let's add the additional rule and tweak how 12b and 12c work. Split both testcases into two (one where we add no new files, and one where the side that doesn't rename a given directory will add files to it), and mark them with the new expectation. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Oct 15, 2020 at 20:46 UTC c64432aacda9054459ce550eca95929897c301bd
2 files changed +230 -26
Documentation/technical/directory-rename-detection.txt
+3
@@ -73,6 +73,9 @@ additional rules:
73 b) Only apply implicit directory renames to directories if the other side
74 of history is the one doing the renaming.
75
76 + c) Do not perform directory rename detection for directories which had no
77 + new paths added to them.
78 +
79 Limitations -- support in different commands
80 --------------------------------------------
81
t/t6423-merge-rename-directories.sh
+227 -26
@@ -4049,31 +4049,124 @@ test_expect_success '12a: Moving one directory hierarchy into another' '
4049 )
4050 '
4051
4052 -# Testcase 12b, Moving two directory hierarchies into each other
4052 +# Testcase 12b1, Moving two directory hierarchies into each other
4053 # (Related to testcases 1c and 12c)
4054 # Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
4055 # Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}}
4056 # Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}}
4057 -# Expected: node1/node2/node1/{leaf1, leaf2},
4057 +# Expected: node1/node2/{leaf3, leaf4}
4058 +# node2/node1/{leaf1, leaf2}
4059 +# NOTE: If there were new files added to the old node1/ or node2/ directories,
4060 +# then we would need to detect renames for those directories and would
4061 +# find that:
4062 +# commit A renames node2/ -> node1/node2/
4063 +# commit B renames node1/ -> node2/node1/
4064 +# Applying those directory renames to the initial result (making all
4065 +# four paths experience a transitive renaming), yields
4066 +# node1/node2/node1/{leaf1, leaf2}
4067 # node2/node1/node2/{leaf3, leaf4}
4068 +# as the result. It may be really weird to have two directories
4069 +# rename each other, but simple rules give weird results when given
4070 +# weird inputs. HOWEVER, the "If" at the beginning of those NOTE was
4071 +# false; there were no new files added and thus there is no directory
4072 +# rename detection to perform. As such, we just have simple renames
4073 +# and the expected answer is:
4074 +# node1/node2/{leaf3, leaf4}
4075 +# node2/node1/{leaf1, leaf2}
4076 +
4077 +test_setup_12b1 () {
4078 + test_create_repo 12b1 &&
4079 + (
4080 + cd 12b1 &&
4081 +
4082 + mkdir -p node1 node2 &&
4083 + echo leaf1 >node1/leaf1 &&
4084 + echo leaf2 >node1/leaf2 &&
4085 + echo leaf3 >node2/leaf3 &&
4086 + echo leaf4 >node2/leaf4 &&
4087 + git add node1 node2 &&
4088 + test_tick &&
4089 + git commit -m "O" &&
4090 +
4091 + git branch O &&
4092 + git branch A &&
4093 + git branch B &&
4094 +
4095 + git checkout A &&
4096 + git mv node2/ node1/ &&
4097 + test_tick &&
4098 + git commit -m "A" &&
4099 +
4100 + git checkout B &&
4101 + git mv node1/ node2/ &&
4102 + test_tick &&
4103 + git commit -m "B"
4104 + )
4105 +}
4106 +
4107 +test_expect_failure '12b1: Moving two directory hierarchies into each other' '
4108 + test_setup_12b1 &&
4109 + (
4110 + cd 12b1 &&
4111 +
4112 + git checkout A^0 &&
4113 +
4114 + git -c merge.directoryRenames=true merge -s recursive B^0 &&
4115 +
4116 + git ls-files -s >out &&
4117 + test_line_count = 4 out &&
4118 +
4119 + git rev-parse >actual \
4120 + HEAD:node2/node1/leaf1 \
4121 + HEAD:node2/node1/leaf2 \
4122 + HEAD:node1/node2/leaf3 \
4123 + HEAD:node1/node2/leaf4 &&
4124 + git rev-parse >expect \
4125 + O:node1/leaf1 \
4126 + O:node1/leaf2 \
4127 + O:node2/leaf3 \
4128 + O:node2/leaf4 &&
4129 + test_cmp expect actual
4130 + )
4131 +'
4132 +
4133 +# Testcase 12b2, Moving two directory hierarchies into each other
4134 +# (Related to testcases 1c and 12c)
4135 +# Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
4136 +# Commit A: node1/{leaf1, leaf2, leaf5, node2/{leaf3, leaf4}}
4137 +# Commit B: node2/{leaf3, leaf4, leaf6, node1/{leaf1, leaf2}}
4138 +# Expected: node1/node2/{node1/{leaf1, leaf2}, leaf6}
4139 +# node2/node1/{node2/{leaf3, leaf4}, leaf5}
4140 # NOTE: Without directory renames, we would expect
4060 -# node2/node1/{leaf1, leaf2},
4061 -# node1/node2/{leaf3, leaf4}
4141 +# A: node2/leaf3 -> node1/node2/leaf3
4142 +# A: node2/leaf1 -> node1/node2/leaf4
4143 +# A: Adds node1/leaf5
4144 +# B: node1/leaf1 -> node2/node1/leaf1
4145 +# B: node1/leaf2 -> node2/node1/leaf2
4146 +# B: Adds node2/leaf6
4147 # with directory rename detection, we note that
4148 # commit A renames node2/ -> node1/node2/
4149 # commit B renames node1/ -> node2/node1/
4065 -# therefore, applying those directory renames to the initial result
4066 -# (making all four paths experience a transitive renaming), yields
4067 -# the expected result.
4150 +# therefore, applying A's directory rename to the paths added in B gives:
4151 +# B: node1/leaf1 -> node1/node2/node1/leaf1
4152 +# B: node1/leaf2 -> node1/node2/node1/leaf2
4153 +# B: Adds node1/node2/leaf6
4154 +# and applying B's directory rename to the paths added in A gives:
4155 +# A: node2/leaf3 -> node2/node1/node2/leaf3
4156 +# A: node2/leaf1 -> node2/node1/node2/leaf4
4157 +# A: Adds node2/node1/leaf5
4158 +# resulting in the expected
4159 +# node1/node2/{node1/{leaf1, leaf2}, leaf6}
4160 +# node2/node1/{node2/{leaf3, leaf4}, leaf5}
4161 #
4162 # You may ask, is it weird to have two directories rename each other?
4163 # To which, I can do no more than shrug my shoulders and say that
4164 # even simple rules give weird results when given weird inputs.
4165
4073 -test_setup_12b () {
4074 - test_create_repo 12b &&
4166 +test_setup_12b2 () {
4167 + test_create_repo 12b2 &&
4168 (
4076 - cd 12b &&
4169 + cd 12b2 &&
4170
4171 mkdir -p node1 node2 &&
4172 echo leaf1 >node1/leaf1 &&
@@ -4090,43 +4183,51 @@ test_setup_12b () {
4183
4184 git checkout A &&
4185 git mv node2/ node1/ &&
4186 + echo leaf5 >node1/leaf5 &&
4187 + git add node1/leaf5 &&
4188 test_tick &&
4189 git commit -m "A" &&
4190
4191 git checkout B &&
4192 git mv node1/ node2/ &&
4193 + echo leaf6 >node2/leaf6 &&
4194 + git add node2/leaf6 &&
4195 test_tick &&
4196 git commit -m "B"
4197 )
4198 }
4199
4103 -test_expect_success '12b: Moving two directory hierarchies into each other' '
4104 - test_setup_12b &&
4200 +test_expect_success '12b2: Moving two directory hierarchies into each other' '
4201 + test_setup_12b2 &&
4202 (
4106 - cd 12b &&
4203 + cd 12b2 &&
4204
4205 git checkout A^0 &&
4206
4207 git -c merge.directoryRenames=true merge -s recursive B^0 &&
4208
4209 git ls-files -s >out &&
4113 - test_line_count = 4 out &&
4210 + test_line_count = 6 out &&
4211
4212 git rev-parse >actual \
4213 HEAD:node1/node2/node1/leaf1 \
4214 HEAD:node1/node2/node1/leaf2 \
4215 HEAD:node2/node1/node2/leaf3 \
4119 - HEAD:node2/node1/node2/leaf4 &&
4216 + HEAD:node2/node1/node2/leaf4 \
4217 + HEAD:node2/node1/leaf5 \
4218 + HEAD:node1/node2/leaf6 &&
4219 git rev-parse >expect \
4220 O:node1/leaf1 \
4221 O:node1/leaf2 \
4222 O:node2/leaf3 \
4124 - O:node2/leaf4 &&
4223 + O:node2/leaf4 \
4224 + A:node1/leaf5 \
4225 + B:node2/leaf6 &&
4226 test_cmp expect actual
4227 )
4228 '
4229
4129 -# Testcase 12c, Moving two directory hierarchies into each other w/ content merge
4230 +# Testcase 12c1, Moving two directory hierarchies into each other w/ content merge
4231 # (Related to testcase 12b)
4232 # Commit O: node1/{ leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
4233 # Commit A: node1/{ leaf1_2, leaf2_2, node2/{leaf3_2, leaf4_2}}
@@ -4134,13 +4235,13 @@ test_expect_success '12b: Moving two directory hierarchies into each other' '
4235 # Expected: Content merge conflicts for each of:
4236 # node1/node2/node1/{leaf1, leaf2},
4237 # node2/node1/node2/{leaf3, leaf4}
4137 -# NOTE: This is *exactly* like 12c, except that every path is modified on
4238 +# NOTE: This is *exactly* like 12b1, except that every path is modified on
4239 # each side of the merge.
4240
4140 -test_setup_12c () {
4141 - test_create_repo 12c &&
4241 +test_setup_12c1 () {
4242 + test_create_repo 12c1 &&
4243 (
4143 - cd 12c &&
4244 + cd 12c1 &&
4245
4246 mkdir -p node1 node2 &&
4247 printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
@@ -4171,10 +4272,10 @@ test_setup_12c () {
4272 )
4273 }
4274
4174 -test_expect_success '12c: Moving one directory hierarchy into another w/ content merge' '
4175 - test_setup_12c &&
4275 +test_expect_failure '12c1: Moving one directory hierarchy into another w/ content merge' '
4276 + test_setup_12c1 &&
4277 (
4177 - cd 12c &&
4278 + cd 12c1 &&
4279
4280 git checkout A^0 &&
4281
@@ -4183,6 +4284,102 @@ test_expect_success '12c: Moving one directory hierarchy into another w/ content
4284 git ls-files -u >out &&
4285 test_line_count = 12 out &&
4286
4287 + git rev-parse >actual \
4288 + :1:node2/node1/leaf1 \
4289 + :1:node2/node1/leaf2 \
4290 + :1:node1/node2/leaf3 \
4291 + :1:node1/node2/leaf4 \
4292 + :2:node2/node1/leaf1 \
4293 + :2:node2/node1/leaf2 \
4294 + :2:node1/node2/leaf3 \
4295 + :2:node1/node2/leaf4 \
4296 + :3:node2/node1/leaf1 \
4297 + :3:node2/node1/leaf2 \
4298 + :3:node1/node2/leaf3 \
4299 + :3:node1/node2/leaf4 &&
4300 + git rev-parse >expect \
4301 + O:node1/leaf1 \
4302 + O:node1/leaf2 \
4303 + O:node2/leaf3 \
4304 + O:node2/leaf4 \
4305 + A:node1/leaf1 \
4306 + A:node1/leaf2 \
4307 + A:node1/node2/leaf3 \
4308 + A:node1/node2/leaf4 \
4309 + B:node2/node1/leaf1 \
4310 + B:node2/node1/leaf2 \
4311 + B:node2/leaf3 \
4312 + B:node2/leaf4 &&
4313 + test_cmp expect actual
4314 + )
4315 +'
4316 +
4317 +# Testcase 12c2, Moving two directory hierarchies into each other w/ content merge
4318 +# (Related to testcase 12b)
4319 +# Commit O: node1/{ leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
4320 +# Commit A: node1/{ leaf1_2, leaf2_2, node2/{leaf3_2, leaf4_2}, leaf5}
4321 +# Commit B: node2/{node1/{leaf1_3, leaf2_3}, leaf3_3, leaf4_3, leaf6}
4322 +# Expected: Content merge conflicts for each of:
4323 +# node1/node2/node1/{leaf1, leaf2}
4324 +# node2/node1/node2/{leaf3, leaf4}
4325 +# plus
4326 +# node2/node1/leaf5
4327 +# node1/node2/leaf6
4328 +# NOTE: This is *exactly* like 12b2, except that every path from O is modified
4329 +# on each side of the merge.
4330 +
4331 +test_setup_12c2 () {
4332 + test_create_repo 12c2 &&
4333 + (
4334 + cd 12c2 &&
4335 +
4336 + mkdir -p node1 node2 &&
4337 + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
4338 + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 &&
4339 + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 &&
4340 + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 &&
4341 + git add node1 node2 &&
4342 + test_tick &&
4343 + git commit -m "O" &&
4344 +
4345 + git branch O &&
4346 + git branch A &&
4347 + git branch B &&
4348 +
4349 + git checkout A &&
4350 + git mv node2/ node1/ &&
4351 + for i in `git ls-files`; do echo side A >>$i; done &&
4352 + git add -u &&
4353 + echo leaf5 >node1/leaf5 &&
4354 + git add node1/leaf5 &&
4355 + test_tick &&
4356 + git commit -m "A" &&
4357 +
4358 + git checkout B &&
4359 + git mv node1/ node2/ &&
4360 + for i in `git ls-files`; do echo side B >>$i; done &&
4361 + git add -u &&
4362 + echo leaf6 >node2/leaf6 &&
4363 + git add node2/leaf6 &&
4364 + test_tick &&
4365 + git commit -m "B"
4366 + )
4367 +}
4368 +
4369 +test_expect_success '12c2: Moving one directory hierarchy into another w/ content merge' '
4370 + test_setup_12c2 &&
4371 + (
4372 + cd 12c2 &&
4373 +
4374 + git checkout A^0 &&
4375 +
4376 + test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 &&
4377 +
4378 + git ls-files -s >out &&
4379 + test_line_count = 14 out &&
4380 + git ls-files -u >out &&
4381 + test_line_count = 12 out &&
4382 +
4383 git rev-parse >actual \
4384 :1:node1/node2/node1/leaf1 \
4385 :1:node1/node2/node1/leaf2 \
@@ -4195,7 +4392,9 @@ test_expect_success '12c: Moving one directory hierarchy into another w/ content
4392 :3:node1/node2/node1/leaf1 \
4393 :3:node1/node2/node1/leaf2 \
4394 :3:node2/node1/node2/leaf3 \
4198 - :3:node2/node1/node2/leaf4 &&
4395 + :3:node2/node1/node2/leaf4 \
4396 + :0:node2/node1/leaf5 \
4397 + :0:node1/node2/leaf6 &&
4398 git rev-parse >expect \
4399 O:node1/leaf1 \
4400 O:node1/leaf2 \
@@ -4208,7 +4407,9 @@ test_expect_success '12c: Moving one directory hierarchy into another w/ content
4407 B:node2/node1/leaf1 \
4408 B:node2/node1/leaf2 \
4409 B:node2/leaf3 \
4211 - B:node2/leaf4 &&
4410 + B:node2/leaf4 \
4411 + A:node1/leaf5 \
4412 + B:node2/leaf6 &&
4413 test_cmp expect actual
4414 )
4415 '