Merge branch 'js/cherry-pick-usability'
authorJunio C Hamano <gitster@pobox.com>
Wed, 9 Mar 2011 23:56:17 +0000 (15:56 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 9 Mar 2011 23:56:17 +0000 (15:56 -0800)
* js/cherry-pick-usability:
Teach commit about CHERRY_PICK_HEAD
bash: teach __git_ps1 about CHERRY_PICK_HEAD
Introduce CHERRY_PICK_HEAD
t3507: introduce pristine-detach helper

1  2 
Documentation/git-cherry-pick.txt
Documentation/git-commit.txt
builtin/commit.c
builtin/merge.c
builtin/revert.c
wt-status.c
index 749d68a72bf720d82260fe175be0824f7ad069eb,cd848664ce05e60b866ebd2d6b875ae0416626f0..5d85daa5feb1f261bd91557a940e08e1e1501d1c
@@@ -16,6 -16,25 +16,25 @@@ Given one or more existing commits, app
  introduces, recording a new commit for each.  This requires your
  working tree to be clean (no modifications from the HEAD commit).
  
+ When it is not obvious how to apply a change, the following
+ happens:
+ 1. The current branch and `HEAD` pointer stay at the last commit
+    successfully made.
+ 2. The `CHERRY_PICK_HEAD` ref is set to point at the commit that
+    introduced the change that is difficult to apply.
+ 3. Paths in which the change applied cleanly are updated both
+    in the index file and in your working tree.
+ 4. For conflicting paths, the index file records up to three
+    versions, as described in the "TRUE MERGE" section of
+    linkgit:git-merge[1].  The working tree files will include
+    a description of the conflict bracketed by the usual
+    conflict markers `<<<<<<<` and `>>>>>>>`.
+ 5. No other modifications are made.
+ See linkgit:git-merge[1] for some hints on resolving such
+ conflicts.
  OPTIONS
  -------
  <commit>...::
@@@ -79,16 -98,6 +98,16 @@@ effect to your index in a row
        cherry-pick'ed commit, then a fast forward to this commit will
        be performed.
  
 +--strategy=<strategy>::
 +      Use the given merge strategy.  Should only be used once.
 +      See the MERGE STRATEGIES section in linkgit:git-merge[1]
 +      for details.
 +
 +-X<option>::
 +--strategy-option=<option>::
 +      Pass the merge strategy-specific option through to the
 +      merge strategy.  See linkgit:git-merge[1] for details.
 +
  EXAMPLES
  --------
  git cherry-pick master::
@@@ -130,28 -139,6 +149,28 @@@ git rev-list --reverse master \-- READM
        so the result can be inspected and made into a single new
        commit if suitable.
  
 +The following sequence attempts to backport a patch, bails out because
 +the code the patch applies to has changed too much, and then tries
 +again, this time exercising more care about matching up context lines.
 +
 +------------
 +$ git cherry-pick topic^             <1>
 +$ git diff                           <2>
 +$ git reset --merge ORIG_HEAD        <3>
 +$ git cherry-pick -Xpatience topic^  <4>
 +------------
 +<1> apply the change that would be shown by `git show topic^`.
 +In this example, the patch does not apply cleanly, so
 +information about the conflict is written to the index and
 +working tree and no new commit results.
 +<2> summarize changes to be reconciled
 +<3> cancel the cherry-pick.  In other words, return to the
 +pre-cherry-pick state, preserving any local modifications you had in
 +the working tree.
 +<4> try to apply the change introduced by `topic^` again,
 +spending extra time to avoid mistakes based on incorrectly matching
 +context lines.
 +
  Author
  ------
  Written by Junio C Hamano <gitster@pobox.com>
index 8f89f6f08c57335ea6606dd1eaddde98477e9c9e,fd6a1f7e25896edc3826b6a0ae1ca3759e19706c..411fa68e9d592e92f532bb5471a0ef9e28fb36ca
@@@ -84,9 -84,10 +84,10 @@@ OPTION
        linkgit:git-rebase[1] for details.
  
  --reset-author::
-       When used with -C/-c/--amend options, declare that the
-       authorship of the resulting commit now belongs of the committer.
-       This also renews the author timestamp.
+       When used with -C/-c/--amend options, or when committing after a
+       a conflicting cherry-pick, declare that the authorship of the
+       resulting commit now belongs of the committer. This also renews
+       the author timestamp.
  
  --short::
        When doing a dry-run, give the output in the short-format. See
@@@ -214,11 -215,10 +215,11 @@@ FROM UPSTREAM REBASE" section in linkgi
  
  -u[<mode>]::
  --untracked-files[=<mode>]::
 -      Show untracked files (Default: 'all').
 +      Show untracked files.
  +
 -The mode parameter is optional, and is used to specify
 -the handling of untracked files.
 +The mode parameter is optional (defaults to 'all'), and is used to
 +specify the handling of untracked files; when -u is not used, the
 +default is 'normal', i.e. show untracked files and directories.
  +
  The possible options are:
  +
        - 'normal' - Shows untracked files and directories
        - 'all'    - Also shows individual files in untracked directories.
  +
 -See linkgit:git-config[1] for configuration variable
 -used to change the default for when the option is not
 -specified.
 +The default can be changed using the status.showUntrackedFiles
 +configuration variable documented in linkgit:git-config[1].
  
  -v::
  --verbose::
diff --combined builtin/commit.c
index d71e1e0c9c27c4d03cd02b4deaacec67af33917a,f963cfbd44923eaf96522c632ac7663e562b1a3d..82092e5c82cd48c7e3bfc97acadc7f7173b756bf
@@@ -54,9 -54,17 +54,17 @@@ static const char empty_amend_advice[] 
  "it empty. You can repeat your command with --allow-empty, or you can\n"
  "remove the commit entirely with \"git reset HEAD^\".\n";
  
+ static const char empty_cherry_pick_advice[] =
+ "The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+ "If you wish to commit it anyway, use:\n"
+ "\n"
+ "    git commit --allow-empty\n"
+ "\n"
+ "Otherwise, please use 'git reset'\n";
  static unsigned char head_sha1[20];
  
- static char *use_message_buffer;
+ static const char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -68,6 -76,11 +76,11 @@@ static enum 
  
  static const char *logfile, *force_author;
  static const char *template_file;
+ /*
+  * The _message variables are commit names from which to take
+  * the commit message and/or authorship.
+  */
+ static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
  static int all, edit_flag, also, interactive, only, amend, signoff;
@@@ -88,7 -101,8 +101,8 @@@ static enum 
  } cleanup_mode;
  static char *cleanup_arg;
  
- static int use_editor = 1, initial_commit, in_merge, include_status = 1;
+ static enum commit_whence whence;
+ static int use_editor = 1, initial_commit, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
  static struct strbuf message;
@@@ -118,14 -132,14 +132,14 @@@ static struct option builtin_commit_opt
        OPT__VERBOSE(&verbose, "show diff in commit message template"),
  
        OPT_GROUP("Commit message options"),
 -      OPT_FILENAME('F', "file", &logfile, "read log from file"),
 -      OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
 -      OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
 -      OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
 -      OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
 -      OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
 -      OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
 -      OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
 +      OPT_FILENAME('F', "file", &logfile, "read message from file"),
 +      OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
 +      OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
 +      OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
 +      OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
 +      OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
 +      OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
 +      OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
        OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
                    STATUS_FORMAT_SHORT),
        OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
        OPT_SET_INT(0, "porcelain", &status_format,
 -                  "show porcelain output format", STATUS_FORMAT_PORCELAIN),
 +                  "machine-readable output", STATUS_FORMAT_PORCELAIN),
        OPT_BOOLEAN('z', "null", &null_termination,
                    "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 -      { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 +      { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        /* end commit contents options */
  
        { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
        OPT_END()
  };
  
+ static void determine_whence(struct wt_status *s)
+ {
+       if (file_exists(git_path("MERGE_HEAD")))
+               whence = FROM_MERGE;
+       else if (file_exists(git_path("CHERRY_PICK_HEAD")))
+               whence = FROM_CHERRY_PICK;
+       else
+               whence = FROM_COMMIT;
+       if (s)
+               s->whence = whence;
+ }
+ static const char *whence_s(void)
+ {
+       char *s = "";
+       switch (whence) {
+       case FROM_COMMIT:
+               break;
+       case FROM_MERGE:
+               s = "merge";
+               break;
+       case FROM_CHERRY_PICK:
+               s = "cherry-pick";
+               break;
+       }
+       return s;
+ }
  static void rollback_index_files(void)
  {
        switch (commit_style) {
@@@ -378,8 -422,8 +422,8 @@@ static char *prepare_index(int argc, co
         */
        commit_style = COMMIT_PARTIAL;
  
-       if (in_merge)
-               die("cannot do a partial commit during a merge.");
+       if (whence != FROM_COMMIT)
+               die("cannot do a partial commit during a %s.", whence_s());
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@@ -469,18 -513,18 +513,18 @@@ static void determine_author_info(struc
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
  
-       if (use_message && !renew_authorship) {
+       if (author_message) {
                const char *a, *lb, *rb, *eol;
  
-               a = strstr(use_message_buffer, "\nauthor ");
+               a = strstr(author_message_buffer, "\nauthor ");
                if (!a)
-                       die("invalid commit: %s", use_message);
+                       die("invalid commit: %s", author_message);
  
                lb = strchrnul(a + strlen("\nauthor "), '<');
                rb = strchrnul(lb, '>');
                eol = strchrnul(rb, '\n');
                if (!*lb || !*rb || !*eol)
-                       die("invalid commit: %s", use_message);
+                       die("invalid commit: %s", author_message);
  
                if (lb == a + strlen("\nauthor "))
                        /* \nauthor <foo@example.com> */
@@@ -634,18 -678,22 +678,22 @@@ static int prepare_to_commit(const cha
                if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
                        die_errno("could not read SQUASH_MSG");
                hook_arg1 = "squash";
 -      } else if (template_file && !stat(template_file, &statbuf)) {
 +      } else if (template_file) {
                if (strbuf_read_file(&sb, template_file, 0) < 0)
                        die_errno("could not read '%s'", template_file);
                hook_arg1 = "template";
        }
  
        /*
-        * This final case does not modify the template message,
-        * it just sets the argument to the prepare-commit-msg hook.
+        * The remaining cases don't modify the template message, but
+        * just set the argument(s) to the prepare-commit-msg hook.
         */
-       else if (in_merge)
+       else if (whence == FROM_MERGE)
                hook_arg1 = "merge";
+       else if (whence == FROM_CHERRY_PICK) {
+               hook_arg1 = "commit";
+               hook_arg2 = "CHERRY_PICK_HEAD";
+       }
  
        if (squash_message) {
                /*
        strbuf_addstr(&committer_ident, git_committer_info(0));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
-               if (in_merge)
+               if (whence != FROM_COMMIT)
                        fprintf(fp,
                                "#\n"
-                               "# It looks like you may be committing a MERGE.\n"
+                               "# It looks like you may be committing a %s.\n"
                                "# If this is not correct, please remove the file\n"
                                "#      %s\n"
                                "# and try again.\n"
                                "#\n",
-                               git_path("MERGE_HEAD"));
+                               whence_s(),
+                               git_path(whence == FROM_MERGE
+                                        ? "MERGE_HEAD"
+                                        : "CHERRY_PICK_HEAD"));
                fprintf(fp,
                        "\n"
                        "# Please enter the commit message for your changes.");
  
        fclose(fp);
  
-       if (!commitable && !in_merge && !allow_empty &&
+       /*
+        * Reject an attempt to record a non-merge empty commit without
+        * explicit --allow-empty. In the cherry-pick case, it may be
+        * empty due to conflict resolution, which the user should okay.
+        */
+       if (!commitable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(head_sha1))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(empty_amend_advice, stderr);
+               else if (whence == FROM_CHERRY_PICK)
+                       fputs(empty_cherry_pick_advice, stderr);
                return 0;
        }
  
@@@ -898,6 -955,28 +955,28 @@@ static void handle_untracked_files_arg(
                die("Invalid untracked files mode '%s'", untracked_files_arg);
  }
  
+ static const char *read_commit_message(const char *name)
+ {
+       const char *out_enc, *out;
+       struct commit *commit;
+       commit = lookup_commit_reference_by_name(name);
+       if (!commit)
+               die("could not lookup commit %s", name);
+       out_enc = get_commit_output_encoding();
+       out = logmsg_reencode(commit, out_enc);
+       /*
+        * If we failed to reencode the buffer, just copy it
+        * byte for byte so the user can try to fix it up.
+        * This also handles the case where input and output
+        * encodings are identical.
+        */
+       if (out == NULL)
+               out = xstrdup(commit->buffer);
+       return out;
+ }
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
-       if (amend && in_merge)
-               die("You are in the middle of a merge -- cannot amend.");
+       if (amend && whence != FROM_COMMIT)
+               die("You are in the middle of a %s -- cannot amend.", whence_s());
        if (fixup_message && squash_message)
                die("Options --squash and --fixup cannot be used together");
        if (use_message)
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
-       if (!use_message && renew_authorship)
+       if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
                die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
-               const char *out_enc;
-               struct commit *commit;
-               commit = lookup_commit_reference_by_name(use_message);
-               if (!commit)
-                       die("could not lookup commit %s", use_message);
-               out_enc = get_commit_output_encoding();
-               use_message_buffer = logmsg_reencode(commit, out_enc);
-               /*
-                * If we failed to reencode the buffer, just copy it
-                * byte for byte so the user can try to fix it up.
-                * This also handles the case where input and output
-                * encodings are identical.
-                */
-               if (use_message_buffer == NULL)
-                       use_message_buffer = xstrdup(commit->buffer);
+               use_message_buffer = read_commit_message(use_message);
+               if (!renew_authorship) {
+                       author_message = use_message;
+                       author_message_buffer = use_message_buffer;
+               }
+       }
+       if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+               author_message = "CHERRY_PICK_HEAD";
+               author_message_buffer = read_commit_message(author_message);
        }
  
        if (!!also + !!only + !!all + !!interactive > 1)
@@@ -1092,7 -1163,7 +1163,7 @@@ int cmd_status(int argc, const char **a
                OPT_BOOLEAN('b', "branch", &status_show_branch,
                            "show branch information"),
                OPT_SET_INT(0, "porcelain", &status_format,
 -                          "show porcelain output format",
 +                          "machine-readable output",
                            STATUS_FORMAT_PORCELAIN),
                OPT_BOOLEAN('z', "null", &null_termination,
                            "terminate entries with NUL"),
        wt_status_prepare(&s);
        gitmodules_config();
        git_config(git_status_config, &s);
-       in_merge = file_exists(git_path("MERGE_HEAD"));
+       determine_whence(&s);
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
        }
  
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
-       s.in_merge = in_merge;
        s.ignore_submodule_arg = ignore_submodule_arg;
        wt_status_collect(&s);
  
@@@ -1302,8 -1372,7 +1372,7 @@@ int cmd_commit(int argc, const char **a
  
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
-       in_merge = file_exists(git_path("MERGE_HEAD"));
-       s.in_merge = in_merge;
+       determine_whence(&s);
  
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
  
                for (c = commit->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
-       } else if (in_merge) {
+       } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
                FILE *fp;
  
                        parents = reduce_heads(parents);
        } else {
                if (!reflog_msg)
-                       reflog_msg = "commit";
+                       reflog_msg = (whence == FROM_CHERRY_PICK)
+                                       ? "commit (cherry-pick)"
+                                       : "commit";
                pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
        }
  
                die("cannot update HEAD ref");
        }
  
+       unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined builtin/merge.c
index a89ddbb65578182d1d0cbd9b0104889a6dc05ac8,5ba2efc21439b3150f3c46552fea10a0922773e1..b4746ee55b389d35a842c0471336f2e22a003430
@@@ -195,7 -195,7 +195,7 @@@ static struct option builtin_merge_opti
        OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
                "option for selected merge strategy", option_parse_x),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
 -              "message to be used for the merge commit (if any)",
 +              "merge commit message (for a non-fast-forward merge)",
                option_parse_message),
        OPT__VERBOSITY(&verbosity),
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
@@@ -582,8 -582,7 +582,8 @@@ static void write_tree_trivial(unsigne
                die("git write-tree failed to write a tree");
  }
  
 -int try_merge_command(const char *strategy, struct commit_list *common,
 +int try_merge_command(const char *strategy, size_t xopts_nr,
 +                    const char **xopts, struct commit_list *common,
                      const char *head_arg, struct commit_list *remotes)
  {
        const char **args;
@@@ -681,8 -680,7 +681,8 @@@ static int try_merge_strategy(const cha
                rollback_lock_file(lock);
                return clean ? 0 : 1;
        } else {
 -              return try_merge_command(strategy, common, head_arg, remoteheads);
 +              return try_merge_command(strategy, xopts_nr, xopts,
 +                                              common, head_arg, remoteheads);
        }
  }
  
@@@ -797,32 -795,6 +797,32 @@@ static void add_strategies(const char *
  
  }
  
 +static void write_merge_msg(void)
 +{
 +      int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 +      if (fd < 0)
 +              die_errno("Could not open '%s' for writing",
 +                        git_path("MERGE_MSG"));
 +      if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
 +              die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
 +      close(fd);
 +}
 +
 +static void read_merge_msg(void)
 +{
 +      strbuf_reset(&merge_msg);
 +      if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
 +              die_errno("Could not read from '%s'", git_path("MERGE_MSG"));
 +}
 +
 +static void run_prepare_commit_msg(void)
 +{
 +      write_merge_msg();
 +      run_hook(get_index_file(), "prepare-commit-msg",
 +               git_path("MERGE_MSG"), "merge", NULL, NULL);
 +      read_merge_msg();
 +}
 +
  static int merge_trivial(void)
  {
        unsigned char result_tree[20], result_commit[20];
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
 +      run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
        finish(result_commit, "In-index merge");
        drop_save();
@@@ -864,7 -835,6 +864,7 @@@ static int finish_automerge(struct comm
        }
        free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
 +      run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
        finish(result_commit, buf.buf);
@@@ -999,6 -969,13 +999,13 @@@ int cmd_merge(int argc, const char **ar
                else
                        die("You have not concluded your merge (MERGE_HEAD exists).");
        }
+       if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+               if (advice_resolve_conflict)
+                       die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+                           "Please, commit your changes before you can merge.");
+               else
+                       die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).");
+       }
        resolve_undo_clear();
  
        if (verbosity < 0)
                        die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
                close(fd);
                strbuf_addch(&merge_msg, '\n');
 -              fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 -              if (fd < 0)
 -                      die_errno("Could not open '%s' for writing",
 -                                git_path("MERGE_MSG"));
 -              if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
 -                      merge_msg.len)
 -                      die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
 -              close(fd);
 +              write_merge_msg();
                fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
                if (fd < 0)
                        die_errno("Could not open '%s' for writing",
diff --combined builtin/revert.c
index dc1b702edc6b4bd95ebf41c12bf1da6fe9d6850d,b3a3201c44b188147d72f1e2d1fa5bc2c8e4e557..c57b872fe114145ec82bf33717c2a8d7f0982204
@@@ -3,7 -3,6 +3,6 @@@
  #include "object.h"
  #include "commit.h"
  #include "tag.h"
- #include "wt-status.h"
  #include "run-command.h"
  #include "exec_cmd.h"
  #include "utf8.h"
@@@ -44,11 -43,7 +43,11 @@@ static const char **commit_argv
  static int allow_rerere_auto;
  
  static const char *me;
 +
 +/* Merge strategy. */
  static const char *strategy;
 +static const char **xopts;
 +static size_t xopts_nr, xopts_alloc;
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
@@@ -59,17 -54,6 +58,17 @@@ static const char * const *revert_or_ch
        return action == REVERT ? revert_usage : cherry_pick_usage;
  }
  
 +static int option_parse_x(const struct option *opt,
 +                        const char *arg, int unset)
 +{
 +      if (unset)
 +              return 0;
 +
 +      ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
 +      xopts[xopts_nr++] = xstrdup(arg);
 +      return 0;
 +}
 +
  static void parse_args(int argc, const char **argv)
  {
        const char * const * usage_str = revert_or_cherry_pick_usage();
@@@ -82,8 -66,6 +81,8 @@@
                OPT_INTEGER('m', "mainline", &mainline, "parent number"),
                OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
                OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
 +              OPT_CALLBACK('X', "strategy-option", &xopts, "option",
 +                      "option for merge strategy", option_parse_x),
                OPT_END(),
                OPT_END(),
                OPT_END(),
@@@ -198,54 -180,20 +197,20 @@@ static void add_message_to_msg(struct s
        strbuf_addstr(msgbuf, p);
  }
  
- static void set_author_ident_env(const char *message)
+ static void write_cherry_pick_head(void)
  {
-       const char *p = message;
-       if (!p)
-               die ("Could not read commit message of %s",
-                               sha1_to_hex(commit->object.sha1));
-       while (*p && *p != '\n') {
-               const char *eol;
-               for (eol = p; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "author ")) {
-                       char *line, *pend, *email, *timestamp;
-                       p += 7;
-                       line = xmemdupz(p, eol - p);
-                       email = strchr(line, '<');
-                       if (!email)
-                               die ("Could not extract author email from %s",
-                                       sha1_to_hex(commit->object.sha1));
-                       if (email == line)
-                               pend = line;
-                       else
-                               for (pend = email; pend != line + 1 &&
-                                               isspace(pend[-1]); pend--);
-                                       ; /* do nothing */
-                       *pend = '\0';
-                       email++;
-                       timestamp = strchr(email, '>');
-                       if (!timestamp)
-                               die ("Could not extract author time from %s",
-                                       sha1_to_hex(commit->object.sha1));
-                       *timestamp = '\0';
-                       for (timestamp++; *timestamp && isspace(*timestamp);
-                                       timestamp++)
-                               ; /* do nothing */
-                       setenv("GIT_AUTHOR_NAME", line, 1);
-                       setenv("GIT_AUTHOR_EMAIL", email, 1);
-                       setenv("GIT_AUTHOR_DATE", timestamp, 1);
-                       free(line);
-                       return;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       die ("No author information found in %s",
-                       sha1_to_hex(commit->object.sha1));
+       int fd;
+       struct strbuf buf = STRBUF_INIT;
+       strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
+       fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno("Could not open '%s' for writing",
+                         git_path("CHERRY_PICK_HEAD"));
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
+               die_errno("Could not write to '%s'", git_path("CHERRY_PICK_HEAD"));
+       strbuf_release(&buf);
  }
  
  static void advise(const char *advice, ...)
@@@ -263,15 -211,18 +228,18 @@@ static void print_advice(void
  
        if (msg) {
                fprintf(stderr, "%s\n", msg);
+               /*
+                * A conflict has occured but the porcelain
+                * (typically rebase --interactive) wants to take care
+                * of the commit itself so remove CHERRY_PICK_HEAD
+                */
+               unlink(git_path("CHERRY_PICK_HEAD"));
                return;
        }
  
        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));
+       advise("and commit the result with 'git commit'");
  }
  
  static void write_message(struct strbuf *msgbuf, const char *filename)
@@@ -328,13 -279,18 +296,13 @@@ static int do_recursive_merge(struct co
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
        int clean, index_fd;
 +      const char **xopt;
        static struct lock_file index_lock;
  
        index_fd = hold_locked_index(&index_lock, 1);
  
        read_cache();
  
 -      /*
 -       * 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";
        next_tree = next ? next->tree : empty_tree();
        base_tree = base ? base->tree : empty_tree();
  
 +      for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
 +              parse_merge_opt(&o, *xopt);
 +
        clean = merge_trees(&o,
                            head_tree,
                            next_tree, base_tree, &result);
@@@ -497,13 -450,14 +465,14 @@@ static int do_pick_commit(void
                base_label = msg.parent_label;
                next = commit;
                next_label = msg.label;
-               set_author_ident_env(msg.message);
                add_message_to_msg(&msgbuf, msg.message);
                if (no_replay) {
                        strbuf_addstr(&msgbuf, "(cherry picked from commit ");
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
+               if (!no_commit)
+                       write_cherry_pick_head();
        }
  
        if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
  
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
 -              res = try_merge_command(strategy, common,
 +              res = try_merge_command(strategy, xopts_nr, xopts, common,
                                        sha1_to_hex(head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
diff --combined wt-status.c
index a82b11d341c9dddb541cc72a5568769d9f538780,fbaaf54dadc11945a70ef2cebc419d461bc8aee7..4daa8bb5242fc689973839a13a3a28477efbb6bf
@@@ -60,7 -60,7 +60,7 @@@ static void wt_status_print_unmerged_he
        color_fprintf_ln(s->fp, c, "# Unmerged paths:");
        if (!advice_status_hints)
                return;
-       if (s->in_merge)
+       if (s->whence != FROM_COMMIT)
                ;
        else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
@@@ -77,7 -77,7 +77,7 @@@ static void wt_status_print_cached_head
        color_fprintf_ln(s->fp, c, "# Changes to be committed:");
        if (!advice_status_hints)
                return;
-       if (s->in_merge)
+       if (s->whence != FROM_COMMIT)
                ; /* NEEDSWORK: use "git reset --unresolve"??? */
        else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
@@@ -323,7 -323,7 +323,7 @@@ static void wt_status_collect_changes_w
      }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_files(&rev, 0);
  }
  
@@@ -348,22 -348,20 +348,22 @@@ static void wt_status_collect_changes_i
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_index(&rev, 1);
  }
  
  static void wt_status_collect_changes_initial(struct wt_status *s)
  {
 +      struct pathspec pathspec;
        int i;
  
 +      init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
  
 -              if (!ce_path_match(ce, s->pathspec))
 +              if (!ce_path_match(ce, &pathspec))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
 +      free_pathspec(&pathspec);
  }
  
  static void wt_status_collect_untracked(struct wt_status *s)