Skip to content

Commit 96e7ffb

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 7fe40b8 commit 96e7ffb

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
@@ -1395,6 +1395,15 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
13951395
return ret;
13961396
}
13971397

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+
13981407
static void get_renamed_dir_portion(const char *old_path, const char *new_path,
13991408
char **old_dir, char **new_dir)
14001409
{
@@ -1450,6 +1459,112 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
14501459
}
14511460
}
14521461

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+
14531568
static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
14541569
struct tree *tree)
14551570
{
@@ -1911,6 +2026,10 @@ static int handle_renames(struct merge_options *o,
19112026
dir_re_head = get_directory_renames(head_pairs, head);
19122027
dir_re_merge = get_directory_renames(merge_pairs, merge);
19132028

2029+
handle_directory_level_conflicts(o,
2030+
dir_re_head, head,
2031+
dir_re_merge, merge);
2032+
19142033
ri->head_renames = get_renames(o, head_pairs, head,
19152034
common, head, merge, entries);
19162035
ri->merge_renames = get_renames(o, merge_pairs, merge,

0 commit comments

Comments
 (0)