Merge branch 'jn/plug-empty-tree-leak'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Aug 2011 23:00:29 +0000 (16:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Aug 2011 23:00:29 +0000 (16:00 -0700)
* jn/plug-empty-tree-leak:
merge-recursive: take advantage of hardcoded empty tree
revert: plug memory leak in "cherry-pick root commit" codepath

1  2 
builtin/revert.c
merge-recursive.c
t/t3503-cherry-pick-root.sh
diff --combined builtin/revert.c
index 1f27c63343904a969e60ab19408495007f59d133,a26a7c93156b8826a92e4f92e5be6d5aee81b6a2..3117776c2c030bec03563f043c4dc8bb34eb17cd
@@@ -3,6 -3,7 +3,6 @@@
  #include "object.h"
  #include "commit.h"
  #include "tag.h"
 -#include "wt-status.h"
  #include "run-command.h"
  #include "exec_cmd.h"
  #include "utf8.h"
@@@ -43,47 -44,25 +43,47 @@@ static const char **commit_argv
  static int allow_rerere_auto;
  
  static const char *me;
 +
 +/* Merge strategy. */
  static const char *strategy;
 +static const char **xopts;
 +static size_t xopts_nr, xopts_alloc;
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
  static char *get_encoding(const char *message);
  
 +static const char * const *revert_or_cherry_pick_usage(void)
 +{
 +      return action == REVERT ? revert_usage : cherry_pick_usage;
 +}
 +
 +static int option_parse_x(const struct option *opt,
 +                        const char *arg, int unset)
 +{
 +      if (unset)
 +              return 0;
 +
 +      ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
 +      xopts[xopts_nr++] = xstrdup(arg);
 +      return 0;
 +}
 +
  static void parse_args(int argc, const char **argv)
  {
 -      const char * const * usage_str =
 -              action == REVERT ?  revert_usage : cherry_pick_usage;
 +      const char * const * usage_str = revert_or_cherry_pick_usage();
        int noop;
        struct option options[] = {
                OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
                OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
 -              OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
 +              { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
 +                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
                OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
                OPT_INTEGER('m', "mainline", &mainline, "parent number"),
                OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
                OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
 +              OPT_CALLBACK('X', "strategy-option", &xopts, "option",
 +                      "option for merge strategy", option_parse_x),
                OPT_END(),
                OPT_END(),
                OPT_END(),
                        OPT_END(),
                };
                if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
 -                      die("program error");
 +                      die(_("program error"));
        }
  
 -      commit_argc = parse_options(argc, argv, NULL, options, usage_str, 0);
 -      if (commit_argc < 1)
 +      commit_argc = parse_options(argc, argv, NULL, options, usage_str,
 +                                  PARSE_OPT_KEEP_ARGV0 |
 +                                  PARSE_OPT_KEEP_UNKNOWN);
 +      if (commit_argc < 2)
                usage_with_options(usage_str, options);
  
        commit_argv = argv;
@@@ -119,9 -96,9 +119,9 @@@ struct commit_message 
  static int get_message(const char *raw_message, struct commit_message *out)
  {
        const char *encoding;
 -      const char *p, *abbrev, *eol;
 +      const char *abbrev, *subject;
 +      int abbrev_len, subject_len;
        char *q;
 -      int abbrev_len, oneline_len;
  
        if (!raw_message)
                return -1;
        abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
        abbrev_len = strlen(abbrev);
  
 -      /* Find beginning and end of commit subject. */
 -      p = out->message;
 -      while (*p && (*p != '\n' || p[1] != '\n'))
 -              p++;
 -      if (*p) {
 -              p += 2;
 -              for (eol = p + 1; *eol && *eol != '\n'; eol++)
 -                      ; /* do nothing */
 -      } else
 -              eol = p;
 -      oneline_len = eol - p;
 +      subject_len = find_commit_subject(out->message, &subject);
  
        out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
 -                            strlen("... ") + oneline_len + 1);
 +                            strlen("... ") + subject_len + 1);
        q = out->parent_label;
        q = mempcpy(q, "parent of ", strlen("parent of "));
        out->label = q;
        q = mempcpy(q, abbrev, abbrev_len);
        q = mempcpy(q, "... ", strlen("... "));
        out->subject = q;
 -      q = mempcpy(q, p, oneline_len);
 +      q = mempcpy(q, subject, subject_len);
        *q = '\0';
        return 0;
  }
@@@ -168,7 -155,7 +168,7 @@@ static char *get_encoding(const char *m
        const char *p = message, *eol;
  
        if (!p)
 -              die ("Could not read commit message of %s",
 +              die (_("Could not read commit message of %s"),
                                sha1_to_hex(commit->object.sha1));
        while (*p && *p != '\n') {
                for (eol = p + 1; *eol && *eol != '\n'; eol++)
@@@ -198,49 -185,77 +198,49 @@@ static void add_message_to_msg(struct s
        strbuf_addstr(msgbuf, p);
  }
  
 -static void set_author_ident_env(const char *message)
 +static void write_cherry_pick_head(void)
  {
 -      const char *p = message;
 -      if (!p)
 -              die ("Could not read commit message of %s",
 -                              sha1_to_hex(commit->object.sha1));
 -      while (*p && *p != '\n') {
 -              const char *eol;
 -
 -              for (eol = p; *eol && *eol != '\n'; eol++)
 -                      ; /* do nothing */
 -              if (!prefixcmp(p, "author ")) {
 -                      char *line, *pend, *email, *timestamp;
 -
 -                      p += 7;
 -                      line = xmemdupz(p, eol - p);
 -                      email = strchr(line, '<');
 -                      if (!email)
 -                              die ("Could not extract author email from %s",
 -                                      sha1_to_hex(commit->object.sha1));
 -                      if (email == line)
 -                              pend = line;
 -                      else
 -                              for (pend = email; pend != line + 1 &&
 -                                              isspace(pend[-1]); pend--);
 -                                      ; /* do nothing */
 -                      *pend = '\0';
 -                      email++;
 -                      timestamp = strchr(email, '>');
 -                      if (!timestamp)
 -                              die ("Could not extract author time from %s",
 -                                      sha1_to_hex(commit->object.sha1));
 -                      *timestamp = '\0';
 -                      for (timestamp++; *timestamp && isspace(*timestamp);
 -                                      timestamp++)
 -                              ; /* do nothing */
 -                      setenv("GIT_AUTHOR_NAME", line, 1);
 -                      setenv("GIT_AUTHOR_EMAIL", email, 1);
 -                      setenv("GIT_AUTHOR_DATE", timestamp, 1);
 -                      free(line);
 -                      return;
 -              }
 -              p = eol;
 -              if (*p == '\n')
 -                      p++;
 -      }
 -      die ("No author information found in %s",
 -                      sha1_to_hex(commit->object.sha1));
 +      int fd;
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
 +
 +      fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
 +      if (fd < 0)
 +              die_errno(_("Could not open '%s' for writing"),
 +                        git_path("CHERRY_PICK_HEAD"));
 +      if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
 +              die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
 +      strbuf_release(&buf);
  }
  
 -static char *help_msg(void)
 +static void advise(const char *advice, ...)
  {
 -      struct strbuf helpbuf = STRBUF_INIT;
 -      char *msg = getenv("GIT_CHERRY_PICK_HELP");
 +      va_list params;
  
 -      if (msg)
 -              return msg;
 +      va_start(params, advice);
 +      vreportf("hint: ", advice, params);
 +      va_end(params);
 +}
  
 -      strbuf_addstr(&helpbuf, "  After resolving the conflicts,\n"
 -              "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n"
 -              "and commit the result");
 +static void print_advice(void)
 +{
 +      char *msg = getenv("GIT_CHERRY_PICK_HELP");
  
 -      if (action == CHERRY_PICK) {
 -              strbuf_addf(&helpbuf, " with: \n"
 -                      "\n"
 -                      "        git commit -c %s\n",
 -                          sha1_to_hex(commit->object.sha1));
 +      if (msg) {
 +              fprintf(stderr, "%s\n", msg);
 +              /*
 +               * A conflict has occured but the porcelain
 +               * (typically rebase --interactive) wants to take care
 +               * of the commit itself so remove CHERRY_PICK_HEAD
 +               */
 +              unlink(git_path("CHERRY_PICK_HEAD"));
 +              return;
        }
 -      else
 -              strbuf_addch(&helpbuf, '.');
 -      return strbuf_detach(&helpbuf, NULL);
 +
 +      advise("after resolving the conflicts, mark the corrected paths");
 +      advise("with 'git add <paths>' or 'git rm <paths>'");
 +      advise("and commit the result with 'git commit'");
  }
  
  static void write_message(struct strbuf *msgbuf, const char *filename)
        int msg_fd = hold_lock_file_for_update(&msg_file, filename,
                                               LOCK_DIE_ON_ERROR);
        if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
 -              die_errno("Could not write to %s.", filename);
 +              die_errno(_("Could not write to %s."), filename);
        strbuf_release(msgbuf);
        if (commit_lock_file(&msg_file) < 0)
 -              die("Error wrapping up %s", filename);
 +              die(_("Error wrapping up %s"), filename);
  }
  
  static struct tree *empty_tree(void)
  {
-       struct tree *tree = xcalloc(1, sizeof(struct tree));
-       tree->object.parsed = 1;
-       tree->object.type = OBJ_TREE;
-       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-       return tree;
+       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
  }
  
  static NORETURN void die_dirty_index(const char *me)
        if (read_cache_unmerged()) {
                die_resolve_conflict(me);
        } else {
 -              if (advice_commit_before_merge)
 -                      die("Your local changes would be overwritten by %s.\n"
 -                          "Please, commit your changes or stash them to proceed.", me);
 -              else
 -                      die("Your local changes would be overwritten by %s.\n", me);
 +              if (advice_commit_before_merge) {
 +                      if (action == REVERT)
 +                              die(_("Your local changes would be overwritten by revert.\n"
 +                                        "Please, commit your changes or stash them to proceed."));
 +                      else
 +                              die(_("Your local changes would be overwritten by cherry-pick.\n"
 +                                        "Please, commit your changes or stash them to proceed."));
 +              } else {
 +                      if (action == REVERT)
 +                              die(_("Your local changes would be overwritten by revert.\n"));
 +                      else
 +                              die(_("Your local changes would be overwritten by cherry-pick.\n"));
 +              }
        }
  }
  
@@@ -298,20 -300,19 +293,20 @@@ static int fast_forward_to(const unsign
        return write_ref_sha1(ref_lock, to, "cherry-pick");
  }
  
 -static void do_recursive_merge(struct commit *base, struct commit *next,
 -                             const char *base_label, const char *next_label,
 -                             unsigned char *head, struct strbuf *msgbuf,
 -                             char *defmsg)
 +static int do_recursive_merge(struct commit *base, struct commit *next,
 +                            const char *base_label, const char *next_label,
 +                            unsigned char *head, struct strbuf *msgbuf)
  {
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
        int clean, index_fd;
 +      const char **xopt;
        static struct lock_file index_lock;
  
        index_fd = hold_locked_index(&index_lock, 1);
  
        read_cache();
 +
        init_merge_options(&o);
        o.ancestor = base ? base_label : "(empty tree)";
        o.branch1 = "HEAD";
        next_tree = next ? next->tree : empty_tree();
        base_tree = base ? base->tree : empty_tree();
  
 +      for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
 +              parse_merge_opt(&o, *xopt);
 +
        clean = merge_trees(&o,
                            head_tree,
                            next_tree, base_tree, &result);
        if (active_cache_changed &&
            (write_cache(index_fd, active_cache, active_nr) ||
             commit_locked_index(&index_lock)))
 -              die("%s: Unable to write new index file", me);
 +              /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
 +              die(_("%s: Unable to write new index file"), me);
        rollback_lock_file(&index_lock);
  
        if (!clean) {
                                        i++;
                        }
                }
 -              write_message(msgbuf, defmsg);
 -              fprintf(stderr, "Automatic %s failed.%s\n",
 -                      me, help_msg());
 -              rerere(allow_rerere_auto);
 -              exit(1);
        }
 -      write_message(msgbuf, defmsg);
 -      fprintf(stderr, "Finished one %s.\n", me);
 +
 +      return !clean;
 +}
 +
 +/*
 + * If we are cherry-pick, and if the merge did not result in
 + * hand-editing, we will hit this commit and inherit the original
 + * author date and name.
 + * If we are revert, or if our cherry-pick results in a hand merge,
 + * we had better say that the current user is responsible for that.
 + */
 +static int run_git_commit(const char *defmsg)
 +{
 +      /* 6 is max possible length of our args array including NULL */
 +      const char *args[6];
 +      int i = 0;
 +
 +      args[i++] = "commit";
 +      args[i++] = "-n";
 +      if (signoff)
 +              args[i++] = "-s";
 +      if (!edit) {
 +              args[i++] = "-F";
 +              args[i++] = defmsg;
 +      }
 +      args[i] = NULL;
 +
 +      return run_command_v_opt(args, RUN_GIT_CMD);
  }
  
  static int do_pick_commit(void)
        struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
        char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
 +      int res;
  
        if (no_commit) {
                /*
                 * to work on.
                 */
                if (write_cache_as_tree(head, 0, NULL))
 -                      die ("Your index file is unmerged.");
 +                      die (_("Your index file is unmerged."));
        } else {
                if (get_sha1("HEAD", head))
 -                      die ("You do not have a valid HEAD");
 +                      die (_("You do not have a valid HEAD"));
                if (index_differs_from("HEAD", 0))
                        die_dirty_index(me);
        }
        discard_cache();
  
        if (!commit->parents) {
 -              if (action == REVERT)
 -                      die ("Cannot revert a root commit");
                parent = NULL;
        }
        else if (commit->parents->next) {
                struct commit_list *p;
  
                if (!mainline)
 -                      die("Commit %s is a merge but no -m option was given.",
 +                      die(_("Commit %s is a merge but no -m option was given."),
                            sha1_to_hex(commit->object.sha1));
  
                for (cnt = 1, p = commit->parents;
                     cnt++)
                        p = p->next;
                if (cnt != mainline || !p)
 -                      die("Commit %s does not have parent %d",
 +                      die(_("Commit %s does not have parent %d"),
                            sha1_to_hex(commit->object.sha1), mainline);
                parent = p->item;
        } else if (0 < mainline)
 -              die("Mainline was specified but commit %s is not a merge.",
 +              die(_("Mainline was specified but commit %s is not a merge."),
                    sha1_to_hex(commit->object.sha1));
        else
                parent = commit->parents->item;
  
 -      if (allow_ff && !hashcmp(parent->object.sha1, head))
 +      if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
                return fast_forward_to(commit->object.sha1, head);
  
        if (parent && parse_commit(parent) < 0)
 -              die("%s: cannot parse parent commit %s",
 +              /* TRANSLATORS: The first %s will be "revert" or
 +                 "cherry-pick", the second %s a SHA1 */
 +              die(_("%s: cannot parse parent commit %s"),
                    me, sha1_to_hex(parent->object.sha1));
  
        if (get_message(commit->buffer, &msg) != 0)
 -              die("Cannot get commit message for %s",
 +              die(_("Cannot get commit message for %s"),
                                sha1_to_hex(commit->object.sha1));
  
        /*
                strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
                strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
  
 -              if (commit->parents->next) {
 +              if (commit->parents && commit->parents->next) {
                        strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
                        strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
                }
                base_label = msg.parent_label;
                next = commit;
                next_label = msg.label;
 -              set_author_ident_env(msg.message);
                add_message_to_msg(&msgbuf, msg.message);
                if (no_replay) {
                        strbuf_addstr(&msgbuf, "(cherry picked from commit ");
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
 +              if (!no_commit)
 +                      write_cherry_pick_head();
        }
  
 -      if (!strategy || !strcmp(strategy, "recursive") || action == REVERT)
 -              do_recursive_merge(base, next, base_label, next_label,
 -                                 head, &msgbuf, defmsg);
 -      else {
 -              int res;
 +      if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
 +              res = do_recursive_merge(base, next, base_label, next_label,
 +                                       head, &msgbuf);
 +              write_message(&msgbuf, defmsg);
 +      } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
 +
                write_message(&msgbuf, defmsg);
 +
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
 -              res = try_merge_command(strategy, common,
 +              res = try_merge_command(strategy, xopts_nr, xopts, common,
                                        sha1_to_hex(head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
 -              if (res) {
 -                      fprintf(stderr, "Automatic %s with strategy %s failed.%s\n",
 -                              me, strategy, help_msg());
 -                      rerere(allow_rerere_auto);
 -                      exit(1);
 -              }
        }
  
 -      free_message(&msg);
 -
 -      /*
 -       *
 -       * If we are cherry-pick, and if the merge did not result in
 -       * hand-editing, we will hit this commit and inherit the original
 -       * author date and name.
 -       * If we are revert, or if our cherry-pick results in a hand merge,
 -       * we had better say that the current user is responsible for that.
 -       */
 -
 -      if (!no_commit) {
 -              /* 6 is max possible length of our args array including NULL */
 -              const char *args[6];
 -              int res;
 -              int i = 0;
 -
 -              args[i++] = "commit";
 -              args[i++] = "-n";
 -              if (signoff)
 -                      args[i++] = "-s";
 -              if (!edit) {
 -                      args[i++] = "-F";
 -                      args[i++] = defmsg;
 -              }
 -              args[i] = NULL;
 -              res = run_command_v_opt(args, RUN_GIT_CMD);
 -              free(defmsg);
 -
 -              return res;
 +      if (res) {
 +              error(action == REVERT
 +                    ? _("could not revert %s... %s")
 +                    : _("could not apply %s... %s"),
 +                    find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
 +                    msg.subject);
 +              print_advice();
 +              rerere(allow_rerere_auto);
 +      } else {
 +              if (!no_commit)
 +                      res = run_git_commit(defmsg);
        }
  
 +      free_message(&msg);
        free(defmsg);
  
 -      return 0;
 +      return res;
  }
  
  static void prepare_revs(struct rev_info *revs)
  {
 -      int argc = 0;
 -      int i;
 -      const char **argv = xmalloc((commit_argc + 4) * sizeof(*argv));
 +      int argc;
  
 -      argv[argc++] = NULL;
 -      argv[argc++] = "--no-walk";
 +      init_revisions(revs, NULL);
 +      revs->no_walk = 1;
        if (action != REVERT)
 -              argv[argc++] = "--reverse";
 -      for (i = 0; i < commit_argc; i++)
 -              argv[argc++] = commit_argv[i];
 -      argv[argc++] = NULL;
 +              revs->reverse = 1;
 +
 +      argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
 +      if (argc > 1)
 +              usage(*revert_or_cherry_pick_usage());
  
 -      init_revisions(revs, NULL);
 -      setup_revisions(argc - 1, argv, revs, NULL);
        if (prepare_revision_walk(revs))
 -              die("revision walk setup failed");
 +              die(_("revision walk setup failed"));
  
        if (!revs->commits)
 -              die("empty commit set passed");
 +              die(_("empty commit set passed"));
 +}
  
 -      free(argv);
 +static void read_and_refresh_cache(const char *me)
 +{
 +      static struct lock_file index_lock;
 +      int index_fd = hold_locked_index(&index_lock, 0);
 +      if (read_index_preload(&the_index, NULL) < 0)
 +              die(_("git %s: failed to read the index"), me);
 +      refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
 +      if (the_index.cache_changed) {
 +              if (write_index(&the_index, index_fd) ||
 +                  commit_locked_index(&index_lock))
 +                      die(_("git %s: failed to refresh the index"), me);
 +      }
 +      rollback_lock_file(&index_lock);
  }
  
  static int revert_or_cherry_pick(int argc, const char **argv)
  
        if (allow_ff) {
                if (signoff)
 -                      die("cherry-pick --ff cannot be used with --signoff");
 +                      die(_("cherry-pick --ff cannot be used with --signoff"));
                if (no_commit)
 -                      die("cherry-pick --ff cannot be used with --no-commit");
 +                      die(_("cherry-pick --ff cannot be used with --no-commit"));
                if (no_replay)
 -                      die("cherry-pick --ff cannot be used with -x");
 +                      die(_("cherry-pick --ff cannot be used with -x"));
                if (edit)
 -                      die("cherry-pick --ff cannot be used with --edit");
 +                      die(_("cherry-pick --ff cannot be used with --edit"));
        }
  
 -      if (read_cache() < 0)
 -              die("git %s: failed to read the index", me);
 +      read_and_refresh_cache(me);
  
        prepare_revs(&revs);
  
diff --combined merge-recursive.c
index 0cc1e6fc1498e9fc51c9e382aee6a20e439a4ef0,7695fe89196521a339ef422678f7ed3cb078cb38..0804eb44e7974462d9e5a84e708e23e406628399
@@@ -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)
@@@ -63,62 -62,20 +63,62 @@@ 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_TWO
 +};
 +
 +struct rename_df_conflict_info {
 +      enum rename_type rename_type;
 +      struct diff_filepair *pair1;
 +      struct diff_filepair *pair2;
 +      const char *branch1;
 +      const char *branch2;
 +      struct stage_data *dst_entry1;
 +      struct stage_data *dst_entry2;
 +};
 +
  /*
   * Since we want to write the index eventually, we cannot reuse the index
   * for these (temporary) data.
   */
 -struct stage_data
 -{
 -      struct
 -      {
 +struct stage_data {
 +      struct {
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
 +      struct rename_df_conflict_info *rename_df_conflict_info;
        unsigned processed:1;
  };
  
 +static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
 +                                               struct diff_filepair *pair1,
 +                                               struct diff_filepair *pair2,
 +                                               const char *branch1,
 +                                               const char *branch2,
 +                                               struct stage_data *dst_entry1,
 +                                               struct stage_data *dst_entry2)
 +{
 +      struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
 +      ci->rename_type = rename_type;
 +      ci->pair1 = pair1;
 +      ci->branch1 = branch1;
 +      ci->branch2 = branch2;
 +
 +      ci->dst_entry1 = dst_entry1;
 +      dst_entry1->rename_df_conflict_info = ci;
 +      dst_entry1->processed = 0;
 +
 +      assert(!pair2 == !dst_entry2);
 +      if (dst_entry2) {
 +              ci->dst_entry2 = dst_entry2;
 +              ci->pair2 = pair2;
 +              dst_entry2->rename_df_conflict_info = ci;
 +              dst_entry2->processed = 0;
 +      }
 +}
 +
  static int show(struct merge_options *o, int v)
  {
        return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
@@@ -135,6 -92,7 +135,6 @@@ 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))
        strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
  
        va_start(ap, fmt);
 -      len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
 +      strbuf_vaddf(&o->obuf, fmt, ap);
        va_end(ap);
  
 -      if (len < 0)
 -              len = 0;
 -      if (len >= strbuf_avail(&o->obuf)) {
 -              strbuf_grow(&o->obuf, len + 2);
 -              va_start(ap, fmt);
 -              len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
 -              va_end(ap);
 -              if (len >= strbuf_avail(&o->obuf)) {
 -                      die("this should not happen, your snprintf is broken");
 -              }
 -      }
 -      strbuf_setlen(&o->obuf, o->obuf.len + len);
        strbuf_add(&o->obuf, "\n", 1);
        if (!o->buffer_output)
                flush_output(o);
@@@ -166,10 -136,16 +166,10 @@@ static void output_commit_title(struct 
                if (parse_commit(commit) != 0)
                        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;
 +                      int len = find_commit_subject(commit->buffer, &title);
 +                      if (len)
 +                              printf("%.*s\n", len, title);
                }
        }
  }
@@@ -209,7 -185,7 +209,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);
@@@ -262,9 -238,9 +262,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;
 +      init_pathspec(&match_all, NULL);
 +      if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
                return 0;
        n = o->current_file_set.nr + o->current_directory_set.nr;
        return n;
@@@ -297,7 -271,7 +297,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;
  }
@@@ -320,9 -294,9 +320,9 @@@ static struct string_list *get_unmerged
                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 void make_room_for_directories_of_df_conflicts(struct merge_options *o,
 +                                                    struct string_list *entries)
  {
 +      /* If there are D/F conflicts, and the paths currently exist
 +       * in the working copy as a file, we want to remove them to
 +       * make room for the corresponding directory.  Such paths will
 +       * later be processed in process_df_entry() at the end.  If
 +       * the corresponding directory ends up being removed by the
 +       * merge, then the file will be reinstated at that time;
 +       * otherwise, if the file is not supposed to be removed by the
 +       * merge, the contents of the file will be placed in another
 +       * unique filename.
 +       *
 +       * NOTE: This function relies on the fact that entries for a
 +       * D/F conflict will appear adjacent in the index, with the
 +       * entries for the file appearing before entries for paths
 +       * below the corresponding directory.
 +       */
 +      const char *last_file = NULL;
 +      int last_len = 0;
 +      int i;
 +
 +      for (i = 0; i < entries->nr; i++) {
 +              const char *path = entries->items[i].string;
 +              int len = strlen(path);
 +              struct stage_data *e = entries->items[i].util;
 +
 +              /*
 +               * Check if last_file & path correspond to a D/F conflict;
 +               * i.e. whether path is last_file+'/'+<something>.
 +               * If so, remove last_file to make room for path and friends.
 +               */
 +              if (last_file &&
 +                  len > last_len &&
 +                  memcmp(path, last_file, last_len) == 0 &&
 +                  path[last_len] == '/') {
 +                      output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
 +                      unlink(last_file);
 +              }
 +
 +              /*
 +               * Determine whether path could exist as a file in the
 +               * working directory as a possible D/F conflict.  This
 +               * will only occur when it exists in stage 2 as a
 +               * file.
 +               */
 +              if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
 +                      last_file = path;
 +                      last_len = len;
 +              } else {
 +                      last_file = NULL;
 +              }
 +      }
 +}
 +
 +struct rename {
        struct diff_filepair *pair;
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
@@@ -418,16 -338,13 +418,16 @@@ static struct string_list *get_renames(
        opts.detect_rename = DIFF_DETECT_RENAME;
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
 -                          500;
 -      opts.warn_on_too_large_rename = 1;
 +                          1000;
 +      opts.rename_score = o->rename_score;
 +      opts.show_rename_progress = o->show_rename_progress;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        if (diff_setup_done(&opts) < 0)
                die("diff setup failed");
        diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
        diffcore_std(&opts);
 +      if (opts.needed_rename_limit > o->needed_rename_limit)
 +              o->needed_rename_limit = opts.needed_rename_limit;
        for (i = 0; i < diff_queued_diff.nr; ++i) {
                struct string_list_item *item;
                struct rename *re;
                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,
 +static int update_stages_options(const char *path, struct diff_filespec *o,
                         struct diff_filespec *a, struct diff_filespec *b,
 -                       int clear)
 +                       int clear, int options)
  {
 -      int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
        if (clear)
                if (remove_file_from_cache(path))
                        return -1;
        return 0;
  }
  
 +static int update_stages(const char *path, struct diff_filespec *o,
 +                       struct diff_filespec *a, struct diff_filespec *b,
 +                       int clear)
 +{
 +      int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
 +      return update_stages_options(path, o, a, b, clear, options);
 +}
 +
 +static int update_stages_and_entry(const char *path,
 +                                 struct stage_data *entry,
 +                                 struct diff_filespec *o,
 +                                 struct diff_filespec *a,
 +                                 struct diff_filespec *b,
 +                                 int clear)
 +{
 +      int options;
 +
 +      entry->processed = 0;
 +      entry->stages[1].mode = o->mode;
 +      entry->stages[2].mode = a->mode;
 +      entry->stages[3].mode = b->mode;
 +      hashcpy(entry->stages[1].sha, o->sha1);
 +      hashcpy(entry->stages[2].sha, a->sha1);
 +      hashcpy(entry->stages[3].sha, b->sha1);
 +      options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
 +      return update_stages_options(path, o, a, b, clear, options);
 +}
 +
  static int remove_file(struct merge_options *o, int clean,
                       const char *path, int no_wd)
  {
@@@ -542,7 -432,7 +542,7 @@@ static char *unique_path(struct merge_o
               lstat(newpath, &st) == 0)
                sprintf(p, "_%d", suffix++);
  
 -      string_list_insert(newpath, &o->current_file_set);
 +      string_list_insert(&o->current_file_set, newpath);
        return newpath;
  }
  
@@@ -635,15 -525,13 +635,15 @@@ static void update_file_flags(struct me
                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)
@@@ -703,7 -591,8 +703,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,
@@@ -719,26 -608,22 +719,26 @@@ static int merge_3way(struct merge_opti
                      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;
                }
        }
        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(name1);
        free(name2);
@@@ -830,8 -716,8 +830,8 @@@ static struct merge_file_info merge_fil
                        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);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
  
        return result;
  }
  
 -static void conflict_rename_rename(struct merge_options *o,
 -                                 struct rename *ren1,
 -                                 const char *branch1,
 -                                 struct rename *ren2,
 -                                 const char *branch2)
 +static void conflict_rename_delete(struct merge_options *o,
 +                                 struct diff_filepair *pair,
 +                                 const char *rename_branch,
 +                                 const char *other_branch)
  {
 +      char *dest_name = pair->two->path;
 +      int df_conflict = 0;
 +      struct stat st;
 +
 +      output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
 +             "and deleted in %s",
 +             pair->one->path, pair->two->path, rename_branch,
 +             other_branch);
 +      if (!o->call_depth)
 +              update_stages(dest_name, NULL,
 +                            rename_branch == o->branch1 ? pair->two : NULL,
 +                            rename_branch == o->branch1 ? NULL : pair->two,
 +                            1);
 +      if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
 +              dest_name = unique_path(o, dest_name, rename_branch);
 +              df_conflict = 1;
 +      }
 +      update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
 +      if (df_conflict)
 +              free(dest_name);
 +}
 +
 +static void conflict_rename_rename_1to2(struct merge_options *o,
 +                                      struct diff_filepair *pair1,
 +                                      const char *branch1,
 +                                      struct diff_filepair *pair2,
 +                                      const char *branch2)
 +{
 +      /* One file was renamed in both branches, but to different names. */
        char *del[2];
        int delp = 0;
 -      const char *ren1_dst = ren1->pair->two->path;
 -      const char *ren2_dst = ren2->pair->two->path;
 +      const char *ren1_dst = pair1->two->path;
 +      const char *ren2_dst = pair2->two->path;
        const char *dst_name1 = ren1_dst;
        const char *dst_name2 = ren2_dst;
 -      if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
 +      struct stat st;
 +      if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
                dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren1_dst, branch2, dst_name1);
 -              remove_file(o, 0, ren1_dst, 0);
        }
 -      if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
 +      if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
                dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren2_dst, branch1, dst_name2);
 -              remove_file(o, 0, ren2_dst, 0);
        }
        if (o->call_depth) {
                remove_file_from_cache(dst_name1);
                /*
                 * 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);
 +               * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
 +               * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
                 */
        } else {
 -              update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
 -              update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
 +              update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
 +              update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
 +
 +              update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
 +              update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
        }
        while (delp--)
                free(del[delp]);
  }
  
 -static void conflict_rename_dir(struct merge_options *o,
 -                              struct rename *ren1,
 -                              const char *branch1)
 -{
 -      char *new_path = unique_path(o, ren1->pair->two->path, branch1);
 -      output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
 -      remove_file(o, 0, ren1->pair->two->path, 0);
 -      update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
 -      free(new_path);
 -}
 -
 -static void conflict_rename_rename_2(struct merge_options *o,
 -                                   struct rename *ren1,
 -                                   const char *branch1,
 -                                   struct rename *ren2,
 -                                   const char *branch2)
 +static void conflict_rename_rename_2to1(struct merge_options *o,
 +                                      struct rename *ren1,
 +                                      const char *branch1,
 +                                      struct rename *ren2,
 +                                      const char *branch2)
  {
 +      /* Two files were renamed to the same thing. */
        char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
        char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
        output(o, 1, "Renaming %s to %s and %s to %s instead",
@@@ -940,22 -806,22 +940,22 @@@ static int process_renames(struct merge
                           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
 +              string_list_insert(&a_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
        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
 +              string_list_insert(&b_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
  
        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;
                        ren2 = ren1;
                        ren1 = tmp;
                }
 -              src = ren1->pair->one->path;
  
                ren1->dst_entry->processed = 1;
                ren1->src_entry->processed = 1;
                        ren2->dst_entry->processed = 1;
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
 -                              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);
 +                              setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
 +                                                            ren1->pair,
 +                                                            ren2->pair,
 +                                                            branch1,
 +                                                            branch2,
 +                                                            ren1->dst_entry,
 +                                                            ren2->dst_entry);
                        } else {
 -                              struct merge_file_info mfi;
                                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_stages_and_entry(ren1_dst,
 +                                                      ren1->dst_entry,
 +                                                      ren1->pair->one,
 +                                                      ren1->pair->two,
 +                                                      ren2->pair->two,
 +                                                      1 /* clear */);
                        }
                } 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;
 +                      remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
  
 +                      hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
 +                      src_other.mode = ren1->src_entry->stages[other_stage].mode;
 +                      hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
 +                      dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
                        try_merge = 0;
  
 -                      if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
 -                              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)) {
 +                              if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
 +                                      ren1->dst_entry->processed = 0;
 +                                      setup_rename_df_conflict_info(RENAME_DELETE,
 +                                                                    ren1->pair,
 +                                                                    NULL,
 +                                                                    branch1,
 +                                                                    branch2,
 +                                                                    ren1->dst_entry,
 +                                                                    NULL);
 +                              } else {
 +                                      clean_merge = 0;
 +                                      conflict_rename_delete(o, ren1->pair, branch1, branch2);
 +                              }
 +                      } else if ((dst_other.mode == ren1->pair->two->mode) &&
 +                                 sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
 +                              /* Added file on the other side
 +                                 identical to the file being
 +                                 renamed: clean merge */
 +                              update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
                                const char *new_path;
                                clean_merge = 0;
                                                    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);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
                                }
 -                      } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
 +                      } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
                                       "Rename %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
 -                              conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
 +                              conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
                        } else
                                try_merge = 1;
  
                        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_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
 +                              if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
 +                                      setup_rename_df_conflict_info(RENAME_NORMAL,
 +                                                                    ren1->pair,
 +                                                                    NULL,
 +                                                                    branch1,
 +                                                                    NULL,
 +                                                                    ren1->dst_entry,
 +                                                                    NULL);
                                }
                        }
                }
@@@ -1153,137 -1056,6 +1153,137 @@@ static unsigned char *stage_sha(const u
        return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
  }
  
 +static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
 +{
 +      void *buf;
 +      enum object_type type;
 +      unsigned long size;
 +      buf = read_sha1_file(sha1, &type, &size);
 +      if (!buf)
 +              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));
 +      }
 +      strbuf_attach(dst, buf, size, size + 1);
 +      return 0;
 +}
 +
 +static int blob_unchanged(const unsigned char *o_sha,
 +                        const unsigned char *a_sha,
 +                        int renormalize, const char *path)
 +{
 +      struct strbuf o = STRBUF_INIT;
 +      struct strbuf a = STRBUF_INIT;
 +      int ret = 0; /* assume changed for safety */
 +
 +      if (sha_eq(o_sha, a_sha))
 +              return 1;
 +      if (!renormalize)
 +              return 0;
 +
 +      assert(o_sha && a_sha);
 +      if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
 +              goto error_return;
 +      /*
 +       * Note: binary | is used so that both renormalizations are
 +       * performed.  Comparison can be skipped if both files are
 +       * 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))
 +              ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
 +
 +error_return:
 +      strbuf_release(&o);
 +      strbuf_release(&a);
 +      return ret;
 +}
 +
 +static void handle_delete_modify(struct merge_options *o,
 +                               const char *path,
 +                               const char *new_path,
 +                               unsigned char *a_sha, int a_mode,
 +                               unsigned char *b_sha, int b_mode)
 +{
 +      if (!a_sha) {
 +              output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
 +                     "and modified in %s. Version %s of %s left in tree%s%s.",
 +                     path, o->branch1,
 +                     o->branch2, o->branch2, path,
 +                     path == new_path ? "" : " at ",
 +                     path == new_path ? "" : new_path);
 +              update_file(o, 0, b_sha, b_mode, new_path);
 +      } else {
 +              output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
 +                     "and modified in %s. Version %s of %s left in tree%s%s.",
 +                     path, o->branch2,
 +                     o->branch1, o->branch1, path,
 +                     path == new_path ? "" : " at ",
 +                     path == new_path ? "" : new_path);
 +              update_file(o, 0, a_sha, a_mode, new_path);
 +      }
 +}
 +
 +static int merge_content(struct merge_options *o,
 +                       const char *path,
 +                       unsigned char *o_sha, int o_mode,
 +                       unsigned char *a_sha, int a_mode,
 +                       unsigned char *b_sha, int b_mode,
 +                       const char *df_rename_conflict_branch)
 +{
 +      const char *reason = "content";
 +      struct merge_file_info mfi;
 +      struct diff_filespec one, a, b;
 +      struct stat st;
 +      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;
 +
 +      mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
 +      if (df_rename_conflict_branch &&
 +          lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
 +              df_conflict_remains = 1;
 +      }
 +
 +      if (mfi.clean && !df_conflict_remains &&
 +          sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
 +              output(o, 3, "Skipped %s (merged same as existing)", path);
 +      else
 +              output(o, 2, "Auto-merging %s", path);
 +
 +      if (!mfi.clean) {
 +              if (S_ISGITLINK(mfi.mode))
 +                      reason = "submodule";
 +              output(o, 1, "CONFLICT (%s): Merge conflict in %s",
 +                              reason, path);
 +      }
 +
 +      if (df_conflict_remains) {
 +              const char *new_path;
 +              update_file_flags(o, mfi.sha, mfi.mode, path,
 +                                o->call_depth || mfi.clean, 0);
 +              new_path = unique_path(o, path, df_rename_conflict_branch);
 +              mfi.clean = 0;
 +              output(o, 1, "Adding as %s instead", new_path);
 +              update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
 +      } 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)
        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 (entry->rename_df_conflict_info)
 +              return 1; /* Such cases are handled elsewhere. */
 +
 +      entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
 -                  (sha_eq(a_sha, o_sha) && !b_sha) ||
 -                  (!a_sha && sha_eq(b_sha, o_sha))) {
 +                  (!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);
                        /* do not touch working file if it did not exist */
                        remove_file(o, 1, path, !a_sha);
 +              } else if (string_list_has_string(&o->current_directory_set,
 +                                                path)) {
 +                      entry->processed = 0;
 +                      return 1; /* Assume clean until processed */
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
 -                      if (!a_sha) {
 -                              output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
 -                                     "and modified in %s. Version %s of %s left in tree.",
 -                                     path, o->branch1,
 -                                     o->branch2, o->branch2, path);
 -                              update_file(o, 0, b_sha, b_mode, path);
 -                      } else {
 -                              output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
 -                                     "and modified in %s. Version %s of %s left in tree.",
 -                                     path, o->branch2,
 -                                     o->branch1, o->branch1, path);
 -                              update_file(o, 0, a_sha, a_mode, path);
 -                      }
 +                      handle_delete_modify(o, path, path,
 +                                           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. */
 -              const char *add_branch;
 -              const char *other_branch;
                unsigned mode;
                const unsigned char *sha;
 -              const char *conf;
  
                if (a_sha) {
 -                      add_branch = o->branch1;
 -                      other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
 -                      conf = "file/directory";
                } else {
 -                      add_branch = o->branch2;
 -                      other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
 -                      conf = "directory/file";
                }
                if (string_list_has_string(&o->current_directory_set, path)) {
 -                      const char *new_path = unique_path(o, path, add_branch);
 -                      clean_merge = 0;
 -                      output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 -                             "Adding %s as %s",
 -                             conf, path, other_branch, path, new_path);
 -                      remove_file(o, 0, path, 0);
 -                      update_file(o, 0, sha, mode, new_path);
 +                      /* Handle D->F conflicts after all subfiles */
 +                      entry->processed = 0;
 +                      return 1; /* Assume clean until processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
        } 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
        return clean_merge;
  }
  
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 +/*
 + * Per entry merge function for D/F (and/or rename) conflicts.  In the
 + * cases we can cleanly resolve D/F conflicts, process_entry() can
 + * clean out all the files below the directory for us.  All D/F
 + * conflict cases must be handled here at the end to make sure any
 + * directories that can be cleaned out, are.
 + *
 + * Some rename conflicts may also be handled here that don't necessarily
 + * involve D/F conflicts, since the code to handle them is generic enough
 + * to handle those rename conflicts with or without D/F conflicts also
 + * being involved.
 + */
 +static int process_df_entry(struct merge_options *o,
 +                          const char *path, struct stage_data *entry)
  {
 -      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.";
 +      int clean_merge = 1;
 +      unsigned o_mode = entry->stages[1].mode;
 +      unsigned a_mode = entry->stages[2].mode;
 +      unsigned b_mode = entry->stages[3].mode;
 +      unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
 +      unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
 +      unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 +      struct stat st;
 +
 +      entry->processed = 1;
 +      if (entry->rename_df_conflict_info) {
 +              struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
 +              char *src;
 +              switch (conflict_info->rename_type) {
 +              case RENAME_NORMAL:
 +                      clean_merge = merge_content(o, path,
 +                                                  o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
 +                                                  conflict_info->branch1);
 +                      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:
 +                      src = conflict_info->pair1->one->path;
 +                      clean_merge = 0;
 +                      output(o, 1, "CONFLICT (rename/rename): "
 +                             "Rename \"%s\"->\"%s\" in branch \"%s\" "
 +                             "rename \"%s\"->\"%s\" in \"%s\"%s",
 +                             src, conflict_info->pair1->two->path, conflict_info->branch1,
 +                             src, conflict_info->pair2->two->path, conflict_info->branch2,
 +                             o->call_depth ? " (left unresolved)" : "");
 +                      if (o->call_depth) {
 +                              remove_file_from_cache(src);
 +                              update_file(o, 0, conflict_info->pair1->one->sha1,
 +                                          conflict_info->pair1->one->mode, src);
 +                      }
 +                      conflict_rename_rename_1to2(o, conflict_info->pair1,
 +                                                  conflict_info->branch1,
 +                                                  conflict_info->pair2,
 +                                                  conflict_info->branch2);
 +                      conflict_info->dst_entry2->processed = 1;
 +                      break;
 +              default:
 +                      entry->processed = 0;
 +                      break;
 +              }
 +      } else if (o_sha && (!a_sha || !b_sha)) {
 +              /* Modify/delete; deleted side may have put a directory in the way */
 +              const char *new_path = path;
 +              if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
 +                      new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
 +              clean_merge = 0;
 +              handle_delete_modify(o, path, new_path,
 +                                   a_sha, a_mode, b_sha, b_mode);
 +      } else if (!o_sha && !!a_sha != !!b_sha) {
 +              /* directory -> (directory, file) */
 +              const char *add_branch;
 +              const char *other_branch;
 +              unsigned mode;
 +              const unsigned char *sha;
 +              const char *conf;
 +
 +              if (a_sha) {
 +                      add_branch = o->branch1;
 +                      other_branch = o->branch2;
 +                      mode = a_mode;
 +                      sha = a_sha;
 +                      conf = "file/directory";
 +              } else {
 +                      add_branch = o->branch2;
 +                      other_branch = o->branch1;
 +                      mode = b_mode;
 +                      sha = b_sha;
 +                      conf = "directory/file";
 +              }
 +              if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
 +                      const char *new_path = unique_path(o, path, add_branch);
 +                      clean_merge = 0;
 +                      output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 +                             "Adding %s as %s",
 +                             conf, path, other_branch, path, new_path);
 +                      update_file(o, 0, sha, mode, new_path);
 +              } else {
 +                      output(o, 2, "Adding %s", path);
 +                      update_file(o, 1, sha, mode, path);
 +              }
 +      } else {
 +              entry->processed = 0;
 +              return 1; /* not handled; assume clean until processed */
        }
 -      return msgs;
 +
 +      return clean_merge;
  }
  
  int merge_trees(struct merge_options *o,
        }
  
        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;
        }
                get_files_dirs(o, merge);
  
                entries = get_unmerged();
 +              make_room_for_directories_of_df_conflicts(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);
                                && !process_entry(o, path, e))
                                clean = 0;
                }
 +              for (i = 0; i < entries->nr; i++) {
 +                      const char *path = entries->items[i].string;
 +                      struct stage_data *e = entries->items[i].util;
 +                      if (!e->processed
 +                              && !process_df_entry(o, path, e))
 +                              clean = 0;
 +              }
 +              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);
@@@ -1601,12 -1309,10 +1601,10 @@@ int merge_recursive(struct merge_option
  
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
-               /* if there is no common ancestor, make an empty tree */
-               struct tree *tree = xcalloc(1, sizeof(struct tree));
+               /* if there is no common ancestor, use an empty tree */
+               struct tree *tree;
  
-               tree->object.parsed = 1;
-               tree->object.type = OBJ_TREE;
-               pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+               tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
  
                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;
  }
  
@@@ -1709,15 -1412,15 +1707,15 @@@ int merge_recursive_generic(struct merg
  static int merge_recursive_config(const char *var, const char *value, void *cb)
  {
        struct merge_options *o = cb;
 -      if (!strcasecmp(var, "merge.verbosity")) {
 +      if (!strcmp(var, "merge.verbosity")) {
                o->verbosity = git_config_int(var, value);
                return 0;
        }
 -      if (!strcasecmp(var, "diff.renamelimit")) {
 +      if (!strcmp(var, "diff.renamelimit")) {
                o->diff_rename_limit = git_config_int(var, value);
                return 0;
        }
 -      if (!strcasecmp(var, "merge.renamelimit")) {
 +      if (!strcmp(var, "merge.renamelimit")) {
                o->merge_rename_limit = git_config_int(var, value);
                return 0;
        }
@@@ -1731,7 -1434,6 +1729,7 @@@ void init_merge_options(struct merge_op
        o->buffer_output = 1;
        o->diff_rename_limit = -1;
        o->merge_rename_limit = -1;
 +      o->renormalize = 0;
        git_config(merge_recursive_config, o);
        if (getenv("GIT_MERGE_VERBOSITY"))
                o->verbosity =
        memset(&o->current_directory_set, 0, sizeof(struct string_list));
        o->current_directory_set.strdup_strings = 1;
  }
 +
 +int parse_merge_opt(struct merge_options *o, const char *s)
 +{
 +      if (!s || !*s)
 +              return -1;
 +      if (!strcmp(s, "ours"))
 +              o->recursive_variant = MERGE_RECURSIVE_OURS;
 +      else if (!strcmp(s, "theirs"))
 +              o->recursive_variant = MERGE_RECURSIVE_THEIRS;
 +      else if (!strcmp(s, "subtree"))
 +              o->subtree_shift = "";
 +      else if (!prefixcmp(s, "subtree="))
 +              o->subtree_shift = s + strlen("subtree=");
 +      else if (!strcmp(s, "patience"))
 +              o->xdl_opts |= XDF_PATIENCE_DIFF;
 +      else if (!strcmp(s, "histogram"))
 +              o->xdl_opts |= XDF_HISTOGRAM_DIFF;
 +      else if (!strcmp(s, "ignore-space-change"))
 +              o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
 +      else if (!strcmp(s, "ignore-all-space"))
 +              o->xdl_opts |= XDF_IGNORE_WHITESPACE;
 +      else if (!strcmp(s, "ignore-space-at-eol"))
 +              o->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
 +      else if (!strcmp(s, "renormalize"))
 +              o->renormalize = 1;
 +      else if (!strcmp(s, "no-renormalize"))
 +              o->renormalize = 0;
 +      else if (!prefixcmp(s, "rename-threshold=")) {
 +              const char *score = s + strlen("rename-threshold=");
 +              if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
 +                      return -1;
 +      }
 +      else
 +              return -1;
 +      return 0;
 +}
index 9aefe3a1becac200f2c29beee84fab278f9fcfa0,472e5b80d7ac9b5c90810bd0869510e1e2970f81..e27f39d1e5b0fb0ac3a1fc6417f0f3240934f07d
@@@ -1,6 -1,6 +1,6 @@@
  #!/bin/sh
  
 -test_description='test cherry-picking a root commit'
 +test_description='test cherry-picking (and reverting) a root commit'
  
  . ./test-lib.sh
  
@@@ -16,38 -16,40 +16,63 @@@ test_expect_success setup 
        echo second > file2 &&
        git add file2 &&
        test_tick &&
-       git commit -m "second"
+       git commit -m "second" &&
+       git symbolic-ref HEAD refs/heads/third &&
+       rm .git/index file2 &&
+       echo third > file3 &&
+       git add file3 &&
+       test_tick &&
+       git commit -m "third"
  
  '
  
  test_expect_success 'cherry-pick a root commit' '
  
+       git checkout second^0 &&
        git cherry-pick master &&
 -      test first = $(cat file1)
 +      echo first >expect &&
 +      test_cmp expect file1
 +
 +'
 +
 +test_expect_success 'revert a root commit' '
 +
 +      git revert master &&
 +      test_path_is_missing file1
 +
 +'
 +
 +test_expect_success 'cherry-pick a root commit with an external strategy' '
 +
 +      git cherry-pick --strategy=resolve master &&
 +      echo first >expect &&
 +      test_cmp expect file1
 +
 +'
 +
 +test_expect_success 'revert a root commit with an external strategy' '
 +
 +      git revert --strategy=resolve master &&
 +      test_path_is_missing file1
  
  '
  
+ test_expect_success 'cherry-pick two root commits' '
+       echo first >expect.file1 &&
+       echo second >expect.file2 &&
+       echo third >expect.file3 &&
+       git checkout second^0 &&
+       git cherry-pick master third &&
+       test_cmp expect.file1 file1 &&
+       test_cmp expect.file2 file2 &&
+       test_cmp expect.file3 file3 &&
+       git rev-parse --verify HEAD^^ &&
+       test_must_fail git rev-parse --verify HEAD^^^
+ '
  test_done