@ -87,6 +87,29 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,
@@ -87,6 +87,29 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,
string_list_init(&entry->possible_new_dirs, 0);
}
static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
char *target_file)
{
struct collision_entry key;
hashmap_entry_init(&key, strhash(target_file));
key.target_file = target_file;
return hashmap_get(hashmap, &key, NULL);
}
static int collision_cmp(void *unused_cmp_data,
const struct collision_entry *e1,
const struct collision_entry *e2,
const void *unused_keydata)
{
return strcmp(e1->target_file, e2->target_file);
}
static void collision_init(struct hashmap *map)
{
hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0);
}
static void flush_output(struct merge_options *o)
{
if (o->buffer_output < 2 && o->obuf.len) {
@ -1404,6 +1427,31 @@ static int tree_has_path(struct tree *tree, const char *path)
@@ -1404,6 +1427,31 @@ static int tree_has_path(struct tree *tree, const char *path)
&hashy, &mode_o);
}
/*
* Return a new string that replaces the beginning portion (which matches
* entry->dir), with entry->new_dir. In perl-speak:
* new_path_name = (old_path =~ s/entry->dir/entry->new_dir/);
* NOTE:
* Caller must ensure that old_path starts with entry->dir + '/'.
*/
static char *apply_dir_rename(struct dir_rename_entry *entry,
const char *old_path)
{
struct strbuf new_path = STRBUF_INIT;
int oldlen, newlen;
if (entry->non_unique_new_dir)
return NULL;
oldlen = strlen(entry->dir);
newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
strbuf_grow(&new_path, newlen);
strbuf_addbuf(&new_path, &entry->new_dir);
strbuf_addstr(&new_path, &old_path[oldlen]);
return strbuf_detach(&new_path, NULL);
}
static void get_renamed_dir_portion(const char *old_path, const char *new_path,
char **old_dir, char **new_dir)
{
@ -1673,6 +1721,84 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
@@ -1673,6 +1721,84 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
return dir_renames;
}
static struct dir_rename_entry *check_dir_renamed(const char *path,
struct hashmap *dir_renames)
{
char temp[PATH_MAX];
char *end;
struct dir_rename_entry *entry;
strcpy(temp, path);
while ((end = strrchr(temp, '/'))) {
*end = '\0';
entry = dir_rename_find_entry(dir_renames, temp);
if (entry)
return entry;
}
return NULL;
}
static void compute_collisions(struct hashmap *collisions,
struct hashmap *dir_renames,
struct diff_queue_struct *pairs)
{
int i;
/*
* Multiple files can be mapped to the same path due to directory
* renames done by the other side of history. Since that other
* side of history could have merged multiple directories into one,
* if our side of history added the same file basename to each of
* those directories, then all N of them would get implicitly
* renamed by the directory rename detection into the same path,
* and we'd get an add/add/.../add conflict, and all those adds
* from *this* side of history. This is not representable in the
* index, and users aren't going to easily be able to make sense of
* it. So we need to provide a good warning about what's
* happening, and fall back to no-directory-rename detection
* behavior for those paths.
*
* See testcases 9e and all of section 5 from t6043 for examples.
*/
collision_init(collisions);
for (i = 0; i < pairs->nr; ++i) {
struct dir_rename_entry *dir_rename_ent;
struct collision_entry *collision_ent;
char *new_path;
struct diff_filepair *pair = pairs->queue[i];
if (pair->status == 'D')
continue;
dir_rename_ent = check_dir_renamed(pair->two->path,
dir_renames);
if (!dir_rename_ent)
continue;
new_path = apply_dir_rename(dir_rename_ent, pair->two->path);
if (!new_path)
/*
* dir_rename_ent->non_unique_new_path is true, which
* means there is no directory rename for us to use,
* which means it won't cause us any additional
* collisions.
*/
continue;
collision_ent = collision_find_entry(collisions, new_path);
if (!collision_ent) {
collision_ent = xcalloc(1,
sizeof(struct collision_entry));
hashmap_entry_init(collision_ent, strhash(new_path));
hashmap_put(collisions, collision_ent);
collision_ent->target_file = new_path;
} else {
free(new_path);
}
string_list_insert(&collision_ent->source_files,
pair->two->path);
}
}
/*
* Get information of all renames which occurred in 'pairs', making use of
* 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,
@@ -1682,6 +1808,7 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
*/
static struct string_list *get_renames(struct merge_options *o,
struct diff_queue_struct *pairs,
struct hashmap *dir_renames,
struct tree *tree,
struct tree *o_tree,
struct tree *a_tree,
@ -1689,8 +1816,12 @@ static struct string_list *get_renames(struct merge_options *o,
@@ -1689,8 +1816,12 @@ static struct string_list *get_renames(struct merge_options *o,
struct string_list *entries)
{
int i;
struct hashmap collisions;
struct hashmap_iter iter;
struct collision_entry *e;
struct string_list *renames;
compute_collisions(&collisions, dir_renames, pairs);
renames = xcalloc(1, sizeof(struct string_list));
for (i = 0; i < pairs->nr; ++i) {
@ -1721,6 +1852,13 @@ static struct string_list *get_renames(struct merge_options *o,
@@ -1721,6 +1852,13 @@ static struct string_list *get_renames(struct merge_options *o,
item = string_list_insert(renames, pair->one->path);
item->util = re;
}
hashmap_iter_init(&collisions, &iter);
while ((e = hashmap_iter_next(&iter))) {
free(e->target_file);
string_list_clear(&e->source_files, 0);
}
hashmap_free(&collisions, 1);
return renames;
}
@ -2030,9 +2168,11 @@ static int handle_renames(struct merge_options *o,
@@ -2030,9 +2168,11 @@ static int handle_renames(struct merge_options *o,
dir_re_head, head,
dir_re_merge, merge);
ri->head_renames = get_renames(o, head_pairs, head,
common, head, merge, entries);
ri->merge_renames = get_renames(o, merge_pairs, merge,
ri->head_renames = get_renames(o, head_pairs,
dir_re_merge, head,
common, head, merge, entries);
ri->merge_renames = get_renames(o, merge_pairs,
dir_re_head, merge,
common, head, merge, entries);
clean = process_renames(o, ri->head_renames, ri->merge_renames);