merge-recursive: Structure process_df_entry() to handle more cases
[gitweb.git] / merge-recursive.c
index 20e1779428ee553a940592c09bbabe9ca9800a39..aa496cd3601ddad933d07481d454258029863d48 100644 (file)
@@ -63,6 +63,22 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
        return a && b && hashcmp(a, b) == 0;
 }
 
+enum rename_type {
+       RENAME_NORMAL = 0,
+       RENAME_DELETE,
+       RENAME_ONE_FILE_TO_TWO
+};
+
+struct rename_df_conflict_info {
+       enum rename_type rename_type;
+       struct diff_filepair *pair1;
+       struct diff_filepair *pair2;
+       const char *branch1;
+       const char *branch2;
+       struct stage_data *dst_entry1;
+       struct stage_data *dst_entry2;
+};
+
 /*
  * Since we want to write the index eventually, we cannot reuse the index
  * for these (temporary) data.
@@ -74,9 +90,37 @@ struct stage_data
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
+       struct rename_df_conflict_info *rename_df_conflict_info;
        unsigned processed:1;
 };
 
+static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
+                                                struct diff_filepair *pair1,
+                                                struct diff_filepair *pair2,
+                                                const char *branch1,
+                                                const char *branch2,
+                                                struct stage_data *dst_entry1,
+                                                struct stage_data *dst_entry2)
+{
+       struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+       ci->rename_type = rename_type;
+       ci->pair1 = pair1;
+       ci->branch1 = branch1;
+       ci->branch2 = branch2;
+
+       ci->dst_entry1 = dst_entry1;
+       dst_entry1->rename_df_conflict_info = ci;
+       dst_entry1->processed = 0;
+
+       assert(!pair2 == !dst_entry2);
+       if (dst_entry2) {
+               ci->dst_entry2 = dst_entry2;
+               ci->pair2 = pair2;
+               dst_entry2->rename_df_conflict_info = ci;
+               dst_entry2->processed = 0;
+       }
+}
+
 static int show(struct merge_options *o, int v)
 {
        return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
@@ -373,11 +417,10 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
+static int update_stages_options(const char *path, struct diff_filespec *o,
                         struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
+                        int clear, int options)
 {
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -393,6 +436,34 @@ static int update_stages(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,
+                                  struct diff_filespec *a,
+                                  struct diff_filespec *b,
+                                  int clear)
+{
+       int options;
+
+       entry->processed = 0;
+       entry->stages[1].mode = o->mode;
+       entry->stages[2].mode = a->mode;
+       entry->stages[3].mode = b->mode;
+       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);
+}
+
 static int remove_file(struct merge_options *o, int clean,
                       const char *path, int no_wd)
 {
@@ -730,12 +801,32 @@ static struct merge_file_info merge_file(struct merge_options *o,
        return result;
 }
 
-static void conflict_rename_rename(struct merge_options *o,
-                                  struct rename *ren1,
-                                  const char *branch1,
-                                  struct rename *ren2,
-                                  const char *branch2)
+static void conflict_rename_delete(struct merge_options *o,
+                                  struct diff_filepair *pair,
+                                  const char *rename_branch,
+                                  const char *other_branch)
 {
+       char *dest_name = pair->two->path;
+
+       output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
+              "and deleted in %s",
+              pair->one->path, pair->two->path, rename_branch,
+              other_branch);
+       if (!o->call_depth)
+               update_stages(dest_name, NULL,
+                             rename_branch == o->branch1 ? pair->two : NULL,
+                             rename_branch == o->branch1 ? NULL : pair->two,
+                             1);
+       update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+                                       struct rename *ren1,
+                                       const char *branch1,
+                                       struct rename *ren2,
+                                       const char *branch2)
+{
+       /* One file was renamed in both branches, but to different names. */
        char *del[2];
        int delp = 0;
        const char *ren1_dst = ren1->pair->two->path;
@@ -771,23 +862,13 @@ static void conflict_rename_rename(struct merge_options *o,
                free(del[delp]);
 }
 
-static void conflict_rename_dir(struct merge_options *o,
-                               struct rename *ren1,
-                               const char *branch1)
-{
-       char *new_path = unique_path(o, ren1->pair->two->path, branch1);
-       output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(o, 0, ren1->pair->two->path, 0);
-       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
-       free(new_path);
-}
-
-static void conflict_rename_rename_2(struct merge_options *o,
-                                    struct rename *ren1,
-                                    const char *branch1,
-                                    struct rename *ren2,
-                                    const char *branch2)
+static void conflict_rename_rename_2to1(struct merge_options *o,
+                                       struct rename *ren1,
+                                       const char *branch1,
+                                       struct rename *ren2,
+                                       const char *branch2)
 {
+       /* Two files were renamed to the same thing. */
        char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
        char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
        output(o, 1, "Renaming %s to %s and %s to %s instead",
@@ -889,7 +970,7 @@ static int process_renames(struct merge_options *o,
                                        update_file(o, 0, ren1->pair->one->sha1,
                                                    ren1->pair->one->mode, src);
                                }
-                               conflict_rename_rename(o, ren1, branch1, ren2, branch2);
+                               conflict_rename_rename_1to2(o, ren1, branch1, ren2, branch2);
                        } else {
                                struct merge_file_info mfi;
                                remove_file(o, 1, ren1_src, 1);
@@ -924,37 +1005,34 @@ static int process_renames(struct merge_options *o,
                        struct string_list_item *item;
                        /* we only use sha1 and mode of these */
                        struct diff_filespec src_other, dst_other;
-                       int try_merge, stage = a_renames == renames1 ? 3: 2;
+                       int try_merge;
 
-                       remove_file(o, 1, ren1_src, o->call_depth || stage == 3);
+                       /*
+                        * unpack_trees loads entries from common-commit
+                        * into stage 1, from head-commit into stage 2, and
+                        * from merge-commit into stage 3.  We keep track
+                        * of which side corresponds to the rename.
+                        */
+                       int renamed_stage = a_renames == renames1 ? 2 : 3;
+                       int other_stage =   a_renames == renames1 ? 3 : 2;
 
-                       hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
-                       src_other.mode = ren1->src_entry->stages[stage].mode;
-                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
-                       dst_other.mode = ren1->dst_entry->stages[stage].mode;
+                       remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
 
+                       hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
+                       src_other.mode = ren1->src_entry->stages[other_stage].mode;
+                       hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
+                       dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
                        try_merge = 0;
 
-                       if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+                       if (sha_eq(src_other.sha1, null_sha1)) {
                                clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
-                                      " directory %s added in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               conflict_rename_dir(o, ren1, branch1);
-                       } else if (sha_eq(src_other.sha1, null_sha1)) {
-                               clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
-                                      "and deleted in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      branch2);
-                               update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                               if (!o->call_depth)
-                                       update_stages(ren1_dst, NULL,
-                                                       branch1 == o->branch1 ?
-                                                       ren1->pair->two : NULL,
-                                                       branch1 == o->branch1 ?
-                                                       NULL : ren1->pair->two, 1);
+                               conflict_rename_delete(o, ren1->pair, branch1, branch2);
+                       } else if ((dst_other.mode == ren1->pair->two->mode) &&
+                                  sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
+                               /* Added file on the other side
+                                  identical to the file being
+                                  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;
@@ -997,7 +1075,7 @@ static int process_renames(struct merge_options *o,
                                       "Rename %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
+                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
@@ -1111,6 +1189,67 @@ static int blob_unchanged(const unsigned char *o_sha,
        return ret;
 }
 
+static void handle_delete_modify(struct merge_options *o,
+                                const char *path,
+                                unsigned char *a_sha, int a_mode,
+                                unsigned char *b_sha, int b_mode)
+{
+       if (!a_sha) {
+               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+                      "and modified in %s. Version %s of %s left in tree.",
+                      path, o->branch1,
+                      o->branch2, o->branch2, path);
+               update_file(o, 0, b_sha, b_mode, path);
+       } else {
+               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+                      "and modified in %s. Version %s of %s left in tree.",
+                      path, o->branch2,
+                      o->branch1, o->branch1, path);
+               update_file(o, 0, a_sha, a_mode, path);
+       }
+}
+
+
+static int merge_content(struct merge_options *o,
+                        const char *path,
+                        unsigned char *o_sha, int o_mode,
+                        unsigned char *a_sha, int a_mode,
+                        unsigned char *b_sha, int b_mode)
+{
+       const char *reason = "content";
+       struct merge_file_info mfi;
+       struct diff_filespec one, a, b;
+
+       if (!o_sha) {
+               reason = "add/add";
+               o_sha = (unsigned char *)null_sha1;
+       }
+       one.path = a.path = b.path = (char *)path;
+       hashcpy(one.sha1, o_sha);
+       one.mode = o_mode;
+       hashcpy(a.sha1, a_sha);
+       a.mode = a_mode;
+       hashcpy(b.sha1, b_sha);
+       b.mode = b_mode;
+
+       mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
+       if (mfi.clean && sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+               output(o, 3, "Skipped %s (merged same as existing)", path);
+       else
+               output(o, 2, "Auto-merging %s", path);
+
+       if (!mfi.clean) {
+               if (S_ISGITLINK(mfi.mode))
+                       reason = "submodule";
+               output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+                               reason, path);
+       }
+
+       update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+       return mfi.clean;
+
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -1128,6 +1267,9 @@ 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);
 
+       if (entry->rename_df_conflict_info)
+               return 1; /* Such cases are handled elsewhere. */
+
        entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
@@ -1143,19 +1285,8 @@ static int process_entry(struct merge_options *o,
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
-                       if (!a_sha) {
-                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, o->branch1,
-                                      o->branch2, o->branch2, path);
-                               update_file(o, 0, b_sha, b_mode, path);
-                       } else {
-                               output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                                      "and modified in %s. Version %s of %s left in tree.",
-                                      path, o->branch2,
-                                      o->branch1, o->branch1, path);
-                               update_file(o, 0, a_sha, a_mode, path);
-                       }
+                       handle_delete_modify(o, path,
+                                            a_sha, a_mode, b_sha, b_mode);
                }
 
        } else if ((!o_sha && a_sha && !b_sha) ||
@@ -1190,34 +1321,8 @@ static int process_entry(struct merge_options *o,
        } else if (a_sha && b_sha) {
                /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
-               const char *reason = "content";
-               struct merge_file_info mfi;
-               struct diff_filespec one, a, b;
-
-               if (!o_sha) {
-                       reason = "add/add";
-                       o_sha = (unsigned char *)null_sha1;
-               }
-               output(o, 2, "Auto-merging %s", path);
-               one.path = a.path = b.path = (char *)path;
-               hashcpy(one.sha1, o_sha);
-               one.mode = o_mode;
-               hashcpy(a.sha1, a_sha);
-               a.mode = a_mode;
-               hashcpy(b.sha1, b_sha);
-               b.mode = b_mode;
-
-               mfi = merge_file(o, &one, &a, &b,
-                                o->branch1, o->branch2);
-
-               clean_merge = mfi.clean;
-               if (!mfi.clean) {
-                       if (S_ISGITLINK(mfi.mode))
-                               reason = "submodule";
-                       output(o, 1, "CONFLICT (%s): Merge conflict in %s",
-                                       reason, path);
-               }
-               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+               clean_merge = merge_content(o, path,
+                                           o_sha, o_mode, a_sha, a_mode, b_sha, b_mode);
        } else if (!o_sha && !a_sha && !b_sha) {
                /*
                 * this entry was deleted altogether. a_mode == 0 means
@@ -1231,13 +1336,19 @@ static int process_entry(struct merge_options *o,
 }
 
 /*
- * 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.
+ * Per entry merge function for D/F (and/or rename) conflicts.  In the
+ * cases we can cleanly resolve D/F conflicts, process_entry() can
+ * clean out all the files below the directory for us.  All D/F
+ * conflict cases must be handled here at the end to make sure any
+ * directories that can be cleaned out, are.
+ *
+ * Some rename conflicts may also be handled here that don't necessarily
+ * involve D/F conflicts, since the code to handle them is generic enough
+ * to handle those rename conflicts with or without D/F conflicts also
+ * being involved.
  */
 static int process_df_entry(struct merge_options *o,
-                        const char *path, struct stage_data *entry)
+                           const char *path, struct stage_data *entry)
 {
        int clean_merge = 1;
        unsigned o_mode = entry->stages[1].mode;
@@ -1246,43 +1357,47 @@ static int process_df_entry(struct merge_options *o,
        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 (entry->rename_df_conflict_info) {
+               die("Not yet implemented.");
+       } else if (!o_sha && !!a_sha != !!b_sha) {
+               /* directory -> (directory, file) */
+               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 (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);
+               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);
+               }
        } else {
-               output(o, 2, "Adding %s", path);
-               update_file(o, 1, sha, mode, path);
+               entry->processed = 0;
+               return 1; /* not handled; assume clean until processed */
        }
 
        return clean_merge;
@@ -1344,6 +1459,12 @@ int merge_trees(struct merge_options *o,
                                && !process_df_entry(o, path, e))
                                clean = 0;
                }
+               for (i = 0; i < entries->nr; i++) {
+                       struct stage_data *e = entries->items[i].util;
+                       if (!e->processed)
+                               die("Unprocessed path??? %s",
+                                   entries->items[i].string);
+               }
 
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);