Merge branch 'jn/merge-renormalize'
authorJunio C Hamano <gitster@pobox.com>
Fri, 3 Sep 2010 16:43:41 +0000 (09:43 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 3 Sep 2010 16:43:41 +0000 (09:43 -0700)
* jn/merge-renormalize:
merge-recursive --renormalize
rerere: never renormalize
rerere: migrate to parse-options API
t4200 (rerere): modernize style
ll-merge: let caller decide whether to renormalize
ll-merge: make flag easier to populate
Documentation/technical: document ll_merge
merge-trees: let caller decide whether to renormalize
merge-trees: push choice to renormalize away from low level
t6038 (merge.renormalize): check that it can be turned off
t6038 (merge.renormalize): try checkout -m and cherry-pick
t6038 (merge.renormalize): style nitpicks
Don't expand CRLFs when normalizing text during merge
Try normalizing files to avoid delete/modify conflicts when merging
Avoid conflicts when merging branches with mixed normalization

Conflicts:
builtin/rerere.c
t/t4200-rerere.sh

1  2 
Documentation/gitattributes.txt
builtin/checkout.c
builtin/merge-recursive.c
builtin/merge.c
builtin/rerere.c
builtin/revert.c
cache.h
merge-recursive.c
merge-recursive.h
rerere.c
t/t4200-rerere.sh
index 2e2370ccdbc05a8926bae109a659fbcc9912e32f,da553ff0061d50a08f385e9800d85e1cbc837421..e5a27d875eb63dbfeccbf5357983f5c94668ffeb
@@@ -317,6 -317,17 +317,17 @@@ command is "cat")
        smudge = cat
  ------------------------
  
+ For best results, `clean` should not alter its output further if it is
+ run twice ("clean->clean" should be equivalent to "clean"), and
+ multiple `smudge` commands should not alter `clean`'s output
+ ("smudge->smudge->clean" should be equivalent to "clean").  See the
+ section on merging below.
+ The "indent" filter is well-behaved in this regard: it will not modify
+ input that is already correctly indented.  In this case, the lack of a
+ smudge filter means that the clean filter _must_ accept its own output
+ without modifying it.
  
  Interaction between checkin/checkout attributes
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@@ -331,6 -342,29 +342,29 @@@ In the check-out codepath, the blob con
  with `text`, and then `ident` and fed to `filter`.
  
  
+ Merging branches with differing checkin/checkout attributes
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ If you have added attributes to a file that cause the canonical
+ repository format for that file to change, such as adding a
+ clean/smudge filter or text/eol/ident attributes, merging anything
+ where the attribute is not in place would normally cause merge
+ conflicts.
+ To prevent these unnecessary merge conflicts, git can be told to run a
+ virtual check-out and check-in of all three stages of a file when
+ resolving a three-way merge by setting the `merge.renormalize`
+ configuration variable.  This prevents changes caused by check-in
+ conversion from causing spurious merge conflicts when a converted file
+ is merged with an unconverted file.
+ As long as a "smudge->clean" results in the same output as a "clean"
+ even on files that are already smudged, this strategy will
+ automatically resolve all filter-related conflicts.  Filters that do
+ not act in this way may cause additional merge conflicts that must be
+ resolved manually.
  Generating diff text
  ~~~~~~~~~~~~~~~~~~~~
  
@@@ -441,8 -475,6 +475,8 @@@ patterns are available
  
  - `cpp` suitable for source code in the C and C++ languages.
  
 +- `csharp` suitable for source code in the C# language.
 +
  - `html` suitable for HTML/XHTML documents.
  
  - `java` suitable for source code in the Java language.
diff --combined builtin/checkout.c
index 7250e5c23cee7deda557c16945373b59d99424ee,24b67d5dea437ef05af2ca78419f16e6ecd12656..ff5ac1e0ff20ad8e5b30289cd3f24f0c64eb0627
@@@ -32,11 -32,7 +32,11 @@@ 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;
@@@ -154,6 -150,10 +154,10 @@@ static int checkout_merged(int pos, str
        read_mmblob(&ours, active_cache[pos+1]->sha1);
        read_mmblob(&theirs, active_cache[pos+2]->sha1);
  
+       /*
+        * NEEDSWORK: re-create conflicts from merges with
+        * merge.renormalize set, too
+        */
        status = ll_merge(&result_buf, path, &ancestor, "base",
                          &ours, "ours", &theirs, "theirs", 0);
        free(ancestor.ptr);
@@@ -283,6 -283,7 +287,6 @@@ static void show_local_changes(struct o
        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.output_format |= DIFF_FORMAT_NAME_STATUS;
        if (diff_setup_done(&rev.diffopt) < 0)
                die("diff_setup_done failed");
@@@ -376,7 -377,7 +380,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.";
 +              set_porcelain_error_msgs(topts.msgs, "checkout");
  
                refresh_cache(REFRESH_QUIET);
  
                topts.dir = xcalloc(1, sizeof(*topts.dir));
                topts.dir->flags |= DIR_SHOW_IGNORED;
                topts.dir->exclude_per_dir = ".gitignore";
 +              topts.show_all_errors = 1;
                tree = parse_tree_indirect(old->commit ?
                                           old->commit->object.sha1 :
                                           (unsigned char *)EMPTY_TREE_SHA1_BIN);
                         */
  
                        add_files_to_cache(NULL, NULL, 0);
+                       /*
+                        * NEEDSWORK: carrying over local changes
+                        * when branches have different end-of-line
+                        * normalization (or clean+smudge rules) is
+                        * a pain; plumb in an option to set
+                        * o.renormalize?
+                        */
                        init_merge_options(&o);
                        o.verbosity = 0;
                        work = write_tree_from_memory(&o);
@@@ -515,8 -522,7 +526,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) {
@@@ -665,10 -668,7 +676,10 @@@ 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_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, "log for new branch"),
                OPT_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
        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;
@@@ -877,12 -869,8 +888,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 3d00adbfc79aa0d5e4e6b3367bc5b70f8df36857,c2d4677fd3db9b4e1360e0c45fdd2d4a110b5629..78b9db76a0819529e7f6cab44319acd2978c9e9b
@@@ -3,9 -3,6 +3,9 @@@
  #include "tag.h"
  #include "merge-recursive.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 -29,7 +32,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];
                                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
                                die("Unknown option %s", arg);
                        continue;
diff --combined builtin/merge.c
index 47e705ba9b6bb043b28183422d518b65cf513478,037cd47e7016c24815841b69e77394520f4731c6..da26cd629a4ba16f95f9c27d28f67573dcf21c1e
@@@ -54,6 -54,7 +54,7 @@@ static size_t use_strategies_nr, use_st
  static const char **xopts;
  static size_t xopts_nr, xopts_alloc;
  static const char *branch;
+ static int option_renormalize;
  static int verbosity;
  static int allow_rerere_auto;
  
@@@ -486,8 -487,7 +487,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_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);
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -625,6 -627,11 +628,11 @@@ static int try_merge_strategy(const cha
                if (!strcmp(strategy, "subtree"))
                        o.subtree_shift = "";
  
+               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;
                                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
                                die("Unknown option for merge-recursive: -X%s", xopts[x]);
                }
@@@ -705,8 -716,7 +717,8 @@@ int checkout_fast_forward(const unsigne
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
 -      opts.msgs = get_porcelain_error_msgs();
 +      opts.show_all_errors = 1;
 +      set_porcelain_error_msgs(opts.msgs, "merge");
  
        trees[nr_trees] = parse_tree_indirect(head);
        if (!trees[nr_trees++])
@@@ -818,7 -828,7 +830,7 @@@ static int finish_automerge(struct comm
        return 0;
  }
  
- static int suggest_conflicts(void)
+ static int suggest_conflicts(int renormalizing)
  {
        FILE *fp;
        int pos;
@@@ -1303,5 -1313,5 +1315,5 @@@ int cmd_merge(int argc, const char **ar
                        "stopped before committing as requested\n");
                return 0;
        } else
-               return suggest_conflicts();
+               return suggest_conflicts(option_renormalize);
  }
diff --combined builtin/rerere.c
index 67793fa2c795777b02c340551a5c4cee152ff68b,295fe75d8f0b12685a4e89a67d40d9627830d05a..642bf35587ed994948d3eeac0a189ae29e39bf11
@@@ -1,13 -1,16 +1,16 @@@
  #include "builtin.h"
  #include "cache.h"
  #include "dir.h"
+ #include "parse-options.h"
  #include "string-list.h"
  #include "rerere.h"
  #include "xdiff/xdiff.h"
  #include "xdiff-interface.h"
  
- static const char git_rerere_usage[] =
- "git rerere [clear | status | diff | gc]";
+ static const char * const rerere_usage[] = {
+       "git rerere [clear | status | diff | gc]",
+       NULL,
+ };
  
  /* these values are days */
  static int cutoff_noresolve = 15;
@@@ -19,12 -22,6 +22,12 @@@ static time_t rerere_created_at(const c
        return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
  }
  
 +static time_t rerere_last_used_at(const char *name)
 +{
 +      struct stat st;
 +      return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
 +}
 +
  static void unlink_rr_item(const char *name)
  {
        unlink(rerere_path(name, "thisimage"));
@@@ -46,7 -43,7 +49,7 @@@ static int git_rerere_gc_config(const c
  
  static void garbage_collect(struct string_list *rr)
  {
 -      struct string_list to_remove = { NULL, 0, 0, 1 };
 +      struct string_list to_remove = STRING_LIST_INIT_DUP;
        DIR *dir;
        struct dirent *e;
        int i, cutoff;
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
 -              then = rerere_created_at(e->d_name);
 -              if (!then)
 -                      continue;
 -              cutoff = (has_rerere_resolution(e->d_name)
 -                        ? cutoff_resolve : cutoff_noresolve);
 +
 +              then = rerere_last_used_at(e->d_name);
 +              if (then) {
 +                      cutoff = cutoff_resolve;
 +              } else {
 +                      then = rerere_created_at(e->d_name);
 +                      if (!then)
 +                              continue;
 +                      cutoff = cutoff_noresolve;
 +              }
                if (then < now - cutoff * 86400)
 -                      string_list_append(e->d_name, &to_remove);
 +                      string_list_append(&to_remove, e->d_name);
        }
        for (i = 0; i < to_remove.nr; i++)
                unlink_rr_item(to_remove.items[i].string);
@@@ -113,26 -105,27 +116,27 @@@ static int diff_two(const char *file1, 
  
  int cmd_rerere(int argc, const char **argv, const char *prefix)
  {
 -      struct string_list merge_rr = { NULL, 0, 0, 1 };
 +      struct string_list merge_rr = STRING_LIST_INIT_DUP;
-       int i, fd, flags = 0;
-       if (2 < argc) {
-               if (!strcmp(argv[1], "-h"))
-                       usage(git_rerere_usage);
-               if (!strcmp(argv[1], "--rerere-autoupdate"))
-                       flags = RERERE_AUTOUPDATE;
-               else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
-                       flags = RERERE_NOAUTOUPDATE;
-               if (flags) {
-                       argc--;
-                       argv++;
-               }
-       }
-       if (argc < 2)
+       int i, fd, autoupdate = -1, flags = 0;
+       struct option options[] = {
+               OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
+                       "register clean resolutions in index", 1),
+               OPT_END(),
+       };
+       argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+       if (autoupdate == 1)
+               flags = RERERE_AUTOUPDATE;
+       if (autoupdate == 0)
+               flags = RERERE_NOAUTOUPDATE;
+       if (argc < 1)
                return rerere(flags);
  
-       if (!strcmp(argv[1], "forget")) {
-               const char **pathspec = get_pathspec(prefix, argv + 2);
+       if (!strcmp(argv[0], "forget")) {
+               const char **pathspec = get_pathspec(prefix, argv + 1);
                return rerere_forget(pathspec);
        }
  
        if (fd < 0)
                return 0;
  
-       if (!strcmp(argv[1], "clear")) {
+       if (!strcmp(argv[0], "clear")) {
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *name = (const char *)merge_rr.items[i].util;
                        if (!has_rerere_resolution(name))
                                unlink_rr_item(name);
                }
 -              unlink_or_warn(git_path("rr-cache/MERGE_RR"));
 +              unlink_or_warn(git_path("MERGE_RR"));
-       } else if (!strcmp(argv[1], "gc"))
+       } else if (!strcmp(argv[0], "gc"))
                garbage_collect(&merge_rr);
-       else if (!strcmp(argv[1], "status"))
+       else if (!strcmp(argv[0], "status"))
                for (i = 0; i < merge_rr.nr; i++)
                        printf("%s\n", merge_rr.items[i].string);
-       else if (!strcmp(argv[1], "diff"))
+       else if (!strcmp(argv[0], "diff"))
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
                        diff_two(rerere_path(name, "preimage"), path, path, path);
                }
        else
-               usage(git_rerere_usage);
+               usage_with_options(rerere_usage, options);
  
        string_list_clear(&merge_rr, 1);
        return 0;
diff --combined builtin/revert.c
index 5ca81580d37b20e6f65fc8e8d1cdf09228f33ca1,11132533c6d597ee6f920f5e78e8171cc5f30a44..4b47ace36b51566acb5b748f679070f02cbbf568
@@@ -50,14 -50,10 +50,14 @@@ static const char *strategy
  
  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 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"),
                        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;
@@@ -102,9 -96,9 +102,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;
  }
@@@ -231,30 -235,27 +231,30 @@@ static void set_author_ident_env(const 
                        sha1_to_hex(commit->object.sha1));
  }
  
 -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);
 +              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>'");
 +
 +      if (action == CHERRY_PICK)
 +              advise("and commit the result with 'git commit -c %s'",
 +                     find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
  }
  
  static void write_message(struct strbuf *msgbuf, const char *filename)
@@@ -304,9 -305,10 +304,9 @@@ 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;
        index_fd = hold_locked_index(&index_lock, 1);
  
        read_cache();
+       /*
+        * NEEDSWORK: cherry-picking between branches with
+        * different end-of-line normalization is a pain;
+        * plumb in an option to set o.renormalize?
+        * (or better: arbitrary -X options)
+        */
        init_merge_options(&o);
        o.ancestor = base ? base_label : "(empty tree)";
        o.branch1 = "HEAD";
                                        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) {
                /*
                }
        }
  
 -      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,
                                        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("could not %s %s... %s",
 +                    action == REVERT ? "revert" : "apply",
 +                    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");
  
        if (!revs->commits)
                die("empty commit set passed");
 -
 -      free(argv);
  }
  
  static int revert_or_cherry_pick(int argc, const char **argv)
diff --combined cache.h
index 733d4d1c81fe47bb2fa68aa3a76bade464de3051,aa725b0d3168e93001a96979fd8e29c6c7910f3a..be02a422d199b8269771ddedb4f13378f082e003
+++ b/cache.h
@@@ -179,7 -179,8 +179,7 @@@ struct cache_entry 
  #define CE_UNHASHED  (0x200000)
  #define CE_CONFLICTED (0x800000)
  
 -/* Only remove in work directory, not index */
 -#define CE_WT_REMOVE (0x400000)
 +#define CE_WT_REMOVE (0x400000) /* remove in work directory */
  
  #define CE_UNPACKED  (0x1000000)
  
@@@ -448,7 -449,7 +448,7 @@@ extern int init_db(const char *template
                                alloc = alloc_nr(alloc); \
                        x = xrealloc((x), alloc * sizeof(*(x))); \
                } \
 -      } while(0)
 +      } while (0)
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
@@@ -640,9 -641,6 +640,9 @@@ extern char *git_pathdup(const char *fm
  /* Return a statically allocated filename matching the sha1 signature */
  extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
  extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern char *git_path_submodule(const char *path, const char *fmt, ...)
 +      __attribute__((format (printf, 2, 3)));
 +
  extern char *sha1_file_name(const unsigned char *sha1);
  extern char *sha1_pack_name(const unsigned char *sha1);
  extern char *sha1_pack_index_name(const unsigned char *sha1);
@@@ -813,7 -811,6 +813,7 @@@ const char *show_date_relative(unsigne
                               char *timebuf,
                               size_t timebuf_size);
  int parse_date(const char *date, char *buf, int bufsize);
 +int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -1057,6 -1054,7 +1057,7 @@@ extern void trace_argv_printf(const cha
  extern int convert_to_git(const char *path, const char *src, size_t len,
                            struct strbuf *dst, enum safe_crlf checksafe);
  extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
+ extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
  
  /* add */
  /*
@@@ -1099,14 -1097,6 +1100,14 @@@ void overlay_tree_on_cache(const char *
  
  char *alias_lookup(const char *alias);
  int split_cmdline(char *cmdline, const char ***argv);
 +/* Takes a negative value returned by split_cmdline */
 +const char *split_cmdline_strerror(int cmdline_errno);
 +
 +/* git.c */
 +struct startup_info {
 +      int have_repository;
 +};
 +extern struct startup_info *startup_info;
  
  /* builtin/merge.c */
  int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
diff --combined merge-recursive.c
index df90be44a51f3af5ebf9eb65e95b7d4fc18cc2f9,762b5494d216b6a3cfe7e79847071e61d9669732..aadd48c4fc73d693979956735e601d969594107e
@@@ -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();
 +      set_porcelain_error_msgs(opts.msgs, "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)
@@@ -644,7 -647,9 +644,9 @@@ static int merge_3way(struct merge_opti
  
        merge_status = ll_merge(result_buf, a->path, &orig, base_name,
                                &src1, name1, &src2, name2,
-                               (!!o->call_depth) | (favor << 1));
+                               ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
+                                (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
+                                create_ll_flag(favor)));
  
        free(name1);
        free(name2);
@@@ -713,8 -718,8 +715,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);
  
@@@ -803,18 -808,17 +805,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;
        }
  
                                        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)
@@@ -1062,6 -1058,53 +1064,53 @@@ 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;
+ }
  /* 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);
  
 +      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)
        } 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";
 +      }
 +      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 msgs;
 +
 +      return clean_merge;
 +}
 +
 +void set_porcelain_error_msgs(const char **msgs, const char *cmd)
 +{
 +      const char *msg;
 +      char *tmp;
 +      const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
 +      if (advice_commit_before_merge)
 +              msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
 +                      "Please, commit your changes or stash them before you can %s.";
 +      else
 +              msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
 +      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
 +      sprintf(tmp, msg, cmd, cmd2);
 +      msgs[ERROR_WOULD_OVERWRITE] = tmp;
 +      msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
 +
 +      msgs[ERROR_NOT_UPTODATE_DIR] =
 +              "Updating the following directories would lose untracked files in it:\n%s";
 +
 +      if (advice_commit_before_merge)
 +              msg = "The following untracked working tree files would be %s by %s:\n%%s"
 +                      "Please move or remove them before you can %s.";
 +      else
 +              msg = "The following untracked working tree files would be %s by %s:\n%%s";
 +      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
 +      sprintf(tmp, msg, "removed", cmd, cmd2);
 +      msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
 +      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
 +      sprintf(tmp, msg, "overwritten", cmd, cmd2);
 +      msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
 +
 +      /*
 +       * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
 +       * cannot easily display it as a list.
 +       */
 +      msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'.  Cannot bind.";
 +
 +      msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
 +              "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
 +      msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
 +              "The following Working tree files would be overwritten by sparse checkout update:\n%s";
 +      msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
 +              "The following Working tree files would be removed by sparse checkout update:\n%s";
  }
  
  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);
@@@ -1525,6 -1486,7 +1575,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 =
diff --combined merge-recursive.h
index 08f985036781f67eeda3fde1ae35f62f42f31096,c5fbe796bb799d02d115420d16657788acc1f0d8..196f0531062a095d57afc92c16c57a67ec93f31c
@@@ -14,6 -14,7 +14,7 @@@ struct merge_options 
        } recursive_variant;
        const char *subtree_shift;
        unsigned buffer_output : 1;
+       unsigned renormalize : 1;
        int verbosity;
        int diff_rename_limit;
        int merge_rename_limit;
        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);
 +/*
 + * Sets the list of user-friendly error messages to be used by the
 + * command "cmd" (either merge or checkout)
 + */
 +void set_porcelain_error_msgs(const char **msgs, const char *cmd);
  
  /* merge_trees() but with recursive ancestor consolidation */
  int merge_recursive(struct merge_options *o,
diff --combined rerere.c
index f42649c4991000786629ee3773ab7b8055403cce,e40af0df8754a1f08877d40a3d9cda247aecd48e..861ca7c815b4857f1fde399617860e5a179e2ca3
+++ 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);
  }
@@@ -319,6 -319,10 +319,10 @@@ static int handle_cache(const char *pat
                if (!mmfile[i].ptr && !mmfile[i].size)
                        mmfile[i].ptr = xstrdup("");
        }
+       /*
+        * NEEDSWORK: handle conflicts from merges with
+        * merge.renormalize set, too
+        */
        ll_merge(&result, path, &mmfile[0], NULL,
                 &mmfile[1], "ours",
                 &mmfile[2], "theirs", 0);
@@@ -354,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 */
                }
        }
@@@ -378,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));
@@@ -432,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
@@@ -553,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);
@@@ -583,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");
diff --combined t/t4200-rerere.sh
index 093b1389116250ada236b5949fab269fd567b643,876f09a6fef39fa2f7ef1ba8bd4898225bd5125a..36255d608a7af7d85f479986e302138401f25a8d
  #
  
  test_description='git rerere
+ ! [fifth] version1
+  ! [first] first
+   ! [fourth] version1
+    ! [master] initial
+     ! [second] prefer first over second
+      ! [third] version2
+ ------
+      + [third] version2
+ +      [fifth] version1
+   +    [fourth] version1
+ + +  + [third^] third
+     -  [second] prefer first over second
+  +  +  [first] first
+     +  [second^] second
+ ++++++ [master] initial
  '
  
  . ./test-lib.sh
  
- test_expect_success 'setup' "
-       cat > a1 <<- EOF &&
+ test_expect_success 'setup' '
+       cat >a1 <<-\EOF &&
        Some title
        ==========
-       Whether 'tis nobler in the mind to suffer
+       Whether '\''tis nobler in the mind to suffer
        The slings and arrows of outrageous fortune,
        Or to take arms against a sea of troubles,
        And by opposing end them? To die: to sleep;
        No more; and by a sleep to say we end
        The heart-ache and the thousand natural shocks
-       That flesh is heir to, 'tis a consummation
-       Devoutly to be wish'd.
+       That flesh is heir to, '\''tis a consummation
+       Devoutly to be wish'\''d.
        EOF
  
        git add a1 &&
+       test_tick &&
        git commit -q -a -m initial &&
  
-       git checkout -b first &&
-       cat >> a1 <<- EOF &&
+       cat >>a1 <<-\EOF &&
        Some title
        ==========
        To die, to sleep;
-       To sleep: perchance to dream: ay, there's the rub;
+       To sleep: perchance to dream: ay, there'\''s the rub;
        For in that sleep of death what dreams may come
        When we have shuffled off this mortal coil,
-       Must give us pause: there's the respect
+       Must give us pause: there'\''s the respect
        That makes calamity of so long life;
        EOF
+       git checkout -b first &&
+       test_tick &&
        git commit -q -a -m first &&
  
        git checkout -b second master &&
        git show first:a1 |
-       sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 &&
-       echo '* END *' >>a1 &&
+       sed -e "s/To die, t/To die! T/" -e "s/Some title/Some Title/" >a1 &&
+       echo "* END *" >>a1 &&
+       test_tick &&
        git commit -q -a -m second
- "
+ '
  
  test_expect_success 'nothing recorded without rerere' '
-       (rm -rf .git/rr-cache; git config rerere.enabled false) &&
+       rm -rf .git/rr-cache &&
+       git config rerere.enabled false &&
        test_must_fail git merge first &&
        ! test -d .git/rr-cache
  '
  
- # activate rerere, old style
- test_expect_success 'conflicting merge' '
+ test_expect_success 'activate rerere, old style (conflicting merge)' '
        git reset --hard &&
        mkdir .git/rr-cache &&
-       git config --unset rerere.enabled &&
-       test_must_fail git merge first
- '
+       test_might_fail git config --unset rerere.enabled &&
+       test_must_fail git merge first &&
  
- sha1=$(perl -pe 's/   .*//' .git/MERGE_RR)
- rr=.git/rr-cache/$sha1
- test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1 &&
+       grep "^=======\$" $rr/preimage &&
+       ! test -f $rr/postimage &&
+       ! test -f $rr/thisimage
+ '
  
  test_expect_success 'rerere.enabled works, too' '
        rm -rf .git/rr-cache &&
        git config rerere.enabled true &&
        git reset --hard &&
        test_must_fail git merge first &&
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1 &&
        grep ^=======$ $rr/preimage
  '
  
- test_expect_success 'no postimage or thisimage yet' \
-       "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+ test_expect_success 'set up rr-cache' '
+       rm -rf .git/rr-cache &&
+       git config rerere.enabled true &&
+       git reset --hard &&
+       test_must_fail git merge first &&
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1
+ '
  
- test_expect_success 'preimage has right number of lines' '
+ test_expect_success 'rr-cache looks sane' '
+       # no postimage or thisimage yet
+       ! test -f $rr/postimage &&
+       ! test -f $rr/thisimage &&
  
+       # preimage has right number of lines
        cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) &&
+       echo $cnt &&
        test $cnt = 13
  '
  
- git show first:a1 > a1
- cat > expect << EOF
- --- a/a1
- +++ b/a1
- @@ -1,4 +1,4 @@
- -Some Title
- +Some title
-  ==========
-  Whether 'tis nobler in the mind to suffer
-  The slings and arrows of outrageous fortune,
- @@ -8,21 +8,11 @@
-  The heart-ache and the thousand natural shocks
-  That flesh is heir to, 'tis a consummation
-  Devoutly to be wish'd.
- -<<<<<<<
- -Some Title
- -==========
- -To die! To sleep;
- -=======
-  Some title
-  ==========
-  To die, to sleep;
- ->>>>>>>
-  To sleep: perchance to dream: ay, there's the rub;
-  For in that sleep of death what dreams may come
-  When we have shuffled off this mortal coil,
-  Must give us pause: there's the respect
-  That makes calamity of so long life;
- -<<<<<<<
- -=======
- -* END *
- ->>>>>>>
- EOF
- git rerere diff > out
- test_expect_success 'rerere diff' 'test_cmp expect out'
- cat > expect << EOF
- a1
- EOF
- git rerere status > out
- test_expect_success 'rerere status' 'test_cmp expect out'
- test_expect_success 'commit succeeds' \
-       "git commit -q -a -m 'prefer first over second'"
- test_expect_success 'recorded postimage' "test -f $rr/postimage"
- oldmtimepost=$(test-chmtime -v -60 $rr/postimage |cut -f 1)
- test_expect_success 'another conflicting merge' '
-       git checkout -b third master &&
-       git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
-       git commit -q -a -m third &&
-       test_must_fail git pull . first
+ test_expect_success 'rerere diff' '
+       git show first:a1 >a1 &&
+       cat >expect <<-\EOF &&
+       --- a/a1
+       +++ b/a1
+       @@ -1,4 +1,4 @@
+       -Some Title
+       +Some title
+        ==========
+        Whether '\''tis nobler in the mind to suffer
+        The slings and arrows of outrageous fortune,
+       @@ -8,21 +8,11 @@
+        The heart-ache and the thousand natural shocks
+        That flesh is heir to, '\''tis a consummation
+        Devoutly to be wish'\''d.
+       -<<<<<<<
+       -Some Title
+       -==========
+       -To die! To sleep;
+       -=======
+        Some title
+        ==========
+        To die, to sleep;
+       ->>>>>>>
+        To sleep: perchance to dream: ay, there'\''s the rub;
+        For in that sleep of death what dreams may come
+        When we have shuffled off this mortal coil,
+        Must give us pause: there'\''s the respect
+        That makes calamity of so long life;
+       -<<<<<<<
+       -=======
+       -* END *
+       ->>>>>>>
+       EOF
+       git rerere diff >out &&
+       test_cmp expect out
  '
  
- git show first:a1 | sed 's/To die: t/To die! T/' > expect
- test_expect_success 'rerere kicked in' "! grep ^=======$ a1"
- test_expect_success 'rerere prefers first change' 'test_cmp a1 expect'
- test_expect_success 'rerere updates postimage timestamp' '
-       newmtimepost=$(test-chmtime -v +0 $rr/postimage |cut -f 1) &&
-       test $oldmtimepost -lt $newmtimepost
+ test_expect_success 'rerere status' '
+       echo a1 >expect &&
+       git rerere status >out &&
+       test_cmp expect out
  '
  
- rm $rr/postimage
- echo "$sha1   a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR
+ test_expect_success 'first postimage wins' '
+       git show first:a1 | sed "s/To die: t/To die! T/" >expect &&
  
- test_expect_success 'rerere clear' 'git rerere clear'
+       git commit -q -a -m "prefer first over second" &&
+       test -f $rr/postimage &&
  
- test_expect_success 'clear removed the directory' "test ! -d $rr"
++      oldmtimepost=$(test-chmtime -v -60 $rr/postimage | cut -f 1) &&
 +
- mkdir $rr
- echo Hello > $rr/preimage
- echo World > $rr/postimage
+       git checkout -b third master &&
+       git show second^:a1 | sed "s/To die: t/To die! T/" >a1 &&
+       git commit -q -a -m third &&
  
- sha2=4000000000000000000000000000000000000000
- rr2=.git/rr-cache/$sha2
- mkdir $rr2
- echo Hello > $rr2/preimage
+       test_must_fail git pull . first &&
+       # rerere kicked in
+       ! grep "^=======\$" a1 &&
+       test_cmp expect a1
+ '
  
- almost_15_days_ago=$((60-15*86400))
- just_over_15_days_ago=$((-1-15*86400))
- almost_60_days_ago=$((60-60*86400))
- just_over_60_days_ago=$((-1-60*86400))
++test_expect_success 'rerere updates postimage timestamp' '
++      newmtimepost=$(test-chmtime -v +0 $rr/postimage | cut -f 1) &&
++      test $oldmtimepost -lt $newmtimepost
++'
 +
- test-chmtime =$just_over_60_days_ago $rr/preimage
- test-chmtime =$almost_60_days_ago $rr/postimage
- test-chmtime =$almost_15_days_ago $rr2/preimage
+ test_expect_success 'rerere clear' '
+       rm $rr/postimage &&
+       echo "$sha1     a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR &&
+       git rerere clear &&
+       ! test -d $rr
+ '
  
- test_expect_success 'garbage collection (part1)' 'git rerere gc'
+ test_expect_success 'set up for garbage collection tests' '
+       mkdir -p $rr &&
+       echo Hello >$rr/preimage &&
+       echo World >$rr/postimage &&
  
- test_expect_success 'young or recently used records still live' \
-       "test -f $rr/preimage && test -f $rr2/preimage"
+       sha2=4000000000000000000000000000000000000000 &&
+       rr2=.git/rr-cache/$sha2 &&
+       mkdir $rr2 &&
+       echo Hello >$rr2/preimage &&
  
- test-chmtime =$just_over_60_days_ago $rr/postimage
- test-chmtime =$just_over_15_days_ago $rr2/preimage
+       almost_15_days_ago=$((60-15*86400)) &&
+       just_over_15_days_ago=$((-1-15*86400)) &&
+       almost_60_days_ago=$((60-60*86400)) &&
+       just_over_60_days_ago=$((-1-60*86400)) &&
  
- test_expect_success 'garbage collection (part2)' 'git rerere gc'
 -      test-chmtime =$almost_60_days_ago $rr/preimage &&
++      test-chmtime =$just_over_60_days_ago $rr/preimage &&
++      test-chmtime =$almost_60_days_ago $rr/postimage &&
+       test-chmtime =$almost_15_days_ago $rr2/preimage
+ '
  
- test_expect_success 'old records rest in peace' \
-       "test ! -f $rr/preimage && test ! -f $rr2/preimage"
 -test_expect_success 'garbage collection preserves young records' '
++test_expect_success 'gc preserves young or recently used records' '
+       git rerere gc &&
+       test -f $rr/preimage &&
+       test -f $rr2/preimage
+ '
  
- test_expect_success 'file2 added differently in two branches' '
+ test_expect_success 'old records rest in peace' '
 -      test-chmtime =$just_over_60_days_ago $rr/preimage &&
++      test-chmtime =$just_over_60_days_ago $rr/postimage &&
+       test-chmtime =$just_over_15_days_ago $rr2/preimage &&
+       git rerere gc &&
+       ! test -f $rr/preimage &&
+       ! test -f $rr2/preimage
+ '
+ test_expect_success 'setup: file2 added differently in two branches' '
        git reset --hard &&
        git checkout -b fourth &&
-       echo Hallo > file2 &&
+       echo Hallo >file2 &&
        git add file2 &&
+       test_tick &&
        git commit -m version1 &&
        git checkout third &&
-       echo Bello > file2 &&
+       echo Bello >file2 &&
        git add file2 &&
+       test_tick &&
        git commit -m version2 &&
        test_must_fail git merge fourth &&
-       echo Cello > file2 &&
+       echo Cello >file2 &&
        git add file2 &&
        git commit -m resolution
  '
  
  test_expect_success 'resolution was recorded properly' '
+       echo Cello >expected &&
        git reset --hard HEAD~2 &&
        git checkout -b fifth &&
-       echo Hallo > file3 &&
+       echo Hallo >file3 &&
        git add file3 &&
+       test_tick &&
        git commit -m version1 &&
        git checkout third &&
-       echo Bello > file3 &&
+       echo Bello >file3 &&
        git add file3 &&
+       test_tick &&
        git commit -m version2 &&
        git tag version2 &&
        test_must_fail git merge fifth &&
-       test Cello = "$(cat file3)" &&
-       test 0 != $(git ls-files -u | wc -l)
+       test_cmp expected file3 &&
+       test_must_fail git update-index --refresh
  '
  
  test_expect_success 'rerere.autoupdate' '
-       git config rerere.autoupdate true
+       git config rerere.autoupdate true &&
        git reset --hard &&
        git checkout version2 &&
        test_must_fail git merge fifth &&
-       test 0 = $(git ls-files -u | wc -l)
+       git update-index --refresh
  '
  
  test_expect_success 'merge --rerere-autoupdate' '
-       git config --unset rerere.autoupdate
+       test_might_fail git config --unset rerere.autoupdate &&
        git reset --hard &&
        git checkout version2 &&
        test_must_fail git merge --rerere-autoupdate fifth &&
-       test 0 = $(git ls-files -u | wc -l)
+       git update-index --refresh
  '
  
  test_expect_success 'merge --no-rerere-autoupdate' '
-       git config rerere.autoupdate true
+       headblob=$(git rev-parse version2:file3) &&
+       mergeblob=$(git rev-parse fifth:file3) &&
+       cat >expected <<-EOF &&
+       100644 $headblob 2      file3
+       100644 $mergeblob 3     file3
+       EOF
+       git config rerere.autoupdate true &&
        git reset --hard &&
        git checkout version2 &&
        test_must_fail git merge --no-rerere-autoupdate fifth &&
-       test 2 = $(git ls-files -u | wc -l)
+       git ls-files -u >actual &&
+       test_cmp expected actual
+ '
+ test_expect_success 'set up an unresolved merge' '
+       headblob=$(git rev-parse version2:file3) &&
+       mergeblob=$(git rev-parse fifth:file3) &&
+       cat >expected.unresolved <<-EOF &&
+       100644 $headblob 2      file3
+       100644 $mergeblob 3     file3
+       EOF
+       test_might_fail git config --unset rerere.autoupdate &&
+       git reset --hard &&
+       git checkout version2 &&
+       fifth=$(git rev-parse fifth) &&
+       echo "$fifth            branch 'fifth' of ." |
+       git fmt-merge-msg >msg &&
+       ancestor=$(git merge-base version2 fifth) &&
+       test_must_fail git merge-recursive "$ancestor" -- HEAD fifth &&
+       git ls-files --stage >failedmerge &&
+       cp file3 file3.conflict &&
+       git ls-files -u >actual &&
+       test_cmp expected.unresolved actual
+ '
+ test_expect_success 'explicit rerere' '
+       test_might_fail git config --unset rerere.autoupdate &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git update-index --refresh -q &&
+       git rerere &&
+       git ls-files -u >actual &&
+       test_cmp expected.unresolved actual
+ '
+ test_expect_success 'explicit rerere with autoupdate' '
+       git config rerere.autoupdate true &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git update-index --refresh -q &&
+       git rerere &&
+       git update-index --refresh
+ '
+ test_expect_success 'explicit rerere --rerere-autoupdate overrides' '
+       git config rerere.autoupdate false &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere &&
+       git ls-files -u >actual1 &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate &&
+       git update-index --refresh &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate --no-rerere-autoupdate &&
+       git ls-files -u >actual2 &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate &&
+       git update-index --refresh &&
+       test_cmp expected.unresolved actual1 &&
+       test_cmp expected.unresolved actual2
+ '
+ test_expect_success 'rerere --no-no-rerere-autoupdate' '
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git rerere --no-no-rerere-autoupdate 2>err &&
+       grep [Uu]sage err &&
+       test_must_fail git update-index --refresh
+ '
+ test_expect_success 'rerere -h' '
+       test_must_fail git rerere -h >help &&
+       grep [Uu]sage help
  '
  
  test_done