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