]> git.ipfire.org Git - thirdparty/git.git/blobdiff - merge-recursive.c
Merge branch 'en/merge-recursive-directory-rename-fixes'
[thirdparty/git.git] / merge-recursive.c
index e86571c91be9e82ae9b88371db4c706103e88785..11869ad81ce82334834e48acfb2fc353db08bec9 100644 (file)
@@ -45,14 +45,16 @@ struct path_hashmap_entry {
 };
 
 static int path_hashmap_cmp(const void *cmp_data,
-                           const void *entry,
-                           const void *entry_or_key,
+                           const struct hashmap_entry *eptr,
+                           const struct hashmap_entry *entry_or_key,
                            const void *keydata)
 {
-       const struct path_hashmap_entry *a = entry;
-       const struct path_hashmap_entry *b = entry_or_key;
+       const struct path_hashmap_entry *a, *b;
        const char *key = keydata;
 
+       a = container_of(eptr, const struct path_hashmap_entry, e);
+       b = container_of(entry_or_key, const struct path_hashmap_entry, e);
+
        if (ignore_case)
                return strcasecmp(a->path, key ? key : b->path);
        else
@@ -75,7 +77,7 @@ static unsigned int path_hash(const char *path)
  *                       in get_directory_renames() for details
  */
 struct dir_rename_entry {
-       struct hashmap_entry ent; /* must be the first member! */
+       struct hashmap_entry ent;
        char *dir;
        unsigned non_unique_new_dir:1;
        struct strbuf new_dir;
@@ -89,18 +91,20 @@ static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
 
        if (dir == NULL)
                return NULL;
-       hashmap_entry_init(&key, strhash(dir));
+       hashmap_entry_init(&key.ent, strhash(dir));
        key.dir = dir;
-       return hashmap_get(hashmap, &key, NULL);
+       return hashmap_get_entry(hashmap, &key, ent, NULL);
 }
 
 static int dir_rename_cmp(const void *unused_cmp_data,
-                         const void *entry,
-                         const void *entry_or_key,
+                         const struct hashmap_entry *eptr,
+                         const struct hashmap_entry *entry_or_key,
                          const void *unused_keydata)
 {
-       const struct dir_rename_entry *e1 = entry;
-       const struct dir_rename_entry *e2 = entry_or_key;
+       const struct dir_rename_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct dir_rename_entry, ent);
+       e2 = container_of(entry_or_key, const struct dir_rename_entry, ent);
 
        return strcmp(e1->dir, e2->dir);
 }
@@ -113,7 +117,7 @@ static void dir_rename_init(struct hashmap *map)
 static void dir_rename_entry_init(struct dir_rename_entry *entry,
                                  char *directory)
 {
-       hashmap_entry_init(entry, strhash(directory));
+       hashmap_entry_init(&entry->ent, strhash(directory));
        entry->dir = directory;
        entry->non_unique_new_dir = 0;
        strbuf_init(&entry->new_dir, 0);
@@ -121,7 +125,7 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,
 }
 
 struct collision_entry {
-       struct hashmap_entry ent; /* must be the first member! */
+       struct hashmap_entry ent;
        char *target_file;
        struct string_list source_files;
        unsigned reported_already:1;
@@ -132,22 +136,27 @@ static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
 {
        struct collision_entry key;
 
-       hashmap_entry_init(&key, strhash(target_file));
+       hashmap_entry_init(&key.ent, strhash(target_file));
        key.target_file = target_file;
-       return hashmap_get(hashmap, &key, NULL);
+       return hashmap_get_entry(hashmap, &key, ent, NULL);
 }
 
-static int collision_cmp(void *unused_cmp_data,
-                        const struct collision_entry *e1,
-                        const struct collision_entry *e2,
+static int collision_cmp(const void *unused_cmp_data,
+                        const struct hashmap_entry *eptr,
+                        const struct hashmap_entry *entry_or_key,
                         const void *unused_keydata)
 {
+       const struct collision_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct collision_entry, ent);
+       e2 = container_of(entry_or_key, const struct collision_entry, ent);
+
        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);
+       hashmap_init(map, collision_cmp, NULL, 0);
 }
 
 static void flush_output(struct merge_options *opt)
@@ -464,8 +473,8 @@ static int save_files_dirs(const struct object_id *oid,
        strbuf_addstr(base, path);
 
        FLEX_ALLOC_MEM(entry, path, base->buf, base->len);
-       hashmap_entry_init(entry, path_hash(entry->path));
-       hashmap_add(&opt->priv->current_file_dir_set, entry);
+       hashmap_entry_init(&entry->e, path_hash(entry->path));
+       hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
 
        strbuf_setlen(base, baselen);
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
@@ -743,8 +752,8 @@ static char *unique_path(struct merge_options *opt,
        }
 
        FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);
-       hashmap_entry_init(entry, path_hash(entry->path));
-       hashmap_add(&opt->priv->current_file_dir_set, entry);
+       hashmap_entry_init(&entry->e, path_hash(entry->path));
+       hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
        return strbuf_detach(&newpath, NULL);
 }
 
@@ -1942,6 +1951,16 @@ static char *apply_dir_rename(struct dir_rename_entry *entry,
                return NULL;
 
        oldlen = strlen(entry->dir);
+       if (entry->new_dir.len == 0)
+               /*
+                * If someone renamed/merged a subdirectory into the root
+                * directory (e.g. 'some/subdir' -> ''), then we want to
+                * avoid returning
+                *     '' + '/filename'
+                * as the rename; we need to make old_path + oldlen advance
+                * past the '/' character.
+                */
+               oldlen++;
        newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
        strbuf_grow(&new_path, newlen);
        strbuf_addbuf(&new_path, &entry->new_dir);
@@ -1954,8 +1973,8 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
                                    char **old_dir, char **new_dir)
 {
        char *end_of_old, *end_of_new;
-       int old_len, new_len;
 
+       /* Default return values: NULL, meaning no rename */
        *old_dir = NULL;
        *new_dir = NULL;
 
@@ -1966,43 +1985,91 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
         *    "a/b/c/d" was renamed to "a/b/some/thing/else"
         * so, for this example, this function returns "a/b/c/d" in
         * *old_dir and "a/b/some/thing/else" in *new_dir.
-        *
-        * Also, if the basename of the file changed, we don't care.  We
-        * want to know which portion of the directory, if any, changed.
+        */
+
+       /*
+        * If the basename of the file changed, we don't care.  We want
+        * to know which portion of the directory, if any, changed.
         */
        end_of_old = strrchr(old_path, '/');
        end_of_new = strrchr(new_path, '/');
 
-       if (end_of_old == NULL || end_of_new == NULL)
+       /*
+        * If end_of_old is NULL, old_path wasn't in a directory, so there
+        * could not be a directory rename (our rule elsewhere that a
+        * directory which still exists is not considered to have been
+        * renamed means the root directory can never be renamed -- because
+        * the root directory always exists).
+        */
+       if (end_of_old == NULL)
+               return; /* Note: *old_dir and *new_dir are still NULL */
+
+       /*
+        * If new_path contains no directory (end_of_new is NULL), then we
+        * have a rename of old_path's directory to the root directory.
+        */
+       if (end_of_new == NULL) {
+               *old_dir = xstrndup(old_path, end_of_old - old_path);
+               *new_dir = xstrdup("");
                return;
+       }
+
+       /* Find the first non-matching character traversing backwards */
        while (*--end_of_new == *--end_of_old &&
               end_of_old != old_path &&
               end_of_new != new_path)
                ; /* Do nothing; all in the while loop */
+
        /*
-        * We've found the first non-matching character in the directory
-        * paths.  That means the current directory we were comparing
-        * represents the rename.  Move end_of_old and end_of_new back
-        * to the full directory name.
+        * If both got back to the beginning of their strings, then the
+        * directory didn't change at all, only the basename did.
         */
-       if (*end_of_old == '/')
-               end_of_old++;
-       if (*end_of_old != '/')
-               end_of_new++;
-       end_of_old = strchr(end_of_old, '/');
-       end_of_new = strchr(end_of_new, '/');
+       if (end_of_old == old_path && end_of_new == new_path &&
+           *end_of_old == *end_of_new)
+               return; /* Note: *old_dir and *new_dir are still NULL */
 
        /*
-        * It may have been the case that old_path and new_path were the same
-        * directory all along.  Don't claim a rename if they're the same.
+        * If end_of_new got back to the beginning of its string, and
+        * end_of_old got back to the beginning of some subdirectory, then
+        * we have a rename/merge of a subdirectory into the root, which
+        * needs slightly special handling.
+        *
+        * Note: There is no need to consider the opposite case, with a
+        * rename/merge of the root directory into some subdirectory
+        * because as noted above the root directory always exists so it
+        * cannot be considered to be renamed.
         */
-       old_len = end_of_old - old_path;
-       new_len = end_of_new - new_path;
-
-       if (old_len != new_len || strncmp(old_path, new_path, old_len)) {
-               *old_dir = xstrndup(old_path, old_len);
-               *new_dir = xstrndup(new_path, new_len);
+       if (end_of_new == new_path &&
+           end_of_old != old_path && end_of_old[-1] == '/') {
+               *old_dir = xstrndup(old_path, --end_of_old - old_path);
+               *new_dir = xstrdup("");
+               return;
        }
+
+       /*
+        * We've found the first non-matching character in the directory
+        * paths.  That means the current characters we were looking at
+        * were part of the first non-matching subdir name going back from
+        * the end of the strings.  Get the whole name by advancing both
+        * end_of_old and end_of_new to the NEXT '/' character.  That will
+        * represent the entire directory rename.
+        *
+        * The reason for the increment is cases like
+        *    a/b/star/foo/whatever.c -> a/b/tar/foo/random.c
+        * After dropping the basename and going back to the first
+        * non-matching character, we're now comparing:
+        *    a/b/s          and         a/b/
+        * and we want to be comparing:
+        *    a/b/star/      and         a/b/tar/
+        * but without the pre-increment, the one on the right would stay
+        * a/b/.
+        */
+       end_of_old = strchr(++end_of_old, '/');
+       end_of_new = strchr(++end_of_new, '/');
+
+       /* Copy the old and new directories into *old_dir and *new_dir. */
+       *old_dir = xstrndup(old_path, end_of_old - old_path);
+       *new_dir = xstrndup(new_path, end_of_new - new_path);
 }
 
 static void remove_hashmap_entries(struct hashmap *dir_renames,
@@ -2013,7 +2080,7 @@ static void remove_hashmap_entries(struct hashmap *dir_renames,
 
        for (i = 0; i < items_to_remove->nr; i++) {
                entry = items_to_remove->items[i].util;
-               hashmap_remove(dir_renames, entry, NULL);
+               hashmap_remove(dir_renames, &entry->ent, NULL);
        }
        string_list_clear(items_to_remove, 0);
 }
@@ -2136,8 +2203,8 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
        struct string_list remove_from_head = STRING_LIST_INIT_NODUP;
        struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
 
-       hashmap_iter_init(dir_re_head, &iter);
-       while ((head_ent = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(dir_re_head, &iter, head_ent,
+                               ent /* member name */) {
                merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
                if (merge_ent &&
                    !head_ent->non_unique_new_dir &&
@@ -2161,8 +2228,8 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
        remove_hashmap_entries(dir_re_head, &remove_from_head);
        remove_hashmap_entries(dir_re_merge, &remove_from_merge);
 
-       hashmap_iter_init(dir_re_merge, &iter);
-       while ((merge_ent = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(dir_re_merge, &iter, merge_ent,
+                               ent /* member name */) {
                head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
                if (tree_has_path(opt->repo, merge, merge_ent->dir)) {
                        /* 2. This wasn't a directory rename after all */
@@ -2241,7 +2308,7 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs)
                if (!entry) {
                        entry = xmalloc(sizeof(*entry));
                        dir_rename_entry_init(entry, old_dir);
-                       hashmap_put(dir_renames, entry);
+                       hashmap_put(dir_renames, &entry->ent);
                } else {
                        free(old_dir);
                }
@@ -2266,8 +2333,8 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs)
         * we set non_unique_new_dir.  Once we've determined the winner (or
         * that there is no winner), we no longer need possible_new_dirs.
         */
-       hashmap_iter_init(dir_renames, &iter);
-       while ((entry = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(dir_renames, &iter, entry,
+                               ent /* member name */) {
                int max = 0;
                int bad_max = 0;
                char *best = NULL;
@@ -2370,8 +2437,9 @@ static void compute_collisions(struct hashmap *collisions,
                if (!collision_ent) {
                        collision_ent = xcalloc(1,
                                                sizeof(struct collision_entry));
-                       hashmap_entry_init(collision_ent, strhash(new_path));
-                       hashmap_put(collisions, collision_ent);
+                       hashmap_entry_init(&collision_ent->ent,
+                                               strhash(new_path));
+                       hashmap_put(collisions, &collision_ent->ent);
                        collision_ent->target_file = new_path;
                } else {
                        free(new_path);
@@ -2624,12 +2692,12 @@ static struct string_list *get_renames(struct merge_options *opt,
                                                             entries);
        }
 
-       hashmap_iter_init(&collisions, &iter);
-       while ((e = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(&collisions, &iter, e,
+                               ent /* member name */) {
                free(e->target_file);
                string_list_clear(&e->source_files, 0);
        }
-       hashmap_free(&collisions, 1);
+       hashmap_free_entries(&collisions, struct collision_entry, ent);
        return renames;
 }
 
@@ -2842,13 +2910,13 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,
        struct hashmap_iter iter;
        struct dir_rename_entry *e;
 
-       hashmap_iter_init(dir_renames, &iter);
-       while ((e = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(dir_renames, &iter, e,
+                               ent /* member name */) {
                free(e->dir);
                strbuf_release(&e->new_dir);
                /* possible_new_dirs already cleared in get_directory_renames */
        }
-       hashmap_free(dir_renames, 1);
+       hashmap_free_entries(dir_renames, struct dir_rename_entry, ent);
        free(dir_renames);
 
        free(pairs->queue);
@@ -3475,7 +3543,8 @@ static int merge_trees_internal(struct merge_options *opt,
                string_list_clear(entries, 1);
                free(entries);
 
-               hashmap_free(&opt->priv->current_file_dir_set, 1);
+               hashmap_free_entries(&opt->priv->current_file_dir_set,
+                                       struct path_hashmap_entry, e);
 
                if (clean < 0) {
                        unpack_trees_finish(opt);