fast-export: ensure that a renamed file is printed after all references
[gitweb.git] / merge-recursive.c
index 917397ca7a0c3a702681e1c3e93eb385bb6f28fb..a576f9b10ec27cd46ee305301c4f68eddde1fe52 100644 (file)
@@ -1019,14 +1019,22 @@ static int process_renames(struct merge_options *o,
 
                                if (mfi.clean &&
                                    sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode)
+                                   mfi.mode == ren1->pair->two->mode) {
                                        /*
-                                        * This messaged is part of
+                                        * This message is part of
                                         * t6022 test. If you change
                                         * it update the test too.
                                         */
                                        output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
-                               else {
+
+                                       /* There may be higher stage entries left
+                                        * in the index (e.g. due to a D/F
+                                        * conflict) that need to be resolved.
+                                        */
+                                       if (!ren1->dst_entry->stages[2].mode !=
+                                           !ren1->dst_entry->stages[3].mode)
+                                               ren1->dst_entry->processed = 0;
+                               } else {
                                        if (mfi.merge || !mfi.clean)
                                                output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
@@ -1072,6 +1080,7 @@ static int process_entry(struct merge_options *o,
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
+       entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
@@ -1104,33 +1113,28 @@ static int process_entry(struct merge_options *o,
        } else if ((!o_sha && a_sha && !b_sha) ||
                   (!o_sha && !a_sha && b_sha)) {
                /* Case B: Added in one. */
-               const char *add_branch;
-               const char *other_branch;
                unsigned mode;
                const unsigned char *sha;
-               const char *conf;
 
                if (a_sha) {
-                       add_branch = o->branch1;
-                       other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
-                       conf = "file/directory";
                } else {
-                       add_branch = o->branch2;
-                       other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
-                       conf = "directory/file";
                }
                if (string_list_has_string(&o->current_directory_set, path)) {
-                       const 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);
-                       remove_file(o, 0, path, 0);
-                       update_file(o, 0, sha, mode, new_path);
+                       /* Handle D->F conflicts after all subfiles */
+                       entry->processed = 0;
+                       /* But get any file out of the way now, so conflicted
+                        * entries below the directory of the same name can
+                        * be put in the working directory.
+                        */
+                       if (a_sha)
+                               output(o, 2, "Removing %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(o, 0, path, !a_sha);
+                       return 1; /* Assume clean till processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1178,6 +1182,64 @@ static int process_entry(struct merge_options *o,
        return clean_merge;
 }
 
+/*
+ * Per entry merge function for D/F conflicts, to be called only after
+ * all files below dir have been processed.  We do this because in the
+ * cases we can cleanly resolve D/F conflicts, process_entry() can clean
+ * out all the files below the directory for us.
+ */
+static int process_df_entry(struct merge_options *o,
+                        const char *path, struct stage_data *entry)
+{
+       int clean_merge = 1;
+       unsigned o_mode = entry->stages[1].mode;
+       unsigned a_mode = entry->stages[2].mode;
+       unsigned b_mode = entry->stages[3].mode;
+       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+       const char *add_branch;
+       const char *other_branch;
+       unsigned mode;
+       const unsigned char *sha;
+       const char *conf;
+       struct stat st;
+
+       /* We currently only handle D->F cases */
+       assert((!o_sha && a_sha && !b_sha) ||
+              (!o_sha && !a_sha && b_sha));
+
+       entry->processed = 1;
+
+       if (a_sha) {
+               add_branch = o->branch1;
+               other_branch = o->branch2;
+               mode = a_mode;
+               sha = a_sha;
+               conf = "file/directory";
+       } else {
+               add_branch = o->branch2;
+               other_branch = o->branch1;
+               mode = b_mode;
+               sha = b_sha;
+               conf = "directory/file";
+       }
+       if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+               const 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);
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, sha, mode, new_path);
+       } else {
+               output(o, 2, "Adding %s", path);
+               update_file(o, 1, sha, mode, path);
+       }
+
+       return clean_merge;
+}
+
 struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 {
        struct unpack_trees_error_msgs msgs = {
@@ -1249,6 +1311,13 @@ int merge_trees(struct merge_options *o,
                                && !process_entry(o, path, e))
                                clean = 0;
                }
+               for (i = 0; i < entries->nr; i++) {
+                       const char *path = entries->items[i].string;
+                       struct stage_data *e = entries->items[i].util;
+                       if (!e->processed
+                               && !process_df_entry(o, path, e))
+                               clean = 0;
+               }
 
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);