@ -1510,6 +1510,91 @@ static void remove_hashmap_entries(struct hashmap *dir_renames,
@@ -1510,6 +1510,91 @@ static void remove_hashmap_entries(struct hashmap *dir_renames,
string_list_clear(items_to_remove, 0);
}
/*
* See if there is a directory rename for path, and if there are any file
* level conflicts for the renamed location. If there is a rename and
* there are no conflicts, return the new name. Otherwise, return NULL.
*/
static char *handle_path_level_conflicts(struct merge_options *o,
const char *path,
struct dir_rename_entry *entry,
struct hashmap *collisions,
struct tree *tree)
{
char *new_path = NULL;
struct collision_entry *collision_ent;
int clean = 1;
struct strbuf collision_paths = STRBUF_INIT;
/*
* entry has the mapping of old directory name to new directory name
* that we want to apply to path.
*/
new_path = apply_dir_rename(entry, path);
if (!new_path) {
/* This should only happen when entry->non_unique_new_dir set */
if (!entry->non_unique_new_dir)
BUG("entry->non_unqiue_dir not set and !new_path");
output(o, 1, _("CONFLICT (directory rename split): "
"Unclear where to place %s because directory "
"%s was renamed to multiple other directories, "
"with no destination getting a majority of the "
"files."),
path, entry->dir);
clean = 0;
return NULL;
}
/*
* The caller needs to have ensured that it has pre-populated
* collisions with all paths that map to new_path. Do a quick check
* to ensure that's the case.
*/
collision_ent = collision_find_entry(collisions, new_path);
if (collision_ent == NULL)
BUG("collision_ent is NULL");
/*
* Check for one-sided add/add/.../add conflicts, i.e.
* where implicit renames from the other side doing
* directory rename(s) can affect this side of history
* to put multiple paths into the same location. Warn
* and bail on directory renames for such paths.
*/
if (collision_ent->reported_already) {
clean = 0;
} else if (tree_has_path(tree, new_path)) {
collision_ent->reported_already = 1;
strbuf_add_separated_string_list(&collision_paths, ", ",
&collision_ent->source_files);
output(o, 1, _("CONFLICT (implicit dir rename): Existing "
"file/dir at %s in the way of implicit "
"directory rename(s) putting the following "
"path(s) there: %s."),
new_path, collision_paths.buf);
clean = 0;
} else if (collision_ent->source_files.nr > 1) {
collision_ent->reported_already = 1;
strbuf_add_separated_string_list(&collision_paths, ", ",
&collision_ent->source_files);
output(o, 1, _("CONFLICT (implicit dir rename): Cannot map "
"more than one path to %s; implicit directory "
"renames tried to put these paths there: %s"),
new_path, collision_paths.buf);
clean = 0;
}
/* Free memory we no longer need */
strbuf_release(&collision_paths);
if (!clean && new_path) {
free(new_path);
return NULL;
}
return new_path;
}
/*
* There are a couple things we want to do at the directory level:
* 1. Check for both sides renaming to the same thing, in order to avoid
@ -1789,6 +1874,59 @@ static void compute_collisions(struct hashmap *collisions,
@@ -1789,6 +1874,59 @@ static void compute_collisions(struct hashmap *collisions,
}
}
static char *check_for_directory_rename(struct merge_options *o,
const char *path,
struct tree *tree,
struct hashmap *dir_renames,
struct hashmap *dir_rename_exclusions,
struct hashmap *collisions,
int *clean_merge)
{
char *new_path = NULL;
struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames);
struct dir_rename_entry *oentry = NULL;
if (!entry)
return new_path;
/*
* This next part is a little weird. We do not want to do an
* implicit rename into a directory we renamed on our side, because
* that will result in a spurious rename/rename(1to2) conflict. An
* example:
* Base commit: dumbdir/afile, otherdir/bfile
* Side 1: smrtdir/afile, otherdir/bfile
* Side 2: dumbdir/afile, dumbdir/bfile
* Here, while working on Side 1, we could notice that otherdir was
* renamed/merged to dumbdir, and change the diff_filepair for
* otherdir/bfile into a rename into dumbdir/bfile. However, Side
* 2 will notice the rename from dumbdir to smrtdir, and do the
* transitive rename to move it from dumbdir/bfile to
* smrtdir/bfile. That gives us bfile in dumbdir vs being in
* smrtdir, a rename/rename(1to2) conflict. We really just want
* the file to end up in smrtdir. And the way to achieve that is
* to not let Side1 do the rename to dumbdir, since we know that is
* the source of one of our directory renames.
*
* That's why oentry and dir_rename_exclusions is here.
*
* As it turns out, this also prevents N-way transient rename
* confusion; See testcases 9c and 9d of t6043.
*/
oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf);
if (oentry) {
output(o, 1, _("WARNING: Avoiding applying %s -> %s rename "
"to %s, because %s itself was renamed."),
entry->dir, entry->new_dir.buf, path, entry->new_dir.buf);
} else {
new_path = handle_path_level_conflicts(o, path, entry,
collisions, tree);
*clean_merge &= (new_path != NULL);
}
return new_path;
}
/*
* Get information of all renames which occurred in 'pairs', making use of
* any implicit directory renames inferred from the other side of history.
@ -1799,11 +1937,13 @@ static void compute_collisions(struct hashmap *collisions,
@@ -1799,11 +1937,13 @@ static void compute_collisions(struct hashmap *collisions,
static struct string_list *get_renames(struct merge_options *o,
struct diff_queue_struct *pairs,
struct hashmap *dir_renames,
struct hashmap *dir_rename_exclusions,
struct tree *tree,
struct tree *o_tree,
struct tree *a_tree,
struct tree *b_tree,
struct string_list *entries)
struct string_list *entries,
int *clean_merge)
{
int i;
struct hashmap collisions;
@ -1818,11 +1958,22 @@ static struct string_list *get_renames(struct merge_options *o,
@@ -1818,11 +1958,22 @@ static struct string_list *get_renames(struct merge_options *o,
struct string_list_item *item;
struct rename *re;
struct diff_filepair *pair = pairs->queue[i];
char *new_path; /* non-NULL only with directory renames */
if (pair->status != 'R') {
if (pair->status == 'D') {
diff_free_filepair(pair);
continue;
}
new_path = check_for_directory_rename(o, pair->two->path, tree,
dir_renames,
dir_rename_exclusions,
&collisions,
clean_merge);
if (pair->status != 'R' && !new_path) {
diff_free_filepair(pair);
continue;
}
re = xmalloc(sizeof(*re));
re->processed = 0;
re->pair = pair;
@ -2140,7 +2291,7 @@ static int handle_renames(struct merge_options *o,
@@ -2140,7 +2291,7 @@ static int handle_renames(struct merge_options *o,
{
struct diff_queue_struct *head_pairs, *merge_pairs;
struct hashmap *dir_re_head, *dir_re_merge;
int clean;
int clean = 1;
ri->head_renames = NULL;
ri->merge_renames = NULL;
@ -2159,13 +2310,20 @@ static int handle_renames(struct merge_options *o,
@@ -2159,13 +2310,20 @@ static int handle_renames(struct merge_options *o,
dir_re_merge, merge);
ri->head_renames = get_renames(o, head_pairs,
dir_re_merge, head,
common, head, merge, entries);
dir_re_merge, dir_re_head, head,
common, head, merge, entries,
&clean);
if (clean < 0)
goto cleanup;
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);
dir_re_head, dir_re_merge, merge,
common, head, merge, entries,
&clean);
if (clean < 0)
goto cleanup;
clean &= process_renames(o, ri->head_renames, ri->merge_renames);
cleanup:
/*
* Some cleanup is deferred until cleanup_renames() because the
* data structures are still needed and referenced in