gitweb: Add a feature to show side-by-side diff
[gitweb.git] / merge-recursive.c
index c5746988196b8139ca3234f8b30d03af56d33324..cc664c39b66b0bb499dec13a22880a6096423fa6 100644 (file)
@@ -63,20 +63,86 @@ 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_ONE,
+       RENAME_ONE_FILE_TO_TWO,
+       RENAME_TWO_FILES_TO_ONE
+};
+
+struct rename_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 diff_filespec ren1_other;
+       struct diff_filespec ren2_other;
+};
+
 /*
  * 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_conflict_info *rename_conflict_info;
        unsigned processed:1;
 };
 
+static inline void setup_rename_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 merge_options *o,
+                                             struct stage_data *src_entry1,
+                                             struct stage_data *src_entry2)
+{
+       struct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info));
+       ci->rename_type = rename_type;
+       ci->pair1 = pair1;
+       ci->branch1 = branch1;
+       ci->branch2 = branch2;
+
+       ci->dst_entry1 = dst_entry1;
+       dst_entry1->rename_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_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;
+
+               ci->ren1_other.path = pair1->one->path;
+               hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha);
+               ci->ren1_other.mode = src_entry1->stages[ostage1].mode;
+
+               ci->ren2_other.path = pair2->one->path;
+               hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha);
+               ci->ren2_other.mode = src_entry2->stages[ostage2].mode;
+       }
+}
+
 static int show(struct merge_options *o, int v)
 {
        return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
@@ -93,7 +159,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))
@@ -104,21 +169,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);
@@ -201,7 +254,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");
@@ -244,7 +297,9 @@ static int save_files_dirs(const unsigned char *sha1,
 static int get_files_dirs(struct merge_options *o, struct tree *tree)
 {
        int n;
-       if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o))
+       struct pathspec match_all;
+       init_pathspec(&match_all, NULL);
+       if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
                return 0;
        n = o->current_file_set.nr + o->current_directory_set.nr;
        return n;
@@ -302,8 +357,109 @@ 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 tree, 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 processed before the corresponding file involved in the D/F
+        * conflict.  If the D/F directory ends up being removed by the
+        * merge, then we won't have to touch the D/F file.  If the D/F
+        * directory needs to be written to the working copy, then the D/F
+        * file will simply be removed (in make_room_for_path()) to make
+        * room for the necessary paths.  Note that if both the directory
+        * and the file need to be present, then the D/F file will be
+        * reinstated with a new unique name at the time it is processed.
+        */
+       struct string_list df_sorted_entries;
+       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. */
+       memset(&df_sorted_entries, 0, sizeof(struct string_list));
+       for (i = 0; i < entries->nr; i++) {
+               struct string_list_item *next = &entries->items[i];
+               string_list_append(&df_sorted_entries, next->string)->util =
+                                  next->util;
+       }
+       qsort(df_sorted_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 < df_sorted_entries.nr; i++) {
+               const char *path = df_sorted_entries.items[i].string;
+               int len = strlen(path);
+               struct stage_data *e = df_sorted_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;
+               }
+       }
+       string_list_clear(&df_sorted_entries, 0);
+}
+
+struct rename {
        struct diff_filepair *pair;
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
@@ -333,13 +489,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;
@@ -373,11 +532,21 @@ static struct string_list *get_renames(struct merge_options *o,
        return renames;
 }
 
-static int update_stages(const char *path, struct diff_filespec *o,
-                        struct diff_filespec *a, struct diff_filespec *b,
-                        int clear)
+static int update_stages(const char *path, const struct diff_filespec *o,
+                        const struct diff_filespec *a,
+                        const struct diff_filespec *b)
 {
-       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+
+       /*
+        * NOTE: It is usually a bad idea to call update_stages on a path
+        * before calling update_file on that same path, since it can
+        * sometimes lead to spurious "refusing to lose untracked file..."
+        * messages from update_file (via make_room_for path via
+        * would_lose_untracked).  Instead, reverse the order of the calls
+        * (executing update_file first and then update_stages).
+        */
+       int clear = 1;
+       int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
@@ -393,6 +562,20 @@ static int update_stages(const char *path, struct diff_filespec *o,
        return 0;
 }
 
+static void update_entry(struct stage_data *entry,
+                        struct diff_filespec *o,
+                        struct diff_filespec *a,
+                        struct diff_filespec *b)
+{
+       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);
+}
+
 static int remove_file(struct merge_options *o, int clean,
                       const char *path, int no_wd)
 {
@@ -448,7 +631,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));
 
@@ -465,18 +672,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)
 {
-       int status;
+       return !was_tracked(path) && file_exists(path);
+}
+
+static int make_room_for_path(struct merge_options *o, const char *path)
+{
+       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) {
@@ -544,7 +775,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;
@@ -588,8 +819,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,
@@ -598,29 +828,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;
                }
        }
@@ -643,10 +877,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);
@@ -656,12 +887,12 @@ static int merge_3way(struct merge_options *o,
        return merge_status;
 }
 
-static struct merge_file_info merge_file(struct merge_options *o,
-                                        struct diff_filespec *one,
-                                        struct diff_filespec *a,
-                                        struct diff_filespec *b,
-                                        const char *branch1,
-                                        const char *branch2)
+static struct merge_file_info merge_file_1(struct merge_options *o,
+                                          const struct diff_filespec *one,
+                                          const struct diff_filespec *a,
+                                          const struct diff_filespec *b,
+                                          const char *branch1,
+                                          const char *branch2)
 {
        struct merge_file_info result;
        result.merge = 0;
@@ -715,8 +946,10 @@ static struct merge_file_info merge_file(struct merge_options *o,
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = merge_submodule(result.sha, one->path, one->sha1,
-                                                      a->sha1, b->sha1);
+                       result.clean = merge_submodule(result.sha,
+                                                      one->path, one->sha1,
+                                                      a->sha1, b->sha1,
+                                                      !o->call_depth);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
 
@@ -730,74 +963,303 @@ 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 struct merge_file_info
+merge_file_special_markers(struct merge_options *o,
+                          const struct diff_filespec *one,
+                          const struct diff_filespec *a,
+                          const struct diff_filespec *b,
+                          const char *branch1,
+                          const char *filename1,
+                          const char *branch2,
+                          const char *filename2)
 {
-       char *del[2];
-       int delp = 0;
-       const char *ren1_dst = ren1->pair->two->path;
-       const char *ren2_dst = ren2->pair->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)) {
-               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);
+       char *side1 = NULL;
+       char *side2 = NULL;
+       struct merge_file_info mfi;
+
+       if (filename1) {
+               side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2);
+               sprintf(side1, "%s:%s", branch1, filename1);
+       }
+       if (filename2) {
+               side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2);
+               sprintf(side2, "%s:%s", branch2, filename2);
        }
-       if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
-               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);
+
+       mfi = merge_file_1(o, one, a, b,
+                          side1 ? side1 : branch1, side2 ? side2 : branch2);
+       free(side1);
+       free(side2);
+       return mfi;
+}
+
+static struct merge_file_info merge_file(struct merge_options *o,
+                                        const char *path,
+                                        const unsigned char *o_sha, int o_mode,
+                                        const unsigned char *a_sha, int a_mode,
+                                        const unsigned char *b_sha, int b_mode,
+                                        const char *branch1,
+                                        const char *branch2)
+{
+       struct diff_filespec one, a, b;
+
+       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;
+       return merge_file_1(o, &one, &a, &b, branch1, branch2);
+}
+
+static void handle_change_delete(struct merge_options *o,
+                                const char *path,
+                                const unsigned char *o_sha, int o_mode,
+                                const unsigned char *a_sha, int a_mode,
+                                const unsigned char *b_sha, int b_mode,
+                                const char *change, const char *change_past)
+{
+       char *renamed = NULL;
+       if (dir_in_way(path, !o->call_depth)) {
+               renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
        }
+
        if (o->call_depth) {
-               remove_file_from_cache(dst_name1);
-               remove_file_from_cache(dst_name2);
                /*
-                * Uncomment to leave the conflicting names in the resulting tree
-                *
-                * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
-                * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+                * We cannot arbitrarily accept either a_sha or b_sha as
+                * correct; since there is no true "middle point" between
+                * them, simply reuse the base version for virtual merge base.
                 */
+               remove_file_from_cache(path);
+               update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
+       } else if (!a_sha) {
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch1,
+                      change_past, o->branch2, o->branch2, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               update_file(o, 0, b_sha, b_mode, renamed ? renamed : path);
        } else {
-               update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
-               update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+               output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
+                      "and %s in %s. Version %s of %s left in tree%s%s.",
+                      change, path, o->branch2,
+                      change_past, o->branch1, o->branch1, path,
+                      NULL == renamed ? "" : " at ",
+                      NULL == renamed ? "" : renamed);
+               if (renamed)
+                       update_file(o, 0, a_sha, a_mode, renamed);
+               /*
+                * No need to call update_file() on path when !renamed, since
+                * that would needlessly touch path.  We could call
+                * update_file_flags() with update_cache=0 and update_wd=0,
+                * but that's a no-op.
+                */
        }
-       while (delp--)
-               free(del[delp]);
+       free(renamed);
 }
 
-static void conflict_rename_dir(struct merge_options *o,
-                               struct rename *ren1,
-                               const char *branch1)
+static void conflict_rename_delete(struct merge_options *o,
+                                  struct diff_filepair *pair,
+                                  const char *rename_branch,
+                                  const char *other_branch)
 {
-       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);
+       const struct diff_filespec *orig = pair->one;
+       const struct diff_filespec *dest = pair->two;
+       const unsigned char *a_sha = NULL;
+       const unsigned char *b_sha = NULL;
+       int a_mode = 0;
+       int b_mode = 0;
+
+       if (rename_branch == o->branch1) {
+               a_sha = dest->sha1;
+               a_mode = dest->mode;
+       } else {
+               b_sha = dest->sha1;
+               b_mode = dest->mode;
+       }
+
+       handle_change_delete(o,
+                            o->call_depth ? orig->path : dest->path,
+                            orig->sha1, orig->mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "rename", "renamed");
+
+       if (o->call_depth) {
+               remove_file_from_cache(dest->path);
+       } else {
+               update_stages(dest->path, NULL,
+                             rename_branch == o->branch1 ? dest : NULL,
+                             rename_branch == o->branch1 ? NULL : dest);
+       }
+
+}
+
+static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
+                                                struct stage_data *entry,
+                                                int stage)
+{
+       unsigned char *sha = entry->stages[stage].sha;
+       unsigned mode = entry->stages[stage].mode;
+       if (mode == 0 || is_null_sha1(sha))
+               return NULL;
+       hashcpy(target->sha1, sha);
+       target->mode = mode;
+       return target;
+}
+
+static void handle_file(struct merge_options *o,
+                       struct diff_filespec *rename,
+                       int stage,
+                       struct rename_conflict_info *ci)
+{
+       char *dst_name = rename->path;
+       struct stage_data *dst_entry;
+       const char *cur_branch, *other_branch;
+       struct diff_filespec other;
+       struct diff_filespec *add;
+
+       if (stage == 2) {
+               dst_entry = ci->dst_entry1;
+               cur_branch = ci->branch1;
+               other_branch = ci->branch2;
+       } else {
+               dst_entry = ci->dst_entry2;
+               cur_branch = ci->branch2;
+               other_branch = ci->branch1;
+       }
+
+       add = filespec_from_entry(&other, dst_entry, stage ^ 1);
+       if (add) {
+               char *add_name = unique_path(o, rename->path, other_branch);
+               update_file(o, 0, add->sha1, add->mode, add_name);
+
+               remove_file(o, 0, rename->path, 0);
+               dst_name = unique_path(o, rename->path, cur_branch);
+       } else {
+               if (dir_in_way(rename->path, !o->call_depth)) {
+                       dst_name = unique_path(o, rename->path, cur_branch);
+                       output(o, 1, "%s is a directory in %s adding as %s instead",
+                              rename->path, other_branch, dst_name);
+               }
+       }
+       update_file(o, 0, rename->sha1, rename->mode, dst_name);
+       if (stage == 2)
+               update_stages(rename->path, NULL, rename, add);
+       else
+               update_stages(rename->path, NULL, add, rename);
+
+       if (dst_name != rename->path)
+               free(dst_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+                                       struct rename_conflict_info *ci)
+{
+       /* One file was renamed in both branches, but to different names. */
+       struct diff_filespec *one = ci->pair1->one;
+       struct diff_filespec *a = ci->pair1->two;
+       struct diff_filespec *b = ci->pair2->two;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename \"%s\"->\"%s\" in branch \"%s\" "
+              "rename \"%s\"->\"%s\" in \"%s\"%s",
+              one->path, a->path, ci->branch1,
+              one->path, b->path, ci->branch2,
+              o->call_depth ? " (left unresolved)" : "");
+       if (o->call_depth) {
+               struct merge_file_info mfi;
+               struct diff_filespec other;
+               struct diff_filespec *add;
+               mfi = merge_file(o, one->path,
+                                one->sha1, one->mode,
+                                a->sha1, a->mode,
+                                b->sha1, b->mode,
+                                ci->branch1, ci->branch2);
+               /*
+                * FIXME: For rename/add-source conflicts (if we could detect
+                * such), this is wrong.  We should instead find a unique
+                * pathname and then either rename the add-source file to that
+                * unique path, or use that unique path instead of src here.
+                */
+               update_file(o, 0, mfi.sha, mfi.mode, one->path);
+
+               /*
+                * Above, we put the merged content at the merge-base's
+                * path.  Now we usually need to delete both a->path and
+                * b->path.  However, the rename on each side of the merge
+                * could also be involved in a rename/add conflict.  In
+                * such cases, we should keep the added file around,
+                * resolving the conflict at that path in its favor.
+                */
+               add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, a->path);
+               else
+                       remove_file_from_cache(a->path);
+               add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
+               if (add)
+                       update_file(o, 0, add->sha1, add->mode, b->path);
+               else
+                       remove_file_from_cache(b->path);
+       } else {
+               handle_file(o, a, 2, ci);
+               handle_file(o, b, 3, ci);
+       }
 }
 
-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_conflict_info *ci)
 {
-       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);
+       /* Two files, a & b, were renamed to the same thing, c. */
+       struct diff_filespec *a = ci->pair1->one;
+       struct diff_filespec *b = ci->pair2->one;
+       struct diff_filespec *c1 = ci->pair1->two;
+       struct diff_filespec *c2 = ci->pair2->two;
+       char *path = c1->path; /* == c2->path */
+       struct merge_file_info mfi_c1;
+       struct merge_file_info mfi_c2;
+
+       output(o, 1, "CONFLICT (rename/rename): "
+              "Rename %s->%s in %s. "
+              "Rename %s->%s in %s",
+              a->path, c1->path, ci->branch1,
+              b->path, c2->path, ci->branch2);
+
+       remove_file(o, 1, a->path, would_lose_untracked(a->path));
+       remove_file(o, 1, b->path, would_lose_untracked(b->path));
+
+       mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,
+                                           o->branch1, c1->path,
+                                           o->branch2, ci->ren1_other.path);
+       mfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,
+                                           o->branch1, ci->ren2_other.path,
+                                           o->branch2, c2->path);
+
+       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);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
+               update_file(o, 0, mfi_c2.sha, 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);
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
+               update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
+               free(new_path2);
+               free(new_path1);
+       }
 }
 
 static int process_renames(struct merge_options *o,
@@ -812,20 +1274,20 @@ 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;
                const char *ren1_src, *ren1_dst;
+               struct string_list_item *lookup;
 
                if (i >= a_renames->nr) {
                        ren2 = b_renames->items[j++].util;
@@ -856,113 +1318,137 @@ static int process_renames(struct merge_options *o,
                        ren2 = ren1;
                        ren1 = tmp;
                }
-               src = ren1->pair->one->path;
-
-               ren1->dst_entry->processed = 1;
-               ren1->src_entry->processed = 1;
 
                if (ren1->processed)
                        continue;
                ren1->processed = 1;
+               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;
 
                ren1_src = ren1->pair->one->path;
                ren1_dst = ren1->pair->two->path;
 
                if (ren2) {
+                       /* One file renamed on both sides */
                        const char *ren2_src = ren2->pair->one->path;
                        const char *ren2_dst = ren2->pair->two->path;
-                       /* Renamed in 1 and renamed in 2 */
+                       enum rename_type rename_type;
                        if (strcmp(ren1_src, ren2_src) != 0)
-                               die("ren1.src != ren2.src");
+                               die("ren1_src != ren2_src");
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
+                               rename_type = RENAME_ONE_FILE_TO_TWO;
                                clean_merge = 0;
-                               output(o, 1, "CONFLICT (rename/rename): "
-                                      "Rename \"%s\"->\"%s\" in branch \"%s\" "
-                                      "rename \"%s\"->\"%s\" in \"%s\"%s",
-                                      src, ren1_dst, branch1,
-                                      src, ren2_dst, branch2,
-                                      o->call_depth ? " (left unresolved)": "");
-                               if (o->call_depth) {
-                                       remove_file_from_cache(src);
-                                       update_file(o, 0, ren1->pair->one->sha1,
-                                                   ren1->pair->one->mode, src);
-                               }
-                               conflict_rename_rename(o, ren1, branch1, ren2, branch2);
                        } else {
-                               struct merge_file_info mfi;
+                               rename_type = RENAME_ONE_FILE_TO_ONE;
+                               /* BUG: We should only remove ren1_src in
+                                * the base stage (think of rename +
+                                * add-source cases).
+                                */
                                remove_file(o, 1, ren1_src, 1);
-                               mfi = merge_file(o,
-                                                ren1->pair->one,
-                                                ren1->pair->two,
-                                                ren2->pair->two,
-                                                branch1,
-                                                branch2);
-                               if (mfi.merge || !mfi.clean)
-                                       output(o, 1, "Renaming %s->%s", src, ren1_dst);
-
-                               if (mfi.merge)
-                                       output(o, 2, "Auto-merging %s", ren1_dst);
-
-                               if (!mfi.clean) {
-                                       output(o, 1, "CONFLICT (content): merge conflict in %s",
-                                              ren1_dst);
-                                       clean_merge = 0;
-
-                                       if (!o->call_depth)
-                                               update_stages(ren1_dst,
-                                                             ren1->pair->one,
-                                                             ren1->pair->two,
-                                                             ren2->pair->two,
-                                                             1 /* clear */);
-                               }
-                               update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+                               update_entry(ren1->dst_entry,
+                                            ren1->pair->one,
+                                            ren1->pair->two,
+                                            ren2->pair->two);
                        }
+                       setup_rename_conflict_info(rename_type,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  NULL,
+                                                  NULL);
+               } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {
+                       /* Two different files renamed to the same thing */
+                       char *ren2_dst;
+                       ren2 = lookup->util;
+                       ren2_dst = ren2->pair->two->path;
+                       if (strcmp(ren1_dst, ren2_dst) != 0)
+                               die("ren1_dst != ren2_dst");
+
+                       clean_merge = 0;
+                       ren2->processed = 1;
+                       /*
+                        * BUG: We should only mark src_entry as processed
+                        * if we are not dealing with a rename + add-source
+                        * case.
+                        */
+                       ren2->src_entry->processed = 1;
+
+                       setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,
+                                                  ren1->pair,
+                                                  ren2->pair,
+                                                  branch1,
+                                                  branch2,
+                                                  ren1->dst_entry,
+                                                  ren2->dst_entry,
+                                                  o,
+                                                  ren1->src_entry,
+                                                  ren2->src_entry);
+
                } else {
                        /* Renamed in 1, maybe changed in 2 */
-                       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;
+                       /* 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;
+                       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)) {
-                               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);
+                       if (sha_eq(src_other.sha1, null_sha1)) {
+                               setup_rename_conflict_info(RENAME_DELETE,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          branch2,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        } 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);
+                               /*
+                                * Added file on the other side identical to
+                                * the file being renamed: clean merge.
+                                * Also, there is no need to overwrite the
+                                * file already in the working copy, so call
+                                * update_file_flags() instead of
+                                * update_file().
+                                */
+                               update_file_flags(o,
+                                                 ren1->pair->two->sha1,
+                                                 ren1->pair->two->mode,
+                                                 ren1_dst,
+                                                 1, /* update_cache */
+                                                 0  /* update_wd    */);
                        } 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. "
@@ -971,45 +1457,24 @@ static int process_renames(struct merge_options *o,
                                       ren1_dst, branch2);
                                if (o->call_depth) {
                                        struct merge_file_info mfi;
-                                       struct diff_filespec one, a, b;
-
-                                       one.path = a.path = b.path =
-                                               (char *)ren1_dst;
-                                       hashcpy(one.sha1, null_sha1);
-                                       one.mode = 0;
-                                       hashcpy(a.sha1, ren1->pair->two->sha1);
-                                       a.mode = ren1->pair->two->mode;
-                                       hashcpy(b.sha1, dst_other.sha1);
-                                       b.mode = dst_other.mode;
-                                       mfi = merge_file(o, &one, &a, &b,
-                                                        branch1,
-                                                        branch2);
+                                       mfi = merge_file(o, ren1_dst, null_sha1, 0,
+                                                        ren1->pair->two->sha1, ren1->pair->two->mode,
+                                                        dst_other.sha1, dst_other.mode,
+                                                        branch1, branch2);
                                        output(o, 1, "Adding merged %s", ren1_dst);
-                                       update_file(o, 0,
-                                                   mfi.sha,
-                                                   mfi.mode,
-                                                   ren1_dst);
+                                       update_file(o, 0, mfi.sha, mfi.mode, 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_2(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
 
                        if (try_merge) {
                                struct diff_filespec *one, *a, *b;
-                               struct merge_file_info mfi;
                                src_other.path = (char *)ren1_src;
 
                                one = ren1->pair->one;
@@ -1020,42 +1485,17 @@ static int process_renames(struct merge_options *o,
                                        b = ren1->pair->two;
                                        a = &src_other;
                                }
-                               mfi = merge_file(o, one, a, b,
-                                               o->branch1, o->branch2);
-
-                               if (mfi.clean &&
-                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode) {
-                                       /*
-                                        * 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);
-
-                                       /* 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)
-                                               output(o, 2, "Auto-merging %s", ren1_dst);
-                                       if (!mfi.clean) {
-                                               output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s",
-                                                      ren1_dst);
-                                               clean_merge = 0;
-
-                                               if (!o->call_depth)
-                                                       update_stages(ren1_dst,
-                                                                     one, a, b, 1);
-                                       }
-                                       update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
-                               }
+                               update_entry(ren1->dst_entry, one, a, b);
+                               setup_rename_conflict_info(RENAME_NORMAL,
+                                                          ren1->pair,
+                                                          NULL,
+                                                          branch1,
+                                                          NULL,
+                                                          ren1->dst_entry,
+                                                          NULL,
+                                                          o,
+                                                          NULL,
+                                                          NULL);
                        }
                }
        }
@@ -1117,6 +1557,124 @@ static int blob_unchanged(const unsigned char *o_sha,
        return ret;
 }
 
+static void handle_modify_delete(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)
+{
+       handle_change_delete(o,
+                            path,
+                            o_sha, o_mode,
+                            a_sha, a_mode,
+                            b_sha, b_mode,
+                            "modify", "modified");
+}
+
+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,
+                        struct rename_conflict_info *rename_conflict_info)
+{
+       const char *reason = "content";
+       const char *path1 = NULL, *path2 = NULL;
+       struct merge_file_info mfi;
+       struct diff_filespec one, a, b;
+       unsigned df_conflict_remains = 0;
+
+       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;
+
+       if (rename_conflict_info) {
+               struct diff_filepair *pair1 = rename_conflict_info->pair1;
+
+               path1 = (o->branch1 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+               /* If rename_conflict_info->pair2 != NULL, we are in
+                * RENAME_ONE_FILE_TO_ONE case.  Otherwise, we have a
+                * normal rename.
+                */
+               path2 = (rename_conflict_info->pair2 ||
+                        o->branch2 == rename_conflict_info->branch1) ?
+                       pair1->two->path : pair1->one->path;
+
+               if (dir_in_way(path, !o->call_depth))
+                       df_conflict_remains = 1;
+       }
+       mfi = merge_file_special_markers(o, &one, &a, &b,
+                                        o->branch1, path1,
+                                        o->branch2, path2);
+
+       if (mfi.clean && !df_conflict_remains &&
+           sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
+               int path_renamed_outside_HEAD;
+               output(o, 3, "Skipped %s (merged same as existing)", path);
+               /*
+                * The content merge resulted in the same file contents we
+                * already had.  We can return early if those file contents
+                * are recorded at the correct path (which may not be true
+                * if the merge involves a rename).
+                */
+               path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
+               if (!path_renamed_outside_HEAD) {
+                       add_cacheinfo(mfi.mode, mfi.sha, path,
+                                     0, (!o->call_depth), 0);
+                       return mfi.clean;
+               }
+       } 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);
+               if (rename_conflict_info && !df_conflict_remains)
+                       update_stages(path, &one, &a, &b);
+       }
+
+       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, rename_conflict_info->branch1);
+               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;
+
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -1135,7 +1693,34 @@ static int process_entry(struct merge_options *o,
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
        entry->processed = 1;
-       if (o_sha && (!a_sha || !b_sha)) {
+       if (entry->rename_conflict_info) {
+               struct rename_conflict_info *conflict_info = entry->rename_conflict_info;
+               switch (conflict_info->rename_type) {
+               case RENAME_NORMAL:
+               case RENAME_ONE_FILE_TO_ONE:
+                       clean_merge = merge_content(o, path,
+                                                   o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+                                                   conflict_info);
+                       break;
+               case RENAME_DELETE:
+                       clean_merge = 0;
+                       conflict_rename_delete(o, conflict_info->pair1,
+                                              conflict_info->branch1,
+                                              conflict_info->branch2);
+                       break;
+               case RENAME_ONE_FILE_TO_TWO:
+                       clean_merge = 0;
+                       conflict_rename_rename_1to2(o, conflict_info);
+                       break;
+               case RENAME_TWO_FILES_TO_ONE:
+                       clean_merge = 0;
+                       conflict_rename_rename_2to1(o, conflict_info);
+                       break;
+               default:
+                       entry->processed = 0;
+                       break;
+               }
+       } else if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
                    (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
@@ -1147,83 +1732,58 @@ static int process_entry(struct merge_options *o,
                        /* do not touch working file if it did not exist */
                        remove_file(o, 1, path, !a_sha);
                } else {
-                       /* Deleted in one and changed in the other */
+                       /* Modify/delete; deleted side may have put a directory in the way */
                        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_modify_delete(o, path, o_sha, o_mode,
+                                            a_sha, a_mode, b_sha, b_mode);
                }
-
        } else if ((!o_sha && a_sha && !b_sha) ||
                   (!o_sha && !a_sha && b_sha)) {
                /* Case B: Added in one. */
+               /* [nothing|directory] -> ([nothing|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 (string_list_has_string(&o->current_directory_set, 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 */
+               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);
+                       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);
+                       /* do not overwrite file if already present */
+                       update_file_flags(o, sha, mode, path, 1, !a_sha);
                }
        } 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,
+                                           NULL);
        } else if (!o_sha && !a_sha && !b_sha) {
                /*
                 * this entry was deleted altogether. a_mode == 0 means
@@ -1236,64 +1796,6 @@ 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;
-}
-
 int merge_trees(struct merge_options *o,
                struct tree *head,
                struct tree *merge,
@@ -1333,10 +1835,11 @@ 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);
-               for (i = 0; i < entries->nr; i++) {
+               for (i = entries->nr-1; 0 <= i; i--) {
                        const char *path = entries->items[i].string;
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed
@@ -1344,11 +1847,10 @@ int merge_trees(struct merge_options *o,
                                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;
+                       if (!e->processed)
+                               die("Unprocessed path??? %s",
+                                   entries->items[i].string);
                }
 
                string_list_clear(re_merge, 0);
@@ -1410,12 +1912,10 @@ int merge_recursive(struct merge_options *o,
 
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
+               /* if there is no common ancestor, use an empty tree */
+               struct tree *tree;
 
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+               tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
@@ -1458,6 +1958,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;
 }
 
@@ -1515,15 +2018,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;
        }
@@ -1549,4 +2052,42 @@ 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, "histogram"))
+               o->xdl_opts |= XDF_HISTOGRAM_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;
 }