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 <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 e95ab70aac4f38f1d365cdc03a7a041920c9c27b
2 files changed +150 -3
merge-recursive.c
+143 -3
@@ -87,6 +87,29 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,
87 string_list_init(&entry->possible_new_dirs, 0);
88 }
89
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 +
113 static void flush_output(struct merge_options *o)
114 {
115 if (o->buffer_output < 2 && o->obuf.len) {
@@ -1404,6 +1427,31 @@ static int tree_has_path(struct tree *tree, const char *path)
1427 &hashy, &mode_o);
1428 }
1429
1430 +/*
1431 + * Return a new string that replaces the beginning portion (which matches
1432 + * entry->dir), with entry->new_dir. In perl-speak:
1433 + * new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
1434 + * NOTE:
1435 + * Caller must ensure that old_path starts with entry->dir + '/'.
1436 + */
1437 +static char *apply_dir_rename(struct dir_rename_entry *entry,
1438 + const char *old_path)
1439 +{
1440 + struct strbuf new_path = STRBUF_INIT;
1441 + int oldlen, newlen;
1442 +
1443 + if (entry->non_unique_new_dir)
1444 + return NULL;
1445 +
1446 + oldlen = strlen(entry->dir);
1447 + newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
1448 + strbuf_grow(&new_path, newlen);
1449 + strbuf_addbuf(&new_path, &entry->new_dir);
1450 + strbuf_addstr(&new_path, &old_path[oldlen]);
1451 +
1452 + return strbuf_detach(&new_path, NULL);
1453 +}
1454 +
1455 static void get_renamed_dir_portion(const char *old_path, const char *new_path,
1456 char **old_dir, char **new_dir)
1457 {
@@ -1673,6 +1721,84 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
1721 return dir_renames;
1722 }
1723
1724 +static struct dir_rename_entry *check_dir_renamed(const char *path,
1725 + struct hashmap *dir_renames)
1726 +{
1727 + char temp[PATH_MAX];
1728 + char *end;
1729 + struct dir_rename_entry *entry;
1730 +
1731 + strcpy(temp, path);
1732 + while ((end = strrchr(temp, '/'))) {
1733 + *end = '\0';
1734 + entry = dir_rename_find_entry(dir_renames, temp);
1735 + if (entry)
1736 + return entry;
1737 + }
1738 + return NULL;
1739 +}
1740 +
1741 +static void compute_collisions(struct hashmap *collisions,
1742 + struct hashmap *dir_renames,
1743 + struct diff_queue_struct *pairs)
1744 +{
1745 + int i;
1746 +
1747 + /*
1748 + * Multiple files can be mapped to the same path due to directory
1749 + * renames done by the other side of history. Since that other
1750 + * side of history could have merged multiple directories into one,
1751 + * if our side of history added the same file basename to each of
1752 + * those directories, then all N of them would get implicitly
1753 + * renamed by the directory rename detection into the same path,
1754 + * and we'd get an add/add/.../add conflict, and all those adds
1755 + * from *this* side of history. This is not representable in the
1756 + * index, and users aren't going to easily be able to make sense of
1757 + * it. So we need to provide a good warning about what's
1758 + * happening, and fall back to no-directory-rename detection
1759 + * behavior for those paths.
1760 + *
1761 + * See testcases 9e and all of section 5 from t6043 for examples.
1762 + */
1763 + collision_init(collisions);
1764 +
1765 + for (i = 0; i < pairs->nr; ++i) {
1766 + struct dir_rename_entry *dir_rename_ent;
1767 + struct collision_entry *collision_ent;
1768 + char *new_path;
1769 + struct diff_filepair *pair = pairs->queue[i];
1770 +
1771 + if (pair->status == 'D')
1772 + continue;
1773 + dir_rename_ent = check_dir_renamed(pair->two->path,
1774 + dir_renames);
1775 + if (!dir_rename_ent)
1776 + continue;
1777 +
1778 + new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
1779 + if (!new_path)
1780 + /*
1781 + * dir_rename_ent->non_unique_new_path is true, which
1782 + * means there is no directory rename for us to use,
1783 + * which means it won't cause us any additional
1784 + * collisions.
1785 + */
1786 + continue;
1787 + collision_ent = collision_find_entry(collisions, new_path);
1788 + if (!collision_ent) {
1789 + collision_ent = xcalloc(1,
1790 + sizeof(struct collision_entry));
1791 + hashmap_entry_init(collision_ent, strhash(new_path));
1792 + hashmap_put(collisions, collision_ent);
1793 + collision_ent->target_file = new_path;
1794 + } else {
1795 + free(new_path);
1796 + }
1797 + string_list_insert(&collision_ent->source_files,
1798 + pair->two->path);
1799 + }
1800 +}
1801 +
1802 /*
1803 * Get information of all renames which occurred in 'pairs', making use of
1804 * any implicit directory renames inferred from the other side of history.
@@ -1682,6 +1808,7 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
1808 */
1809 static struct string_list *get_renames(struct merge_options *o,
1810 struct diff_queue_struct *pairs,
1811 + struct hashmap *dir_renames,
1812 struct tree *tree,
1813 struct tree *o_tree,
1814 struct tree *a_tree,
@@ -1689,8 +1816,12 @@ static struct string_list *get_renames(struct merge_options *o,
1816 struct string_list *entries)
1817 {
1818 int i;
1819 + struct hashmap collisions;
1820 + struct hashmap_iter iter;
1821 + struct collision_entry *e;
1822 struct string_list *renames;
1823
1824 + compute_collisions(&collisions, dir_renames, pairs);
1825 renames = xcalloc(1, sizeof(struct string_list));
1826
1827 for (i = 0; i < pairs->nr; ++i) {
@@ -1721,6 +1852,13 @@ static struct string_list *get_renames(struct merge_options *o,
1852 item = string_list_insert(renames, pair->one->path);
1853 item->util = re;
1854 }
1855 +
1856 + hashmap_iter_init(&collisions, &iter);
1857 + while ((e = hashmap_iter_next(&iter))) {
1858 + free(e->target_file);
1859 + string_list_clear(&e->source_files, 0);
1860 + }
1861 + hashmap_free(&collisions, 1);
1862 return renames;
1863 }
1864
@@ -2030,9 +2168,11 @@ static int handle_renames(struct merge_options *o,
2168 dir_re_head, head,
2169 dir_re_merge, merge);
2170
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,
2171 + ri->head_renames = get_renames(o, head_pairs,
2172 + dir_re_merge, head,
2173 + common, head, merge, entries);
2174 + ri->merge_renames = get_renames(o, merge_pairs,
2175 + dir_re_head, merge,
2176 common, head, merge, entries);
2177 clean = process_renames(o, ri->head_renames, ri->merge_renames);
2178
merge-recursive.h
+7
@@ -47,6 +47,13 @@ struct dir_rename_entry {
47 struct string_list possible_new_dirs;
48 };
49
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 +
57 /* merge_trees() but with recursive ancestor consolidation */
58 int merge_recursive(struct merge_options *o,
59 struct commit *h1,