Merge branch 'sb/merge-recursive-copy-paste-fix'
authorJunio C Hamano <gitster@pobox.com>
Tue, 30 Sep 2014 05:17:21 +0000 (22:17 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 Sep 2014 05:17:22 +0000 (22:17 -0700)
"git merge-recursive" had a small bug that could have made it
mishandle "one side deleted, the other side did not touch it" in a
rare corner case, where the other side actually did touch to cause
the blob object names to be different but both blobs before and
after the change normalize to the same (e.g. correcting mistake to
check in a blob with CRLF line endings by replacing it with another
blob that records the same contents with LF line endings).

* sb/merge-recursive-copy-paste-fix:
  merge-recursive: remove stale commented debugging code
  merge-recursive: fix copy-paste mistake

1  2 
merge-recursive.c

diff --combined merge-recursive.c
@@@ -20,7 -20,6 +20,7 @@@
  #include "attr.h"
  #include "merge-recursive.h"
  #include "dir.h"
 +#include "submodule.h"
  
  static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
        return lookup_tree(shifted);
  }
  
 -/*
 - * A virtual commit has (const char *)commit->util set to the name.
 - */
 -
  static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
  {
 -      struct commit *commit = xcalloc(1, sizeof(struct commit));
 +      struct commit *commit = alloc_commit_node();
 +      struct merge_remote_desc *desc = xmalloc(sizeof(*desc));
 +
 +      desc->name = comment;
 +      desc->obj = (struct object *)commit;
        commit->tree = tree;
 -      commit->util = (void*)comment;
 -      /* avoid warnings */
 +      commit->util = desc;
        commit->object.parsed = 1;
        return commit;
  }
@@@ -62,86 -62,20 +62,86 @@@ static int sha_eq(const unsigned char *
        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;
@@@ -158,18 -92,33 +158,18 @@@ static void flush_output(struct merge_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))
                return;
  
 -      strbuf_grow(&o->obuf, o->call_depth * 2 + 2);
 -      memset(o->obuf.buf + o->obuf.len, ' ', o->call_depth * 2);
 -      strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
 +      strbuf_addchars(&o->obuf, ' ', 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);
 +      strbuf_addch(&o->obuf, '\n');
        if (!o->buffer_output)
                flush_output(o);
  }
@@@ -181,18 -130,22 +181,18 @@@ static void output_commit_title(struct 
        for (i = o->call_depth; i--;)
                fputs("  ", stdout);
        if (commit->util)
 -              printf("virtual %s\n", (char *)commit->util);
 +              printf("virtual %s\n", merge_remote_util(commit)->name);
        else {
                printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
                if (parse_commit(commit) != 0)
 -                      printf("(bad commit)\n");
 +                      printf(_("(bad commit)\n"));
                else {
 -                      const char *s;
 -                      int len;
 -                      for (s = commit->buffer; *s; s++)
 -                              if (*s == '\n' && s[1] == '\n') {
 -                                      s += 2;
 -                                      break;
 -                              }
 -                      for (len = 0; s[len] && '\n' != s[len]; len++)
 -                              ; /* do nothing */
 -                      printf("%.*s\n", len, s);
 +                      const char *title;
 +                      const char *msg = get_commit_buffer(commit, NULL);
 +                      int len = find_commit_subject(msg, &title);
 +                      if (len)
 +                              printf("%.*s\n", len, title);
 +                      unuse_commit_buffer(commit, msg);
                }
        }
  }
@@@ -201,11 -154,9 +201,11 @@@ static int add_cacheinfo(unsigned int m
                const char *path, int stage, int refresh, int options)
  {
        struct cache_entry *ce;
 -      ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
 +      ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage,
 +                            (refresh ? (CE_MATCH_REFRESH |
 +                                        CE_MATCH_IGNORE_MISSING) : 0 ));
        if (!ce)
 -              return error("addinfo_cache failed for path '%s'", path);
 +              return error(_("addinfo_cache failed for path '%s'"), path);
        return add_cache_entry(ce, options);
  }
  
@@@ -234,7 -185,7 +234,7 @@@ static int git_merge_trees(int index_on
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 -      opts.msgs = get_porcelain_error_msgs();
 +      setup_unpack_trees_porcelain(&opts, "merge");
  
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@@ -253,9 -204,9 +253,9 @@@ struct tree *write_tree_from_memory(str
                int i;
                fprintf(stderr, "BUG: There are unmerged index entries:\n");
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const 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");
                active_cache_tree = cache_tree();
  
        if (!cache_tree_fully_valid(active_cache_tree) &&
 -          cache_tree_update(active_cache_tree,
 -                            active_cache, active_nr, 0, 0) < 0)
 -              die("error building trees");
 +          cache_tree_update(&the_index, 0) < 0)
 +              die(_("error building trees"));
  
        result = lookup_tree(active_cache_tree->sha1);
  
@@@ -286,9 -238,9 +286,9 @@@ static int save_files_dirs(const unsign
        newpath[baselen + len] = '\0';
  
        if (S_ISDIR(mode))
 -              string_list_insert(newpath, &o->current_directory_set);
 +              string_list_insert(&o->current_directory_set, newpath);
        else
 -              string_list_insert(newpath, &o->current_file_set);
 +              string_list_insert(&o->current_file_set, newpath);
        free(newpath);
  
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
  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;
 +      memset(&match_all, 0, sizeof(match_all));
 +      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;
@@@ -321,7 -271,7 +321,7 @@@ static struct stage_data *insert_stage_
                        e->stages[2].sha, &e->stages[2].mode);
        get_tree_entry(b->object.sha1, path,
                        e->stages[3].sha, &e->stages[3].mode);
 -      item = string_list_insert(path, entries);
 +      item = string_list_insert(entries, path);
        item->util = e;
        return e;
  }
@@@ -340,13 -290,13 +340,13 @@@ static struct string_list *get_unmerged
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *item;
                struct stage_data *e;
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
  
 -              item = string_list_lookup(ce->name, unmerged);
 +              item = string_list_lookup(unmerged, ce->name);
                if (!item) {
 -                      item = string_list_insert(ce->name, unmerged);
 +                      item = string_list_insert(unmerged, ce->name);
                        item->util = xcalloc(1, sizeof(struct stage_data));
                }
                e = item->util;
        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;
@@@ -486,19 -335,16 +486,19 @@@ static struct string_list *get_renames(
        renames = xcalloc(1, sizeof(struct string_list));
        diff_setup(&opts);
        DIFF_OPT_SET(&opts, RECURSIVE);
 +      DIFF_OPT_CLR(&opts, RENAME_EMPTY);
        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_setup_done(&opts);
        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;
                re = xmalloc(sizeof(*re));
                re->processed = 0;
                re->pair = pair;
 -              item = string_list_lookup(re->pair->one->path, entries);
 +              item = string_list_lookup(entries, re->pair->one->path);
                if (!item)
                        re->src_entry = insert_stage_data(re->pair->one->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->src_entry = item->util;
  
 -              item = string_list_lookup(re->pair->two->path, entries);
 +              item = string_list_lookup(entries, re->pair->two->path);
                if (!item)
                        re->dst_entry = insert_stage_data(re->pair->two->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->dst_entry = item->util;
 -              item = string_list_insert(pair->one->path, renames);
 +              item = string_list_insert(renames, pair->one->path);
                item->util = re;
        }
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        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;
        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)
  {
                        return -1;
        }
        if (update_working_directory) {
 +              if (ignore_case) {
 +                      struct cache_entry *ce;
 +                      ce = cache_file_exists(path, strlen(path), ignore_case);
 +                      if (ce && ce_stage(ce) == 0)
 +                              return 0;
 +              }
                if (remove_path(path))
                        return -1;
        }
        return 0;
  }
  
 +/* add a string to a strbuf, but converting "/" to "_" */
 +static void add_flattened_path(struct strbuf *out, const char *s)
 +{
 +      size_t i = out->len;
 +      strbuf_addstr(out, s);
 +      for (; i < out->len; i++)
 +              if (out->buf[i] == '/')
 +                      out->buf[i] = '_';
 +}
 +
  static char *unique_path(struct merge_options *o, const char *path, const char *branch)
  {
 -      char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
 +      struct strbuf newpath = STRBUF_INIT;
        int suffix = 0;
        struct stat st;
 -      char *p = newpath + strlen(path);
 -      strcpy(newpath, path);
 -      *(p++) = '~';
 -      strcpy(p, branch);
 -      for (; *p; ++p)
 -              if ('/' == *p)
 -                      *p = '_';
 -      while (string_list_has_string(&o->current_file_set, newpath) ||
 -             string_list_has_string(&o->current_directory_set, newpath) ||
 -             lstat(newpath, &st) == 0)
 -              sprintf(p, "_%d", suffix++);
 -
 -      string_list_insert(newpath, &o->current_file_set);
 -      return newpath;
 +      size_t base_len;
 +
 +      strbuf_addf(&newpath, "%s~", path);
 +      add_flattened_path(&newpath, branch);
 +
 +      base_len = newpath.len;
 +      while (string_list_has_string(&o->current_file_set, newpath.buf) ||
 +             string_list_has_string(&o->current_directory_set, newpath.buf) ||
 +             lstat(newpath.buf, &st) == 0) {
 +              strbuf_setlen(&newpath, base_len);
 +              strbuf_addf(&newpath, "_%d", suffix++);
 +      }
 +
 +      string_list_insert(&o->current_file_set, newpath.buf);
 +      return strbuf_detach(&newpath, NULL);
  }
  
 -static void flush_buffer(int fd, const char *buf, unsigned long size)
 +static int dir_in_way(const char *path, int check_working_copy)
  {
 -      while (size > 0) {
 -              long ret = write_in_full(fd, buf, size);
 -              if (ret < 0) {
 -                      /* Ignore epipe */
 -                      if (errno == EPIPE)
 -                              break;
 -                      die_errno("merge-recursive");
 -              } else if (!ret) {
 -                      die("merge-recursive: disk full?");
 -              }
 -              size -= ret;
 -              buf += ret;
 +      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 would_lose_untracked(const char *path)
 +static int was_tracked(const char *path)
  {
        int pos = cache_name_pos(path, strlen(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;
 -      const char *msg = "failed to create path '%s'%s";
 +      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) {
 +              if (status == SCLD_EXISTS) {
                        /* something else exists */
 -                      error(msg, path, ": perhaps a D/F conflict?");
 +                      error(msg, path, _(": perhaps a D/F conflict?"));
                        return -1;
                }
                die(msg, path, "");
         * tracking it.
         */
        if (would_lose_untracked(path))
 -              return error("refusing to lose untracked file at '%s'",
 +              return error(_("refusing to lose untracked file at '%s'"),
                             path);
  
        /* Successful unlink is good.. */
        if (errno == ENOENT)
                return 0;
        /* .. but not some other error (who really cares what?) */
 -      return error(msg, path, ": perhaps a D/F conflict?");
 +      return error(msg, path, _(": perhaps a D/F conflict?"));
  }
  
  static void update_file_flags(struct merge_options *o,
                void *buf;
                unsigned long size;
  
 -              if (S_ISGITLINK(mode))
 +              if (S_ISGITLINK(mode)) {
                        /*
                         * We may later decide to recursively descend into
                         * the submodule directory and update its index
                         * and/or work tree, but we do not do that now.
                         */
 +                      update_wd = 0;
                        goto update_index;
 +              }
  
                buf = read_sha1_file(sha, &type, &size);
                if (!buf)
 -                      die("cannot read object %s '%s'", sha1_to_hex(sha), path);
 +                      die(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
                if (type != OBJ_BLOB)
 -                      die("blob expected for %s '%s'", sha1_to_hex(sha), path);
 +                      die(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
                if (S_ISREG(mode)) {
                        struct strbuf strbuf = STRBUF_INIT;
                        if (convert_to_working_tree(path, buf, size, &strbuf)) {
                        }
                }
  
 -              if (make_room_for_path(path) < 0) {
 +              if (make_room_for_path(o, path) < 0) {
                        update_wd = 0;
                        free(buf);
                        goto update_index;
                                mode = 0666;
                        fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
                        if (fd < 0)
 -                              die_errno("failed to open '%s'", path);
 -                      flush_buffer(fd, buf, size);
 +                              die_errno(_("failed to open '%s'"), path);
 +                      write_in_full(fd, buf, size);
                        close(fd);
                } else if (S_ISLNK(mode)) {
                        char *lnk = xmemdupz(buf, size);
                        safe_create_leading_directories_const(path);
                        unlink(path);
                        if (symlink(lnk, path))
 -                              die_errno("failed to symlink '%s'", path);
 +                              die_errno(_("failed to symlink '%s'"), path);
                        free(lnk);
                } else
 -                      die("do not know what to do with %06o %s '%s'",
 +                      die(_("do not know what to do with %06o %s '%s'"),
                            mode, sha1_to_hex(sha), path);
                free(buf);
        }
@@@ -819,7 -591,8 +819,7 @@@ static void update_file(struct merge_op
  
  /* Low level file merging, update and removal */
  
 -struct merge_file_info
 -{
 +struct merge_file_info {
        unsigned char sha[20];
        unsigned mode;
        unsigned clean:1,
  
  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;
                }
        }
        if (strcmp(a->path, b->path) ||
            (o->ancestor != NULL && strcmp(a->path, one->path) != 0)) {
                base_name = o->ancestor == NULL ? NULL :
 -                      xstrdup(mkpath("%s:%s", o->ancestor, one->path));
 -              name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
 -              name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
 +                      mkpathdup("%s:%s", o->ancestor, one->path);
 +              name1 = mkpathdup("%s:%s", branch1, a->path);
 +              name2 = mkpathdup("%s:%s", branch2, b->path);
        } else {
                base_name = o->ancestor == NULL ? NULL :
 -                      xstrdup(mkpath("%s", o->ancestor));
 -              name1 = xstrdup(mkpath("%s", branch1));
 -              name2 = xstrdup(mkpath("%s", branch2));
 +                      mkpathdup("%s", o->ancestor);
 +              name1 = mkpathdup("%s", branch1);
 +              name2 = mkpathdup("%s", branch2);
        }
  
        read_mmblob(&orig, one->sha1);
        read_mmblob(&src2, b->sha1);
  
        merge_status = ll_merge(result_buf, a->path, &orig, base_name,
 -                              &src1, name1, &src2, name2,
 -                              (!!o->call_depth) | (favor << 1));
 +                              &src1, name1, &src2, name2, &ll_opts);
  
 +      free(base_name);
        free(name1);
        free(name2);
        free(orig.ptr);
        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;
                                                  branch1, branch2);
  
                        if ((merge_status < 0) || !result_buf.ptr)
 -                              die("Failed to execute internal merge");
 +                              die(_("Failed to execute internal merge"));
  
                        if (write_sha1_file(result_buf.ptr, result_buf.size,
                                            blob_type, result.sha))
 -                              die("Unable to add %s to database",
 +                              die(_("Unable to add %s to database"),
                                    a->path);
  
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
 -                      result.clean = 0;
 -                      hashcpy(result.sha, a->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);
  
                        if (!sha_eq(a->sha1, b->sha1))
                                result.clean = 0;
                } else {
 -                      die("unsupported object type in the tree");
 +                      die(_("unsupported object type in the tree"));
                }
        }
  
        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);
 -      }
 -      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);
 +      char *side1 = NULL;
 +      char *side2 = NULL;
 +      struct merge_file_info mfi;
 +
 +      if (filename1)
 +              side1 = xstrfmt("%s:%s", branch1, filename1);
 +      if (filename2)
 +              side2 = xstrfmt("%s:%s", branch2, filename2);
 +
 +      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_one(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) {
 +              if (!renamed) {
 +                      output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 +                             "and %s in %s. Version %s of %s left in tree."),
 +                             change, path, o->branch1, change_past,
 +                             o->branch2, o->branch2, path);
 +                      update_file(o, 0, b_sha, b_mode, path);
 +              } else {
 +                      output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 +                             "and %s in %s. Version %s of %s left in tree at %s."),
 +                             change, path, o->branch1, change_past,
 +                             o->branch2, o->branch2, path, renamed);
 +                      update_file(o, 0, b_sha, b_mode, renamed);
 +              }
        } else {
 -              update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
 -              update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
 +              if (!renamed) {
 +                      output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 +                             "and %s in %s. Version %s of %s left in tree."),
 +                             change, path, o->branch2, change_past,
 +                             o->branch1, o->branch1, path);
 +              } else {
 +                      output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
 +                             "and %s in %s. Version %s of %s left in tree at %s."),
 +                             change, path, o->branch2, change_past,
 +                             o->branch1, o->branch1, path, 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 void conflict_rename_rename_2(struct merge_options *o,
 -                                   struct rename *ren1,
 -                                   const char *branch1,
 -                                   struct rename *ren2,
 -                                   const char *branch2)
 +static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,
 +                                               struct stage_data *entry,
 +                                               int stage)
  {
 -      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);
 +      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_one(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_2to1(struct merge_options *o,
 +                                      struct rename_conflict_info *ci)
 +{
 +      /* 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,
                           struct string_list *b_renames)
  {
        int clean_merge = 1, i, j;
 -      struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
 +      struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
 +      struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
  
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &a_by_dst)->util
 -                      = sre->dst_entry;
 +              string_list_insert(&a_by_dst, sre->pair->two->path)->util
 +                      = (void *)sre;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &b_by_dst)->util
 -                      = sre->dst_entry;
 +              string_list_insert(&b_by_dst, sre->pair->two->path)->util
 +                      = (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;
                        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.
 +                               * 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. "
 -                                     "%s added in %s",
 +                              output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
 +                                     "%s added in %s"),
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                if (o->call_depth) {
                                        struct merge_file_info mfi;
 -                                      struct diff_filespec one, a, b;
 -
 -                                      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);
 -                                      output(o, 1, "Adding merged %s", ren1_dst);
 -                                      update_file(o, 0,
 -                                                  mfi.sha,
 -                                                  mfi.mode,
 -                                                  ren1_dst);
 +                                      mfi = merge_file_one(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);
 +                                      try_merge = 0;
                                } else {
 -                                      new_path = unique_path(o, ren1_dst, branch2);
 -                                      output(o, 1, "Adding as %s instead", new_path);
 +                                      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(ren1_dst, renames2Dst))) {
 -                              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;
                                        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 messaged is part of
 -                                       * t6022 test. If you change
 -                                       * it update the test too.
 -                                       */
 -                                      output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
 -                              else {
 -                                      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);
                        }
                }
        }
@@@ -1524,10 -1063,10 +1524,10 @@@ static int read_sha1_strbuf(const unsig
        unsigned long size;
        buf = read_sha1_file(sha1, &type, &size);
        if (!buf)
 -              return error("cannot read object %s", sha1_to_hex(sha1));
 +              return error(_("cannot read object %s"), sha1_to_hex(sha1));
        if (type != OBJ_BLOB) {
                free(buf);
 -              return error("object %s is not a blob", sha1_to_hex(sha1));
 +              return error(_("object %s is not a blob"), sha1_to_hex(sha1));
        }
        strbuf_attach(dst, buf, size, size + 1);
        return 0;
  
  static int blob_unchanged(const unsigned char *o_sha,
                          const unsigned char *a_sha,
 -                        const char *path)
 +                        int renormalize, const char *path)
  {
        struct strbuf o = STRBUF_INIT;
        struct strbuf a = STRBUF_INIT;
  
        if (sha_eq(o_sha, a_sha))
                return 1;
 -      if (!merge_renormalize)
 +      if (!renormalize)
                return 0;
  
        assert(o_sha && a_sha);
         * unchanged since their sha1s have already been compared.
         */
        if (renormalize_buffer(path, o.buf, o.len, &o) |
-           renormalize_buffer(path, a.buf, o.len, &a))
+           renormalize_buffer(path, a.buf, a.len, &a))
                ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
  
  error_return:
        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)
  {
-       /*
-       printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
-       print_index_entry("\tpath: ", entry);
-       */
        int clean_merge = 1;
 +      int normalize = o->renormalize;
        unsigned o_mode = entry->stages[1].mode;
        unsigned a_mode = entry->stages[2].mode;
        unsigned b_mode = entry->stages[3].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);
  
 -      if (o_sha && (!a_sha || !b_sha)) {
 +      entry->processed = 1;
 +      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, path)) ||
 -                  (!a_sha && blob_unchanged(o_sha, b_sha, path))) {
 +                  (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
 +                  (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
 -                              output(o, 2, "Removing %s", path);
 +                              output(o, 2, _("Removing %s"), path);
                        /* 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;
                        other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
 -                      conf = "file/directory";
 +                      conf = _("file/directory");
                } else {
                        add_branch = o->branch2;
                        other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
 -                      conf = "directory/file";
 +                      conf = _("directory/file");
                }
 -              if (string_list_has_string(&o->current_directory_set, path)) {
 -                      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",
 +                      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);
 +                      output(o, 2, _("Adding %s"), 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
                 */
                remove_file(o, 1, path, !a_mode);
        } else
 -              die("Fatal merge failure, shouldn't happen.");
 +              die(_("Fatal merge failure, shouldn't happen."));
  
        return clean_merge;
  }
  
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 -{
 -      struct unpack_trees_error_msgs msgs = {
 -              /* would_overwrite */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_file */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_dir */
 -              "Updating '%s' would lose untracked files in it.  Aborting.",
 -              /* would_lose_untracked */
 -              "Untracked working tree file '%s' would be %s by merge.  Aborting",
 -              /* bind_overlap -- will not happen here */
 -              NULL,
 -      };
 -      if (advice_commit_before_merge) {
 -              msgs.would_overwrite = msgs.not_uptodate_file =
 -                      "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
 -                      "Please, commit your changes or stash them before you can merge.";
 -      }
 -      return msgs;
 -}
 -
  int merge_trees(struct merge_options *o,
                struct tree *head,
                struct tree *merge,
        }
  
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
 -              output(o, 0, "Already uptodate!");
 +              output(o, 0, _("Already up-to-date!"));
                *result = head;
                return 1;
        }
  
        if (code != 0) {
                if (show(o, 4) || o->call_depth)
 -                      die("merging of trees %s and %s failed",
 +                      die(_("merging of trees %s and %s failed"),
                            sha1_to_hex(head->object.sha1),
                            sha1_to_hex(merge->object.sha1));
                else
                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
                                && !process_entry(o, path, e))
                                clean = 0;
                }
 +              for (i = 0; i < entries->nr; i++) {
 +                      struct stage_data *e = entries->items[i].util;
 +                      if (!e->processed)
 +                              die(_("Unprocessed path??? %s"),
 +                                  entries->items[i].string);
 +              }
  
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
@@@ -1901,7 -1334,7 +1897,7 @@@ int merge_recursive(struct merge_option
        int clean;
  
        if (show(o, 4)) {
 -              output(o, 4, "Merging:");
 +              output(o, 4, _("Merging:"));
                output_commit_title(o, h1);
                output_commit_title(o, h2);
        }
        }
  
        if (show(o, 5)) {
 -              output(o, 5, "found %u common ancestor(s):", commit_list_count(ca));
 +              unsigned cnt = commit_list_count(ca);
 +
 +              output(o, 5, Q_("found %u common ancestor:",
 +                              "found %u common ancestors:", cnt), cnt);
                for (iter = ca; iter; iter = iter->next)
                        output_commit_title(o, iter->item);
        }
  
        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(EMPTY_TREE_SHA1_BIN);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
  
                o->call_depth--;
  
                if (!merged_common_ancestors)
 -                      die("merge returned no commit");
 +                      die(_("merge returned no commit"));
        }
  
        discard_cache();
                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;
  }
  
@@@ -1997,7 -1426,7 +1993,7 @@@ int merge_recursive_generic(struct merg
                            const unsigned char **base_list,
                            struct commit **result)
  {
 -      int clean, index_fd;
 +      int clean;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
        struct commit *head_commit = get_ref(head, o->branch1);
        struct commit *next_commit = get_ref(merge, o->branch2);
                for (i = 0; i < num_base_list; ++i) {
                        struct commit *base;
                        if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))
 -                              return error("Could not parse object '%s'",
 +                              return error(_("Could not parse object '%s'"),
                                        sha1_to_hex(base_list[i]));
                        commit_list_insert(base, &ca);
                }
        }
  
 -      index_fd = hold_locked_index(lock, 1);
 +      hold_locked_index(lock, 1);
        clean = merge_recursive(o, head_commit, next_commit, ca,
                        result);
        if (active_cache_changed &&
 -                      (write_cache(index_fd, active_cache, active_nr) ||
 -                       commit_locked_index(lock)))
 -              return error("Unable to write index.");
 +          write_locked_index(&the_index, lock, COMMIT_LOCK))
 +              return error(_("Unable to write index."));
  
        return clean ? 0 : 1;
  }
  
 -static int merge_recursive_config(const char *var, const char *value, void *cb)
 +static void merge_recursive_config(struct merge_options *o)
  {
 -      struct merge_options *o = cb;
 -      if (!strcasecmp(var, "merge.verbosity")) {
 -              o->verbosity = git_config_int(var, value);
 -              return 0;
 -      }
 -      if (!strcasecmp(var, "diff.renamelimit")) {
 -              o->diff_rename_limit = git_config_int(var, value);
 -              return 0;
 -      }
 -      if (!strcasecmp(var, "merge.renamelimit")) {
 -              o->merge_rename_limit = git_config_int(var, value);
 -              return 0;
 -      }
 -      return git_xmerge_config(var, value, cb);
 +      git_config_get_int("merge.verbosity", &o->verbosity);
 +      git_config_get_int("diff.renamelimit", &o->diff_rename_limit);
 +      git_config_get_int("merge.renamelimit", &o->merge_rename_limit);
 +      git_config(git_xmerge_config, NULL);
  }
  
  void init_merge_options(struct merge_options *o)
        o->buffer_output = 1;
        o->diff_rename_limit = -1;
        o->merge_rename_limit = -1;
 -      git_config(merge_recursive_config, o);
 +      o->renormalize = 0;
 +      merge_recursive_config(o);
        if (getenv("GIT_MERGE_VERBOSITY"))
                o->verbosity =
                        strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
        if (o->verbosity >= 5)
                o->buffer_output = 0;
        strbuf_init(&o->obuf, 0);
 -      memset(&o->current_file_set, 0, sizeof(struct string_list));
 -      o->current_file_set.strdup_strings = 1;
 -      memset(&o->current_directory_set, 0, sizeof(struct string_list));
 -      o->current_directory_set.strdup_strings = 1;
 +      string_list_init(&o->current_file_set, 1);
 +      string_list_init(&o->current_directory_set, 1);
 +      string_list_init(&o->df_conflict_file_set, 1);
 +}
 +
 +int parse_merge_opt(struct merge_options *o, const char *s)
 +{
 +      const char *arg;
 +
 +      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 (skip_prefix(s, "subtree=", &arg))
 +              o->subtree_shift = arg;
 +      else if (!strcmp(s, "patience"))
 +              o->xdl_opts = DIFF_WITH_ALG(o, PATIENCE_DIFF);
 +      else if (!strcmp(s, "histogram"))
 +              o->xdl_opts = DIFF_WITH_ALG(o, HISTOGRAM_DIFF);
 +      else if (skip_prefix(s, "diff-algorithm=", &arg)) {
 +              long value = parse_algorithm_value(arg);
 +              if (value < 0)
 +                      return -1;
 +              /* clear out previous settings */
 +              DIFF_XDL_CLR(o, NEED_MINIMAL);
 +              o->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
 +              o->xdl_opts |= value;
 +      }
 +      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 (skip_prefix(s, "rename-threshold=", &arg)) {
 +              if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
 +                      return -1;
 +      }
 +      else
 +              return -1;
 +      return 0;
  }