Git fork

Merge branch 'en/ort-rename-fixes' into maint-2.51

Various bugs about rename handling in "ort" merge strategy have
been fixed.

* en/ort-rename-fixes:
merge-ort: fix directory rename on top of source of other rename/delete
merge-ort: fix incorrect file handling
merge-ort: clarify the interning of strings in opt->priv->path
t6423: fix missed staging of file in testcases 12i,12j,12k
t6423: document two bugs with rename-to-self testcases
merge-ort: drop unnecessary temporary in check_for_directory_rename()
merge-ort: update comments to modern testfile location

+542 -28
+40 -15
merge-ort.c
··· 316 316 * (e.g. "drivers/firmware/raspberrypi.c"). 317 317 * * store all relevant paths in the repo, both directories and 318 318 * files (e.g. drivers, drivers/firmware would also be included) 319 - * * these keys serve to intern all the path strings, which allows 320 - * us to do pointer comparison on directory names instead of 321 - * strcmp; we just have to be careful to use the interned strings. 319 + * * these keys serve to intern *all* path strings, which allows us 320 + * to do pointer comparisons on file & directory names instead of 321 + * using strcmp; however, for this pointer-comparison optimization 322 + * to work, any code path that independently computes a path needs 323 + * to check for it existing in this strmap, and if so, point to 324 + * the path in this strmap instead of their computed copy. See 325 + * the "reuse known pointer" comment in 326 + * apply_directory_rename_modifications() for an example. 322 327 * 323 328 * The values of paths: 324 329 * * either a pointer to a merged_info, or a conflict_info struct ··· 2163 2168 /* 2164 2169 * FIXME: If opt->priv->call_depth && !clean, then we really 2165 2170 * should not make result->mode match either a->mode or 2166 - * b->mode; that causes t6036 "check conflicting mode for 2171 + * b->mode; that causes t6416 "check conflicting mode for 2167 2172 * regular file" to fail. It would be best to use some other 2168 2173 * mode, but we'll confuse all kinds of stuff if we use one 2169 2174 * where S_ISREG(result->mode) isn't true, and if we use ··· 2313 2318 return strbuf_detach(&new_path, NULL); 2314 2319 } 2315 2320 2316 - static int path_in_way(struct strmap *paths, const char *path, unsigned side_mask) 2321 + static int path_in_way(struct strmap *paths, 2322 + const char *path, 2323 + unsigned side_mask, 2324 + struct diff_filepair *p) 2317 2325 { 2318 2326 struct merged_info *mi = strmap_get(paths, path); 2319 2327 struct conflict_info *ci; 2320 2328 if (!mi) 2321 2329 return 0; 2322 2330 INITIALIZE_CI(ci, mi); 2323 - return mi->clean || (side_mask & (ci->filemask | ci->dirmask)); 2331 + return mi->clean || (side_mask & (ci->filemask | ci->dirmask)) 2332 + /* See testcases 12[npq] of t6423 for this next condition */ 2333 + || ((ci->filemask & 0x01) && 2334 + strcmp(p->one->path, path)); 2324 2335 } 2325 2336 2326 2337 /* ··· 2332 2343 static char *handle_path_level_conflicts(struct merge_options *opt, 2333 2344 const char *path, 2334 2345 unsigned side_index, 2346 + struct diff_filepair *p, 2335 2347 struct strmap_entry *rename_info, 2336 2348 struct strmap *collisions) 2337 2349 { ··· 2366 2378 */ 2367 2379 if (c_info->reported_already) { 2368 2380 clean = 0; 2369 - } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index)) { 2381 + } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index, p)) { 2370 2382 c_info->reported_already = 1; 2371 2383 strbuf_add_separated_string_list(&collision_paths, ", ", 2372 2384 &c_info->source_files); ··· 2520 2532 * happening, and fall back to no-directory-rename detection 2521 2533 * behavior for those paths. 2522 2534 * 2523 - * See testcases 9e and all of section 5 from t6043 for examples. 2535 + * See testcases 9e and all of section 5 from t6423 for examples. 2524 2536 */ 2525 2537 for (i = 0; i < pairs->nr; ++i) { 2526 2538 struct strmap_entry *rename_info; ··· 2573 2585 static char *check_for_directory_rename(struct merge_options *opt, 2574 2586 const char *path, 2575 2587 unsigned side_index, 2588 + struct diff_filepair *p, 2576 2589 struct strmap *dir_renames, 2577 2590 struct strmap *dir_rename_exclusions, 2578 2591 struct strmap *collisions, ··· 2580 2593 { 2581 2594 char *new_path; 2582 2595 struct strmap_entry *rename_info; 2583 - struct strmap_entry *otherinfo; 2584 2596 const char *new_dir; 2585 2597 int other_side = 3 - side_index; 2586 2598 ··· 2615 2627 * to not let Side1 do the rename to dumbdir, since we know that is 2616 2628 * the source of one of our directory renames. 2617 2629 * 2618 - * That's why otherinfo and dir_rename_exclusions is here. 2630 + * That's why dir_rename_exclusions is here. 2619 2631 * 2620 2632 * As it turns out, this also prevents N-way transient rename 2621 - * confusion; See testcases 9c and 9d of t6043. 2633 + * confusion; See testcases 9c and 9d of t6423. 2622 2634 */ 2623 2635 new_dir = rename_info->value; /* old_dir = rename_info->key; */ 2624 - otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); 2625 - if (otherinfo) { 2636 + if (strmap_contains(dir_rename_exclusions, new_dir)) { 2626 2637 path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1, 2627 2638 rename_info->key, path, new_dir, NULL, 2628 2639 _("WARNING: Avoiding applying %s -> %s rename " ··· 2631 2642 return NULL; 2632 2643 } 2633 2644 2634 - new_path = handle_path_level_conflicts(opt, path, side_index, 2645 + new_path = handle_path_level_conflicts(opt, path, side_index, p, 2635 2646 rename_info, 2636 2647 &collisions[side_index]); 2637 2648 *clean_merge &= (new_path != NULL); ··· 2874 2885 newpath = new_ent->key; 2875 2886 newinfo = new_ent->value; 2876 2887 } 2888 + 2889 + /* 2890 + * Directory renames can result in rename-to-self; the code 2891 + * below assumes we have A->B with different A & B, and tries 2892 + * to move all entries to path B. If A & B are the same path, 2893 + * the logic can get confused, so skip further processing when 2894 + * A & B are already the same path. 2895 + * 2896 + * As a reminder, we can avoid strcmp here because all paths 2897 + * are interned in opt->priv->paths; see the comment above 2898 + * "paths" in struct merge_options_internal. 2899 + */ 2900 + if (oldpath == newpath) 2901 + continue; 2877 2902 2878 2903 /* 2879 2904 * If pair->one->path isn't in opt->priv->paths, that means ··· 3419 3444 } 3420 3445 3421 3446 new_path = check_for_directory_rename(opt, p->two->path, 3422 - side_index, 3447 + side_index, p, 3423 3448 dir_renames_for_side, 3424 3449 rename_exclusions, 3425 3450 collisions,
+502 -13
t/t6423-merge-rename-directories.sh
··· 4731 4731 4732 4732 mkdir -p source/subdir && 4733 4733 echo foo >source/subdir/foo && 4734 - echo bar >source/bar && 4734 + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && 4735 4735 echo baz >source/baz && 4736 4736 git add source && 4737 4737 git commit -m orig && ··· 4747 4747 git switch B && 4748 4748 git mv source/bar source/subdir/bar && 4749 4749 echo more baz >>source/baz && 4750 + git add source/baz && 4750 4751 git commit -m B 4751 4752 ) 4752 4753 } ··· 4758 4759 4759 4760 git checkout A^0 && 4760 4761 4762 + # NOTE: A potentially better resolution would be for 4763 + # source/bar -> source/subdir/bar 4764 + # to use the directory rename to become 4765 + # source/bar -> source/bar 4766 + # (a rename to self), and thus we end up with bar with 4767 + # a path conflict (given merge.directoryRenames=conflict). 4768 + # However, since the relevant renames optimization 4769 + # prevents us from noticing 4770 + # source/bar -> source/subdir/bar 4771 + # as a rename and looking at it just as 4772 + # delete source/bar 4773 + # add source/subdir/bar 4774 + # the directory rename of source/subdir/bar -> source/bar does 4775 + # not look like a rename-to-self situation but a 4776 + # rename-on-top-of-other-file situation. We do not want 4777 + # stage 1 entries from an unrelated file, so we expect an 4778 + # error about there being a file in the way. 4779 + 4780 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 4781 + 4782 + grep "CONFLICT (implicit dir rename).*source/bar in the way" out && 4783 + test_path_is_missing source/bar && 4784 + test_path_is_file source/subdir/bar && 4785 + test_path_is_file source/baz && 4786 + 4787 + git ls-files >actual && 4788 + uniq <actual >tracked && 4789 + test_line_count = 3 tracked && 4790 + 4791 + git status --porcelain -uno >actual && 4792 + cat >expect <<-\EOF && 4793 + M source/baz 4794 + R source/bar -> source/subdir/bar 4795 + EOF 4796 + test_cmp expect actual 4797 + ) 4798 + ' 4799 + 4800 + # Testcase 12i2, Identical to 12i except that source/subdir/bar modified on unrenamed side 4801 + # Commit O: source/{subdir/foo, bar, baz_1} 4802 + # Commit A: source/{foo, bar_2, baz_1} 4803 + # Commit B: source/{subdir/{foo, bar}, baz_2} 4804 + # Expected: source/{foo, bar, baz_2}, with conflicts on 4805 + # source/bar vs. source/subdir/bar 4806 + 4807 + test_setup_12i2 () { 4808 + git init 12i2 && 4809 + ( 4810 + cd 12i2 && 4811 + 4812 + mkdir -p source/subdir && 4813 + echo foo >source/subdir/foo && 4814 + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && 4815 + echo baz >source/baz && 4816 + git add source && 4817 + git commit -m orig && 4818 + 4819 + git branch O && 4820 + git branch A && 4821 + git branch B && 4822 + 4823 + git switch A && 4824 + git mv source/subdir/foo source/foo && 4825 + echo 8 >> source/bar && 4826 + git add source/bar && 4827 + git commit -m A && 4828 + 4829 + git switch B && 4830 + git mv source/bar source/subdir/bar && 4831 + echo more baz >>source/baz && 4832 + git add source/baz && 4833 + git commit -m B 4834 + ) 4835 + } 4836 + 4837 + test_expect_success '12i2: Directory rename causes rename-to-self' ' 4838 + test_setup_12i2 && 4839 + ( 4840 + cd 12i2 && 4841 + 4842 + git checkout A^0 && 4843 + 4761 4844 test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && 4762 4845 4763 4846 test_path_is_missing source/subdir && ··· 4771 4854 git status --porcelain -uno >actual && 4772 4855 cat >expect <<-\EOF && 4773 4856 UU source/bar 4774 - M source/baz 4857 + M source/baz 4775 4858 EOF 4776 4859 test_cmp expect actual 4777 4860 ) ··· 4806 4889 git switch B && 4807 4890 git mv bar subdir/bar && 4808 4891 echo more baz >>baz && 4892 + git add baz && 4809 4893 git commit -m B 4810 4894 ) 4811 4895 } ··· 4817 4901 4818 4902 git checkout A^0 && 4819 4903 4820 - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && 4904 + # NOTE: A potentially better resolution would be for 4905 + # bar -> subdir/bar 4906 + # to use the directory rename to become 4907 + # bar -> bar 4908 + # (a rename to self), and thus we end up with bar with 4909 + # a path conflict (given merge.directoryRenames=conflict). 4910 + # However, since the relevant renames optimization 4911 + # prevents us from noticing 4912 + # bar -> subdir/bar 4913 + # as a rename and looking at it just as 4914 + # delete bar 4915 + # add subdir/bar 4916 + # the directory rename of subdir/bar -> bar does not look 4917 + # like a rename-to-self situation but a 4918 + # rename-on-top-of-other-file situation. We do not want 4919 + # stage 1 entries from an unrelated file, so we expect an 4920 + # error about there being a file in the way. 4821 4921 4822 - test_path_is_missing subdir && 4823 - test_path_is_file bar && 4922 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 4923 + grep "CONFLICT (implicit dir rename).*bar in the way" out && 4924 + 4925 + test_path_is_missing bar && 4926 + test_path_is_file subdir/bar && 4824 4927 test_path_is_file baz && 4825 4928 4826 4929 git ls-files >actual && ··· 4829 4932 4830 4933 git status --porcelain -uno >actual && 4831 4934 cat >expect <<-\EOF && 4832 - UU bar 4833 - M baz 4935 + M baz 4936 + R bar -> subdir/bar 4834 4937 EOF 4835 4938 test_cmp expect actual 4836 4939 ) ··· 4865 4968 git switch B && 4866 4969 git mv dirA/bar dirB/bar && 4867 4970 echo more baz >>dirA/baz && 4971 + git add dirA/baz && 4868 4972 git commit -m B 4869 4973 ) 4870 4974 } ··· 4876 4980 4877 4981 git checkout A^0 && 4878 4982 4879 - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && 4983 + # NOTE: A potentially better resolution would be for 4984 + # dirA/bar -> dirB/bar 4985 + # to use the directory rename (dirB/ -> dirA/) to become 4986 + # dirA/bar -> dirA/bar 4987 + # (a rename to self), and thus we end up with bar with 4988 + # a path conflict (given merge.directoryRenames=conflict). 4989 + # However, since the relevant renames optimization 4990 + # prevents us from noticing 4991 + # dirA/bar -> dirB/bar 4992 + # as a rename and looking at it just as 4993 + # delete dirA/bar 4994 + # add dirB/bar 4995 + # the directory rename of dirA/bar -> dirB/bar does 4996 + # not look like a rename-to-self situation but a 4997 + # rename-on-top-of-other-file situation. We do not want 4998 + # stage 1 entries from an unrelated file, so we expect an 4999 + # error about there being a file in the way. 4880 5000 4881 - test_path_is_missing dirB && 4882 - test_path_is_file dirA/bar && 5001 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 5002 + grep "CONFLICT (implicit dir rename).*dirA/bar in the way" out && 5003 + 5004 + test_path_is_missing dirA/bar && 5005 + test_path_is_file dirB/bar && 4883 5006 test_path_is_file dirA/baz && 4884 5007 4885 5008 git ls-files >actual && ··· 4888 5011 4889 5012 git status --porcelain -uno >actual && 4890 5013 cat >expect <<-\EOF && 4891 - UU dirA/bar 4892 - M dirA/baz 5014 + M dirA/baz 5015 + R dirA/bar -> dirB/bar 4893 5016 EOF 4894 5017 test_cmp expect actual 4895 5018 ) ··· 5056 5179 ) 5057 5180 ' 5058 5181 5182 + # Testcase 12n, Directory rename transitively makes rename back to self 5183 + # 5184 + # (Since this is a cherry-pick instead of merge, the labels are a bit weird. 5185 + # O, the original commit, is A~1 rather than what branch O points to.) 5186 + # 5187 + # Commit O: tools/hello 5188 + # world 5189 + # Commit A: tools/hello 5190 + # tools/world 5191 + # Commit B: hello 5192 + # In words: 5193 + # A: world -> tools/world 5194 + # B: tools/ -> /, i.e. rename all of tools to toplevel directory 5195 + # delete world 5196 + # 5197 + # Expected: 5198 + # CONFLICT (file location): tools/world vs. world 5199 + # 5200 + 5059 5201 test_setup_12n () { 5060 5202 git init 12n && 5061 5203 ( ··· 5092 5234 git checkout -q B^0 && 5093 5235 5094 5236 test_must_fail git cherry-pick A^0 >out && 5095 - grep "CONFLICT (file location).*should perhaps be moved" out 5237 + test_grep "CONFLICT (file location).*should perhaps be moved" out && 5238 + 5239 + # Should have 1 entry for hello, and 2 for world 5240 + test_stdout_line_count = 3 git ls-files -s && 5241 + test_stdout_line_count = 1 git ls-files -s hello && 5242 + test_stdout_line_count = 2 git ls-files -s world 5243 + ) 5244 + ' 5245 + 5246 + # Testcase 12n2, Directory rename transitively makes rename back to self 5247 + # 5248 + # Commit O: tools/hello 5249 + # world 5250 + # Commit A: tools/hello 5251 + # tools/world 5252 + # Commit B: hello 5253 + # In words: 5254 + # A: world -> tools/world 5255 + # B: tools/ -> /, i.e. rename all of tools to toplevel directory 5256 + # delete world 5257 + # 5258 + # Expected: 5259 + # CONFLICT (file location): tools/world vs. world 5260 + # 5261 + 5262 + test_setup_12n2 () { 5263 + git init 12n2 && 5264 + ( 5265 + cd 12n2 && 5266 + 5267 + mkdir tools && 5268 + echo hello >tools/hello && 5269 + git add tools/hello && 5270 + echo world >world && 5271 + git add world && 5272 + git commit -m "O" && 5273 + 5274 + git branch O && 5275 + git branch A && 5276 + git branch B && 5277 + 5278 + git switch A && 5279 + git mv world tools/world && 5280 + git commit -m "Move world into tools/" && 5281 + 5282 + git switch B && 5283 + git mv tools/hello hello && 5284 + git rm world && 5285 + git commit -m "Move hello from tools/ to toplevel" 5286 + ) 5287 + } 5288 + 5289 + test_expect_success '12n2: Directory rename transitively makes rename back to self' ' 5290 + test_setup_12n2 && 5291 + ( 5292 + cd 12n2 && 5293 + 5294 + git checkout -q B^0 && 5295 + 5296 + test_might_fail git -c merge.directoryRenames=true merge A^0 >out && 5297 + 5298 + # Should have 1 entry for hello, and either 0 or 2 for world 5299 + # 5300 + # NOTE: Since merge.directoryRenames=true, there is no path 5301 + # conflict for world vs. tools/world; it should end up at 5302 + # world. The fact that world was unmodified on side A, means 5303 + # there was no content conflict; we should just take the 5304 + # content from side B -- i.e. delete the file. So merging 5305 + # could just delete world. 5306 + # 5307 + # However, rename-to-self-via-directory-rename is a bit more 5308 + # challenging. Relax this test to allow world to be treated 5309 + # as a modify/delete conflict as well, meaning it will have 5310 + # two higher order stages, that just so happen to match. 5311 + # 5312 + test_stdout_line_count = 1 git ls-files -s hello && 5313 + test_stdout_line_count = 2 git ls-files -s world && 5314 + test_grep "CONFLICT (modify/delete).*world deleted in HEAD" out 5315 + ) 5316 + ' 5317 + 5318 + # Testcase 12o, Directory rename hits other rename source; file still in way on same side 5319 + # Commit O: A/file1_1 5320 + # A/stuff 5321 + # B/file1_2 5322 + # B/stuff 5323 + # C/other 5324 + # Commit A: A/file1_1 5325 + # A/stuff 5326 + # B/stuff 5327 + # C/file1_2 5328 + # C/other 5329 + # Commit B: D/file2_1 5330 + # A/stuff 5331 + # B/file1_2 5332 + # B/stuff 5333 + # A/other 5334 + # In words: 5335 + # A: rename B/file1_2 -> C/file1_2 5336 + # B: rename C/ -> A/ 5337 + # rename A/file1_1 -> D/file2_1 5338 + # Rationale: 5339 + # A/stuff is unmodified, it shows up in final output 5340 + # B/stuff is unmodified, it shows up in final output 5341 + # C/other touched on one side (rename to A), so A/other shows up in output 5342 + # A/file1 is renamed to D/file2 5343 + # B/file1 -> C/file1 and even though C/ -> A/, A/file1 is 5344 + # "in the way" so we don't do the directory rename 5345 + # Expected: A/stuff 5346 + # B/stuff 5347 + # A/other 5348 + # D/file2 5349 + # C/file1 5350 + # + CONFLICT (implicit dir rename): A/file1 in way of C/file1 5351 + # 5352 + 5353 + test_setup_12o () { 5354 + git init 12o && 5355 + ( 5356 + cd 12o && 5357 + 5358 + mkdir -p A B C && 5359 + echo 1 >A/file1 && 5360 + echo 2 >B/file1 && 5361 + echo other >C/other && 5362 + echo Astuff >A/stuff && 5363 + echo Bstuff >B/stuff && 5364 + git add . && 5365 + git commit -m "O" && 5366 + 5367 + git branch O && 5368 + git branch A && 5369 + git branch B && 5370 + 5371 + git switch A && 5372 + git mv B/file1 C/ && 5373 + git add . && 5374 + git commit -m "A" && 5375 + 5376 + git switch B && 5377 + mkdir -p D && 5378 + git mv A/file1 D/file2 && 5379 + git mv C/other A/other && 5380 + git add . && 5381 + git commit -m "B" 5382 + ) 5383 + } 5384 + 5385 + test_expect_success '12o: Directory rename hits other rename source; file still in way on same side' ' 5386 + test_setup_12o && 5387 + ( 5388 + cd 12o && 5389 + 5390 + git checkout -q A^0 && 5391 + 5392 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 5393 + 5394 + test_stdout_line_count = 5 git ls-files -s && 5395 + test_stdout_line_count = 1 git ls-files -s A/other && 5396 + test_stdout_line_count = 1 git ls-files -s A/stuff && 5397 + test_stdout_line_count = 1 git ls-files -s B/stuff && 5398 + test_stdout_line_count = 1 git ls-files -s D/file2 && 5399 + 5400 + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && 5401 + test_stdout_line_count = 1 git ls-files -s C/file1 5402 + ) 5403 + ' 5404 + 5405 + # Testcase 12p, Directory rename hits other rename source; file still in way on other side 5406 + # Commit O: A/file1_1 5407 + # A/stuff 5408 + # B/file1_2 5409 + # B/stuff 5410 + # C/other 5411 + # Commit A: D/file2_1 5412 + # A/stuff 5413 + # B/stuff 5414 + # C/file1_2 5415 + # C/other 5416 + # Commit B: A/file1_1 5417 + # A/stuff 5418 + # B/file1_2 5419 + # B/stuff 5420 + # A/other 5421 + # Short version: 5422 + # A: rename A/file1_1 -> D/file2_1 5423 + # rename B/file1_2 -> C/file1_2 5424 + # B: Rename C/ -> A/ 5425 + # Rationale: 5426 + # A/stuff is unmodified, it shows up in final output 5427 + # B/stuff is unmodified, it shows up in final output 5428 + # C/other touched on one side (rename to A), so A/other shows up in output 5429 + # A/file1 is renamed to D/file2 5430 + # B/file1 -> C/file1 and even though C/ -> A/, A/file1 is 5431 + # "in the way" so we don't do the directory rename 5432 + # Expected: A/stuff 5433 + # B/stuff 5434 + # A/other 5435 + # D/file2 5436 + # C/file1 5437 + # + CONFLICT (implicit dir rename): A/file1 in way of C/file1 5438 + # 5439 + 5440 + test_setup_12p () { 5441 + git init 12p && 5442 + ( 5443 + cd 12p && 5444 + 5445 + mkdir -p A B C && 5446 + echo 1 >A/file1 && 5447 + echo 2 >B/file1 && 5448 + echo other >C/other && 5449 + echo Astuff >A/stuff && 5450 + echo Bstuff >B/stuff && 5451 + git add . && 5452 + git commit -m "O" && 5453 + 5454 + git branch O && 5455 + git branch A && 5456 + git branch B && 5457 + 5458 + git switch A && 5459 + mkdir -p D && 5460 + git mv A/file1 D/file2 && 5461 + git mv B/file1 C/ && 5462 + git add . && 5463 + git commit -m "A" && 5464 + 5465 + git switch B && 5466 + git mv C/other A/other && 5467 + git add . && 5468 + git commit -m "B" 5469 + ) 5470 + } 5471 + 5472 + test_expect_success '12p: Directory rename hits other rename source; file still in way on other side' ' 5473 + test_setup_12p && 5474 + ( 5475 + cd 12p && 5476 + 5477 + git checkout -q A^0 && 5478 + 5479 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 5480 + 5481 + test_stdout_line_count = 5 git ls-files -s && 5482 + test_stdout_line_count = 1 git ls-files -s A/other && 5483 + test_stdout_line_count = 1 git ls-files -s A/stuff && 5484 + test_stdout_line_count = 1 git ls-files -s B/stuff && 5485 + test_stdout_line_count = 1 git ls-files -s D/file2 && 5486 + 5487 + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && 5488 + test_stdout_line_count = 1 git ls-files -s C/file1 5096 5489 ) 5097 5490 ' 5098 5491 5492 + # Testcase 12q, Directory rename hits other rename source; file removed though 5493 + # Commit O: A/file1_1 5494 + # A/stuff 5495 + # B/file1_2 5496 + # B/stuff 5497 + # C/other 5498 + # Commit A: A/stuff 5499 + # B/stuff 5500 + # C/file1_2 5501 + # C/other 5502 + # Commit B: D/file2_1 5503 + # A/stuff 5504 + # B/file1_2 5505 + # B/stuff 5506 + # A/other 5507 + # In words: 5508 + # A: delete A/file1_1, rename B/file1_2 -> C/file1_2 5509 + # B: Rename C/ -> A/, rename A/file1_1 -> D/file2_1 5510 + # Rationale: 5511 + # A/stuff is unmodified, it shows up in final output 5512 + # B/stuff is unmodified, it shows up in final output 5513 + # C/other touched on one side (rename to A), so A/other shows up in output 5514 + # A/file1 is rename/delete to D/file2, so two stages for D/file2 5515 + # B/file1 -> C/file1 and even though C/ -> A/, A/file1 as a source was 5516 + # "in the way" (ish) so we don't do the directory rename 5517 + # Expected: A/stuff 5518 + # B/stuff 5519 + # A/other 5520 + # D/file2 (two stages) 5521 + # C/file1 5522 + # + CONFLICT (implicit dir rename): A/file1 in way of C/file1 5523 + # + CONFLICT (rename/delete): D/file2 5524 + # 5525 + 5526 + test_setup_12q () { 5527 + git init 12q && 5528 + ( 5529 + cd 12q && 5530 + 5531 + mkdir -p A B C && 5532 + echo 1 >A/file1 && 5533 + echo 2 >B/file1 && 5534 + echo other >C/other && 5535 + echo Astuff >A/stuff && 5536 + echo Bstuff >B/stuff && 5537 + git add . && 5538 + git commit -m "O" && 5539 + 5540 + git branch O && 5541 + git branch A && 5542 + git branch B && 5543 + 5544 + git switch A && 5545 + git rm A/file1 && 5546 + git mv B/file1 C/ && 5547 + git add . && 5548 + git commit -m "A" && 5549 + 5550 + git switch B && 5551 + mkdir -p D && 5552 + git mv A/file1 D/file2 && 5553 + git mv C/other A/other && 5554 + git add . && 5555 + git commit -m "B" 5556 + ) 5557 + } 5558 + 5559 + test_expect_success '12q: Directory rename hits other rename source; file removed though' ' 5560 + test_setup_12q && 5561 + ( 5562 + cd 12q && 5563 + 5564 + git checkout -q A^0 && 5565 + 5566 + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && 5567 + 5568 + grep "CONFLICT (rename/delete).*A/file1.*D/file2" out && 5569 + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && 5570 + 5571 + test_stdout_line_count = 6 git ls-files -s && 5572 + test_stdout_line_count = 1 git ls-files -s A/other && 5573 + test_stdout_line_count = 1 git ls-files -s A/stuff && 5574 + test_stdout_line_count = 1 git ls-files -s B/stuff && 5575 + test_stdout_line_count = 2 git ls-files -s D/file2 && 5576 + 5577 + # This is a slightly suboptimal resolution; allowing the 5578 + # rename of C/ -> A/ to affect C/file1 and further rename 5579 + # it to A/file1 would probably be preferable, but since 5580 + # A/file1 existed as the source of another rename, allowing 5581 + # the dir rename of C/file1 -> A/file1 would mean modifying 5582 + # the code so that renames do not adjust both their source 5583 + # and target paths in all cases. 5584 + ! grep "CONFLICT (file location)" out && 5585 + test_stdout_line_count = 1 git ls-files -s C/file1 5586 + ) 5587 + ' 5099 5588 5100 5589 ########################################################################### 5101 5590 # SECTION 13: Checking informational and conflict messages