merge-recursive: check for directory level conflicts

Before trying to apply directory renames to paths within the given directories, we want to make sure that there aren't conflicts at the directory level. There will be additional checks at the individual file level too, which will be added later. Reviewed-by: Stefan Beller <sbeller@google.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Feb 14, 2018 at 10:51 UTC 53e32d4652a5cb073c8add8e7867fd7e76c2deff
1 file changed +119
merge-recursive.c
+119
@@ -1385,6 +1385,15 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
1385 return ret;
1386 }
1387
1388 +static int tree_has_path(struct tree *tree, const char *path)
1389 +{
1390 + unsigned char hashy[GIT_MAX_RAWSZ];
1391 + unsigned int mode_o;
1392 +
1393 + return !get_tree_entry(tree->object.oid.hash, path,
1394 + hashy, &mode_o);
1395 +}
1396 +
1397 static void get_renamed_dir_portion(const char *old_path, const char *new_path,
1398 char **old_dir, char **new_dir)
1399 {
@@ -1440,6 +1449,112 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
1449 }
1450 }
1451
1452 +static void remove_hashmap_entries(struct hashmap *dir_renames,
1453 + struct string_list *items_to_remove)
1454 +{
1455 + int i;
1456 + struct dir_rename_entry *entry;
1457 +
1458 + for (i = 0; i < items_to_remove->nr; i++) {
1459 + entry = items_to_remove->items[i].util;
1460 + hashmap_remove(dir_renames, entry, NULL);
1461 + }
1462 + string_list_clear(items_to_remove, 0);
1463 +}
1464 +
1465 +/*
1466 + * There are a couple things we want to do at the directory level:
1467 + * 1. Check for both sides renaming to the same thing, in order to avoid
1468 + * implicit renaming of files that should be left in place. (See
1469 + * testcase 6b in t6043 for details.)
1470 + * 2. Prune directory renames if there are still files left in the
1471 + * the original directory. These represent a partial directory rename,
1472 + * i.e. a rename where only some of the files within the directory
1473 + * were renamed elsewhere. (Technically, this could be done earlier
1474 + * in get_directory_renames(), except that would prevent us from
1475 + * doing the previous check and thus failing testcase 6b.)
1476 + * 3. Check for rename/rename(1to2) conflicts (at the directory level).
1477 + * In the future, we could potentially record this info as well and
1478 + * omit reporting rename/rename(1to2) conflicts for each path within
1479 + * the affected directories, thus cleaning up the merge output.
1480 + * NOTE: We do NOT check for rename/rename(2to1) conflicts at the
1481 + * directory level, because merging directories is fine. If it
1482 + * causes conflicts for files within those merged directories, then
1483 + * that should be detected at the individual path level.
1484 + */
1485 +static void handle_directory_level_conflicts(struct merge_options *o,
1486 + struct hashmap *dir_re_head,
1487 + struct tree *head,
1488 + struct hashmap *dir_re_merge,
1489 + struct tree *merge)
1490 +{
1491 + struct hashmap_iter iter;
1492 + struct dir_rename_entry *head_ent;
1493 + struct dir_rename_entry *merge_ent;
1494 +
1495 + struct string_list remove_from_head = STRING_LIST_INIT_NODUP;
1496 + struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
1497 +
1498 + hashmap_iter_init(dir_re_head, &iter);
1499 + while ((head_ent = hashmap_iter_next(&iter))) {
1500 + merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
1501 + if (merge_ent &&
1502 + !head_ent->non_unique_new_dir &&
1503 + !merge_ent->non_unique_new_dir &&
1504 + !strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) {
1505 + /* 1. Renamed identically; remove it from both sides */
1506 + string_list_append(&remove_from_head,
1507 + head_ent->dir)->util = head_ent;
1508 + strbuf_release(&head_ent->new_dir);
1509 + string_list_append(&remove_from_merge,
1510 + merge_ent->dir)->util = merge_ent;
1511 + strbuf_release(&merge_ent->new_dir);
1512 + } else if (tree_has_path(head, head_ent->dir)) {
1513 + /* 2. This wasn't a directory rename after all */
1514 + string_list_append(&remove_from_head,
1515 + head_ent->dir)->util = head_ent;
1516 + strbuf_release(&head_ent->new_dir);
1517 + }
1518 + }
1519 +
1520 + remove_hashmap_entries(dir_re_head, &remove_from_head);
1521 + remove_hashmap_entries(dir_re_merge, &remove_from_merge);
1522 +
1523 + hashmap_iter_init(dir_re_merge, &iter);
1524 + while ((merge_ent = hashmap_iter_next(&iter))) {
1525 + head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
1526 + if (tree_has_path(merge, merge_ent->dir)) {
1527 + /* 2. This wasn't a directory rename after all */
1528 + string_list_append(&remove_from_merge,
1529 + merge_ent->dir)->util = merge_ent;
1530 + } else if (head_ent &&
1531 + !head_ent->non_unique_new_dir &&
1532 + !merge_ent->non_unique_new_dir) {
1533 + /* 3. rename/rename(1to2) */
1534 + /*
1535 + * We can assume it's not rename/rename(1to1) because
1536 + * that was case (1), already checked above. So we
1537 + * know that head_ent->new_dir and merge_ent->new_dir
1538 + * are different strings.
1539 + */
1540 + output(o, 1, _("CONFLICT (rename/rename): "
1541 + "Rename directory %s->%s in %s. "
1542 + "Rename directory %s->%s in %s"),
1543 + head_ent->dir, head_ent->new_dir.buf, o->branch1,
1544 + head_ent->dir, merge_ent->new_dir.buf, o->branch2);
1545 + string_list_append(&remove_from_head,
1546 + head_ent->dir)->util = head_ent;
1547 + strbuf_release(&head_ent->new_dir);
1548 + string_list_append(&remove_from_merge,
1549 + merge_ent->dir)->util = merge_ent;
1550 + strbuf_release(&merge_ent->new_dir);
1551 + }
1552 + }
1553 +
1554 + remove_hashmap_entries(dir_re_head, &remove_from_head);
1555 + remove_hashmap_entries(dir_re_merge, &remove_from_merge);
1556 +}
1557 +
1558 static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
1559 struct tree *tree)
1560 {
@@ -1901,6 +2016,10 @@ static int handle_renames(struct merge_options *o,
2016 dir_re_head = get_directory_renames(head_pairs, head);
2017 dir_re_merge = get_directory_renames(merge_pairs, merge);
2018
2019 + handle_directory_level_conflicts(o,
2020 + dir_re_head, head,
2021 + dir_re_merge, merge);
2022 +
2023 ri->head_renames = get_renames(o, head_pairs, head,
2024 common, head, merge, entries);
2025 ri->merge_renames = get_renames(o, merge_pairs, merge,