merge-ort: implement check_for_directory_rename()

This is copied from merge-recursive.c, with minor tweaks due to using strmap API and the fact that it can use opt->priv->paths to get all pathnames that exist instead of taking a tree object. This depends on a new function, handle_path_level_conflicts(), which just has a placeholder die-not-yet-implemented implementation for now; a subsequent patch will implement it. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Jan 19, 2021 at 19:53 UTC 47325e8533a35ee48e85f85543e4f054d21a36c1
1 file changed +66 -1
merge-ort.c
+66 -1
@@ -864,6 +864,21 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
864 *new_dir = xstrndup(new_path, end_of_new - new_path);
865 }
866
867 +/*
868 + * See if there is a directory rename for path, and if there are any file
869 + * level conflicts on the given side for the renamed location. If there is
870 + * a rename and there are no conflicts, return the new name. Otherwise,
871 + * return NULL.
872 + */
873 +static char *handle_path_level_conflicts(struct merge_options *opt,
874 + const char *path,
875 + unsigned side_index,
876 + struct strmap_entry *rename_info,
877 + struct strmap *collisions)
878 +{
879 + die("Not yet implemented");
880 +}
881 +
882 static void increment_count(struct strmap *dir_rename_count,
883 char *old_dir,
884 char *new_dir)
@@ -1078,7 +1093,57 @@ static char *check_for_directory_rename(struct merge_options *opt,
1093 struct strmap *collisions,
1094 int *clean_merge)
1095 {
1081 - die("Not yet implemented.");
1096 + char *new_path = NULL;
1097 + struct strmap_entry *rename_info;
1098 + struct strmap_entry *otherinfo = NULL;
1099 + const char *new_dir;
1100 +
1101 + if (strmap_empty(dir_renames))
1102 + return new_path;
1103 + rename_info = check_dir_renamed(path, dir_renames);
1104 + if (!rename_info)
1105 + return new_path;
1106 + /* old_dir = rename_info->key; */
1107 + new_dir = rename_info->value;
1108 +
1109 + /*
1110 + * This next part is a little weird. We do not want to do an
1111 + * implicit rename into a directory we renamed on our side, because
1112 + * that will result in a spurious rename/rename(1to2) conflict. An
1113 + * example:
1114 + * Base commit: dumbdir/afile, otherdir/bfile
1115 + * Side 1: smrtdir/afile, otherdir/bfile
1116 + * Side 2: dumbdir/afile, dumbdir/bfile
1117 + * Here, while working on Side 1, we could notice that otherdir was
1118 + * renamed/merged to dumbdir, and change the diff_filepair for
1119 + * otherdir/bfile into a rename into dumbdir/bfile. However, Side
1120 + * 2 will notice the rename from dumbdir to smrtdir, and do the
1121 + * transitive rename to move it from dumbdir/bfile to
1122 + * smrtdir/bfile. That gives us bfile in dumbdir vs being in
1123 + * smrtdir, a rename/rename(1to2) conflict. We really just want
1124 + * the file to end up in smrtdir. And the way to achieve that is
1125 + * to not let Side1 do the rename to dumbdir, since we know that is
1126 + * the source of one of our directory renames.
1127 + *
1128 + * That's why otherinfo and dir_rename_exclusions is here.
1129 + *
1130 + * As it turns out, this also prevents N-way transient rename
1131 + * confusion; See testcases 9c and 9d of t6043.
1132 + */
1133 + otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir);
1134 + if (otherinfo) {
1135 + path_msg(opt, rename_info->key, 1,
1136 + _("WARNING: Avoiding applying %s -> %s rename "
1137 + "to %s, because %s itself was renamed."),
1138 + rename_info->key, new_dir, path, new_dir);
1139 + return NULL;
1140 + }
1141 +
1142 + new_path = handle_path_level_conflicts(opt, path, side_index,
1143 + rename_info, collisions);
1144 + *clean_merge &= (new_path != NULL);
1145 +
1146 + return new_path;
1147 }
1148
1149 static void apply_directory_rename_modifications(struct merge_options *opt,