Skip to content

Commit 53e32d4

Browse files
newrengitster
authored andcommitted
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 <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8383408 commit 53e32d4

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

merge-recursive.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,15 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
13851385
return ret;
13861386
}
13871387

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+
13881397
static void get_renamed_dir_portion(const char *old_path, const char *new_path,
13891398
char **old_dir, char **new_dir)
13901399
{
@@ -1440,6 +1449,112 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
14401449
}
14411450
}
14421451

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+
14431558
static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
14441559
struct tree *tree)
14451560
{
@@ -1901,6 +2016,10 @@ static int handle_renames(struct merge_options *o,
19012016
dir_re_head = get_directory_renames(head_pairs, head);
19022017
dir_re_merge = get_directory_renames(merge_pairs, merge);
19032018

2019+
handle_directory_level_conflicts(o,
2020+
dir_re_head, head,
2021+
dir_re_merge, merge);
2022+
19042023
ri->head_renames = get_renames(o, head_pairs, head,
19052024
common, head, merge, entries);
19062025
ri->merge_renames = get_renames(o, merge_pairs, merge,

0 commit comments

Comments
 (0)