merge-ort: implement apply_directory_rename_modifications()

This function roughly follows the same outline as the function of the same name from merge-recursive.c, but the code diverges in multiple ways due to some special considerations: * merge-ort's version needs to update opt->priv->paths with any new paths (and opt->priv->paths points to struct conflict_infos which track quite a bit of metadata for each path); merge-recursive's version would directly update the index * merge-ort requires that opt->priv->paths has any leading directories of any relevant files also be included in the set of paths. And due to pointer equality requirements on merged_info.directory_name, we have to be careful how we compute and insert these. * due to the above requirements on opt->priv->paths, merge-ort's version starts with a long comment to explain all the special considerations that need to be handled * merge-ort can use the full data stored in opt->priv->paths to avoid making expensive get_tree_entry() calls to regather the necessary data. * due to messages being deferred automatically in merge-ort, this is the best place to handle conflict messages whereas in merge-recursive.c they are deferred manually so that processing of entries does all the printing 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 089d82bc18514d78513a85e95de257f74da18205
1 file changed +167 -1
merge-ort.c
+167 -1
@@ -1223,7 +1223,173 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
1223 struct diff_filepair *pair,
1224 char *new_path)
1225 {
1226 - die("Not yet implemented.");
1226 + /*
1227 + * The basic idea is to get the conflict_info from opt->priv->paths
1228 + * at old path, and insert it into new_path; basically just this:
1229 + * ci = strmap_get(&opt->priv->paths, old_path);
1230 + * strmap_remove(&opt->priv->paths, old_path, 0);
1231 + * strmap_put(&opt->priv->paths, new_path, ci);
1232 + * However, there are some factors complicating this:
1233 + * - opt->priv->paths may already have an entry at new_path
1234 + * - Each ci tracks its containing directory, so we need to
1235 + * update that
1236 + * - If another ci has the same containing directory, then
1237 + * the two char*'s MUST point to the same location. See the
1238 + * comment in struct merged_info. strcmp equality is not
1239 + * enough; we need pointer equality.
1240 + * - opt->priv->paths must hold the parent directories of any
1241 + * entries that are added. So, if this directory rename
1242 + * causes entirely new directories, we must recursively add
1243 + * parent directories.
1244 + * - For each parent directory added to opt->priv->paths, we
1245 + * also need to get its parent directory stored in its
1246 + * conflict_info->merged.directory_name with all the same
1247 + * requirements about pointer equality.
1248 + */
1249 + struct string_list dirs_to_insert = STRING_LIST_INIT_NODUP;
1250 + struct conflict_info *ci, *new_ci;
1251 + struct strmap_entry *entry;
1252 + const char *branch_with_new_path, *branch_with_dir_rename;
1253 + const char *old_path = pair->two->path;
1254 + const char *parent_name;
1255 + const char *cur_path;
1256 + int i, len;
1257 +
1258 + entry = strmap_get_entry(&opt->priv->paths, old_path);
1259 + old_path = entry->key;
1260 + ci = entry->value;
1261 + VERIFY_CI(ci);
1262 +
1263 + /* Find parent directories missing from opt->priv->paths */
1264 + cur_path = new_path;
1265 + while (1) {
1266 + /* Find the parent directory of cur_path */
1267 + char *last_slash = strrchr(cur_path, '/');
1268 + if (last_slash) {
1269 + parent_name = xstrndup(cur_path, last_slash - cur_path);
1270 + } else {
1271 + parent_name = opt->priv->toplevel_dir;
1272 + break;
1273 + }
1274 +
1275 + /* Look it up in opt->priv->paths */
1276 + entry = strmap_get_entry(&opt->priv->paths, parent_name);
1277 + if (entry) {
1278 + free((char*)parent_name);
1279 + parent_name = entry->key; /* reuse known pointer */
1280 + break;
1281 + }
1282 +
1283 + /* Record this is one of the directories we need to insert */
1284 + string_list_append(&dirs_to_insert, parent_name);
1285 + cur_path = parent_name;
1286 + }
1287 +
1288 + /* Traverse dirs_to_insert and insert them into opt->priv->paths */
1289 + for (i = dirs_to_insert.nr-1; i >= 0; --i) {
1290 + struct conflict_info *dir_ci;
1291 + char *cur_dir = dirs_to_insert.items[i].string;
1292 +
1293 + dir_ci = xcalloc(1, sizeof(*dir_ci));
1294 +
1295 + dir_ci->merged.directory_name = parent_name;
1296 + len = strlen(parent_name);
1297 + /* len+1 because of trailing '/' character */
1298 + dir_ci->merged.basename_offset = (len > 0 ? len+1 : len);
1299 + dir_ci->dirmask = ci->filemask;
1300 + strmap_put(&opt->priv->paths, cur_dir, dir_ci);
1301 +
1302 + parent_name = cur_dir;
1303 + }
1304 +
1305 + /*
1306 + * We are removing old_path from opt->priv->paths. old_path also will
1307 + * eventually need to be freed, but it may still be used by e.g.
1308 + * ci->pathnames. So, store it in another string-list for now.
1309 + */
1310 + string_list_append(&opt->priv->paths_to_free, old_path);
1311 +
1312 + assert(ci->filemask == 2 || ci->filemask == 4);
1313 + assert(ci->dirmask == 0);
1314 + strmap_remove(&opt->priv->paths, old_path, 0);
1315 +
1316 + branch_with_new_path = (ci->filemask == 2) ? opt->branch1 : opt->branch2;
1317 + branch_with_dir_rename = (ci->filemask == 2) ? opt->branch2 : opt->branch1;
1318 +
1319 + /* Now, finally update ci and stick it into opt->priv->paths */
1320 + ci->merged.directory_name = parent_name;
1321 + len = strlen(parent_name);
1322 + ci->merged.basename_offset = (len > 0 ? len+1 : len);
1323 + new_ci = strmap_get(&opt->priv->paths, new_path);
1324 + if (!new_ci) {
1325 + /* Place ci back into opt->priv->paths, but at new_path */
1326 + strmap_put(&opt->priv->paths, new_path, ci);
1327 + } else {
1328 + int index;
1329 +
1330 + /* A few sanity checks */
1331 + VERIFY_CI(new_ci);
1332 + assert(ci->filemask == 2 || ci->filemask == 4);
1333 + assert((new_ci->filemask & ci->filemask) == 0);
1334 + assert(!new_ci->merged.clean);
1335 +
1336 + /* Copy stuff from ci into new_ci */
1337 + new_ci->filemask |= ci->filemask;
1338 + if (new_ci->dirmask)
1339 + new_ci->df_conflict = 1;
1340 + index = (ci->filemask >> 1);
1341 + new_ci->pathnames[index] = ci->pathnames[index];
1342 + new_ci->stages[index].mode = ci->stages[index].mode;
1343 + oidcpy(&new_ci->stages[index].oid, &ci->stages[index].oid);
1344 +
1345 + free(ci);
1346 + ci = new_ci;
1347 + }
1348 +
1349 + if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) {
1350 + /* Notify user of updated path */
1351 + if (pair->status == 'A')
1352 + path_msg(opt, new_path, 1,
1353 + _("Path updated: %s added in %s inside a "
1354 + "directory that was renamed in %s; moving "
1355 + "it to %s."),
1356 + old_path, branch_with_new_path,
1357 + branch_with_dir_rename, new_path);
1358 + else
1359 + path_msg(opt, new_path, 1,
1360 + _("Path updated: %s renamed to %s in %s, "
1361 + "inside a directory that was renamed in %s; "
1362 + "moving it to %s."),
1363 + pair->one->path, old_path, branch_with_new_path,
1364 + branch_with_dir_rename, new_path);
1365 + } else {
1366 + /*
1367 + * opt->detect_directory_renames has the value
1368 + * MERGE_DIRECTORY_RENAMES_CONFLICT, so mark these as conflicts.
1369 + */
1370 + ci->path_conflict = 1;
1371 + if (pair->status == 'A')
1372 + path_msg(opt, new_path, 0,
1373 + _("CONFLICT (file location): %s added in %s "
1374 + "inside a directory that was renamed in %s, "
1375 + "suggesting it should perhaps be moved to "
1376 + "%s."),
1377 + old_path, branch_with_new_path,
1378 + branch_with_dir_rename, new_path);
1379 + else
1380 + path_msg(opt, new_path, 0,
1381 + _("CONFLICT (file location): %s renamed to %s "
1382 + "in %s, inside a directory that was renamed "
1383 + "in %s, suggesting it should perhaps be "
1384 + "moved to %s."),
1385 + pair->one->path, old_path, branch_with_new_path,
1386 + branch_with_dir_rename, new_path);
1387 + }
1388 +
1389 + /*
1390 + * Finally, record the new location.
1391 + */
1392 + pair->two->path = new_path;
1393 }
1394
1395 /*** Function Grouping: functions related to regular rename detection ***/