merge-recursive: improve handling for rename/rename(2to1) conflicts
[gitweb.git] / merge-recursive.c
index 24d979022e8e2d46baa15e53ef3c8f69760ba497..ead6054a75b6bd6317f2162661d6122c2fa853f7 100644 (file)
@@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)
 enum rename_type {
        RENAME_NORMAL = 0,
        RENAME_VIA_DIR,
+       RENAME_ADD,
        RENAME_DELETE,
        RENAME_ONE_FILE_TO_ONE,
        RENAME_ONE_FILE_TO_TWO,
@@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
                                              struct stage_data *src_entry1,
                                              struct stage_data *src_entry2)
 {
+       int ostage1 = 0, ostage2;
        struct rename_conflict_info *ci;
 
        /*
@@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
                dst_entry2->rename_conflict_info = ci;
        }
 
-       if (rename_type == RENAME_TWO_FILES_TO_ONE) {
-               /*
-                * For each rename, there could have been
-                * modifications on the side of history where that
-                * file was not renamed.
-                */
-               int ostage1 = o->branch1 == branch1 ? 3 : 2;
-               int ostage2 = ostage1 ^ 1;
+       /*
+        * For each rename, there could have been
+        * modifications on the side of history where that
+        * file was not renamed.
+        */
+       if (rename_type == RENAME_ADD ||
+           rename_type == RENAME_TWO_FILES_TO_ONE) {
+               ostage1 = o->branch1 == branch1 ? 3 : 2;
 
                ci->ren1_other.path = pair1->one->path;
                oidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);
                ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+       }
+
+       if (rename_type == RENAME_TWO_FILES_TO_ONE) {
+               ostage2 = ostage1 ^ 1;
 
                ci->ren2_other.path = pair2->one->path;
                oidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);
@@ -690,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,
        return 0;
 }
 
-static int update_stages_for_stage_data(struct merge_options *opt,
-                                       const char *path,
-                                       const struct stage_data *stage_data)
-{
-       struct diff_filespec o, a, b;
-
-       o.mode = stage_data->stages[1].mode;
-       oidcpy(&o.oid, &stage_data->stages[1].oid);
-
-       a.mode = stage_data->stages[2].mode;
-       oidcpy(&a.oid, &stage_data->stages[2].oid);
-
-       b.mode = stage_data->stages[3].mode;
-       oidcpy(&b.oid, &stage_data->stages[3].oid);
-
-       return update_stages(opt, path,
-                            is_null_oid(&o.oid) ? NULL : &o,
-                            is_null_oid(&a.oid) ? NULL : &a,
-                            is_null_oid(&b.oid) ? NULL : &b);
-}
-
 static void update_entry(struct stage_data *entry,
                         struct diff_filespec *o,
                         struct diff_filespec *a,
@@ -1559,7 +1544,6 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
        return target;
 }
 
-#if 0 // #if-0-ing avoids unused function warning; will make live in next commit
 static int handle_file_collision(struct merge_options *o,
                                 const char *collide_path,
                                 const char *prev_path1,
@@ -1575,6 +1559,20 @@ static int handle_file_collision(struct merge_options *o,
        char *alt_path = NULL;
        const char *update_path = collide_path;
 
+       /*
+        * It's easiest to get the correct things into stage 2 and 3, and
+        * to make sure that the content merge puts HEAD before the other
+        * branch if we just ensure that branch1 == o->branch1.  So, simply
+        * flip arguments around if we don't have that.
+        */
+       if (branch1 != o->branch1) {
+               return handle_file_collision(o, collide_path,
+                                            prev_path2, prev_path1,
+                                            branch2, branch1,
+                                            b_oid, b_mode,
+                                            a_oid, a_mode);
+       }
+
        /*
         * In the recursive case, we just opt to undo renames
         */
@@ -1678,7 +1676,38 @@ static int handle_file_collision(struct merge_options *o,
         */
        return mfi.clean;
 }
-#endif
+
+static int handle_rename_add(struct merge_options *o,
+                            struct rename_conflict_info *ci)
+{
+       /* a was renamed to c, and a separate c was added. */
+       struct diff_filespec *a = ci->pair1->one;
+       struct diff_filespec *c = ci->pair1->two;
+       char *path = c->path;
+       char *prev_path_desc;
+       struct merge_file_info mfi;
+
+       int other_stage = (ci->branch1 == o->branch1 ? 3 : 2);
+
+       output(o, 1, _("CONFLICT (rename/add): "
+              "Rename %s->%s in %s.  Added %s in %s"),
+              a->path, c->path, ci->branch1,
+              c->path, ci->branch2);
+
+       prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
+       if (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,
+                                   o->branch1, o->branch2,
+                                   1 + o->call_depth * 2, &mfi))
+               return -1;
+       free(prev_path_desc);
+
+       return handle_file_collision(o,
+                                    c->path, a->path, NULL,
+                                    ci->branch1, ci->branch2,
+                                    &mfi.oid, mfi.mode,
+                                    &ci->dst_entry1->stages[other_stage].oid,
+                                    ci->dst_entry1->stages[other_stage].mode);
+}
 
 static int handle_file(struct merge_options *o,
                        struct diff_filespec *rename,
@@ -1820,7 +1849,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,
        char *path_side_2_desc;
        struct merge_file_info mfi_c1;
        struct merge_file_info mfi_c2;
-       int ret;
 
        output(o, 1, _("CONFLICT (rename/rename): "
               "Rename %s->%s in %s. "
@@ -1828,81 +1856,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,
               a->path, c1->path, ci->branch1,
               b->path, c2->path, ci->branch2);
 
-       remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
-       remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
-
        path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
        path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
        if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
                                    o->branch1, o->branch2,
-                                   o->call_depth * 2, &mfi_c1) ||
+                                   1 + o->call_depth * 2, &mfi_c1) ||
            merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
                                    o->branch1, o->branch2,
-                                   o->call_depth * 2, &mfi_c2))
+                                   1 + o->call_depth * 2, &mfi_c2))
                return -1;
        free(path_side_1_desc);
        free(path_side_2_desc);
 
-       if (o->call_depth) {
-               /*
-                * If mfi_c1.clean && mfi_c2.clean, then it might make
-                * sense to do a two-way merge of those results.  But, I
-                * think in all cases, it makes sense to have the virtual
-                * merge base just undo the renames; they can be detected
-                * again later for the non-recursive merge.
-                */
-               remove_file(o, 0, path, 0);
-               ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
-               if (!ret)
-                       ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
-                                         b->path);
-       } else {
-               char *new_path1 = unique_path(o, path, ci->branch1);
-               char *new_path2 = unique_path(o, path, ci->branch2);
-               output(o, 1, _("Renaming %s to %s and %s to %s instead"),
-                      a->path, new_path1, b->path, new_path2);
-               if (was_dirty(o, path))
-                       output(o, 1, _("Refusing to lose dirty file at %s"),
-                              path);
-               else if (would_lose_untracked(path))
-                       /*
-                        * Only way we get here is if both renames were from
-                        * a directory rename AND user had an untracked file
-                        * at the location where both files end up after the
-                        * two directory renames.  See testcase 10d of t6043.
-                        */
-                       output(o, 1, _("Refusing to lose untracked file at "
-                                      "%s, even though it's in the way."),
-                              path);
-               else
-                       remove_file(o, 0, path, 0);
-               ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
-               if (!ret)
-                       ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
-                                         new_path2);
-               /*
-                * unpack_trees() actually populates the index for us for
-                * "normal" rename/rename(2to1) situtations so that the
-                * correct entries are at the higher stages, which would
-                * make the call below to update_stages_for_stage_data
-                * unnecessary.  However, if either of the renames came
-                * from a directory rename, then unpack_trees() will not
-                * have gotten the right data loaded into the index, so we
-                * need to do so now.  (While it'd be tempting to move this
-                * call to update_stages_for_stage_data() to
-                * apply_directory_rename_modifications(), that would break
-                * our intermediate calls to would_lose_untracked() since
-                * those rely on the current in-memory index.  See also the
-                * big "NOTE" in update_stages()).
-                */
-               if (update_stages_for_stage_data(o, path, ci->dst_entry1))
-                       ret = -1;
-
-               free(new_path2);
-               free(new_path1);
-       }
-
-       return ret;
+       return handle_file_collision(o, path, a->path, b->path,
+                                    ci->branch1, ci->branch2,
+                                    &mfi_c1.oid, mfi_c1.mode,
+                                    &mfi_c2.oid, mfi_c2.mode);
 }
 
 /*
@@ -2860,47 +2829,23 @@ static int process_renames(struct merge_options *o,
                                                      0  /* update_wd    */))
                                        clean_merge = -1;
                        } else if (!oid_eq(&dst_other.oid, &null_oid)) {
-                               clean_merge = 0;
-                               try_merge = 1;
-                               output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
-                                      "%s added in %s"),
-                                      ren1_src, ren1_dst, branch1,
-                                      ren1_dst, branch2);
-                               if (o->call_depth) {
-                                       struct merge_file_info mfi;
-                                       struct diff_filespec one, a, b;
-
-                                       oidcpy(&one.oid, &null_oid);
-                                       one.mode = 0;
-                                       one.path = ren1->pair->two->path;
-
-                                       oidcpy(&a.oid, &ren1->pair->two->oid);
-                                       a.mode = ren1->pair->two->mode;
-                                       a.path = one.path;
-
-                                       oidcpy(&b.oid, &dst_other.oid);
-                                       b.mode = dst_other.mode;
-                                       b.path = one.path;
-
-                                       if (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,
-                                                                   branch1, branch2,
-                                                                   o->call_depth * 2, &mfi)) {
-                                               clean_merge = -1;
-                                               goto cleanup_and_return;
-                                       }
-                                       output(o, 1, _("Adding merged %s"), ren1_dst);
-                                       if (update_file(o, 0, &mfi.oid,
-                                                       mfi.mode, ren1_dst))
-                                               clean_merge = -1;
-                                       try_merge = 0;
-                               } else {
-                                       char *new_path = unique_path(o, ren1_dst, branch2);
-                                       output(o, 1, _("Adding as %s instead"), new_path);
-                                       if (update_file(o, 0, &dst_other.oid,
-                                                       dst_other.mode, new_path))
-                                               clean_merge = -1;
-                                       free(new_path);
-                               }
+                               /*
+                                * Probably not a clean merge, but it's
+                                * premature to set clean_merge to 0 here,
+                                * because if the rename merges cleanly and
+                                * the merge exactly matches the newly added
+                                * file, then the merge will be clean.
+                                */
+                               setup_rename_conflict_info(RENAME_ADD,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          ren1->src_entry,
+                                                          NULL);
                        } else
                                try_merge = 1;
 
@@ -3312,6 +3257,15 @@ static int process_entry(struct merge_options *o,
                                                  conflict_info->branch2))
                                clean_merge = -1;
                        break;
+               case RENAME_ADD:
+                       /*
+                        * Probably unclean merge, but if the renamed file
+                        * merges cleanly and the result can then be
+                        * two-way merged cleanly with the added file, I
+                        * guess it's a clean merge?
+                        */
+                       clean_merge = handle_rename_add(o, conflict_info);
+                       break;
                case RENAME_DELETE:
                        clean_merge = 0;
                        if (handle_rename_delete(o,
@@ -3326,9 +3280,14 @@ static int process_entry(struct merge_options *o,
                                clean_merge = -1;
                        break;
                case RENAME_TWO_FILES_TO_ONE:
-                       clean_merge = 0;
-                       if (handle_rename_rename_2to1(o, conflict_info))
-                               clean_merge = -1;
+                       /*
+                        * Probably unclean merge, but if the two renamed
+                        * files merge cleanly and the two resulting files
+                        * can then be two-way merged cleanly, I guess it's
+                        * a clean merge?
+                        */
+                       clean_merge = handle_rename_rename_2to1(o,
+                                                               conflict_info);
                        break;
                default:
                        entry->processed = 0;