Merge branch 'rs/strbuf-read-file-or-whine'
[git/git.git] / sequencer.c
index c31d48b..a35a0e6 100644 (file)
@@ -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 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 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 @@ static int read_oneliner(struct strbuf *buf,
 
 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)
@@ -393,7 +442,7 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
-                                  to->hash, unborn ? null_sha1 : from->hash,
+                                  to, unborn ? &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@ -438,7 +487,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        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();
 
@@ -448,6 +498,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        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();
@@ -462,6 +513,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        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;
 
@@ -475,9 +527,6 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
                        _(action_name(opts)));
        rollback_lock_file(&index_lock);
 
-       if (opts->signoff)
-               append_signoff(msgbuf, 0, 0);
-
        if (!clean)
                append_conflicts_hint(msgbuf);
 
@@ -489,8 +538,8 @@ static int is_index_unchanged(void)
        struct object_id head_oid;
        struct commit *head_commit;
 
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
-               return error(_("could not resolve HEAD commit\n"));
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
+               return error(_("could not resolve HEAD commit"));
 
        head_commit = lookup_commit(&head_oid);
 
@@ -510,7 +559,7 @@ static int is_index_unchanged(void)
 
        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);
@@ -593,6 +642,18 @@ static int read_env_script(struct argv_array *env)
        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"
@@ -655,8 +716,6 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                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))
@@ -691,21 +750,476 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
        return run_command(&cmd);
 }
 
+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, parents,
+                                oid, 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);
@@ -794,6 +1308,13 @@ static const char *command_to_string(const enum todo_command command)
        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;
@@ -942,6 +1463,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        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;
@@ -958,8 +1480,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
-                       oidcpy(&head, &empty_tree_oid);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
+                       oidcpy(&head, the_hash_algo->empty_tree);
+               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
+                                      NULL, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
@@ -1002,9 +1525,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        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;
        }
@@ -1057,6 +1578,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        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)
@@ -1082,6 +1605,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                }
        }
 
+       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) {
@@ -1115,12 +1641,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
         * write it at all.
         */
        if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
-           update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL,
-                      REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+           update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
+                      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.hash, NULL,
-                      REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+           update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
+                      REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
 
        if (res) {
@@ -1139,9 +1665,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                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());
@@ -1150,6 +1680,7 @@ fast_forward_edit:
 
 leave:
        free_message(commit, &msg);
+       free(author);
        update_abort_safety_file();
 
        return res;
@@ -1184,7 +1715,6 @@ static int read_and_refresh_cache(struct replay_opts *opts)
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed && index_fd >= 0) {
                if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
-                       rollback_lock_file(&index_lock);
                        return error(_("git %s: failed to refresh the index"),
                                _(action_name(opts)));
                }
@@ -1253,20 +1783,26 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        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;
@@ -1639,7 +2175,7 @@ static int rollback_single_pick(void)
        if (!file_exists(git_path_cherry_pick_head()) &&
            !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
-       if (read_ref_full("HEAD", 0, head_oid.hash, NULL))
+       if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
@@ -1787,6 +2323,9 @@ static int make_patch(struct commit *commit, struct replay_opts *opts)
        p = short_commit_name(commit);
        if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
                return -1;
+       if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
+                      NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+               res |= error(_("could not update %s"), "REBASE_HEAD");
 
        strbuf_addf(&buf, "%s/patch", get_dir(opts));
        memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@ -1871,12 +2410,15 @@ static int error_failed_squash(struct commit *commit,
 
 static int do_exec(const char *command_line)
 {
+       struct argv_array child_env = ARGV_ARRAY_INIT;
        const char *child_argv[] = { NULL, NULL };
        int dirty, status;
 
        fprintf(stderr, "Executing: %s\n", command_line);
        child_argv[0] = command_line;
-       status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+       argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+       status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
+                                         child_env.argv);
 
        /* force re-reading of the cache */
        if (discard_cache() < 0 || read_cache() < 0)
@@ -1906,6 +2448,8 @@ static int do_exec(const char *command_line)
                status = 1;
        }
 
+       argv_array_clear(&child_env);
+
        return status;
 }
 
@@ -2033,6 +2577,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
+                       delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
@@ -2133,8 +2678,8 @@ cleanup_head_ref:
                        }
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
-                       if (update_ref(msg, head_ref.buf, head.hash, orig.hash,
-                                       REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+                       if (update_ref(msg, head_ref.buf, &head, &orig,
+                                      REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
@@ -2288,7 +2833,7 @@ int sequencer_continue(struct replay_opts *opts)
                        if (res)
                                goto release_todo_list;
                }
-               if (index_differs_from("HEAD", 0, 0)) {
+               if (index_differs_from("HEAD", NULL, 0)) {
                        res = error_dirty_index(opts);
                        goto release_todo_list;
                }
@@ -2446,14 +2991,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
        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;
@@ -2486,7 +3033,8 @@ int sequencer_make_script(int keep_empty, FILE *out,
                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);
@@ -2495,61 +3043,93 @@ int sequencer_make_script(int keep_empty, FILE *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 {
@@ -2660,6 +3240,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)
 {
@@ -2725,29 +3318,11 @@ 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);
-                       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());
+               if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset,
+                                todo_list.buf.len - offset) < 0) {
                        todo_list_release(&todo_list);
-                       close(fd);
                        return -1;
                }
-               close(fd);
 
                todo_list.current = i;
                if (is_fixup(peek_command(&todo_list, 0)))
@@ -2926,15 +3501,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);
        }