Merge branch 'jf/merge-ignore-ws'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Oct 2010 04:40:54 +0000 (21:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Oct 2010 04:40:54 +0000 (21:40 -0700)
* jf/merge-ignore-ws:
merge-recursive: options to ignore whitespace changes
merge-recursive --patience
ll-merge: replace flag argument with options struct
merge-recursive: expose merge options for builtin merge

1  2 
Documentation/merge-strategies.txt
builtin/checkout.c
builtin/merge-recursive.c
builtin/merge.c
merge-recursive.c
merge-recursive.h
rerere.c
index 8676e26ca20311196d069ab28c08c913271c7631,91faba5da9333c936376c6e91f9296bb15df5b0e..9cf88e2bb5baa51c334e6d7232f926b49aef3681
@@@ -40,6 -40,28 +40,28 @@@ the other tree did, declaring 'our' his
  theirs;;
        This is opposite of 'ours'.
  
+ patience;;
+       With this option, 'merge-recursive' spends a little extra time
+       to avoid mismerges that sometimes occur due to unimportant
+       matching lines (e.g., braces from distinct functions).  Use
+       this when the branches to be merged have diverged wildly.
+       See also linkgit:git-diff[1] `--patience`.
+ ignore-space-change;;
+ ignore-all-space;;
+ ignore-space-at-eol;;
+       Treats lines with the indicated type of whitespace change as
+       unchanged for the sake of a three-way merge.  Whitespace
+       changes mixed with other changes to a line are not ignored.
+       See also linkgit:git-diff[1] `-b`, `-w`, and
+       `--ignore-space-at-eol`.
+ +
+ * If 'their' version only introduces whitespace changes to a line,
+   'our' version is used;
+ * If 'our' version introduces whitespace changes but 'their'
+   version includes a substantial change, 'their' version is used;
+ * Otherwise, the merge proceeds in the usual way.
  renormalize;;
        This runs a virtual check-out and check-in of all three stages
        of a file when resolving a three-way merge.  This option is
@@@ -52,7 -74,7 +74,7 @@@ no-renormalize;
        Disables the `renormalize` option.  This overrides the
        `merge.renormalize` configuration variable.
  
 -subtree[=path];;
 +subtree[=<path>];;
        This option is a more advanced form of 'subtree' strategy, where
        the strategy makes a guess on how two trees must be shifted to
        match with each other when merging.  Instead, the specified path
diff --combined builtin/checkout.c
index a54583b3a4936b341820cf5c639e5647ce456a5a,4d36b2897fc4a31795261194a681266dd3a502be..9240fafb2ad17505bf407363e058d208a8a70497
@@@ -18,7 -18,6 +18,7 @@@
  #include "xdiff-interface.h"
  #include "ll-merge.h"
  #include "resolve-undo.h"
 +#include "submodule.h"
  
  static const char * const checkout_usage[] = {
        "git checkout [options] <branch>",
@@@ -33,15 -32,10 +33,15 @@@ struct checkout_opts 
        int writeout_stage;
        int writeout_error;
  
 +      /* not set by parse_options */
 +      int branch_exists;
 +
        const char *new_branch;
 +      const char *new_branch_force;
        const char *new_orphan_branch;
        int new_branch_log;
        enum branch_track track;
 +      struct diff_options diff_options;
  };
  
  static int post_checkout_hook(struct commit *old, struct commit *new,
@@@ -161,7 -155,7 +161,7 @@@ static int checkout_merged(int pos, str
         * merge.renormalize set, too
         */
        status = ll_merge(&result_buf, path, &ancestor, "base",
-                         &ours, "ours", &theirs, "theirs", 0);
+                         &ours, "ours", &theirs, "theirs", NULL);
        free(ancestor.ptr);
        free(ours.ptr);
        free(theirs.ptr);
@@@ -284,12 -278,12 +284,12 @@@ static int checkout_paths(struct tree *
        return errs;
  }
  
 -static void show_local_changes(struct object *head)
 +static void show_local_changes(struct object *head, struct diff_options *opts)
  {
        struct rev_info rev;
        /* I think we want full paths, even if we're in a subdirectory. */
        init_revisions(&rev, NULL);
 -      rev.abbrev = 0;
 +      rev.diffopt.flags = opts->flags;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
        if (diff_setup_done(&rev.diffopt) < 0)
                die("diff_setup_done failed");
@@@ -383,7 -377,7 +383,7 @@@ static int merge_working_tree(struct ch
                topts.src_index = &the_index;
                topts.dst_index = &the_index;
  
 -              topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
 +              setup_unpack_trees_porcelain(&topts, "checkout");
  
                refresh_cache(REFRESH_QUIET);
  
                die("unable to write new index file");
  
        if (!opts->force && !opts->quiet)
 -              show_local_changes(&new->commit->object);
 +              show_local_changes(&new->commit->object, &opts->diff_options);
  
        return 0;
  }
@@@ -528,8 -522,7 +528,8 @@@ static void update_refs_for_switch(stru
                        }
                }
                else
 -                      create_branch(old->name, opts->new_branch, new->name, 0,
 +                      create_branch(old->name, opts->new_branch, new->name,
 +                                    opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log, opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
                        if (old->path && !strcmp(new->path, old->path))
                                fprintf(stderr, "Already on '%s'\n",
                                        new->name);
 -                      else
 +                      else if (opts->new_branch)
                                fprintf(stderr, "Switched to%s branch '%s'\n",
 -                                      opts->new_branch ? " a new" : "",
 +                                      opts->branch_exists ? " and reset" : " a new",
 +                                      new->name);
 +                      else
 +                              fprintf(stderr, "Switched to branch '%s'\n",
                                        new->name);
                }
                if (old->path && old->name) {
@@@ -621,16 -611,7 +621,16 @@@ static int switch_branches(struct check
  
  static int git_checkout_config(const char *var, const char *value, void *cb)
  {
 -      return git_xmerge_config(var, value, cb);
 +      if (!strcmp(var, "diff.ignoresubmodules")) {
 +              struct checkout_opts *opts = cb;
 +              handle_ignore_submodules_arg(&opts->diff_options, value);
 +              return 0;
 +      }
 +
 +      if (!prefixcmp(var, "submodule."))
 +              return parse_submodule_config_option(var, value);
 +
 +      return git_xmerge_config(var, value, NULL);
  }
  
  static int interactive_checkout(const char *revision, const char **pathspec,
@@@ -687,20 -668,17 +687,20 @@@ int cmd_checkout(int argc, const char *
        int dwim_new_local_branch = 1;
        struct option options[] = {
                OPT__QUIET(&opts.quiet),
 -              OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
 -              OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
 -              OPT_SET_INT('t', "track",  &opts.track, "track",
 +              OPT_STRING('b', NULL, &opts.new_branch, "branch",
 +                         "create and checkout a new branch"),
 +              OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
 +                         "create/reset and checkout a branch"),
 +              OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
 +              OPT_SET_INT('t', "track",  &opts.track, "set upstream info for new branch",
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
 -              OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
 +              OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files",
                            2),
 -              OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
 +              OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files",
                            3),
 -              OPT_BOOLEAN('f', "force", &opts.force, "force"),
 -              OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
 +              OPT_BOOLEAN('f', "force", &opts.force, "force checkout (throw away local modifications)"),
 +              OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
                OPT_STRING(0, "conflict", &conflict_style, "style",
                           "conflict style (merge or diff3)"),
                OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
  
 -      git_config(git_checkout_config, NULL);
 +      gitmodules_config();
 +      git_config(git_checkout_config, &opts);
  
        opts.track = BRANCH_TRACK_UNSPECIFIED;
  
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      /* we can assume from now on new_branch = !new_branch_force */
 +      if (opts.new_branch && opts.new_branch_force)
 +              die("-B cannot be used with -b");
 +
 +      /* copy -B over to -b, so that we can just check the latter */
 +      if (opts.new_branch_force)
 +              opts.new_branch = opts.new_branch_force;
 +
        if (patch_mode && (opts.track > 0 || opts.new_branch
                           || opts.new_branch_log || opts.merge || opts.force))
                die ("--patch is incompatible with all other options");
  
        if (opts.new_orphan_branch) {
                if (opts.new_branch)
 -                      die("--orphan and -b are mutually exclusive");
 +                      die("--orphan and -b|-B are mutually exclusive");
                if (opts.track > 0)
                        die("--orphan cannot be used with -t");
                opts.new_branch = opts.new_orphan_branch;
@@@ -900,12 -869,8 +900,12 @@@ no_reference
                if (strbuf_check_branch_ref(&buf, opts.new_branch))
                        die("git checkout: we do not like '%s' as a branch name.",
                            opts.new_branch);
 -              if (!get_sha1(buf.buf, rev))
 -                      die("git checkout: branch %s already exists", opts.new_branch);
 +              if (!get_sha1(buf.buf, rev)) {
 +                      opts.branch_exists = 1;
 +                      if (!opts.new_branch_force)
 +                              die("git checkout: branch %s already exists",
 +                                  opts.new_branch);
 +              }
                strbuf_release(&buf);
        }
  
index 78b9db76a0819529e7f6cab44319acd2978c9e9b,70e1d258fb49ad917674f62cd1864d3a1517752a..c33091b3ed52bc8539ff82f039ec8c7718f3dcc2
@@@ -2,10 -2,8 +2,11 @@@
  #include "commit.h"
  #include "tag.h"
  #include "merge-recursive.h"
+ #include "xdiff-interface.h"
  
 +static const char builtin_merge_recursive_usage[] =
 +      "git %s <base>... -- <head> <remote> ...";
 +
  static const char *better_branch_name(const char *branch)
  {
        static char githead_env[8 + 40 + 1];
@@@ -32,7 -30,7 +33,7 @@@ int cmd_merge_recursive(int argc, cons
                o.subtree_shift = "";
  
        if (argc < 4)
 -              usagef("%s <base>... -- <head> <remote> ...", argv[0]);
 +              usagef(builtin_merge_recursive_usage, argv[0]);
  
        for (i = 1; i < argc; ++i) {
                const char *arg = argv[i];
                if (!prefixcmp(arg, "--")) {
                        if (!arg[2])
                                break;
-                       if (!strcmp(arg+2, "ours"))
-                               o.recursive_variant = MERGE_RECURSIVE_OURS;
-                       else if (!strcmp(arg+2, "theirs"))
-                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
-                       else if (!strcmp(arg+2, "subtree"))
-                               o.subtree_shift = "";
-                       else if (!prefixcmp(arg+2, "subtree="))
-                               o.subtree_shift = arg + 10;
-                       else if (!strcmp(arg+2, "renormalize"))
-                               o.renormalize = 1;
-                       else if (!strcmp(arg+2, "no-renormalize"))
-                               o.renormalize = 0;
-                       else
+                       if (parse_merge_opt(&o, arg + 2))
                                die("Unknown option %s", arg);
                        continue;
                }
diff --combined builtin/merge.c
index 2dba3b9901cbdca9aee279339ecd3537e15e39da,721c424e558329feff10e55456f15bf728b3b880..10f091b51960cd6ae913b1c4d2ab9e6931862351
@@@ -42,7 -42,7 +42,7 @@@ static const char * const builtin_merge
        NULL
  };
  
 -static int show_diffstat = 1, option_log, squash;
 +static int show_diffstat = 1, shortlog_len, squash;
  static int option_commit = 1, allow_fast_forward = 1;
  static int fast_forward_only;
  static int allow_trivial = 1, have_message;
@@@ -132,7 -132,6 +132,7 @@@ static struct strategy *get_strategy(co
  
        ret = xcalloc(1, sizeof(struct strategy));
        ret->name = xstrdup(name);
 +      ret->attr = NO_TRIVIAL;
        return ret;
  }
  
@@@ -177,9 -176,8 +177,9 @@@ static struct option builtin_merge_opti
        OPT_BOOLEAN(0, "stat", &show_diffstat,
                "show a diffstat at the end of the merge"),
        OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
 -      OPT_BOOLEAN(0, "log", &option_log,
 -              "add list of one-line log to merge commit message"),
 +      { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
 +        "add (at most <n>) entries from shortlog to merge commit message",
 +        PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
        OPT_BOOLEAN(0, "squash", &squash,
                "create a single commit instead of doing a merge"),
        OPT_BOOLEAN(0, "commit", &option_commit,
@@@ -440,7 -438,7 +440,7 @@@ static void merge_name(const char *remo
                strbuf_addstr(&truname, "refs/heads/");
                strbuf_addstr(&truname, remote);
                strbuf_setlen(&truname, truname.len - len);
 -              if (resolve_ref(truname.buf, buf_sha, 0, NULL)) {
 +              if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
                                    sha1_to_hex(remote_head->sha1),
@@@ -489,8 -487,7 +489,8 @@@ static int git_merge_config(const char 
                buf = xstrdup(v);
                argc = split_cmdline(buf, &argv);
                if (argc < 0)
 -                      die("Bad branch.%s.mergeoptions string", branch);
 +                      die("Bad branch.%s.mergeoptions string: %s", branch,
 +                          split_cmdline_strerror(argc));
                argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
                memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
                argc++;
                return git_config_string(&pull_twohead, k, v);
        else if (!strcmp(k, "pull.octopus"))
                return git_config_string(&pull_octopus, k, v);
 -      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
 -              option_log = git_config_bool(k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
 +      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
 +              int is_bool;
 +              shortlog_len = git_config_bool_or_int(k, v, &is_bool);
 +              if (!is_bool && shortlog_len < 0)
 +                      return error("%s: negative length %s", k, v);
 +              if (is_bool && shortlog_len)
 +                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 +              return 0;
 +      }
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -639,25 -629,9 +639,9 @@@ static int try_merge_strategy(const cha
  
                o.renormalize = option_renormalize;
  
-               /*
-                * NEEDSWORK: merge with table in builtin/merge-recursive
-                */
-               for (x = 0; x < xopts_nr; x++) {
-                       if (!strcmp(xopts[x], "ours"))
-                               o.recursive_variant = MERGE_RECURSIVE_OURS;
-                       else if (!strcmp(xopts[x], "theirs"))
-                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
-                       else if (!strcmp(xopts[x], "subtree"))
-                               o.subtree_shift = "";
-                       else if (!prefixcmp(xopts[x], "subtree="))
-                               o.subtree_shift = xopts[x]+8;
-                       else if (!strcmp(xopts[x], "renormalize"))
-                               o.renormalize = 1;
-                       else if (!strcmp(xopts[x], "no-renormalize"))
-                               o.renormalize = 0;
-                       else
+               for (x = 0; x < xopts_nr; x++)
+                       if (parse_merge_opt(&o, xopts[x]))
                                die("Unknown option for merge-recursive: -X%s", xopts[x]);
-               }
  
                o.branch1 = head_arg;
                o.branch2 = remoteheads->item->util;
@@@ -726,7 -700,7 +710,7 @@@ int checkout_fast_forward(const unsigne
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
 -      opts.msgs = get_porcelain_error_msgs();
 +      setup_unpack_trees_porcelain(&opts, "merge");
  
        trees[nr_trees] = parse_tree_indirect(head);
        if (!trees[nr_trees++])
@@@ -1020,12 -994,14 +1004,12 @@@ int cmd_merge(int argc, const char **ar
                for (i = 0; i < argc; i++)
                        merge_name(argv[i], &merge_names);
  
 -              if (have_message && option_log)
 -                      fmt_merge_msg_shortlog(&merge_names, &merge_msg);
 -              else if (!have_message)
 -                      fmt_merge_msg(option_log, &merge_names, &merge_msg);
 -
 -
 -              if (!(have_message && !option_log) && merge_msg.len)
 -                      strbuf_setlen(&merge_msg, merge_msg.len-1);
 +              if (!have_message || shortlog_len) {
 +                      fmt_merge_msg(&merge_names, &merge_msg, !have_message,
 +                                    shortlog_len);
 +                      if (merge_msg.len)
 +                              strbuf_setlen(&merge_msg, merge_msg.len - 1);
 +              }
        }
  
        if (head_invalid || !argc)
diff --combined merge-recursive.c
index c5746988196b8139ca3234f8b30d03af56d33324,9b9f97e6afab1fb41463eb6176d08adbe235a803..325a97bf31c8b25871fe2c2eb4391f57beb02429
@@@ -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)
@@@ -137,10 -136,16 +137,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);
                }
        }
  }
@@@ -180,7 -185,7 +180,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);
@@@ -233,9 -238,9 +233,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);
@@@ -266,7 -271,7 +266,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;
  }
@@@ -289,9 -294,9 +289,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;
@@@ -351,20 -356,20 +351,20 @@@ static struct string_list *get_renames(
                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;
@@@ -427,7 -432,7 +427,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;
  }
  
@@@ -520,15 -525,13 +520,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)
@@@ -605,22 -608,26 +605,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 ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
-                                (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
-                                create_ll_flag(favor)));
+                               &src1, name1, &src2, name2, &ll_opts);
  
        free(name1);
        free(name2);
@@@ -715,8 -719,8 +716,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);
  
@@@ -805,18 -809,17 +806,18 @@@ 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;
        }
  
                                                        ren1->pair->two : NULL,
                                                        branch1 == o->branch1 ?
                                                        NULL : ren1->pair->two, 1);
 +                      } 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;
                                        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;
  
                                if (mfi.clean &&
                                    sha_eq(mfi.sha, ren1->pair->two->sha1) &&
 -                                  mfi.mode == ren1->pair->two->mode)
 +                                  mfi.mode == ren1->pair->two->mode) {
                                        /*
 -                                       * This messaged is part of
 +                                       * This message is part of
                                         * t6022 test. If you change
                                         * it update the test too.
                                         */
                                        output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
 -                              else {
 +
 +                                      /* There may be higher stage entries left
 +                                       * in the index (e.g. due to a D/F
 +                                       * conflict) that need to be resolved.
 +                                       */
 +                                      if (!ren1->dst_entry->stages[2].mode !=
 +                                          !ren1->dst_entry->stages[3].mode)
 +                                              ren1->dst_entry->processed = 0;
 +                              } else {
                                        if (mfi.merge || !mfi.clean)
                                                output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
@@@ -1134,7 -1123,6 +1135,7 @@@ static int process_entry(struct merge_o
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
  
 +      entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
        } 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;
 +                      /* But get any file out of the way now, so conflicted
 +                       * entries below the directory of the same name can
 +                       * be put in the working directory.
 +                       */
 +                      if (a_sha)
 +                              output(o, 2, "Removing %s", path);
 +                      /* do not touch working file if it did not exist */
 +                      remove_file(o, 0, path, !a_sha);
 +                      return 1; /* Assume clean till processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
        return clean_merge;
  }
  
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 +/*
 + * Per entry merge function for D/F conflicts, to be called only after
 + * all files below dir have been processed.  We do this because in the
 + * cases we can cleanly resolve D/F conflicts, process_entry() can clean
 + * out all the files below the directory for us.
 + */
 +static int process_df_entry(struct merge_options *o,
 +                       const char *path, struct stage_data *entry)
  {
 -      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);
 +      const char *add_branch;
 +      const char *other_branch;
 +      unsigned mode;
 +      const unsigned char *sha;
 +      const char *conf;
 +      struct stat st;
 +
 +      /* We currently only handle D->F cases */
 +      assert((!o_sha && a_sha && !b_sha) ||
 +             (!o_sha && !a_sha && b_sha));
 +
 +      entry->processed = 1;
 +
 +      if (a_sha) {
 +              add_branch = o->branch1;
 +              other_branch = o->branch2;
 +              mode = a_mode;
 +              sha = a_sha;
 +              conf = "file/directory";
 +      } else {
 +              add_branch = o->branch2;
 +              other_branch = o->branch1;
 +              mode = b_mode;
 +              sha = b_sha;
 +              conf = "directory/file";
        }
 -      return msgs;
 +      if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
 +              const char *new_path = unique_path(o, path, add_branch);
 +              clean_merge = 0;
 +              output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 +                     "Adding %s as %s",
 +                     conf, path, other_branch, path, new_path);
 +              remove_file(o, 0, path, 0);
 +              update_file(o, 0, sha, mode, new_path);
 +      } else {
 +              output(o, 2, "Adding %s", path);
 +              update_file(o, 1, sha, mode, path);
 +      }
 +
 +      return clean_merge;
  }
  
  int merge_trees(struct merge_options *o,
        }
  
        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;
        }
                                && !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;
 +              }
  
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
@@@ -1550,3 -1500,32 +1551,32 @@@ void init_merge_options(struct merge_op
        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, "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
+               return -1;
+       return 0;
+ }
diff --combined merge-recursive.h
index 34492dbd6ea22d715c98acbeeabe04033ff9a794,d21b446a1df8add6f2824c77a8896b9098d5c0fa..2eb5d1aad531a6ba822d74f1231859aec47c133b
@@@ -15,6 -15,7 +15,7 @@@ struct merge_options 
        const char *subtree_shift;
        unsigned buffer_output : 1;
        unsigned renormalize : 1;
+       long xdl_opts;
        int verbosity;
        int diff_rename_limit;
        int merge_rename_limit;
@@@ -24,6 -25,9 +25,6 @@@
        struct string_list current_directory_set;
  };
  
 -/* Return a list of user-friendly error messages to be used by merge */
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
 -
  /* merge_trees() but with recursive ancestor consolidation */
  int merge_recursive(struct merge_options *o,
                    struct commit *h1,
@@@ -52,6 -56,8 +53,8 @@@ int merge_recursive_generic(struct merg
  void init_merge_options(struct merge_options *o);
  struct tree *write_tree_from_memory(struct merge_options *o);
  
+ int parse_merge_opt(struct merge_options *out, const char *s);
  /* builtin/merge.c */
  int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
  
diff --combined rerere.c
index 861ca7c815b4857f1fde399617860e5a179e2ca3,b180218d0d5420ea9205ffc8e9fccf37e1dc65c8..d2608434750c336c3f3881efda8373b7c67d4b11
+++ b/rerere.c
@@@ -46,7 -46,7 +46,7 @@@ static void read_rr(struct string_list 
                        ; /* do nothing */
                if (i == sizeof(buf))
                        die("filename too long");
 -              string_list_insert(buf, rr)->util = name;
 +              string_list_insert(rr, buf)->util = name;
        }
        fclose(in);
  }
@@@ -325,7 -325,7 +325,7 @@@ static int handle_cache(const char *pat
         */
        ll_merge(&result, path, &mmfile[0], NULL,
                 &mmfile[1], "ours",
-                &mmfile[2], "theirs", 0);
+                &mmfile[2], "theirs", NULL);
        for (i = 0; i < 3; i++)
                free(mmfile[i].ptr);
  
@@@ -358,7 -358,7 +358,7 @@@ static int find_conflict(struct string_
                    ce_same_name(e2, e3) &&
                    S_ISREG(e2->ce_mode) &&
                    S_ISREG(e3->ce_mode)) {
 -                      string_list_insert((const char *)e2->name, conflict);
 +                      string_list_insert(conflict, (const char *)e2->name);
                        i++; /* skip over both #2 and #3 */
                }
        }
@@@ -382,13 -382,7 +382,13 @@@ static int merge(const char *name, cons
        }
        ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0);
        if (!ret) {
 -              FILE *f = fopen(path, "w");
 +              FILE *f;
 +
 +              if (utime(rerere_path(name, "postimage"), NULL) < 0)
 +                      warning("failed utime() on %s: %s",
 +                                      rerere_path(name, "postimage"),
 +                                      strerror(errno));
 +              f = fopen(path, "w");
                if (!f)
                        return error("Could not open %s: %s", path,
                                     strerror(errno));
@@@ -436,8 -430,8 +436,8 @@@ static int update_paths(struct string_l
  
  static int do_plain_rerere(struct string_list *rr, int fd)
  {
 -      struct string_list conflict = { NULL, 0, 0, 1 };
 -      struct string_list update = { NULL, 0, 0, 1 };
 +      struct string_list conflict = STRING_LIST_INIT_DUP;
 +      struct string_list update = STRING_LIST_INIT_DUP;
        int i;
  
        find_conflict(&conflict);
                        if (ret < 1)
                                continue;
                        hex = xstrdup(sha1_to_hex(sha1));
 -                      string_list_insert(path, rr)->util = hex;
 +                      string_list_insert(rr, path)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
                                continue;
                        handle_file(path, NULL, rerere_path(hex, "preimage"));
                if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
                                if (rerere_autoupdate)
 -                                      string_list_insert(path, &update);
 +                                      string_list_insert(&update, path);
                                fprintf(stderr,
                                        "%s '%s' using previous resolution.\n",
                                        rerere_autoupdate
@@@ -557,7 -551,7 +557,7 @@@ int setup_rerere(struct string_list *me
  
  int rerere(int flags)
  {
 -      struct string_list merge_rr = { NULL, 0, 0, 1 };
 +      struct string_list merge_rr = STRING_LIST_INIT_DUP;
        int fd;
  
        fd = setup_rerere(&merge_rr, flags);
@@@ -587,7 -581,7 +587,7 @@@ static int rerere_forget_one_path(cons
        fprintf(stderr, "Updated preimage for '%s'\n", path);
  
  
 -      string_list_insert(path, rr)->util = hex;
 +      string_list_insert(rr, path)->util = hex;
        fprintf(stderr, "Forgot resolution for %s\n", path);
        return 0;
  }
  int rerere_forget(const char **pathspec)
  {
        int i, fd;
 -      struct string_list conflict = { NULL, 0, 0, 1 };
 -      struct string_list merge_rr = { NULL, 0, 0, 1 };
 +      struct string_list conflict = STRING_LIST_INIT_DUP;
 +      struct string_list merge_rr = STRING_LIST_INIT_DUP;
  
        if (read_cache() < 0)
                return error("Could not read index");