Skip to content

Commit ea625cb

Browse files
newrengitster
authored andcommitted
merge-recursive: add computation of collisions due to dir rename & merging
directory renaming and merging can cause one or more files to be moved to where an existing file is, or to cause several files to all be moved to the same (otherwise vacant) location. Add checking and reporting for such cases, falling back to no-directory-rename handling for such paths. Reviewed-by: Stefan Beller <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 53e32d4 commit ea625cb

File tree

2 files changed

+150
-3
lines changed

2 files changed

+150
-3
lines changed

merge-recursive.c

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,
8787
string_list_init(&entry->possible_new_dirs, 0);
8888
}
8989

90+
static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
91+
char *target_file)
92+
{
93+
struct collision_entry key;
94+
95+
hashmap_entry_init(&key, strhash(target_file));
96+
key.target_file = target_file;
97+
return hashmap_get(hashmap, &key, NULL);
98+
}
99+
100+
static int collision_cmp(void *unused_cmp_data,
101+
const struct collision_entry *e1,
102+
const struct collision_entry *e2,
103+
const void *unused_keydata)
104+
{
105+
return strcmp(e1->target_file, e2->target_file);
106+
}
107+
108+
static void collision_init(struct hashmap *map)
109+
{
110+
hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0);
111+
}
112+
90113
static void flush_output(struct merge_options *o)
91114
{
92115
if (o->buffer_output < 2 && o->obuf.len) {
@@ -1394,6 +1417,31 @@ static int tree_has_path(struct tree *tree, const char *path)
13941417
hashy, &mode_o);
13951418
}
13961419

1420+
/*
1421+
* Return a new string that replaces the beginning portion (which matches
1422+
* entry->dir), with entry->new_dir. In perl-speak:
1423+
* new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
1424+
* NOTE:
1425+
* Caller must ensure that old_path starts with entry->dir + '/'.
1426+
*/
1427+
static char *apply_dir_rename(struct dir_rename_entry *entry,
1428+
const char *old_path)
1429+
{
1430+
struct strbuf new_path = STRBUF_INIT;
1431+
int oldlen, newlen;
1432+
1433+
if (entry->non_unique_new_dir)
1434+
return NULL;
1435+
1436+
oldlen = strlen(entry->dir);
1437+
newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
1438+
strbuf_grow(&new_path, newlen);
1439+
strbuf_addbuf(&new_path, &entry->new_dir);
1440+
strbuf_addstr(&new_path, &old_path[oldlen]);
1441+
1442+
return strbuf_detach(&new_path, NULL);
1443+
}
1444+
13971445
static void get_renamed_dir_portion(const char *old_path, const char *new_path,
13981446
char **old_dir, char **new_dir)
13991447
{
@@ -1663,6 +1711,84 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
16631711
return dir_renames;
16641712
}
16651713

1714+
static struct dir_rename_entry *check_dir_renamed(const char *path,
1715+
struct hashmap *dir_renames)
1716+
{
1717+
char temp[PATH_MAX];
1718+
char *end;
1719+
struct dir_rename_entry *entry;
1720+
1721+
strcpy(temp, path);
1722+
while ((end = strrchr(temp, '/'))) {
1723+
*end = '\0';
1724+
entry = dir_rename_find_entry(dir_renames, temp);
1725+
if (entry)
1726+
return entry;
1727+
}
1728+
return NULL;
1729+
}
1730+
1731+
static void compute_collisions(struct hashmap *collisions,
1732+
struct hashmap *dir_renames,
1733+
struct diff_queue_struct *pairs)
1734+
{
1735+
int i;
1736+
1737+
/*
1738+
* Multiple files can be mapped to the same path due to directory
1739+
* renames done by the other side of history. Since that other
1740+
* side of history could have merged multiple directories into one,
1741+
* if our side of history added the same file basename to each of
1742+
* those directories, then all N of them would get implicitly
1743+
* renamed by the directory rename detection into the same path,
1744+
* and we'd get an add/add/.../add conflict, and all those adds
1745+
* from *this* side of history. This is not representable in the
1746+
* index, and users aren't going to easily be able to make sense of
1747+
* it. So we need to provide a good warning about what's
1748+
* happening, and fall back to no-directory-rename detection
1749+
* behavior for those paths.
1750+
*
1751+
* See testcases 9e and all of section 5 from t6043 for examples.
1752+
*/
1753+
collision_init(collisions);
1754+
1755+
for (i = 0; i < pairs->nr; ++i) {
1756+
struct dir_rename_entry *dir_rename_ent;
1757+
struct collision_entry *collision_ent;
1758+
char *new_path;
1759+
struct diff_filepair *pair = pairs->queue[i];
1760+
1761+
if (pair->status == 'D')
1762+
continue;
1763+
dir_rename_ent = check_dir_renamed(pair->two->path,
1764+
dir_renames);
1765+
if (!dir_rename_ent)
1766+
continue;
1767+
1768+
new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
1769+
if (!new_path)
1770+
/*
1771+
* dir_rename_ent->non_unique_new_path is true, which
1772+
* means there is no directory rename for us to use,
1773+
* which means it won't cause us any additional
1774+
* collisions.
1775+
*/
1776+
continue;
1777+
collision_ent = collision_find_entry(collisions, new_path);
1778+
if (!collision_ent) {
1779+
collision_ent = xcalloc(1,
1780+
sizeof(struct collision_entry));
1781+
hashmap_entry_init(collision_ent, strhash(new_path));
1782+
hashmap_put(collisions, collision_ent);
1783+
collision_ent->target_file = new_path;
1784+
} else {
1785+
free(new_path);
1786+
}
1787+
string_list_insert(&collision_ent->source_files,
1788+
pair->two->path);
1789+
}
1790+
}
1791+
16661792
/*
16671793
* Get information of all renames which occurred in 'pairs', making use of
16681794
* any implicit directory renames inferred from the other side of history.
@@ -1672,15 +1798,20 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
16721798
*/
16731799
static struct string_list *get_renames(struct merge_options *o,
16741800
struct diff_queue_struct *pairs,
1801+
struct hashmap *dir_renames,
16751802
struct tree *tree,
16761803
struct tree *o_tree,
16771804
struct tree *a_tree,
16781805
struct tree *b_tree,
16791806
struct string_list *entries)
16801807
{
16811808
int i;
1809+
struct hashmap collisions;
1810+
struct hashmap_iter iter;
1811+
struct collision_entry *e;
16821812
struct string_list *renames;
16831813

1814+
compute_collisions(&collisions, dir_renames, pairs);
16841815
renames = xcalloc(1, sizeof(struct string_list));
16851816

16861817
for (i = 0; i < pairs->nr; ++i) {
@@ -1711,6 +1842,13 @@ static struct string_list *get_renames(struct merge_options *o,
17111842
item = string_list_insert(renames, pair->one->path);
17121843
item->util = re;
17131844
}
1845+
1846+
hashmap_iter_init(&collisions, &iter);
1847+
while ((e = hashmap_iter_next(&iter))) {
1848+
free(e->target_file);
1849+
string_list_clear(&e->source_files, 0);
1850+
}
1851+
hashmap_free(&collisions, 1);
17141852
return renames;
17151853
}
17161854

@@ -2020,9 +2158,11 @@ static int handle_renames(struct merge_options *o,
20202158
dir_re_head, head,
20212159
dir_re_merge, merge);
20222160

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,
2161+
ri->head_renames = get_renames(o, head_pairs,
2162+
dir_re_merge, head,
2163+
common, head, merge, entries);
2164+
ri->merge_renames = get_renames(o, merge_pairs,
2165+
dir_re_head, merge,
20262166
common, head, merge, entries);
20272167
clean = process_renames(o, ri->head_renames, ri->merge_renames);
20282168

merge-recursive.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ struct dir_rename_entry {
4747
struct string_list possible_new_dirs;
4848
};
4949

50+
struct collision_entry {
51+
struct hashmap_entry ent; /* must be the first member! */
52+
char *target_file;
53+
struct string_list source_files;
54+
unsigned reported_already:1;
55+
};
56+
5057
/* merge_trees() but with recursive ancestor consolidation */
5158
int merge_recursive(struct merge_options *o,
5259
struct commit *h1,

0 commit comments

Comments
 (0)