Merge branch 'pw/sequencer-in-process-commit'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Feb 2018 21:39:15 +0000 (13:39 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Feb 2018 21:39:15 +0000 (13:39 -0800)
The sequencer infrastructure is shared across "git cherry-pick",
"git rebase -i", etc., and has always spawned "git commit" when it
needs to create a commit. It has been taught to do so internally,
when able, by reusing the codepath "git commit" itself uses, which
gives performance boost for a few tens of percents in some sample
scenarios.

* pw/sequencer-in-process-commit:
sequencer: run 'prepare-commit-msg' hook
t7505: add tests for cherry-pick and rebase -i/-p
t7505: style fixes
sequencer: assign only free()able strings to gpg_sign
sequencer: improve config handling
t3512/t3513: remove KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
sequencer: try to commit without forking 'git commit'
sequencer: load commit related config
sequencer: simplify adding Signed-off-by: trailer
commit: move print_commit_summary() to libgit
commit: move post-rewrite code to libgit
Add a function to update HEAD after creating a commit
commit: move empty message checks to libgit
t3404: check intermediate squash messages

1  2 
builtin/commit.c
builtin/rebase--helper.c
sequencer.c
sequencer.h
t/t3404-rebase-interactive.sh
t/t3512-cherry-pick-submodule.sh
diff --combined builtin/commit.c
index 4610e3d8e3e04a70678a102cc3e0e7472089a3cf,9f97ff8b98edf779f1acda1fa8b2a329326ac822..5dd766af2842dddb80d30cd73b8be8ccb4956eac
@@@ -31,9 -31,7 +31,7 @@@
  #include "gpg-interface.h"
  #include "column.h"
  #include "sequencer.h"
- #include "notes-utils.h"
  #include "mailmap.h"
- #include "sigchain.h"
  
  static const char * const builtin_commit_usage[] = {
        N_("git commit [<options>] [--] <pathspec>..."),
@@@ -45,31 -43,6 +43,6 @@@ static const char * const builtin_statu
        NULL
  };
  
- static const char implicit_ident_advice_noconfig[] =
- N_("Your name and email address were configured automatically based\n"
- "on your username and hostname. Please check that they are accurate.\n"
- "You can suppress this message by setting them explicitly. Run the\n"
- "following command and follow the instructions in your editor to edit\n"
- "your configuration file:\n"
- "\n"
- "    git config --global --edit\n"
- "\n"
- "After doing this, you may fix the identity used for this commit with:\n"
- "\n"
- "    git commit --amend --reset-author\n");
- static const char implicit_ident_advice_config[] =
- N_("Your name and email address were configured automatically based\n"
- "on your username and hostname. Please check that they are accurate.\n"
- "You can suppress this message by setting them explicitly:\n"
- "\n"
- "    git config --global user.name \"Your Name\"\n"
- "    git config --global user.email you@example.com\n"
- "\n"
- "After doing this, you may fix the identity used for this commit with:\n"
- "\n"
- "    git commit --amend --reset-author\n");
  static const char empty_amend_advice[] =
  N_("You asked to amend the most recent commit, but doing so would make\n"
  "it empty. You can repeat your command with --allow-empty, or you can\n"
@@@ -93,8 -66,6 +66,6 @@@ N_("If you wish to skip this commit, us
  "Then \"git cherry-pick --continue\" will resume cherry-picking\n"
  "the remaining commits.\n");
  
- static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
  static const char *use_message_buffer;
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -118,7 -89,7 +89,7 @@@ static int edit_flag = -1; /* unspecifi
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int config_commit_verbose = -1; /* unspecified */
  static int no_post_rewrite, allow_empty_message;
 -static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
 +static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
  static char *sign_commit;
  
  /*
   * if editor is used, and only the whitespaces if the message
   * is specified explicitly.
   */
- static enum {
-       CLEANUP_SPACE,
-       CLEANUP_NONE,
-       CLEANUP_SCISSORS,
-       CLEANUP_ALL
- } cleanup_mode;
+ static enum commit_msg_cleanup_mode cleanup_mode;
  static const char *cleanup_arg;
  
  static enum commit_whence whence;
  static int sequencer_in_use;
  static int use_editor = 1, include_status = 1;
 -static int show_ignored_in_status, have_option_m;
 +static int have_option_m;
  static struct strbuf message = STRBUF_INIT;
  
  static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@@ -673,7 -639,7 +639,7 @@@ static int prepare_to_commit(const cha
        struct strbuf sb = STRBUF_INIT;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
-       int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
+       int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE);
        int old_display_comment_prefix;
  
        /* This checks and barfs if author is badly specified */
                }
        }
  
 -      if (have_option_m) {
 +      if (have_option_m && !fixup_message) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
                ctx.output_encoding = get_commit_output_encoding();
                format_commit_message(commit, "fixup! %s\n\n",
                                      &sb, &ctx);
 +              if (have_option_m)
 +                      strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (!stat(git_path_merge_msg(), &statbuf)) {
                /*
                struct ident_split ci, ai;
  
                if (whence != FROM_COMMIT) {
-                       if (cleanup_mode == CLEANUP_SCISSORS)
+                       if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                                wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                            whence == FROM_MERGE
                }
  
                fprintf(s->fp, "\n");
-               if (cleanup_mode == CLEANUP_ALL)
+               if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
                                  " Lines starting\nwith '%c' will be ignored, and an empty"
                                  " message aborts the commit.\n"), comment_line_char);
-               else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
+               else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
+                        whence == FROM_COMMIT)
                        wt_status_add_cut_line(s->fp);
-               else /* CLEANUP_SPACE, that is. */
+               else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
                                  " Lines starting\n"
        return 1;
  }
  
- static int rest_is_empty(struct strbuf *sb, int start)
- {
-       int i, eol;
-       const char *nl;
-       /* Check if the rest is just whitespace and Signed-off-by's. */
-       for (i = start; i < sb->len; i++) {
-               nl = memchr(sb->buf + i, '\n', sb->len - i);
-               if (nl)
-                       eol = nl - sb->buf;
-               else
-                       eol = sb->len;
-               if (strlen(sign_off_header) <= eol - i &&
-                   starts_with(sb->buf + i, sign_off_header)) {
-                       i = eol;
-                       continue;
-               }
-               while (i < eol)
-                       if (!isspace(sb->buf[i++]))
-                               return 0;
-       }
-       return 1;
- }
- /*
-  * Find out if the message in the strbuf contains only whitespace and
-  * Signed-off-by lines.
-  */
- static int message_is_empty(struct strbuf *sb)
- {
-       if (cleanup_mode == CLEANUP_NONE && sb->len)
-               return 0;
-       return rest_is_empty(sb, 0);
- }
- /*
-  * See if the user edited the message in the editor or left what
-  * was in the template intact
-  */
- static int template_untouched(struct strbuf *sb)
- {
-       struct strbuf tmpl = STRBUF_INIT;
-       const char *start;
-       if (cleanup_mode == CLEANUP_NONE && sb->len)
-               return 0;
-       if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
-               return 0;
-       strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
-       if (!skip_prefix(sb->buf, tmpl.buf, &start))
-               start = sb->buf;
-       strbuf_release(&tmpl);
-       return rest_is_empty(sb, start - sb->buf);
- }
  static const char *find_author_by_nickname(const char *name)
  {
        struct rev_info revs;
        die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
  }
  
 +static void handle_ignored_arg(struct wt_status *s)
 +{
 +      if (!ignored_arg)
 +              ; /* default already initialized */
 +      else if (!strcmp(ignored_arg, "traditional"))
 +              s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
 +      else if (!strcmp(ignored_arg, "no"))
 +              s->show_ignored_mode = SHOW_NO_IGNORED;
 +      else if (!strcmp(ignored_arg, "matching"))
 +              s->show_ignored_mode = SHOW_MATCHING_IGNORED;
 +      else
 +              die(_("Invalid ignored mode '%s'"), ignored_arg);
 +}
  
  static void handle_untracked_files_arg(struct wt_status *s)
  {
@@@ -1199,8 -1092,8 +1107,8 @@@ static int parse_and_validate_options(i
                f++;
        if (f > 1)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
 -      if (have_option_m && f > 0)
 -              die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
 +      if (have_option_m && (edit_message || use_message || logfile))
 +              die((_("Option -m cannot be combined with -c/-C/-F.")));
        if (f || have_option_m)
                template_file = NULL;
        if (edit_message)
        if (argc == 0 && (also || (only && !amend && !allow_empty)))
                die(_("No paths with --include/--only does not make sense."));
        if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
-               cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+               cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL :
+                                           COMMIT_MSG_CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "verbatim"))
-               cleanup_mode = CLEANUP_NONE;
+               cleanup_mode = COMMIT_MSG_CLEANUP_NONE;
        else if (!strcmp(cleanup_arg, "whitespace"))
-               cleanup_mode = CLEANUP_SPACE;
+               cleanup_mode = COMMIT_MSG_CLEANUP_SPACE;
        else if (!strcmp(cleanup_arg, "strip"))
-               cleanup_mode = CLEANUP_ALL;
+               cleanup_mode = COMMIT_MSG_CLEANUP_ALL;
        else if (!strcmp(cleanup_arg, "scissors"))
-               cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
+               cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
+                                           COMMIT_MSG_CLEANUP_SPACE;
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
@@@ -1379,10 -1274,8 +1289,10 @@@ int cmd_status(int argc, const char **a
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 -              OPT_BOOL(0, "ignored", &show_ignored_in_status,
 -                       N_("show ignored files")),
 +              { OPTION_STRING, 0, "ignored", &ignored_arg,
 +                N_("mode"),
 +                N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
 +                PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        finalize_deferred_config(&s);
  
        handle_untracked_files_arg(&s);
 -      if (show_ignored_in_status)
 -              s.show_ignored_files = 1;
 +      handle_ignored_arg(&s);
 +
 +      if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
 +          s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
 +              die(_("Unsupported combination of ignored and untracked-files arguments"));
 +
        parse_pathspec(&s.pathspec, 0,
                       PATHSPEC_PREFER_FULL,
                       prefix, argv);
        return 0;
  }
  
- static const char *implicit_ident_advice(void)
- {
-       char *user_config = expand_user_path("~/.gitconfig", 0);
-       char *xdg_config = xdg_config_home("config");
-       int config_exists = file_exists(user_config) || file_exists(xdg_config);
-       free(user_config);
-       free(xdg_config);
-       if (config_exists)
-               return _(implicit_ident_advice_config);
-       else
-               return _(implicit_ident_advice_noconfig);
- }
- static void print_summary(const char *prefix, const struct object_id *oid,
-                         int initial_commit)
- {
-       struct rev_info rev;
-       struct commit *commit;
-       struct strbuf format = STRBUF_INIT;
-       const char *head;
-       struct pretty_print_context pctx = {0};
-       struct strbuf author_ident = STRBUF_INIT;
-       struct strbuf committer_ident = STRBUF_INIT;
-       commit = lookup_commit(oid);
-       if (!commit)
-               die(_("couldn't look up newly created commit"));
-       if (parse_commit(commit))
-               die(_("could not parse newly created commit"));
-       strbuf_addstr(&format, "format:%h] %s");
-       format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
-       format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
-       if (strbuf_cmp(&author_ident, &committer_ident)) {
-               strbuf_addstr(&format, "\n Author: ");
-               strbuf_addbuf_percentquote(&format, &author_ident);
-       }
-       if (author_date_is_interesting()) {
-               struct strbuf date = STRBUF_INIT;
-               format_commit_message(commit, "%ad", &date, &pctx);
-               strbuf_addstr(&format, "\n Date: ");
-               strbuf_addbuf_percentquote(&format, &date);
-               strbuf_release(&date);
-       }
-       if (!committer_ident_sufficiently_given()) {
-               strbuf_addstr(&format, "\n Committer: ");
-               strbuf_addbuf_percentquote(&format, &committer_ident);
-               if (advice_implicit_identity) {
-                       strbuf_addch(&format, '\n');
-                       strbuf_addstr(&format, implicit_ident_advice());
-               }
-       }
-       strbuf_release(&author_ident);
-       strbuf_release(&committer_ident);
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.diff = 1;
-       rev.diffopt.output_format =
-               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
-       rev.verbose_header = 1;
-       rev.show_root_diff = 1;
-       get_commit_format(format.buf, &rev);
-       rev.always_show_header = 0;
-       rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
-       rev.diffopt.break_opt = 0;
-       diff_setup_done(&rev.diffopt);
-       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-       if (!head)
-               die_errno(_("unable to resolve HEAD after creating commit"));
-       if (!strcmp(head, "HEAD"))
-               head = _("detached HEAD");
-       else
-               skip_prefix(head, "refs/heads/", &head);
-       printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : "");
-       if (!log_tree_commit(&rev, commit)) {
-               rev.always_show_header = 1;
-               rev.use_terminator = 1;
-               log_tree_commit(&rev, commit);
-       }
-       strbuf_release(&format);
- }
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
        struct wt_status *s = cb;
        return git_status_config(k, v, s);
  }
  
- static int run_rewrite_hook(const struct object_id *oldoid,
-                           const struct object_id *newoid)
- {
-       struct child_process proc = CHILD_PROCESS_INIT;
-       const char *argv[3];
-       int code;
-       struct strbuf sb = STRBUF_INIT;
-       argv[0] = find_hook("post-rewrite");
-       if (!argv[0])
-               return 0;
-       argv[1] = "amend";
-       argv[2] = NULL;
-       proc.argv = argv;
-       proc.in = -1;
-       proc.stdout_to_stderr = 1;
-       code = start_command(&proc);
-       if (code)
-               return code;
-       strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
-       sigchain_push(SIGPIPE, SIG_IGN);
-       write_in_full(proc.in, sb.buf, sb.len);
-       close(proc.in);
-       strbuf_release(&sb);
-       sigchain_pop(SIGPIPE);
-       return finish_command(&proc);
- }
  int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
  {
        struct argv_array hook_env = ARGV_ARRAY_INIT;
@@@ -1673,13 -1439,11 +1460,11 @@@ int cmd_commit(int argc, const char **a
        struct strbuf sb = STRBUF_INIT;
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
-       char *nl;
        struct object_id oid;
        struct commit_list *parents = NULL;
        struct stat statbuf;
        struct commit *current_head = NULL;
        struct commit_extra_header *extra = NULL;
-       struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                                allow_fast_forward = 0;
                }
                if (allow_fast_forward)
 -                      parents = reduce_heads(parents);
 +                      reduce_heads_replace(&parents);
        } else {
                if (!reflog_msg)
                        reflog_msg = (whence == FROM_CHERRY_PICK)
        }
  
        if (verbose || /* Truncate the message just before the diff, if any. */
-           cleanup_mode == CLEANUP_SCISSORS)
+           cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len));
-       if (cleanup_mode != CLEANUP_NONE)
-               strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+       if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
+               strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
  
-       if (message_is_empty(&sb) && !allow_empty_message) {
+       if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
                exit(1);
        }
-       if (template_untouched(&sb) && !allow_empty_message) {
+       if (template_untouched(&sb, template_file, cleanup_mode) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
                exit(1);
        strbuf_release(&author_ident);
        free_commit_extra_headers(extra);
  
-       nl = strchr(sb.buf, '\n');
-       if (nl)
-               strbuf_setlen(&sb, nl + 1 - sb.buf);
-       else
-               strbuf_addch(&sb, '\n');
-       strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
-       strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
-       transaction = ref_transaction_begin(&err);
-       if (!transaction ||
-           ref_transaction_update(transaction, "HEAD", &oid,
-                                  current_head
-                                  ? &current_head->object.oid : &null_oid,
-                                  0, sb.buf, &err) ||
-           ref_transaction_commit(transaction, &err)) {
+       if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
+                                   &err)) {
                rollback_index_files();
                die("%s", err.buf);
        }
-       ref_transaction_free(transaction);
  
        unlink(git_path_cherry_pick_head());
        unlink(git_path_revert_head());
        rerere(0);
        run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
        if (amend && !no_post_rewrite) {
-               struct notes_rewrite_cfg *cfg;
-               cfg = init_copy_notes_for_rewrite("amend");
-               if (cfg) {
-                       /* we are amending, so current_head is not NULL */
-                       copy_note_for_rewrite(cfg, &current_head->object.oid, &oid);
-                       finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
-               }
-               run_rewrite_hook(&current_head->object.oid, &oid);
+               commit_post_rewrite(current_head, &oid);
+       }
+       if (!quiet) {
+               unsigned int flags = 0;
+               if (!current_head)
+                       flags |= SUMMARY_INITIAL_COMMIT;
+               if (author_date_is_interesting())
+                       flags |= SUMMARY_SHOW_AUTHOR_DATE;
+               print_commit_summary(prefix, &oid, flags);
        }
-       if (!quiet)
-               print_summary(prefix, &oid, !current_head);
  
        UNLEAK(err);
        UNLEAK(sb);
diff --combined builtin/rebase--helper.c
index 7daee544b7b4cf3495355e1c6e597272d7b66440,decb8f7a09e42eb94bed264164985e54e13a32f6..00faf14d07403fedd07bfbbaf1123bea07e3e328
@@@ -12,12 -12,10 +12,12 @@@ static const char * const builtin_rebas
  int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
  {
        struct replay_opts opts = REPLAY_OPTS_INIT;
 -      int keep_empty = 0;
 +      unsigned flags = 0, keep_empty = 0;
 +      int abbreviate_commands = 0;
        enum {
 -              CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
 -              CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
 +              CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
 +              CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
 +              ADD_EXEC
        } command = 0;
        struct option options[] = {
                OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
                OPT_CMDMODE(0, "make-script", &command,
                        N_("make rebase script"), MAKE_SCRIPT),
                OPT_CMDMODE(0, "shorten-ids", &command,
 -                      N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
 +                      N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
                OPT_CMDMODE(0, "expand-ids", &command,
 -                      N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
 +                      N_("expand commit ids in the todo list"), EXPAND_OIDS),
                OPT_CMDMODE(0, "check-todo-list", &command,
                        N_("check the todo list"), CHECK_TODO_LIST),
                OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
                        N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
                OPT_CMDMODE(0, "rearrange-squash", &command,
                        N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 +              OPT_CMDMODE(0, "add-exec-commands", &command,
 +                      N_("insert exec commands in todo list"), ADD_EXEC),
                OPT_END()
        };
  
-       git_config(git_default_config, NULL);
+       sequencer_init_config(&opts);
 +      git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
  
        opts.action = REPLAY_INTERACTIVE_REBASE;
        opts.allow_ff = 1;
        argc = parse_options(argc, argv, NULL, options,
                        builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
  
 +      flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
 +      flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 +      flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 +
        if (command == CONTINUE && argc == 1)
                return !!sequencer_continue(&opts);
        if (command == ABORT && argc == 1)
                return !!sequencer_remove_state(&opts);
        if (command == MAKE_SCRIPT && argc > 1)
 -              return !!sequencer_make_script(keep_empty, stdout, argc, argv);
 -      if (command == SHORTEN_SHA1S && argc == 1)
 -              return !!transform_todo_ids(1);
 -      if (command == EXPAND_SHA1S && argc == 1)
 -              return !!transform_todo_ids(0);
 +              return !!sequencer_make_script(stdout, argc, argv, flags);
 +      if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
 +              return !!transform_todos(flags);
        if (command == CHECK_TODO_LIST && argc == 1)
                return !!check_todo_list();
        if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
                return !!skip_unnecessary_picks();
        if (command == REARRANGE_SQUASH && argc == 1)
                return !!rearrange_squash();
 +      if (command == ADD_EXEC && argc == 2)
 +              return !!sequencer_add_exec_commands(argv[1]);
        usage_with_options(builtin_rebase_helper_usage, options);
  }
diff --combined sequencer.c
index 4d3f60594cbf0e9ddf2ea78e8c58eca312b4cac0,86840b4bc1d3e49bb0070aefc6feac39cad1fbf9..5bfdc4044233d5f809f9f1fbc55ebe3da477e3f0
@@@ -1,10 -1,10 +1,10 @@@
  #include "cache.h"
  #include "config.h"
  #include "lockfile.h"
- #include "sequencer.h"
  #include "dir.h"
  #include "object.h"
  #include "commit.h"
+ #include "sequencer.h"
  #include "tag.h"
  #include "run-command.h"
  #include "exec_cmd.h"
  #include "log-tree.h"
  #include "wt-status.h"
  #include "hashmap.h"
+ #include "notes-utils.h"
+ #include "sigchain.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
  const char sign_off_header[] = "Signed-off-by: ";
  static const char cherry_picked_prefix[] = "(cherry picked from commit ";
  
+ GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
  GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
  
  static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
@@@ -130,6 -134,51 +134,51 @@@ static GIT_PATH_FUNC(rebase_path_strate
  static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
  static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
  
+ static int git_sequencer_config(const char *k, const char *v, void *cb)
+ {
+       struct replay_opts *opts = cb;
+       int status;
+       if (!strcmp(k, "commit.cleanup")) {
+               const char *s;
+               status = git_config_string(&s, k, v);
+               if (status)
+                       return status;
+               if (!strcmp(s, "verbatim"))
+                       opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
+               else if (!strcmp(s, "whitespace"))
+                       opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
+               else if (!strcmp(s, "strip"))
+                       opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
+               else if (!strcmp(s, "scissors"))
+                       opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
+               else
+                       warning(_("invalid commit message cleanup mode '%s'"),
+                                 s);
+               return status;
+       }
+       if (!strcmp(k, "commit.gpgsign")) {
+               opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
+               return 0;
+       }
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
+       return git_diff_basic_config(k, v, NULL);
+ }
+ void sequencer_init_config(struct replay_opts *opts)
+ {
+       opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
+       git_config(git_sequencer_config, opts);
+ }
  static inline int is_rebase_i(const struct replay_opts *opts)
  {
        return opts->action == REPLAY_INTERACTIVE_REBASE;
@@@ -347,7 -396,7 +396,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(&empty_tree_oid);
 +      return lookup_tree(the_hash_algo->empty_tree);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -438,8 -487,7 +487,8 @@@ static int do_recursive_merge(struct co
        char **xopt;
        static struct lock_file index_lock;
  
 -      hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
 +      if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
 +              return -1;
  
        read_cache();
  
        o.branch2 = next ? next_label : "(empty tree)";
        if (is_rebase_i(opts))
                o.buffer_output = 2;
 +      o.show_rename_progress = 1;
  
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
        if (is_rebase_i(opts) && clean <= 0)
                fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
 +      diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
        if (clean < 0)
                return clean;
  
                        _(action_name(opts)));
        rollback_lock_file(&index_lock);
  
-       if (opts->signoff)
-               append_signoff(msgbuf, 0, 0);
        if (!clean)
                append_conflicts_hint(msgbuf);
  
@@@ -493,7 -536,7 +539,7 @@@ static int is_index_unchanged(void
        struct commit *head_commit;
  
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
 -              return error(_("could not resolve HEAD commit\n"));
 +              return error(_("could not resolve HEAD commit"));
  
        head_commit = lookup_commit(&head_oid);
  
  
        if (!cache_tree_fully_valid(active_cache_tree))
                if (cache_tree_update(&the_index, 0))
 -                      return error(_("unable to update cache tree\n"));
 +                      return error(_("unable to update cache tree"));
  
        return !oidcmp(&active_cache_tree->oid,
                       &head_commit->tree->object.oid);
@@@ -596,6 -639,18 +642,18 @@@ static int read_env_script(struct argv_
        return 0;
  }
  
+ static char *get_author(const char *message)
+ {
+       size_t len;
+       const char *a;
+       a = find_commit_header(message, "author", &len);
+       if (a)
+               return xmemdupz(a, len);
+       return NULL;
+ }
  static const char staged_changes_advice[] =
  N_("you have staged changes in your working tree\n"
  "If these changes are meant to be squashed into the previous commit, run:\n"
@@@ -658,8 -713,6 +716,6 @@@ static int run_git_commit(const char *d
                argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
-       if (opts->signoff)
-               argv_array_push(&cmd.args, "-s");
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        if ((flags & CLEANUP_MSG))
        return run_command(&cmd);
  }
  
 -      rev.diffopt.detect_rename = 1;
+ static int rest_is_empty(const struct strbuf *sb, int start)
+ {
+       int i, eol;
+       const char *nl;
+       /* Check if the rest is just whitespace and Signed-off-by's. */
+       for (i = start; i < sb->len; i++) {
+               nl = memchr(sb->buf + i, '\n', sb->len - i);
+               if (nl)
+                       eol = nl - sb->buf;
+               else
+                       eol = sb->len;
+               if (strlen(sign_off_header) <= eol - i &&
+                   starts_with(sb->buf + i, sign_off_header)) {
+                       i = eol;
+                       continue;
+               }
+               while (i < eol)
+                       if (!isspace(sb->buf[i++]))
+                               return 0;
+       }
+       return 1;
+ }
+ /*
+  * Find out if the message in the strbuf contains only whitespace and
+  * Signed-off-by lines.
+  */
+ int message_is_empty(const struct strbuf *sb,
+                    enum commit_msg_cleanup_mode cleanup_mode)
+ {
+       if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
+               return 0;
+       return rest_is_empty(sb, 0);
+ }
+ /*
+  * See if the user edited the message in the editor or left what
+  * was in the template intact
+  */
+ int template_untouched(const struct strbuf *sb, const char *template_file,
+                      enum commit_msg_cleanup_mode cleanup_mode)
+ {
+       struct strbuf tmpl = STRBUF_INIT;
+       const char *start;
+       if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
+               return 0;
+       if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+               return 0;
+       strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+       if (!skip_prefix(sb->buf, tmpl.buf, &start))
+               start = sb->buf;
+       strbuf_release(&tmpl);
+       return rest_is_empty(sb, start - sb->buf);
+ }
+ int update_head_with_reflog(const struct commit *old_head,
+                           const struct object_id *new_head,
+                           const char *action, const struct strbuf *msg,
+                           struct strbuf *err)
+ {
+       struct ref_transaction *transaction;
+       struct strbuf sb = STRBUF_INIT;
+       const char *nl;
+       int ret = 0;
+       if (action) {
+               strbuf_addstr(&sb, action);
+               strbuf_addstr(&sb, ": ");
+       }
+       nl = strchr(msg->buf, '\n');
+       if (nl) {
+               strbuf_add(&sb, msg->buf, nl + 1 - msg->buf);
+       } else {
+               strbuf_addbuf(&sb, msg);
+               strbuf_addch(&sb, '\n');
+       }
+       transaction = ref_transaction_begin(err);
+       if (!transaction ||
+           ref_transaction_update(transaction, "HEAD", new_head,
+                                  old_head ? &old_head->object.oid : &null_oid,
+                                  0, sb.buf, err) ||
+           ref_transaction_commit(transaction, err)) {
+               ret = -1;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&sb);
+       return ret;
+ }
+ static int run_rewrite_hook(const struct object_id *oldoid,
+                           const struct object_id *newoid)
+ {
+       struct child_process proc = CHILD_PROCESS_INIT;
+       const char *argv[3];
+       int code;
+       struct strbuf sb = STRBUF_INIT;
+       argv[0] = find_hook("post-rewrite");
+       if (!argv[0])
+               return 0;
+       argv[1] = "amend";
+       argv[2] = NULL;
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+       code = start_command(&proc);
+       if (code)
+               return code;
+       strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
+       sigchain_push(SIGPIPE, SIG_IGN);
+       write_in_full(proc.in, sb.buf, sb.len);
+       close(proc.in);
+       strbuf_release(&sb);
+       sigchain_pop(SIGPIPE);
+       return finish_command(&proc);
+ }
+ void commit_post_rewrite(const struct commit *old_head,
+                        const struct object_id *new_head)
+ {
+       struct notes_rewrite_cfg *cfg;
+       cfg = init_copy_notes_for_rewrite("amend");
+       if (cfg) {
+               /* we are amending, so old_head is not NULL */
+               copy_note_for_rewrite(cfg, &old_head->object.oid, new_head);
+               finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
+       }
+       run_rewrite_hook(&old_head->object.oid, new_head);
+ }
+ static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit)
+ {
+       struct argv_array hook_env = ARGV_ARRAY_INIT;
+       int ret;
+       const char *name;
+       name = git_path_commit_editmsg();
+       if (write_message(msg->buf, msg->len, name, 0))
+               return -1;
+       argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file());
+       argv_array_push(&hook_env, "GIT_EDITOR=:");
+       if (commit)
+               ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+                                 "commit", commit, NULL);
+       else
+               ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+                                 "message", NULL);
+       if (ret)
+               ret = error(_("'prepare-commit-msg' hook failed"));
+       argv_array_clear(&hook_env);
+       return ret;
+ }
+ static const char implicit_ident_advice_noconfig[] =
+ N_("Your name and email address were configured automatically based\n"
+ "on your username and hostname. Please check that they are accurate.\n"
+ "You can suppress this message by setting them explicitly. Run the\n"
+ "following command and follow the instructions in your editor to edit\n"
+ "your configuration file:\n"
+ "\n"
+ "    git config --global --edit\n"
+ "\n"
+ "After doing this, you may fix the identity used for this commit with:\n"
+ "\n"
+ "    git commit --amend --reset-author\n");
+ static const char implicit_ident_advice_config[] =
+ N_("Your name and email address were configured automatically based\n"
+ "on your username and hostname. Please check that they are accurate.\n"
+ "You can suppress this message by setting them explicitly:\n"
+ "\n"
+ "    git config --global user.name \"Your Name\"\n"
+ "    git config --global user.email you@example.com\n"
+ "\n"
+ "After doing this, you may fix the identity used for this commit with:\n"
+ "\n"
+ "    git commit --amend --reset-author\n");
+ static const char *implicit_ident_advice(void)
+ {
+       char *user_config = expand_user_path("~/.gitconfig", 0);
+       char *xdg_config = xdg_config_home("config");
+       int config_exists = file_exists(user_config) || file_exists(xdg_config);
+       free(user_config);
+       free(xdg_config);
+       if (config_exists)
+               return _(implicit_ident_advice_config);
+       else
+               return _(implicit_ident_advice_noconfig);
+ }
+ void print_commit_summary(const char *prefix, const struct object_id *oid,
+                         unsigned int flags)
+ {
+       struct rev_info rev;
+       struct commit *commit;
+       struct strbuf format = STRBUF_INIT;
+       const char *head;
+       struct pretty_print_context pctx = {0};
+       struct strbuf author_ident = STRBUF_INIT;
+       struct strbuf committer_ident = STRBUF_INIT;
+       commit = lookup_commit(oid);
+       if (!commit)
+               die(_("couldn't look up newly created commit"));
+       if (parse_commit(commit))
+               die(_("could not parse newly created commit"));
+       strbuf_addstr(&format, "format:%h] %s");
+       format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+       format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+       if (strbuf_cmp(&author_ident, &committer_ident)) {
+               strbuf_addstr(&format, "\n Author: ");
+               strbuf_addbuf_percentquote(&format, &author_ident);
+       }
+       if (flags & SUMMARY_SHOW_AUTHOR_DATE) {
+               struct strbuf date = STRBUF_INIT;
+               format_commit_message(commit, "%ad", &date, &pctx);
+               strbuf_addstr(&format, "\n Date: ");
+               strbuf_addbuf_percentquote(&format, &date);
+               strbuf_release(&date);
+       }
+       if (!committer_ident_sufficiently_given()) {
+               strbuf_addstr(&format, "\n Committer: ");
+               strbuf_addbuf_percentquote(&format, &committer_ident);
+               if (advice_implicit_identity) {
+                       strbuf_addch(&format, '\n');
+                       strbuf_addstr(&format, implicit_ident_advice());
+               }
+       }
+       strbuf_release(&author_ident);
+       strbuf_release(&committer_ident);
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.diff = 1;
+       rev.diffopt.output_format =
+               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+       rev.verbose_header = 1;
+       rev.show_root_diff = 1;
+       get_commit_format(format.buf, &rev);
+       rev.always_show_header = 0;
++      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
+       rev.diffopt.break_opt = 0;
+       diff_setup_done(&rev.diffopt);
+       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+       if (!head)
+               die_errno(_("unable to resolve HEAD after creating commit"));
+       if (!strcmp(head, "HEAD"))
+               head = _("detached HEAD");
+       else
+               skip_prefix(head, "refs/heads/", &head);
+       printf("[%s%s ", head, (flags & SUMMARY_INITIAL_COMMIT) ?
+                                               _(" (root-commit)") : "");
+       if (!log_tree_commit(&rev, commit)) {
+               rev.always_show_header = 1;
+               rev.use_terminator = 1;
+               log_tree_commit(&rev, commit);
+       }
+       strbuf_release(&format);
+ }
+ static int parse_head(struct commit **head)
+ {
+       struct commit *current_head;
+       struct object_id oid;
+       if (get_oid("HEAD", &oid)) {
+               current_head = NULL;
+       } else {
+               current_head = lookup_commit_reference(&oid);
+               if (!current_head)
+                       return error(_("could not parse HEAD"));
+               if (oidcmp(&oid, &current_head->object.oid)) {
+                       warning(_("HEAD %s is not a commit!"),
+                               oid_to_hex(&oid));
+               }
+               if (parse_commit(current_head))
+                       return error(_("could not parse HEAD commit"));
+       }
+       *head = current_head;
+       return 0;
+ }
+ /*
+  * Try to commit without forking 'git commit'. In some cases we need
+  * to run 'git commit' to display an error message
+  *
+  * Returns:
+  *  -1 - error unable to commit
+  *   0 - success
+  *   1 - run 'git commit'
+  */
+ static int try_to_commit(struct strbuf *msg, const char *author,
+                        struct replay_opts *opts, unsigned int flags,
+                        struct object_id *oid)
+ {
+       struct object_id tree;
+       struct commit *current_head;
+       struct commit_list *parents = NULL;
+       struct commit_extra_header *extra = NULL;
+       struct strbuf err = STRBUF_INIT;
+       struct strbuf commit_msg = STRBUF_INIT;
+       char *amend_author = NULL;
+       const char *hook_commit = NULL;
+       enum commit_msg_cleanup_mode cleanup;
+       int res = 0;
+       if (parse_head(&current_head))
+               return -1;
+       if (flags & AMEND_MSG) {
+               const char *exclude_gpgsig[] = { "gpgsig", NULL };
+               const char *out_enc = get_commit_output_encoding();
+               const char *message = logmsg_reencode(current_head, NULL,
+                                                     out_enc);
+               if (!msg) {
+                       const char *orig_message = NULL;
+                       find_commit_subject(message, &orig_message);
+                       msg = &commit_msg;
+                       strbuf_addstr(msg, orig_message);
+                       hook_commit = "HEAD";
+               }
+               author = amend_author = get_author(message);
+               unuse_commit_buffer(current_head, message);
+               if (!author) {
+                       res = error(_("unable to parse commit author"));
+                       goto out;
+               }
+               parents = copy_commit_list(current_head->parents);
+               extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+       } else if (current_head) {
+               commit_list_insert(current_head, &parents);
+       }
+       if (write_cache_as_tree(tree.hash, 0, NULL)) {
+               res = error(_("git write-tree failed to write a tree"));
+               goto out;
+       }
+       if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
+                                             &current_head->tree->object.oid :
+                                             &empty_tree_oid, &tree)) {
+               res = 1; /* run 'git commit' to display error message */
+               goto out;
+       }
+       if (find_hook("prepare-commit-msg")) {
+               res = run_prepare_commit_msg_hook(msg, hook_commit);
+               if (res)
+                       goto out;
+               if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(),
+                                    2048) < 0) {
+                       res = error_errno(_("unable to read commit message "
+                                             "from '%s'"),
+                                           git_path_commit_editmsg());
+                       goto out;
+               }
+               msg = &commit_msg;
+       }
+       cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
+                                         opts->default_msg_cleanup;
+       if (cleanup != COMMIT_MSG_CLEANUP_NONE)
+               strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
+       if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+               res = 1; /* run 'git commit' to display error message */
+               goto out;
+       }
+       if (commit_tree_extended(msg->buf, msg->len, tree.hash, parents,
+                                oid->hash, author, opts->gpg_sign, extra)) {
+               res = error(_("failed to write commit object"));
+               goto out;
+       }
+       if (update_head_with_reflog(current_head, oid,
+                                   getenv("GIT_REFLOG_ACTION"), msg, &err)) {
+               res = error("%s", err.buf);
+               goto out;
+       }
+       if (flags & AMEND_MSG)
+               commit_post_rewrite(current_head, oid);
+ out:
+       free_commit_extra_headers(extra);
+       strbuf_release(&err);
+       strbuf_release(&commit_msg);
+       free(amend_author);
+       return res;
+ }
+ static int do_commit(const char *msg_file, const char *author,
+                    struct replay_opts *opts, unsigned int flags)
+ {
+       int res = 1;
+       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
+               struct object_id oid;
+               struct strbuf sb = STRBUF_INIT;
+               if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0)
+                       return error_errno(_("unable to read commit message "
+                                            "from '%s'"),
+                                          msg_file);
+               res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
+                                   &oid);
+               strbuf_release(&sb);
+               if (!res) {
+                       unlink(git_path_cherry_pick_head());
+                       unlink(git_path_merge_msg());
+                       if (!is_rebase_i(opts))
+                               print_commit_summary(NULL, &oid,
+                                               SUMMARY_SHOW_AUTHOR_DATE);
+                       return res;
+               }
+       }
+       if (res == 1)
+               return run_git_commit(msg_file, opts, flags);
+       return res;
+ }
  static int is_original_commit_empty(struct commit *commit)
  {
        const struct object_id *ptree_oid;
  
        if (parse_commit(commit))
 -              return error(_("could not parse commit %s\n"),
 +              return error(_("could not parse commit %s"),
                             oid_to_hex(&commit->object.oid));
        if (commit->parents) {
                struct commit *parent = commit->parents->item;
                if (parse_commit(parent))
 -                      return error(_("could not parse parent commit %s\n"),
 +                      return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
                ptree_oid = &parent->tree->object.oid;
        } else {
 -              ptree_oid = &empty_tree_oid; /* commit is root */
 +              ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
  
        return !oidcmp(ptree_oid, &commit->tree->object.oid);
@@@ -797,13 -1305,6 +1308,13 @@@ static const char *command_to_string(co
        die("Unknown command: %d", command);
  }
  
 +static char command_to_char(const enum todo_command command)
 +{
 +      if (command < TODO_COMMENT && todo_command_info[command].c)
 +              return todo_command_info[command].c;
 +      return comment_line_char;
 +}
 +
  static int is_noop(const enum todo_command command)
  {
        return TODO_NOOP <= command;
@@@ -952,6 -1453,7 +1463,7 @@@ static int do_pick_commit(enum todo_com
        struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
+       char *author = NULL;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
        int res, unborn = 0, allow;
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
 -                      oidcpy(&head, &empty_tree_oid);
 +                      oidcpy(&head, the_hash_algo->empty_tree);
                if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
                        opts);
                if (res || command != TODO_REWORD)
                        goto leave;
 -              flags |= EDIT_MSG | AMEND_MSG;
 -              if (command == TODO_REWORD)
 -                      flags |= VERIFY_MSG;
 +              flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG;
                msg_file = NULL;
                goto fast_forward_edit;
        }
                        strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
                        strbuf_addstr(&msgbuf, ")\n");
                }
+               if (!is_fixup(command))
+                       author = get_author(msg.message);
        }
  
        if (command == TODO_REWORD)
                }
        }
  
+       if (opts->signoff)
+               append_signoff(&msgbuf, 0, 0);
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
                res = -1;
        else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
         */
        if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
            update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
 -                     REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
 +                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
        if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
            update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
 -                     REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
 +                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
  
        if (res) {
                goto leave;
        } else if (allow)
                flags |= ALLOW_EMPTY;
-       if (!opts->no_commit)
+       if (!opts->no_commit) {
  fast_forward_edit:
-               res = run_git_commit(msg_file, opts, flags);
+               if (author || command == TODO_REVERT || (flags & AMEND_MSG))
+                       res = do_commit(msg_file, author, opts, flags);
+               else
+                       res = error(_("unable to parse commit author"));
+       }
  
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
  
  leave:
        free_message(commit, &msg);
+       free(author);
        update_abort_safety_file();
  
        return res;
@@@ -1261,26 -1775,20 +1783,26 @@@ static int parse_insn_line(struct todo_
        if (i >= TODO_COMMENT)
                return -1;
  
 +      /* Eat up extra spaces/ tabs before object name */
 +      padding = strspn(bol, " \t");
 +      bol += padding;
 +
        if (item->command == TODO_NOOP) {
 +              if (bol != eol)
 +                      return error(_("%s does not accept arguments: '%s'"),
 +                                   command_to_string(item->command), bol);
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = eol - bol;
                return 0;
        }
  
 -      /* Eat up extra spaces/ tabs before object name */
 -      padding = strspn(bol, " \t");
        if (!padding)
 -              return -1;
 -      bol += padding;
 +              return error(_("missing arguments for %s"),
 +                           command_to_string(item->command));
  
        if (item->command == TODO_EXEC) {
 +              item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
@@@ -2144,7 -2652,7 +2666,7 @@@ cleanup_head_ref
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
                        if (update_ref(msg, head_ref.buf, &head, &orig,
 -                                     REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
 +                                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
@@@ -2456,16 -2964,14 +2978,16 @@@ void append_signoff(struct strbuf *msgb
        strbuf_release(&sob);
  }
  
 -int sequencer_make_script(int keep_empty, FILE *out,
 -              int argc, const char **argv)
 +int sequencer_make_script(FILE *out, int argc, const char **argv,
 +                        unsigned flags)
  {
        char *format = NULL;
        struct pretty_print_context pp = {0};
        struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct commit *commit;
 +      int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 +      const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
  
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
                strbuf_reset(&buf);
                if (!keep_empty && is_original_commit_empty(commit))
                        strbuf_addf(&buf, "%c ", comment_line_char);
 -              strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
 +              strbuf_addf(&buf, "%s %s ", insn,
 +                          oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, &buf);
                strbuf_addch(&buf, '\n');
                fputs(buf.buf, out);
        return 0;
  }
  
 -
 -int transform_todo_ids(int shorten_ids)
 +/*
 + * Add commands after pick and (series of) squash/fixup commands
 + * in the todo list.
 + */
 +int sequencer_add_exec_commands(const char *commands)
  {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
 -      int fd, res, i;
 -      FILE *out;
 +      struct todo_item *item;
 +      struct strbuf *buf = &todo_list.buf;
 +      size_t offset = 0, commands_len = strlen(commands);
 +      int i, first;
  
 -      strbuf_reset(&todo_list.buf);
 -      fd = open(todo_file, O_RDONLY);
 -      if (fd < 0)
 -              return error_errno(_("could not open '%s'"), todo_file);
 -      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 -              close(fd);
 +      if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
 -      }
 -      close(fd);
  
 -      res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
 -      if (res) {
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
  
 -      out = fopen(todo_file, "w");
 -      if (!out) {
 +      first = 1;
 +      /* insert <commands> before every pick except the first one */
 +      for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
 +              if (item->command == TODO_PICK && !first) {
 +                      strbuf_insert(buf, item->offset_in_buf + offset,
 +                                    commands, commands_len);
 +                      offset += commands_len;
 +              }
 +              first = 0;
 +      }
 +
 +      /* append final <commands> */
 +      strbuf_add(buf, commands, commands_len);
 +
 +      i = write_message(buf->buf, buf->len, todo_file, 0);
 +      todo_list_release(&todo_list);
 +      return i;
 +}
 +
 +int transform_todos(unsigned flags)
 +{
 +      const char *todo_file = rebase_path_todo();
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct todo_item *item;
 +      int i;
 +
 +      if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
 +              return error(_("could not read '%s'."), todo_file);
 +
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
 -              return error(_("unable to open '%s' for writing"), todo_file);
 +              return error(_("unusable todo list: '%s'"), todo_file);
        }
 -      for (i = 0; i < todo_list.nr; i++) {
 -              struct todo_item *item = todo_list.items + i;
 -              int bol = item->offset_in_buf;
 -              const char *p = todo_list.buf.buf + bol;
 -              int eol = i + 1 < todo_list.nr ?
 -                      todo_list.items[i + 1].offset_in_buf :
 -                      todo_list.buf.len;
 -
 -              if (item->command >= TODO_EXEC && item->command != TODO_DROP)
 -                      fwrite(p, eol - bol, 1, out);
 -              else {
 -                      const char *id = shorten_ids ?
 -                              short_commit_name(item->commit) :
 -                              oid_to_hex(&item->commit->object.oid);
 -                      int len;
 -
 -                      p += strspn(p, " \t"); /* left-trim command */
 -                      len = strcspn(p, " \t"); /* length of command */
 -
 -                      fprintf(out, "%.*s %s %.*s\n",
 -                              len, p, id, item->arg_len, item->arg);
 +
 +      for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
 +              /* if the item is not a command write it and continue */
 +              if (item->command >= TODO_COMMENT) {
 +                      strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg);
 +                      continue;
                }
 +
 +              /* add command to the buffer */
 +              if (flags & TODO_LIST_ABBREVIATE_CMDS)
 +                      strbuf_addch(&buf, command_to_char(item->command));
 +              else
 +                      strbuf_addstr(&buf, command_to_string(item->command));
 +
 +              /* add commit id */
 +              if (item->commit) {
 +                      const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
 +                                        short_commit_name(item->commit) :
 +                                        oid_to_hex(&item->commit->object.oid);
 +
 +                      strbuf_addf(&buf, " %s", oid);
 +              }
 +              /* add all the rest */
 +              if (!item->arg_len)
 +                      strbuf_addch(&buf, '\n');
 +              else
 +                      strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
        }
 -      fclose(out);
 +
 +      i = write_message(buf.buf, buf.len, todo_file, 0);
        todo_list_release(&todo_list);
 -      return 0;
 +      return i;
  }
  
  enum check_level {
@@@ -2719,19 -3192,6 +3241,19 @@@ leave_check
        return res;
  }
  
 +static int rewrite_file(const char *path, const char *buf, size_t len)
 +{
 +      int rc = 0;
 +      int fd = open(path, O_WRONLY | O_TRUNC);
 +      if (fd < 0)
 +              return error_errno(_("could not open '%s' for writing"), path);
 +      if (write_in_full(fd, buf, len) < 0)
 +              rc = error_errno(_("could not write to '%s'"), path);
 +      if (close(fd) && !rc)
 +              rc = error_errno(_("could not close '%s'"), path);
 +      return rc;
 +}
 +
  /* skip picking commits whose parents are unchanged */
  int skip_unnecessary_picks(void)
  {
                }
                close(fd);
  
 -              fd = open(rebase_path_todo(), O_WRONLY, 0666);
 -              if (fd < 0) {
 -                      error_errno(_("could not open '%s' for writing"),
 -                                  rebase_path_todo());
 -                      todo_list_release(&todo_list);
 -                      return -1;
 -              }
 -              if (write_in_full(fd, todo_list.buf.buf + offset,
 -                              todo_list.buf.len - offset) < 0) {
 -                      error_errno(_("could not write to '%s'"),
 -                                  rebase_path_todo());
 -                      close(fd);
 +              if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset,
 +                               todo_list.buf.len - offset) < 0) {
                        todo_list_release(&todo_list);
                        return -1;
                }
 -              if (ftruncate(fd, todo_list.buf.len - offset) < 0) {
 -                      error_errno(_("could not truncate '%s'"),
 -                                  rebase_path_todo());
 -                      todo_list_release(&todo_list);
 -                      close(fd);
 -                      return -1;
 -              }
 -              close(fd);
  
                todo_list.current = i;
                if (is_fixup(peek_command(&todo_list, 0)))
@@@ -2993,7 -3471,15 +3515,7 @@@ int rearrange_squash(void
                        }
                }
  
 -              fd = open(todo_file, O_WRONLY);
 -              if (fd < 0)
 -                      res = error_errno(_("could not open '%s'"), todo_file);
 -              else if (write(fd, buf.buf, buf.len) < 0)
 -                      res = error_errno(_("could not write to '%s'"), todo_file);
 -              else if (ftruncate(fd, buf.len) < 0)
 -                      res = error_errno(_("could not truncate '%s'"),
 -                                         todo_file);
 -              close(fd);
 +              res = rewrite_file(todo_file, buf.buf, buf.len);
                strbuf_release(&buf);
        }
  
diff --combined sequencer.h
index 81f6d7d393fd1a502e3763cd1717b87ffa243717,96c69482adcd71e3c7b90dfd269e3b591ecefe38..e45b178dfc41d723bf186f20674c4515d7c7fa00
@@@ -1,6 -1,7 +1,7 @@@
  #ifndef SEQUENCER_H
  #define SEQUENCER_H
  
+ const char *git_path_commit_editmsg(void);
  const char *git_path_seq_dir(void);
  
  #define APPEND_SIGNOFF_DEDUP (1u << 0)
@@@ -11,6 -12,13 +12,13 @@@ enum replay_action 
        REPLAY_INTERACTIVE_REBASE
  };
  
+ enum commit_msg_cleanup_mode {
+       COMMIT_MSG_CLEANUP_SPACE,
+       COMMIT_MSG_CLEANUP_NONE,
+       COMMIT_MSG_CLEANUP_SCISSORS,
+       COMMIT_MSG_CLEANUP_ALL
+ };
  struct replay_opts {
        enum replay_action action;
  
@@@ -29,6 -37,7 +37,7 @@@
        int mainline;
  
        char *gpg_sign;
+       enum commit_msg_cleanup_mode default_msg_cleanup;
  
        /* Merge strategy */
        char *strategy;
  };
  #define REPLAY_OPTS_INIT { -1 }
  
+ /* Call this to setup defaults before parsing command line options */
+ void sequencer_init_config(struct replay_opts *opts);
  int sequencer_pick_revisions(struct replay_opts *opts);
  int sequencer_continue(struct replay_opts *opts);
  int sequencer_rollback(struct replay_opts *opts);
  int sequencer_remove_state(struct replay_opts *opts);
  
 -int sequencer_make_script(int keep_empty, FILE *out,
 -              int argc, const char **argv);
 -
 -int transform_todo_ids(int shorten_ids);
 +#define TODO_LIST_KEEP_EMPTY (1U << 0)
 +#define TODO_LIST_SHORTEN_IDS (1U << 1)
 +#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 +int sequencer_make_script(FILE *out, int argc, const char **argv,
 +                        unsigned flags);
 +
 +int sequencer_add_exec_commands(const char *command);
 +int transform_todos(unsigned flags);
  int check_todo_list(void);
  int skip_unnecessary_picks(void);
  int rearrange_squash(void);
@@@ -61,5 -68,19 +72,19 @@@ extern const char sign_off_header[]
  
  void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
  void append_conflicts_hint(struct strbuf *msgbuf);
+ int message_is_empty(const struct strbuf *sb,
+                    enum commit_msg_cleanup_mode cleanup_mode);
+ int template_untouched(const struct strbuf *sb, const char *template_file,
+                      enum commit_msg_cleanup_mode cleanup_mode);
+ int update_head_with_reflog(const struct commit *old_head,
+                           const struct object_id *new_head,
+                           const char *action, const struct strbuf *msg,
+                           struct strbuf *err);
+ void commit_post_rewrite(const struct commit *current_head,
+                        const struct object_id *new_head);
  
+ #define SUMMARY_INITIAL_COMMIT   (1 << 0)
+ #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
+ void print_commit_summary(const char *prefix, const struct object_id *oid,
+                         unsigned int flags);
  #endif
index 481a3500900d0fccb28762ba24b2ca422956a44f,040ef1a4dbc48a153d279614a75cdf8d923c9a28..ef2887bd852b15a0d1c1e6fb1ea9eff09664f27e
@@@ -453,6 -453,10 +453,10 @@@ test_expect_success C_LOCALE_OUTPUT 'sq
                git rebase -i $base &&
        git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
        test_cmp expect-squash-fixup actual-squash-fixup &&
+       git cat-file commit HEAD@{2} |
+               grep "^# This is a combination of 3 commits\."  &&
+       git cat-file commit HEAD@{3} |
+               grep "^# This is a combination of 2 commits\."  &&
        git checkout to-be-rebased &&
        git branch -D squash-fixup
  '
@@@ -1260,28 -1264,6 +1264,28 @@@ test_expect_success 'rebase -i respect
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
  '
  
 +test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
 +      rebase_setup_and_clean abbrevcmd &&
 +      test_commit "first" file1.txt "first line" first &&
 +      test_commit "second" file1.txt "another line" second &&
 +      test_commit "fixup! first" file2.txt "first line again" first_fixup &&
 +      test_commit "squash! second" file1.txt "another line here" second_squash &&
 +      cat >expected <<-EOF &&
 +      p $(git rev-list --abbrev-commit -1 first) first
 +      f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first
 +      x git show HEAD
 +      p $(git rev-list --abbrev-commit -1 second) second
 +      s $(git rev-list --abbrev-commit -1 second_squash) squash! second
 +      x git show HEAD
 +      EOF
 +      git checkout abbrevcmd &&
 +      set_cat_todo_editor &&
 +      test_config rebase.abbreviateCommands true &&
 +      test_must_fail git rebase -i --exec "git show HEAD" \
 +              --autosquash master >actual &&
 +      test_cmp expected actual
 +'
 +
  test_expect_success 'static check of bad command' '
        rebase_setup_and_clean bad-cmd &&
        set_fake_editor &&
@@@ -1336,6 -1318,16 +1340,16 @@@ test_expect_success 'editor saves as CR
  
  SQ="'"
  test_expect_success 'rebase -i --gpg-sign=<key-id>' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       set_fake_editor &&
+       FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
+               >out 2>err &&
+       test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err
+ '
+ test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       test_config commit.gpgsign true &&
        set_fake_editor &&
        FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
                >out 2>err &&
index ce48c4fcca80b183927292cc1e5902cfe286f994,059213767e089e69ad61d14c329930ef097813e7..bd78287841ee053fd56a44a268f8077a222cc266
@@@ -5,45 -5,8 +5,44 @@@ test_description='cherry-pick can handl
  . ./test-lib.sh
  . "$TEST_DIRECTORY"/lib-submodule-update.sh
  
- KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
  KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
  KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
  test_submodule_switch "git cherry-pick"
  
 +test_expect_success 'unrelated submodule/file conflict is ignored' '
 +      test_create_repo sub &&
 +
 +      touch sub/file &&
 +      git -C sub add file &&
 +      git -C sub commit -m "add a file in a submodule" &&
 +
 +      test_create_repo a_repo &&
 +      (
 +              cd a_repo &&
 +              >a_file &&
 +              git add a_file &&
 +              git commit -m "add a file" &&
 +
 +              git branch test &&
 +              git checkout test &&
 +
 +              mkdir sub &&
 +              >sub/content &&
 +              git add sub/content &&
 +              git commit -m "add a regular folder with name sub" &&
 +
 +              echo "123" >a_file &&
 +              git add a_file &&
 +              git commit -m "modify a file" &&
 +
 +              git checkout master &&
 +
 +              git submodule add ../sub sub &&
 +              git submodule update sub &&
 +              git commit -m "add a submodule info folder with name sub" &&
 +
 +              git cherry-pick test
 +      )
 +'
 +
  test_done