merge-recursive: Improve handling of rename target vs. directory addition
[gitweb.git] / merge-recursive.c
index ffcecc7f49ff5f723aa7ce14e3a0b9f5631484cd..c6f177896fa8b0bc5cc302b1874d0710c078a7f9 100644 (file)
@@ -83,15 +83,14 @@ struct rename_df_conflict_info {
  * Since we want to write the index eventually, we cannot reuse the index
  * for these (temporary) data.
  */
-struct stage_data
-{
-       struct
-       {
+struct stage_data {
+       struct {
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
        struct rename_df_conflict_info *rename_df_conflict_info;
        unsigned processed:1;
+       unsigned involved_in_rename:1;
 };
 
 static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
@@ -137,7 +136,6 @@ static void flush_output(struct merge_options *o)
 __attribute__((format (printf, 3, 4)))
 static void output(struct merge_options *o, int v, const char *fmt, ...)
 {
-       int len;
        va_list ap;
 
        if (!show(o, v))
@@ -148,21 +146,9 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
        strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
 
        va_start(ap, fmt);
-       len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
+       strbuf_vaddf(&o->obuf, fmt, ap);
        va_end(ap);
 
-       if (len < 0)
-               len = 0;
-       if (len >= strbuf_avail(&o->obuf)) {
-               strbuf_grow(&o->obuf, len + 2);
-               va_start(ap, fmt);
-               len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
-               va_end(ap);
-               if (len >= strbuf_avail(&o->obuf)) {
-                       die("this should not happen, your snprintf is broken");
-               }
-       }
-       strbuf_setlen(&o->obuf, o->obuf.len + len);
        strbuf_add(&o->obuf, "\n", 1);
        if (!o->buffer_output)
                flush_output(o);
@@ -245,7 +231,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");
@@ -346,8 +332,104 @@ static struct string_list *get_unmerged(void)
        return unmerged;
 }
 
-struct rename
+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 record_df_conflict_files(struct merge_options *o,
+                                    struct string_list *entries)
 {
+       /* If there is a D/F conflict and the file for such a conflict
+        * currently exist in the working copy, we want to allow it to
+        * be removed to make room for the corresponding directory if
+        * needed.  The files underneath the directories of such D/F
+        * conflicts will be handled in process_entry(), while the
+        * files of such D/F conflicts will be processed later in
+        * process_df_entry().  If the corresponding directory ends up
+        * being removed by the merge, then no additional work needs
+        * to be done by process_df_entry() for the conflicting file.
+        * If the directory needs to be written to the working copy,
+        * then the conflicting file will simply be removed (e.g. in
+        * make_room_for_path).  If the directory is written to the
+        * working copy but the file also has a conflict that needs to
+        * be resolved, then process_df_entry() will reinstate the
+        * file with a new unique name.
+        */
+       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);
+
+       string_list_clear(&o->df_conflict_file_set, 1);
+       for (i = 0; i < entries->nr; i++) {
+               const char *path = entries->items[i].string;
+               int len = strlen(path);
+               struct stage_data *e = entries->items[i].util;
+
+               /*
+                * Check if last_file & path correspond to a D/F conflict;
+                * i.e. whether path is last_file+'/'+<something>.
+                * If so, record that it's okay to remove last_file to make
+                * room for path and friends if needed.
+                */
+               if (last_file &&
+                   len > last_len &&
+                   memcmp(path, last_file, last_len) == 0 &&
+                   path[last_len] == '/') {
+                       string_list_insert(&o->df_conflict_file_set, last_file);
+               }
+
+               /*
+                * Determine whether path could exist as a file in the
+                * working directory as a possible D/F conflict.  This
+                * will only occur when it exists in stage 2 as a
+                * file.
+                */
+               if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
+                       last_file = path;
+                       last_len = len;
+               } else {
+                       last_file = NULL;
+               }
+       }
+}
+
+struct rename {
        struct diff_filepair *pair;
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
@@ -377,13 +459,16 @@ static struct string_list *get_renames(struct merge_options *o,
        opts.detect_rename = DIFF_DETECT_RENAME;
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
-                           500;
-       opts.warn_on_too_large_rename = 1;
+                           1000;
+       opts.rename_score = o->rename_score;
+       opts.show_rename_progress = o->show_rename_progress;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        if (diff_setup_done(&opts) < 0)
                die("diff setup failed");
        diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
        diffcore_std(&opts);
+       if (opts.needed_rename_limit > o->needed_rename_limit)
+               o->needed_rename_limit = opts.needed_rename_limit;
        for (i = 0; i < diff_queued_diff.nr; ++i) {
                struct string_list_item *item;
                struct rename *re;
@@ -417,10 +502,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;
@@ -436,23 +523,11 @@ 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)
+static void update_entry(struct stage_data *entry,
+                        struct diff_filespec *o,
+                        struct diff_filespec *a,
+                        struct diff_filespec *b)
 {
-       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;
@@ -460,8 +535,6 @@ 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);
 }
 
 static int remove_file(struct merge_options *o, int clean,
@@ -519,7 +592,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-static int would_lose_untracked(const char *path)
+static int dir_in_way(const char *path, int check_working_copy)
+{
+       int pos, pathlen = strlen(path);
+       char *dirpath = xmalloc(pathlen + 2);
+       struct stat st;
+
+       strcpy(dirpath, path);
+       dirpath[pathlen] = '/';
+       dirpath[pathlen+1] = '\0';
+
+       pos = cache_name_pos(dirpath, pathlen+1);
+
+       if (pos < 0)
+               pos = -1 - pos;
+       if (pos < active_nr &&
+           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
+               free(dirpath);
+               return 1;
+       }
+
+       free(dirpath);
+       return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
+}
+
+static int was_tracked(const char *path)
 {
        int pos = cache_name_pos(path, strlen(path));
 
@@ -536,18 +633,42 @@ static int would_lose_untracked(const char *path)
                switch (ce_stage(active_cache[pos])) {
                case 0:
                case 2:
-                       return 0;
+                       return 1;
                }
                pos++;
        }
-       return file_exists(path);
+       return 0;
 }
 
-static int make_room_for_path(const char *path)
+static int would_lose_untracked(const char *path)
+{
+       return !was_tracked(path) && file_exists(path);
+}
+
+static int make_room_for_path(struct merge_options *o, const char *path)
 {
-       int status;
+       int status, i;
        const char *msg = "failed to create path '%s'%s";
 
+       /* Unlink any D/F conflict files that are in the way */
+       for (i = 0; i < o->df_conflict_file_set.nr; i++) {
+               const char *df_path = o->df_conflict_file_set.items[i].string;
+               size_t pathlen = strlen(path);
+               size_t df_pathlen = strlen(df_path);
+               if (df_pathlen < pathlen &&
+                   path[df_pathlen] == '/' &&
+                   strncmp(path, df_path, df_pathlen) == 0) {
+                       output(o, 3,
+                              "Removing %s to make room for subdirectory\n",
+                              df_path);
+                       unlink(df_path);
+                       unsorted_string_list_delete_item(&o->df_conflict_file_set,
+                                                        i, 0);
+                       break;
+               }
+       }
+
+       /* Make sure leading directories are created */
        status = safe_create_leading_directories_const(path);
        if (status) {
                if (status == -3) {
@@ -615,7 +736,7 @@ static void update_file_flags(struct merge_options *o,
                        }
                }
 
-               if (make_room_for_path(path) < 0) {
+               if (make_room_for_path(o, path) < 0) {
                        update_wd = 0;
                        free(buf);
                        goto update_index;
@@ -659,8 +780,7 @@ static void update_file(struct merge_options *o,
 
 /* Low level file merging, update and removal */
 
-struct merge_file_info
-{
+struct merge_file_info {
        unsigned char sha[20];
        unsigned mode;
        unsigned clean:1,
@@ -669,29 +789,33 @@ 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)
 {
        mmfile_t orig, src1, src2;
+       struct ll_merge_options ll_opts = {0};
        char *base_name, *name1, *name2;
        int merge_status;
-       int favor;
 
-       if (o->call_depth)
-               favor = 0;
-       else {
+       ll_opts.renormalize = o->renormalize;
+       ll_opts.xdl_opts = o->xdl_opts;
+
+       if (o->call_depth) {
+               ll_opts.virtual_ancestor = 1;
+               ll_opts.variant = 0;
+       } else {
                switch (o->recursive_variant) {
                case MERGE_RECURSIVE_OURS:
-                       favor = XDL_MERGE_FAVOR_OURS;
+                       ll_opts.variant = XDL_MERGE_FAVOR_OURS;
                        break;
                case MERGE_RECURSIVE_THEIRS:
-                       favor = XDL_MERGE_FAVOR_THEIRS;
+                       ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
                        break;
                default:
-                       favor = 0;
+                       ll_opts.variant = 0;
                        break;
                }
        }
@@ -714,10 +838,7 @@ static int merge_3way(struct merge_options *o,
        read_mmblob(&src2, b->sha1);
 
        merge_status = ll_merge(result_buf, a->path, &orig, base_name,
-                               &src1, name1, &src2, name2,
-                               ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
-                                (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
-                                create_ll_flag(favor)));
+                               &src1, name1, &src2, name2, &ll_opts);
 
        free(name1);
        free(name2);
@@ -728,9 +849,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)
 {
@@ -807,6 +928,7 @@ static void conflict_rename_delete(struct merge_options *o,
                                   const char *other_branch)
 {
        char *dest_name = pair->two->path;
+       int df_conflict = 0;
 
        output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
               "and deleted in %s",
@@ -815,9 +937,14 @@ 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 (dir_in_way(dest_name, !o->call_depth)) {
+               dest_name = unique_path(o, dest_name, rename_branch);
+               df_conflict = 1;
+       }
        update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
+       if (df_conflict)
+               free(dest_name);
 }
 
 static void conflict_rename_rename_1to2(struct merge_options *o,
@@ -833,17 +960,15 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
        const char *ren2_dst = pair2->two->path;
        const char *dst_name1 = ren1_dst;
        const char *dst_name2 = ren2_dst;
-       if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+       if (dir_in_way(ren1_dst, !o->call_depth)) {
                dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren1_dst, branch2, dst_name1);
-               remove_file(o, 0, ren1_dst, 0);
        }
-       if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
+       if (dir_in_way(ren2_dst, !o->call_depth)) {
                dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren2_dst, branch1, dst_name2);
-               remove_file(o, 0, ren2_dst, 0);
        }
        if (o->call_depth) {
                remove_file_from_cache(dst_name1);
@@ -855,8 +980,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);
@@ -871,17 +996,36 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
                                        struct rename *ren2,
                                        const char *branch2)
 {
+       char *path = ren1->pair->two->path; /* same as ren2->pair->two->path */
        /* 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",
-              ren1->pair->one->path, new_path1,
-              ren2->pair->one->path, new_path2);
-       remove_file(o, 0, ren1->pair->two->path, 0);
-       update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
-       update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
-       free(new_path2);
-       free(new_path1);
+       if (o->call_depth) {
+               struct merge_file_info mfi;
+               struct diff_filespec one, a, b;
+
+               one.path = a.path = b.path = path;
+               hashcpy(one.sha1, null_sha1);
+               one.mode = 0;
+               hashcpy(a.sha1, ren1->pair->two->sha1);
+               a.mode = ren1->pair->two->mode;
+               hashcpy(b.sha1, ren2->pair->two->sha1);
+               b.mode = ren2->pair->two->mode;
+               mfi = merge_file(o, &one, &a, &b, branch1, branch2);
+               output(o, 1, "Adding merged %s", path);
+               update_file(o, 0, mfi.sha, mfi.mode, path);
+       } else {
+               char *new_path1 = unique_path(o, path, branch1);
+               char *new_path2 = unique_path(o, path, branch2);
+               output(o, 1, "Renaming %s to %s and %s to %s instead",
+                      ren1->pair->one->path, new_path1,
+                      ren2->pair->one->path, new_path2);
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode,
+                           new_path1);
+               update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode,
+                           new_path2);
+               free(new_path2);
+               free(new_path1);
+       }
 }
 
 static int process_renames(struct merge_options *o,
@@ -896,16 +1040,15 @@ static int process_renames(struct merge_options *o,
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
                string_list_insert(&a_by_dst, sre->pair->two->path)->util
-                       = sre->dst_entry;
+                       = (void *)sre;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
                string_list_insert(&b_by_dst, sre->pair->two->path)->util
-                       = sre->dst_entry;
+                       = (void *)sre;
        }
 
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
-               char *src;
                struct string_list *renames1, *renames2Dst;
                struct rename *ren1 = NULL, *ren2 = NULL;
                const char *branch1, *branch2;
@@ -940,9 +1083,11 @@ static int process_renames(struct merge_options *o,
                        ren2 = ren1;
                        ren1 = tmp;
                }
-               src = ren1->pair->one->path;
 
                ren1->dst_entry->processed = 1;
+               /* BUG: We should only mark src_entry as processed if we
+                * are not dealing with a rename + add-source case.
+                */
                ren1->src_entry->processed = 1;
 
                if (ren1->processed)
@@ -968,16 +1113,17 @@ static int process_renames(struct merge_options *o,
                                                              branch2,
                                                              ren1->dst_entry,
                                                              ren2->dst_entry);
-                               remove_file(o, 0, ren1_dst, 0);
-                               /* ren2_dst not in head, so no need to delete */
                        } else {
+                               /* BUG: We should only remove ren1_src in
+                                * the base stage (think of rename +
+                                * add-source cases).
+                                */
                                remove_file(o, 1, ren1_src, 1);
-                               update_stages_and_entry(ren1_dst,
-                                                       ren1->dst_entry,
-                                                       ren1->pair->one,
-                                                       ren1->pair->two,
-                                                       ren2->pair->two,
-                                                       1 /* clear */);
+                               update_entry(ren1->dst_entry,
+                                            ren1->pair->one,
+                                            ren1->pair->two,
+                                            ren2->pair->two);
+                               ren1->dst_entry->involved_in_rename = 1;
                        }
                } else {
                        /* Renamed in 1, maybe changed in 2 */
@@ -995,7 +1141,12 @@ static int process_renames(struct merge_options *o,
                        int renamed_stage = a_renames == renames1 ? 2 : 3;
                        int other_stage =   a_renames == renames1 ? 3 : 2;
 
-                       remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+                       /* BUG: We should only remove ren1_src in the base
+                        * stage and in other_stage (think of rename +
+                        * add-source case).
+                        */
+                       remove_file(o, 1, ren1_src,
+                                   renamed_stage == 2 || !was_tracked(ren1_src));
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
                        src_other.mode = ren1->src_entry->stages[other_stage].mode;
@@ -1004,7 +1155,7 @@ static int process_renames(struct merge_options *o,
                        try_merge = 0;
 
                        if (sha_eq(src_other.sha1, null_sha1)) {
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+                               if (dir_in_way(ren1_dst, 0 /*check_wc*/)) {
                                        ren1->dst_entry->processed = 0;
                                        setup_rename_df_conflict_info(RENAME_DELETE,
                                                                      ren1->pair,
@@ -1013,11 +1164,27 @@ static int process_renames(struct merge_options *o,
                                                                      branch2,
                                                                      ren1->dst_entry,
                                                                      NULL);
-                                       remove_file(o, 0, ren1_dst, 0);
                                } else {
                                        clean_merge = 0;
                                        conflict_rename_delete(o, ren1->pair, branch1, branch2);
                                }
+                       } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
+                               char *ren2_src, *ren2_dst;
+                               ren2 = item->util;
+                               ren2_src = ren2->pair->one->path;
+                               ren2_dst = ren2->pair->two->path;
+
+                               clean_merge = 0;
+                               ren2->processed = 1;
+                               remove_file(o, 1, ren2_src,
+                                           renamed_stage == 3 || would_lose_untracked(ren1_src));
+
+                               output(o, 1, "CONFLICT (rename/rename): "
+                                      "Rename %s->%s in %s. "
+                                      "Rename %s->%s in %s",
+                                      ren1_src, ren1_dst, branch1,
+                                      ren2_src, ren2_dst, branch2);
+                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, 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
@@ -1025,7 +1192,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. "
@@ -1054,20 +1220,11 @@ 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;
-                               clean_merge = 0;
-                               ren2->processed = 1;
-                               output(o, 1, "CONFLICT (rename/rename): "
-                                      "Rename %s->%s in %s. "
-                                      "Rename %s->%s in %s",
-                                      ren1_src, ren1_dst, branch1,
-                                      ren2->pair->one->path, ren2->pair->two->path, branch2);
-                               conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
@@ -1083,8 +1240,9 @@ static int process_renames(struct merge_options *o,
                                        b = ren1->pair->two;
                                        a = &src_other;
                                }
-                               update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+                               update_entry(ren1->dst_entry, one, a, b);
+                               ren1->dst_entry->involved_in_rename = 1;
+                               if (dir_in_way(ren1_dst, 0 /*check_wc*/)) {
                                        setup_rename_df_conflict_info(RENAME_NORMAL,
                                                                      ren1->pair,
                                                                      NULL,
@@ -1092,7 +1250,6 @@ static int process_renames(struct merge_options *o,
                                                                      NULL,
                                                                      ren1->dst_entry,
                                                                      NULL);
-                                       remove_file(o, 0, ren1_dst, 0);
                                }
                        }
                }
@@ -1157,26 +1314,31 @@ static int blob_unchanged(const unsigned char *o_sha,
 
 static void handle_delete_modify(struct merge_options *o,
                                 const char *path,
+                                const char *new_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.",
+                      "and modified in %s. Version %s of %s left in tree%s%s.",
                       path, o->branch1,
-                      o->branch2, o->branch2, path);
-               update_file(o, 0, b_sha, b_mode, path);
+                      o->branch2, o->branch2, path,
+                      path == new_path ? "" : " at ",
+                      path == new_path ? "" : new_path);
+               update_file(o, 0, b_sha, b_mode, new_path);
        } else {
                output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree.",
+                      "and modified in %s. Version %s of %s left in tree%s%s.",
                       path, o->branch2,
-                      o->branch1, o->branch1, path);
-               update_file(o, 0, a_sha, a_mode, path);
+                      o->branch1, o->branch1, path,
+                      path == new_path ? "" : " at ",
+                      path == new_path ? "" : new_path);
+               update_file(o, 0, a_sha, a_mode, new_path);
        }
 }
 
-
 static int merge_content(struct merge_options *o,
+                        unsigned involved_in_rename,
                         const char *path,
                         unsigned char *o_sha, int o_mode,
                         unsigned char *a_sha, int a_mode,
@@ -1186,6 +1348,7 @@ static int merge_content(struct merge_options *o,
        const char *reason = "content";
        struct merge_file_info mfi;
        struct diff_filespec one, a, b;
+       unsigned df_conflict_remains = 0;
 
        if (!o_sha) {
                reason = "add/add";
@@ -1200,7 +1363,13 @@ static int merge_content(struct merge_options *o,
        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)
+       if (df_rename_conflict_branch &&
+           dir_in_way(path, !o->call_depth)) {
+               df_conflict_remains = 1;
+       }
+
+       if (mfi.clean && !df_conflict_remains &&
+           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);
@@ -1210,9 +1379,37 @@ static int merge_content(struct merge_options *o,
                        reason = "submodule";
                output(o, 1, "CONFLICT (%s): Merge conflict in %s",
                                reason, path);
+               if (involved_in_rename && !df_conflict_remains)
+                       update_stages(path, &one, &a, &b);
        }
 
-       update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+       if (df_conflict_remains) {
+               char *new_path;
+               if (o->call_depth) {
+                       remove_file_from_cache(path);
+               } else {
+                       if (!mfi.clean)
+                               update_stages(path, &one, &a, &b);
+                       else {
+                               int file_from_stage2 = was_tracked(path);
+                               struct diff_filespec merged;
+                               hashcpy(merged.sha1, mfi.sha);
+                               merged.mode = mfi.mode;
+
+                               update_stages(path, NULL,
+                                             file_from_stage2 ? &merged : NULL,
+                                             file_from_stage2 ? NULL : &merged);
+                       }
+
+               }
+               new_path = unique_path(o, path, df_rename_conflict_branch);
+               output(o, 1, "Adding as %s instead", new_path);
+               update_file(o, 0, mfi.sha, mfi.mode, new_path);
+               free(new_path);
+               mfi.clean = 0;
+       } else {
+               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+       }
        return mfi.clean;
 
 }
@@ -1249,14 +1446,13 @@ static int process_entry(struct merge_options *o,
                                output(o, 2, "Removing %s", path);
                        /* do not touch working file if it did not exist */
                        remove_file(o, 1, path, !a_sha);
-               } else if (string_list_has_string(&o->current_directory_set,
-                                                 path)) {
+               } else if (dir_in_way(path, 0 /*check_wc*/)) {
                        entry->processed = 0;
-                       return 1; /* Assume clean till processed */
+                       return 1; /* Assume clean until processed */
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
-                       handle_delete_modify(o, path,
+                       handle_delete_modify(o, path, path,
                                             a_sha, a_mode, b_sha, b_mode);
                }
 
@@ -1273,18 +1469,10 @@ static int process_entry(struct merge_options *o,
                        mode = b_mode;
                        sha = b_sha;
                }
-               if (string_list_has_string(&o->current_directory_set, path)) {
+               if (dir_in_way(path, 0 /*check_wc*/)) {
                        /* 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 */
+                       return 1; /* Assume clean until processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1292,7 +1480,7 @@ 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. */
-               clean_merge = merge_content(o, path,
+               clean_merge = merge_content(o, entry->involved_in_rename, path,
                                            o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
                                            NULL);
        } else if (!o_sha && !a_sha && !b_sha) {
@@ -1329,7 +1517,6 @@ 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);
-       struct stat st;
 
        entry->processed = 1;
        if (entry->rename_df_conflict_info) {
@@ -1337,7 +1524,7 @@ static int process_df_entry(struct merge_options *o,
                char *src;
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
-                       clean_merge = merge_content(o, path,
+                       clean_merge = merge_content(o, entry->involved_in_rename, path,
                                                    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
                                                    conflict_info->branch1);
                        break;
@@ -1373,11 +1560,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 */
+               char *renamed = NULL;
+               if (dir_in_way(path, !o->call_depth)) {
+                       renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+               }
                clean_merge = 0;
-               handle_delete_modify(o, 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;
@@ -1397,14 +1589,18 @@ static int process_df_entry(struct merge_options *o,
                        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);
+               if (dir_in_way(path, !o->call_depth)) {
+                       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);
+                       if (o->call_depth)
+                               remove_file_from_cache(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);
@@ -1456,6 +1652,7 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
+               record_df_conflict_files(o, entries);
                re_head  = get_renames(o, head, common, head, merge, entries);
                re_merge = get_renames(o, merge, common, head, merge, entries);
                clean = process_renames(o, re_head, re_merge);
@@ -1587,6 +1784,9 @@ int merge_recursive(struct merge_options *o,
                commit_list_insert(h2, &(*result)->parents->next);
        }
        flush_output(o);
+       if (show(o, 2))
+               diff_warn_rename_limit("merge.renamelimit",
+                                      o->needed_rename_limit, 0);
        return clean;
 }
 
@@ -1644,15 +1844,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;
        }
@@ -1678,4 +1878,40 @@ void init_merge_options(struct merge_options *o)
        o->current_file_set.strdup_strings = 1;
        memset(&o->current_directory_set, 0, sizeof(struct string_list));
        o->current_directory_set.strdup_strings = 1;
+       memset(&o->df_conflict_file_set, 0, sizeof(struct string_list));
+       o->df_conflict_file_set.strdup_strings = 1;
+}
+
+int parse_merge_opt(struct merge_options *o, const char *s)
+{
+       if (!s || !*s)
+               return -1;
+       if (!strcmp(s, "ours"))
+               o->recursive_variant = MERGE_RECURSIVE_OURS;
+       else if (!strcmp(s, "theirs"))
+               o->recursive_variant = MERGE_RECURSIVE_THEIRS;
+       else if (!strcmp(s, "subtree"))
+               o->subtree_shift = "";
+       else if (!prefixcmp(s, "subtree="))
+               o->subtree_shift = s + strlen("subtree=");
+       else if (!strcmp(s, "patience"))
+               o->xdl_opts |= XDF_PATIENCE_DIFF;
+       else if (!strcmp(s, "ignore-space-change"))
+               o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+       else if (!strcmp(s, "ignore-all-space"))
+               o->xdl_opts |= XDF_IGNORE_WHITESPACE;
+       else if (!strcmp(s, "ignore-space-at-eol"))
+               o->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+       else if (!strcmp(s, "renormalize"))
+               o->renormalize = 1;
+       else if (!strcmp(s, "no-renormalize"))
+               o->renormalize = 0;
+       else if (!prefixcmp(s, "rename-threshold=")) {
+               const char *score = s + strlen("rename-threshold=");
+               if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
+                       return -1;
+       }
+       else
+               return -1;
+       return 0;
 }