merge-recursive: Fix sorting order and directory change assumptions
[gitweb.git] / merge-recursive.c
index af131508ec7ffa3ac7b1b1b1cadfcda0040becf2..7757e5f1634c69d20a5537dedeec385d7a44a43b 100644 (file)
 #include "dir.h"
 #include "submodule.h"
 
-static const char rename_limit_advice[] =
-"inexact rename detection was skipped because there were too many\n"
-"  files. You may want to set your merge.renamelimit variable to at least\n"
-"  %d and retry this merge.";
-
 static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
 {
@@ -235,7 +230,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if (ce_stage(ce))
-                               fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+                               fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
                                        (int)ce_namelen(ce), ce->name);
                }
                die("Bug in merge-recursive.c");
@@ -336,6 +331,37 @@ static struct string_list *get_unmerged(void)
        return unmerged;
 }
 
+static int string_list_df_name_compare(const void *a, const void *b)
+{
+       const struct string_list_item *one = a;
+       const struct string_list_item *two = b;
+       int onelen = strlen(one->string);
+       int twolen = strlen(two->string);
+       /*
+        * Here we only care that entries for D/F conflicts are
+        * adjacent, in particular with the file of the D/F conflict
+        * appearing before files below the corresponding directory.
+        * The order of the rest of the list is irrelevant for us.
+        *
+        * To achieve this, we sort with df_name_compare and provide
+        * the mode S_IFDIR so that D/F conflicts will sort correctly.
+        * We use the mode S_IFDIR for everything else for simplicity,
+        * since in other cases any changes in their order due to
+        * sorting cause no problems for us.
+        */
+       int cmp = df_name_compare(one->string, onelen, S_IFDIR,
+                                 two->string, twolen, S_IFDIR);
+       /*
+        * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
+        * that 'foo' comes before 'foo/bar'.
+        */
+       if (cmp)
+               return cmp;
+       return onelen - twolen;
+}
+
+
+
 static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
                                                      struct string_list *entries)
 {
@@ -348,16 +374,22 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
         * otherwise, if the file is not supposed to be removed by the
         * merge, the contents of the file will be placed in another
         * unique filename.
-        *
-        * NOTE: This function relies on the fact that entries for a
-        * D/F conflict will appear adjacent in the index, with the
-        * entries for the file appearing before entries for paths
-        * below the corresponding directory.
         */
        const char *last_file = NULL;
        int last_len = 0;
        int i;
 
+       /*
+        * If we're merging merge-bases, we don't want to bother with
+        * any working directory changes.
+        */
+       if (o->call_depth)
+               return;
+
+       /* Ensure D/F conflicts are adjacent in the entries list. */
+       qsort(entries->items, entries->nr, sizeof(*entries->items),
+             string_list_df_name_compare);
+
        for (i = 0; i < entries->nr; i++) {
                const char *path = entries->items[i].string;
                int len = strlen(path);
@@ -464,10 +496,12 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages_options(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear, int options)
+static int update_stages(const char *path, const struct diff_filespec *o,
+                        const struct diff_filespec *a,
+                        const struct diff_filespec *b)
 {
+       int clear = 1;
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -483,14 +517,6 @@ static int update_stages_options(const char *path, struct diff_filespec *o,
        return 0;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
-{
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
-       return update_stages_options(path, o, a, b, clear, options);
-}
-
 static int update_stages_and_entry(const char *path,
                                   struct stage_data *entry,
                                   struct diff_filespec *o,
@@ -507,8 +533,7 @@ static int update_stages_and_entry(const char *path,
        hashcpy(entry->stages[1].sha, o->sha1);
        hashcpy(entry->stages[2].sha, a->sha1);
        hashcpy(entry->stages[3].sha, b->sha1);
-       options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
-       return update_stages_options(path, o, a, b, clear, options);
+       return update_stages(path, o, a, b);
 }
 
 static int remove_file(struct merge_options *o, int clean,
@@ -715,9 +740,9 @@ struct merge_file_info {
 
 static int merge_3way(struct merge_options *o,
                      mmbuffer_t *result_buf,
-                     struct diff_filespec *one,
-                     struct diff_filespec *a,
-                     struct diff_filespec *b,
+                     const struct diff_filespec *one,
+                     const struct diff_filespec *a,
+                     const struct diff_filespec *b,
                      const char *branch1,
                      const char *branch2)
 {
@@ -775,9 +800,9 @@ static int merge_3way(struct merge_options *o,
 }
 
 static struct merge_file_info merge_file(struct merge_options *o,
-                                        struct diff_filespec *one,
-                                        struct diff_filespec *a,
-                                        struct diff_filespec *b,
+                                        const struct diff_filespec *one,
+                                        const struct diff_filespec *a,
+                                        const struct diff_filespec *b,
                                         const char *branch1,
                                         const char *branch2)
 {
@@ -864,8 +889,7 @@ static void conflict_rename_delete(struct merge_options *o,
        if (!o->call_depth)
                update_stages(dest_name, NULL,
                              rename_branch == o->branch1 ? pair->two : NULL,
-                             rename_branch == o->branch1 ? NULL : pair->two,
-                             1);
+                             rename_branch == o->branch1 ? NULL : pair->two);
        if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
                dest_name = unique_path(o, dest_name, rename_branch);
                df_conflict = 1;
@@ -909,8 +933,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
                 * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
                 */
        } else {
-               update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
-               update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+               update_stages(ren1_dst, NULL, pair1->two, NULL);
+               update_stages(ren2_dst, NULL, NULL, pair2->two);
 
                update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
                update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
@@ -1074,7 +1098,6 @@ static int process_renames(struct merge_options *o,
                                   renamed: clean merge */
                                update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
-                               const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
                                output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
@@ -1103,9 +1126,10 @@ static int process_renames(struct merge_options *o,
                                                    ren1_dst);
                                        try_merge = 0;
                                } else {
-                                       new_path = unique_path(o, ren1_dst, branch2);
+                                       char *new_path = unique_path(o, ren1_dst, branch2);
                                        output(o, 1, "Adding as %s instead", new_path);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                                       free(new_path);
                                }
                        } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
                                ren2 = item->util;
@@ -1273,13 +1297,14 @@ static int merge_content(struct merge_options *o,
        }
 
        if (df_conflict_remains) {
-               const char *new_path;
+               char *new_path;
                update_file_flags(o, mfi.sha, mfi.mode, path,
                                  o->call_depth || mfi.clean, 0);
                new_path = unique_path(o, path, df_rename_conflict_branch);
                mfi.clean = 0;
                output(o, 1, "Adding as %s instead", new_path);
                update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+               free(new_path);
        } else {
                update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
        }
@@ -1435,14 +1460,16 @@ static int process_df_entry(struct merge_options *o,
                }
        } else if (o_sha && (!a_sha || !b_sha)) {
                /* Modify/delete; deleted side may have put a directory in the way */
-               const char *new_path = path;
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
-                       new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+               char *renamed = NULL;
+               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+                       renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+               }
                clean_merge = 0;
-               handle_delete_modify(o, path, new_path,
+               handle_delete_modify(o, path, renamed ? renamed : path,
                                     a_sha, a_mode, b_sha, b_mode);
+               free(renamed);
        } else if (!o_sha && !!a_sha != !!b_sha) {
-               /* directory -> (directory, file) */
+               /* directory -> (directory, file) or <nothing> -> (directory, file) */
                const char *add_branch;
                const char *other_branch;
                unsigned mode;
@@ -1463,12 +1490,15 @@ static int process_df_entry(struct merge_options *o,
                        conf = "directory/file";
                }
                if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
-                       const char *new_path = unique_path(o, path, add_branch);
+                       char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
                        update_file(o, 0, sha, mode, new_path);
+                       if (o->call_depth)
+                               remove_file_from_cache(path);
+                       free(new_path);
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1652,8 +1682,9 @@ int merge_recursive(struct merge_options *o,
                commit_list_insert(h2, &(*result)->parents->next);
        }
        flush_output(o);
-       if (o->needed_rename_limit)
-               warning(rename_limit_advice, o->needed_rename_limit);
+       if (show(o, 2))
+               diff_warn_rename_limit("merge.renamelimit",
+                                      o->needed_rename_limit, 0);
        return clean;
 }
 
@@ -1711,15 +1742,15 @@ int merge_recursive_generic(struct merge_options *o,
 static int merge_recursive_config(const char *var, const char *value, void *cb)
 {
        struct merge_options *o = cb;
-       if (!strcasecmp(var, "merge.verbosity")) {
+       if (!strcmp(var, "merge.verbosity")) {
                o->verbosity = git_config_int(var, value);
                return 0;
        }
-       if (!strcasecmp(var, "diff.renamelimit")) {
+       if (!strcmp(var, "diff.renamelimit")) {
                o->diff_rename_limit = git_config_int(var, value);
                return 0;
        }
-       if (!strcasecmp(var, "merge.renamelimit")) {
+       if (!strcmp(var, "merge.renamelimit")) {
                o->merge_rename_limit = git_config_int(var, value);
                return 0;
        }