@matheusbsbs / matheus-bagliano / commits / 37348937ff
Fork 0

merge-recursive: Fix D/F conflicts

The D/F conflicts that can be automatically resolved (file or directory unmodified on one side of history), have the nice property that process_entry() can correctly handle all subpaths of the D/F conflict. In the case of D->F conversions, it will correctly delete all non-conflicting files below the relevant directory and the directory itself (note that both untracked and conflicting files below the directory will prevent its removal). So if we handle D/F conflicts after all other conflicts, they become fairly simple to handle -- we just need to check for whether or not a path (file/directory) is in the way of creating the new content. We do this by having process_entry() defer handling such entries to a subsequent process_df_entry() step. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Jul 9, 2010 at 07:10 UTC 37348937ff391f01981e8af10b2f615268fd2509
2 files changed +80 -19
merge-recursive.c
+77 -16
@@ -1072,6 +1072,7 @@ static int process_entry(struct merge_options *o,
1072 unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
1073 unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
1074
1075 + entry->processed = 1;
1076 if (o_sha && (!a_sha || !b_sha)) {
1077 /* Case A: Deleted in one */
1078 if ((!a_sha && !b_sha) ||
@@ -1104,33 +1105,28 @@ static int process_entry(struct merge_options *o,
1105 } else if ((!o_sha && a_sha && !b_sha) ||
1106 (!o_sha && !a_sha && b_sha)) {
1107 /* Case B: Added in one. */
1107 - const char *add_branch;
1108 - const char *other_branch;
1108 unsigned mode;
1109 const unsigned char *sha;
1111 - const char *conf;
1110
1111 if (a_sha) {
1114 - add_branch = o->branch1;
1115 - other_branch = o->branch2;
1112 mode = a_mode;
1113 sha = a_sha;
1118 - conf = "file/directory";
1114 } else {
1120 - add_branch = o->branch2;
1121 - other_branch = o->branch1;
1115 mode = b_mode;
1116 sha = b_sha;
1124 - conf = "directory/file";
1117 }
1118 if (string_list_has_string(&o->current_directory_set, path)) {
1127 - const char *new_path = unique_path(o, path, add_branch);
1128 - clean_merge = 0;
1129 - output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
1130 - "Adding %s as %s",
1131 - conf, path, other_branch, path, new_path);
1132 - remove_file(o, 0, path, 0);
1133 - update_file(o, 0, sha, mode, new_path);
1119 + /* Handle D->F conflicts after all subfiles */
1120 + entry->processed = 0;
1121 + /* But get any file out of the way now, so conflicted
1122 + * entries below the directory of the same name can
1123 + * be put in the working directory.
1124 + */
1125 + if (a_sha)
1126 + output(o, 2, "Removing %s", path);
1127 + /* do not touch working file if it did not exist */
1128 + remove_file(o, 0, path, !a_sha);
1129 + return 1; /* Assume clean till processed */
1130 } else {
1131 output(o, 2, "Adding %s", path);
1132 update_file(o, 1, sha, mode, path);
@@ -1178,6 +1174,64 @@ static int process_entry(struct merge_options *o,
1174 return clean_merge;
1175 }
1176
1177 +/*
1178 + * Per entry merge function for D/F conflicts, to be called only after
1179 + * all files below dir have been processed. We do this because in the
1180 + * cases we can cleanly resolve D/F conflicts, process_entry() can clean
1181 + * out all the files below the directory for us.
1182 + */
1183 +static int process_df_entry(struct merge_options *o,
1184 + const char *path, struct stage_data *entry)
1185 +{
1186 + int clean_merge = 1;
1187 + unsigned o_mode = entry->stages[1].mode;
1188 + unsigned a_mode = entry->stages[2].mode;
1189 + unsigned b_mode = entry->stages[3].mode;
1190 + unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
1191 + unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
1192 + unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
1193 + const char *add_branch;
1194 + const char *other_branch;
1195 + unsigned mode;
1196 + const unsigned char *sha;
1197 + const char *conf;
1198 + struct stat st;
1199 +
1200 + /* We currently only handle D->F cases */
1201 + assert((!o_sha && a_sha && !b_sha) ||
1202 + (!o_sha && !a_sha && b_sha));
1203 +
1204 + entry->processed = 1;
1205 +
1206 + if (a_sha) {
1207 + add_branch = o->branch1;
1208 + other_branch = o->branch2;
1209 + mode = a_mode;
1210 + sha = a_sha;
1211 + conf = "file/directory";
1212 + } else {
1213 + add_branch = o->branch2;
1214 + other_branch = o->branch1;
1215 + mode = b_mode;
1216 + sha = b_sha;
1217 + conf = "directory/file";
1218 + }
1219 + if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
1220 + const char *new_path = unique_path(o, path, add_branch);
1221 + clean_merge = 0;
1222 + output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
1223 + "Adding %s as %s",
1224 + conf, path, other_branch, path, new_path);
1225 + remove_file(o, 0, path, 0);
1226 + update_file(o, 0, sha, mode, new_path);
1227 + } else {
1228 + output(o, 2, "Adding %s", path);
1229 + update_file(o, 1, sha, mode, path);
1230 + }
1231 +
1232 + return clean_merge;
1233 +}
1234 +
1235 struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
1236 {
1237 struct unpack_trees_error_msgs msgs = {
@@ -1249,6 +1303,13 @@ int merge_trees(struct merge_options *o,
1303 && !process_entry(o, path, e))
1304 clean = 0;
1305 }
1306 + for (i = 0; i < entries->nr; i++) {
1307 + const char *path = entries->items[i].string;
1308 + struct stage_data *e = entries->items[i].util;
1309 + if (!e->processed
1310 + && !process_df_entry(o, path, e))
1311 + clean = 0;
1312 + }
1313
1314 string_list_clear(re_merge, 0);
1315 string_list_clear(re_head, 0);
t/t6035-merge-dir-to-symlink.sh
+3 -3
@@ -56,7 +56,7 @@ test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolv
56 test -f a/b-2/c/d
57 '
58
59 -test_expect_failure 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
59 +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
60 git reset --hard &&
61 git checkout baseline^0 &&
62 git merge -s recursive master &&
@@ -130,7 +130,7 @@ test_expect_success 'merge should not have D/F conflicts (resolve)' '
130 test -f a/b/c/d
131 '
132
133 -test_expect_failure 'merge should not have D/F conflicts (recursive)' '
133 +test_expect_success 'merge should not have D/F conflicts (recursive)' '
134 git reset --hard &&
135 git checkout baseline^0 &&
136 git merge -s recursive test2 &&
@@ -138,7 +138,7 @@ test_expect_failure 'merge should not have D/F conflicts (recursive)' '
138 test -f a/b/c/d
139 '
140
141 -test_expect_failure 'merge should not have F/D conflicts (recursive)' '
141 +test_expect_success 'merge should not have F/D conflicts (recursive)' '
142 git reset --hard &&
143 git checkout -b foo test2 &&
144 git merge -s recursive baseline^0 &&