completion: put matching ctags symbol names directly into COMPREPLY
[gitweb.git] / sequencer.c
index 9adb7bbf1d4815d9e4f67b66e90c9ec327b2fb72..1f729b053bbc6f544fe0b3e8736113082894b24d 100644 (file)
@@ -17,6 +17,8 @@
 #include "argv-array.h"
 #include "quote.h"
 #include "trailer.h"
+#include "log-tree.h"
+#include "wt-status.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -30,31 +32,117 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
 static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
 
+static GIT_PATH_FUNC(rebase_path, "rebase-merge")
+/*
+ * The file containing rebase commands, comments, and empty lines.
+ * This file is created by "git rebase -i" then edited by the user. As
+ * the lines are processed, they are removed from the front of this
+ * file and written to the tail of 'done'.
+ */
+static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+/*
+ * The rebase command lines that have already been processed. A line
+ * is moved here when it is first handled, before any associated user
+ * actions.
+ */
+static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done")
+/*
+ * The file to keep track of how many commands were already processed (e.g.
+ * for the prompt).
+ */
+static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
+/*
+ * The file to keep track of how many commands are to be processed in total
+ * (e.g. for the prompt).
+ */
+static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
+/*
+ * The commit message that is planned to be used for any changes that
+ * need to be committed following a user interaction.
+ */
+static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message")
+/*
+ * The file into which is accumulated the suggested commit message for
+ * squash/fixup commands. When the first of a series of squash/fixups
+ * is seen, the file is created and the commit message from the
+ * previous commit and from the first squash/fixup commit are written
+ * to it. The commit message for each subsequent squash/fixup commit
+ * is appended to the file as it is processed.
+ *
+ * The first line of the file is of the form
+ *     # This is a combination of $count commits.
+ * where $count is the number of commits whose messages have been
+ * written to the file so far (including the initial "pick" commit).
+ * Each time that a commit message is processed, this line is read and
+ * updated. It is deleted just before the combined commit is made.
+ */
+static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
+/*
+ * If the current series of squash/fixups has not yet included a squash
+ * command, then this file exists and holds the commit message of the
+ * original "pick" commit.  (If the series ends without a "squash"
+ * command, then this can be used as the commit message of the combined
+ * commit without opening the editor.)
+ */
+static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
  * GIT_AUTHOR_DATE that will be used for the commit that is currently
  * being rebased.
  */
 static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
+/*
+ * When an "edit" rebase command is being processed, the SHA1 of the
+ * commit to be edited is recorded in this file.  When "git rebase
+ * --continue" is executed, if there are any staged changes then they
+ * will be amended to the HEAD commit, but only provided the HEAD
+ * commit is still the commit to be edited.  When any other rebase
+ * command is processed, this file is deleted.
+ */
+static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
+/*
+ * When we stop at a given patch via the "edit" command, this file contains
+ * the abbreviated commit name of the corresponding patch.
+ */
+static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
+/*
+ * For the post-rewrite hook, we make a list of rewritten commits and
+ * their new sha1s.  The rewritten-pending list keeps the sha1s of
+ * commits that have been processed, but not committed yet,
+ * e.g. because they are waiting for a 'squash' command.
+ */
+static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
+static GIT_PATH_FUNC(rebase_path_rewritten_pending,
+       "rebase-merge/rewritten-pending")
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
  */
 static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
+static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
+static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
+static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
+static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
+static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
+static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
+static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 
-/* We will introduce the 'interactive rebase' mode later */
 static inline int is_rebase_i(const struct replay_opts *opts)
 {
-       return 0;
+       return opts->action == REPLAY_INTERACTIVE_REBASE;
 }
 
 static const char *get_dir(const struct replay_opts *opts)
 {
+       if (is_rebase_i(opts))
+               return rebase_path();
        return git_path_seq_dir();
 }
 
 static const char *get_todo_path(const struct replay_opts *opts)
 {
+       if (is_rebase_i(opts))
+               return rebase_path_todo();
        return git_path_todo_file();
 }
 
@@ -122,7 +210,15 @@ int sequencer_remove_state(struct replay_opts *opts)
 
 static const char *action_name(const struct replay_opts *opts)
 {
-       return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick");
+       switch (opts->action) {
+       case REPLAY_REVERT:
+               return N_("revert");
+       case REPLAY_PICK:
+               return N_("cherry-pick");
+       case REPLAY_INTERACTIVE_REBASE:
+               return N_("rebase -i");
+       }
+       die(_("Unknown action: %d"), opts->action);
 }
 
 struct commit_message {
@@ -347,6 +443,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.ancestor = base ? base_label : "(empty tree)";
        o.branch1 = "HEAD";
        o.branch2 = next ? next_label : "(empty tree)";
+       if (is_rebase_i(opts))
+               o.buffer_output = 2;
 
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
@@ -358,13 +456,17 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        clean = merge_trees(&o,
                            head_tree,
                            next_tree, base_tree, &result);
+       if (is_rebase_i(opts) && clean <= 0)
+               fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
        if (clean < 0)
                return clean;
 
        if (active_cache_changed &&
            write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
-               /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+               /* TRANSLATORS: %s will be "revert", "cherry-pick" or
+                * "rebase -i".
+                */
                return error(_("%s: Unable to write new index file"),
                        _(action_name(opts)));
        rollback_lock_file(&index_lock);
@@ -409,19 +511,64 @@ static int is_index_unchanged(void)
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
 }
 
+static int write_author_script(const char *message)
+{
+       struct strbuf buf = STRBUF_INIT;
+       const char *eol;
+       int res;
+
+       for (;;)
+               if (!*message || starts_with(message, "\n")) {
+missing_author:
+                       /* Missing 'author' line? */
+                       unlink(rebase_path_author_script());
+                       return 0;
+               } else if (skip_prefix(message, "author ", &message))
+                       break;
+               else if ((eol = strchr(message, '\n')))
+                       message = eol + 1;
+               else
+                       goto missing_author;
+
+       strbuf_addstr(&buf, "GIT_AUTHOR_NAME='");
+       while (*message && *message != '\n' && *message != '\r')
+               if (skip_prefix(message, " <", &message))
+                       break;
+               else if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
+       while (*message && *message != '\n' && *message != '\r')
+               if (skip_prefix(message, "> ", &message))
+                       break;
+               else if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
+       while (*message && *message != '\n' && *message != '\r')
+               if (*message != '\'')
+                       strbuf_addch(&buf, *(message++));
+               else
+                       strbuf_addf(&buf, "'\\\\%c'", *(message++));
+       res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
+       strbuf_release(&buf);
+       return res;
+}
+
 /*
- * Read the author-script file into an environment block, ready for use in
- * run_command(), that can be free()d afterwards.
+ * Read a list of environment variable assignments (such as the author-script
+ * file) into an environment block. Returns -1 on error, 0 otherwise.
  */
-static char **read_author_script(void)
+static int read_env_script(struct argv_array *env)
 {
        struct strbuf script = STRBUF_INIT;
        int i, count = 0;
-       char *p, *p2, **env;
-       size_t env_size;
+       char *p, *p2;
 
        if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
-               return NULL;
+               return -1;
 
        for (p = script.buf; *p; p++)
                if (skip_prefix(p, "'\\\\''", (const char **)&p2))
@@ -433,19 +580,12 @@ static char **read_author_script(void)
                        count++;
                }
 
-       env_size = (count + 1) * sizeof(*env);
-       strbuf_grow(&script, env_size);
-       memmove(script.buf + env_size, script.buf, script.len);
-       p = script.buf + env_size;
-       env = (char **)strbuf_detach(&script, NULL);
-
-       for (i = 0; i < count; i++) {
-               env[i] = p;
+       for (i = 0, p = script.buf; i < count; i++) {
+               argv_array_push(env, p);
                p += strlen(p) + 1;
        }
-       env[count] = NULL;
 
-       return env;
+       return 0;
 }
 
 static const char staged_changes_advice[] =
@@ -478,14 +618,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                          int allow_empty, int edit, int amend,
                          int cleanup_commit_message)
 {
-       char **env = NULL;
-       struct argv_array array;
-       int rc;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
 
+       cmd.git_cmd = 1;
+
        if (is_rebase_i(opts)) {
-               env = read_author_script();
-               if (!env) {
+               if (!edit) {
+                       cmd.stdout_to_stderr = 1;
+                       cmd.err = -1;
+               }
+
+               if (read_env_script(&cmd.env_array)) {
                        const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                        return error(_(staged_changes_advice),
@@ -493,39 +637,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                }
        }
 
-       argv_array_init(&array);
-       argv_array_push(&array, "commit");
-       argv_array_push(&array, "-n");
+       argv_array_push(&cmd.args, "commit");
+       argv_array_push(&cmd.args, "-n");
 
        if (amend)
-               argv_array_push(&array, "--amend");
+               argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
-               argv_array_pushf(&array, "-S%s", opts->gpg_sign);
+               argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
        if (opts->signoff)
-               argv_array_push(&array, "-s");
+               argv_array_push(&cmd.args, "-s");
        if (defmsg)
-               argv_array_pushl(&array, "-F", defmsg, NULL);
+               argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        if (cleanup_commit_message)
-               argv_array_push(&array, "--cleanup=strip");
+               argv_array_push(&cmd.args, "--cleanup=strip");
        if (edit)
-               argv_array_push(&array, "-e");
+               argv_array_push(&cmd.args, "-e");
        else if (!cleanup_commit_message &&
                 !opts->signoff && !opts->record_origin &&
                 git_config_get_value("commit.cleanup", &value))
-               argv_array_push(&array, "--cleanup=verbatim");
+               argv_array_push(&cmd.args, "--cleanup=verbatim");
 
        if (allow_empty)
-               argv_array_push(&array, "--allow-empty");
+               argv_array_push(&cmd.args, "--allow-empty");
 
        if (opts->allow_empty_message)
-               argv_array_push(&array, "--allow-empty-message");
+               argv_array_push(&cmd.args, "--allow-empty-message");
 
-       rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL,
-                       (const char *const *)env);
-       argv_array_clear(&array);
-       free(env);
+       if (cmd.err == -1) {
+               /* hide stderr on success */
+               struct strbuf buf = STRBUF_INIT;
+               int rc = pipe_command(&cmd,
+                                     NULL, 0,
+                                     /* stdout is already redirected */
+                                     NULL, 0,
+                                     &buf, 0);
+               if (rc)
+                       fputs(buf.buf, stderr);
+               strbuf_release(&buf);
+               return rc;
+       }
 
-       return rc;
+       return run_command(&cmd);
 }
 
 static int is_original_commit_empty(struct commit *commit)
@@ -586,33 +738,202 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit)
                return 1;
 }
 
+/*
+ * Note that ordering matters in this enum. Not only must it match the mapping
+ * below, it is also divided into several sections that matter.  When adding
+ * new commands, make sure you add it in the right section.
+ */
 enum todo_command {
+       /* commands that handle commits */
        TODO_PICK = 0,
-       TODO_REVERT
+       TODO_REVERT,
+       TODO_EDIT,
+       TODO_REWORD,
+       TODO_FIXUP,
+       TODO_SQUASH,
+       /* commands that do something else than handling a single commit */
+       TODO_EXEC,
+       /* commands that do nothing but are counted for reporting progress */
+       TODO_NOOP,
+       TODO_DROP,
+       /* comments (not counted for reporting progress) */
+       TODO_COMMENT
 };
 
-static const char *todo_command_strings[] = {
-       "pick",
-       "revert"
+static struct {
+       char c;
+       const char *str;
+} todo_command_info[] = {
+       { 'p', "pick" },
+       { 0,   "revert" },
+       { 'e', "edit" },
+       { 'r', "reword" },
+       { 'f', "fixup" },
+       { 's', "squash" },
+       { 'x', "exec" },
+       { 0,   "noop" },
+       { 'd', "drop" },
+       { 0,   NULL }
 };
 
 static const char *command_to_string(const enum todo_command command)
 {
-       if ((size_t)command < ARRAY_SIZE(todo_command_strings))
-               return todo_command_strings[command];
+       if (command < TODO_COMMENT)
+               return todo_command_info[command].str;
        die("Unknown command: %d", command);
 }
 
+static int is_noop(const enum todo_command command)
+{
+       return TODO_NOOP <= command;
+}
+
+static int is_fixup(enum todo_command command)
+{
+       return command == TODO_FIXUP || command == TODO_SQUASH;
+}
+
+static int update_squash_messages(enum todo_command command,
+               struct commit *commit, struct replay_opts *opts)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int count, res;
+       const char *message, *body;
+
+       if (file_exists(rebase_path_squash_msg())) {
+               struct strbuf header = STRBUF_INIT;
+               char *eol, *p;
+
+               if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
+                       return error(_("could not read '%s'"),
+                               rebase_path_squash_msg());
+
+               p = buf.buf + 1;
+               eol = strchrnul(buf.buf, '\n');
+               if (buf.buf[0] != comment_line_char ||
+                   (p += strcspn(p, "0123456789\n")) == eol)
+                       return error(_("unexpected 1st line of squash message:"
+                                      "\n\n\t%.*s"),
+                                    (int)(eol - buf.buf), buf.buf);
+               count = strtol(p, NULL, 10);
+
+               if (count < 1)
+                       return error(_("invalid 1st line of squash message:\n"
+                                      "\n\t%.*s"),
+                                    (int)(eol - buf.buf), buf.buf);
+
+               strbuf_addf(&header, "%c ", comment_line_char);
+               strbuf_addf(&header,
+                           _("This is a combination of %d commits."), ++count);
+               strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
+               strbuf_release(&header);
+       } else {
+               unsigned char head[20];
+               struct commit *head_commit;
+               const char *head_message, *body;
+
+               if (get_sha1("HEAD", head))
+                       return error(_("need a HEAD to fixup"));
+               if (!(head_commit = lookup_commit_reference(head)))
+                       return error(_("could not read HEAD"));
+               if (!(head_message = get_commit_buffer(head_commit, NULL)))
+                       return error(_("could not read HEAD's commit message"));
+
+               find_commit_subject(head_message, &body);
+               if (write_message(body, strlen(body),
+                                 rebase_path_fixup_msg(), 0)) {
+                       unuse_commit_buffer(head_commit, head_message);
+                       return error(_("cannot write '%s'"),
+                                    rebase_path_fixup_msg());
+               }
+
+               count = 2;
+               strbuf_addf(&buf, "%c ", comment_line_char);
+               strbuf_addf(&buf, _("This is a combination of %d commits."),
+                           count);
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addstr(&buf, _("This is the 1st commit message:"));
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_addstr(&buf, body);
+
+               unuse_commit_buffer(head_commit, head_message);
+       }
+
+       if (!(message = get_commit_buffer(commit, NULL)))
+               return error(_("could not read commit message of %s"),
+                            oid_to_hex(&commit->object.oid));
+       find_commit_subject(message, &body);
+
+       if (command == TODO_SQUASH) {
+               unlink(rebase_path_fixup_msg());
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, _("This is the commit message #%d:"), count);
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_addstr(&buf, body);
+       } else if (command == TODO_FIXUP) {
+               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+                           count);
+               strbuf_addstr(&buf, "\n\n");
+               strbuf_add_commented_lines(&buf, body, strlen(body));
+       } else
+               return error(_("unknown command: %d"), command);
+       unuse_commit_buffer(commit, message);
+
+       res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+       strbuf_release(&buf);
+       return res;
+}
+
+static void flush_rewritten_pending(void) {
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char newsha1[20];
+       FILE *out;
+
+       if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
+                       !get_sha1("HEAD", newsha1) &&
+                       (out = fopen(rebase_path_rewritten_list(), "a"))) {
+               char *bol = buf.buf, *eol;
+
+               while (*bol) {
+                       eol = strchrnul(bol, '\n');
+                       fprintf(out, "%.*s %s\n", (int)(eol - bol),
+                                       bol, sha1_to_hex(newsha1));
+                       if (!*eol)
+                               break;
+                       bol = eol + 1;
+               }
+               fclose(out);
+               unlink(rebase_path_rewritten_pending());
+       }
+       strbuf_release(&buf);
+}
+
+static void record_in_rewritten(struct object_id *oid,
+               enum todo_command next_command) {
+       FILE *out = fopen(rebase_path_rewritten_pending(), "a");
+
+       if (!out)
+               return;
+
+       fprintf(out, "%s\n", oid_to_hex(oid));
+       fclose(out);
+
+       if (!is_fixup(next_command))
+               flush_rewritten_pending();
+}
 
 static int do_pick_commit(enum todo_command command, struct commit *commit,
-               struct replay_opts *opts)
+               struct replay_opts *opts, int final_fixup)
 {
+       int edit = opts->edit, cleanup_commit_message = 0;
+       const char *msg_file = edit ? NULL : git_path_merge_msg();
        unsigned char head[20];
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0, allow;
+       int res, unborn = 0, amend = 0, allow = 0;
 
        if (opts->no_commit) {
                /*
@@ -632,9 +953,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        }
        discard_cache();
 
-       if (!commit->parents) {
+       if (!commit->parents)
                parent = NULL;
-       }
        else if (commit->parents->next) {
                /* Reverting or cherry-picking a merge commit */
                int cnt;
@@ -658,11 +978,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        else
                parent = commit->parents->item;
 
-       if (opts->allow_ff &&
-           ((parent && !hashcmp(parent->object.oid.hash, head)) ||
-            (!parent && unborn)))
-               return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
+       if (get_message(commit, &msg) != 0)
+               return error(_("cannot get commit message for %s"),
+                       oid_to_hex(&commit->object.oid));
 
+       if (opts->allow_ff && !is_fixup(command) &&
+           ((parent && !hashcmp(parent->object.oid.hash, head)) ||
+            (!parent && unborn))) {
+               if (is_rebase_i(opts))
+                       write_author_script(msg.message);
+               res = fast_forward_to(commit->object.oid.hash, head, unborn,
+                       opts);
+               if (res || command != TODO_REWORD)
+                       goto leave;
+               edit = amend = 1;
+               msg_file = NULL;
+               goto fast_forward_edit;
+       }
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be a "todo" command like
                   "revert" or "pick", the second %s a SHA1. */
@@ -670,10 +1002,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        command_to_string(command),
                        oid_to_hex(&parent->object.oid));
 
-       if (get_message(commit, &msg) != 0)
-               return error(_("cannot get commit message for %s"),
-                       oid_to_hex(&commit->object.oid));
-
        /*
         * "commit" is an existing commit.  We would want to apply
         * the difference it introduces since its first parent "prev"
@@ -704,14 +1032,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                next = commit;
                next_label = msg.label;
 
-               /*
-                * Append the commit log message to msgbuf; it starts
-                * after the tree, parent, author, committer
-                * information followed by "\n\n".
-                */
-               p = strstr(msg.message, "\n\n");
-               if (p)
-                       strbuf_addstr(&msgbuf, skip_blank_lines(p + 2));
+               /* Append the commit log message to msgbuf. */
+               if (find_commit_subject(msg.message, &p))
+                       strbuf_addstr(&msgbuf, p);
 
                if (opts->record_origin) {
                        if (!has_conforming_footer(&msgbuf, NULL, 0))
@@ -722,7 +1045,32 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                }
        }
 
-       if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
+       if (command == TODO_REWORD)
+               edit = 1;
+       else if (is_fixup(command)) {
+               if (update_squash_messages(command, commit, opts))
+                       return -1;
+               amend = 1;
+               if (!final_fixup)
+                       msg_file = rebase_path_squash_msg();
+               else if (file_exists(rebase_path_fixup_msg())) {
+                       cleanup_commit_message = 1;
+                       msg_file = rebase_path_fixup_msg();
+               } else {
+                       const char *dest = git_path("SQUASH_MSG");
+                       unlink(dest);
+                       if (copy_file(dest, rebase_path_squash_msg(), 0666))
+                               return error(_("could not rename '%s' to '%s'"),
+                                            rebase_path_squash_msg(), dest);
+                       unlink(git_path("MERGE_MSG"));
+                       msg_file = dest;
+                       edit = 1;
+               }
+       }
+
+       if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
+               res = -1;
+       else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
                                         head, &msgbuf, opts);
                if (res < 0)
@@ -777,8 +1125,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                goto leave;
        }
        if (!opts->no_commit)
-               res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(),
-                                    opts, allow, opts->edit, 0, 0);
+fast_forward_edit:
+               res = run_git_commit(msg_file, opts, allow, edit, amend,
+                                    cleanup_commit_message);
+
+       if (!res && final_fixup) {
+               unlink(rebase_path_fixup_msg());
+               unlink(rebase_path_squash_msg());
+       }
 
 leave:
        free_message(commit, &msg);
@@ -837,6 +1191,7 @@ struct todo_list {
        struct strbuf buf;
        struct todo_item *items;
        int nr, alloc, current;
+       int done_nr, total_nr;
 };
 
 #define TODO_LIST_INIT { STRBUF_INIT }
@@ -864,20 +1219,45 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        /* left-trim */
        bol += strspn(bol, " \t");
 
-       for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++)
-               if (skip_prefix(bol, todo_command_strings[i], &bol)) {
+       if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
+               item->command = TODO_COMMENT;
+               item->commit = NULL;
+               item->arg = bol;
+               item->arg_len = eol - bol;
+               return 0;
+       }
+
+       for (i = 0; i < TODO_COMMENT; i++)
+               if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
+                       item->command = i;
+                       break;
+               } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {
+                       bol++;
                        item->command = i;
                        break;
                }
-       if (i >= ARRAY_SIZE(todo_command_strings))
+       if (i >= TODO_COMMENT)
                return -1;
 
+       if (item->command == TODO_NOOP) {
+               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;
 
+       if (item->command == TODO_EXEC) {
+               item->arg = bol;
+               item->arg_len = (int)(eol - bol);
+               return 0;
+       }
+
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
@@ -898,7 +1278,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
 {
        struct todo_item *item;
        char *p = buf, *next_p;
-       int i, res = 0;
+       int i, res = 0, fixup_okay = file_exists(rebase_path_done());
 
        for (i = 1; *p; i++, p = next_p) {
                char *eol = strchrnul(p, '\n');
@@ -913,14 +1293,32 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
                if (parse_insn_line(item, p, eol)) {
                        res = error(_("invalid line %d: %.*s"),
                                i, (int)(eol - p), p);
-                       item->command = -1;
+                       item->command = TODO_NOOP;
                }
+
+               if (fixup_okay)
+                       ; /* do nothing */
+               else if (is_fixup(item->command))
+                       return error(_("cannot '%s' without a previous commit"),
+                               command_to_string(item->command));
+               else if (!is_noop(item->command))
+                       fixup_okay = 1;
        }
-       if (!todo_list->nr)
-               return error(_("no commits parsed."));
+
        return res;
 }
 
+static int count_commands(struct todo_list *todo_list)
+{
+       int count = 0, i;
+
+       for (i = 0; i < todo_list->nr; i++)
+               if (todo_list->items[i].command != TODO_COMMENT)
+                       count++;
+
+       return count;
+}
+
 static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
 {
@@ -938,8 +1336,16 @@ static int read_populate_todo(struct todo_list *todo_list,
        close(fd);
 
        res = parse_insn_buffer(todo_list->buf.buf, todo_list);
-       if (res)
+       if (res) {
+               if (is_rebase_i(opts))
+                       return error(_("please fix this using "
+                                      "'git rebase --edit-todo'."));
                return error(_("unusable instruction sheet: '%s'"), todo_file);
+       }
+
+       if (!todo_list->nr &&
+           (!is_rebase_i(opts) || !file_exists(rebase_path_done())))
+               return error(_("no commits parsed."));
 
        if (!is_rebase_i(opts)) {
                enum todo_command valid =
@@ -955,6 +1361,26 @@ static int read_populate_todo(struct todo_list *todo_list,
                                return error(_("cannot revert during a cherry-pick."));
        }
 
+       if (is_rebase_i(opts)) {
+               struct todo_list done = TODO_LIST_INIT;
+               FILE *f = fopen(rebase_path_msgtotal(), "w");
+
+               if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
+                               !parse_insn_buffer(done.buf.buf, &done))
+                       todo_list->done_nr = count_commands(&done);
+               else
+                       todo_list->done_nr = 0;
+
+               todo_list->total_nr = todo_list->done_nr
+                       + count_commands(todo_list);
+               todo_list_release(&done);
+
+               if (f) {
+                       fprintf(f, "%d\n", todo_list->total_nr);
+                       fclose(f);
+               }
+       }
+
        return 0;
 }
 
@@ -1003,6 +1429,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
        return 0;
 }
 
+static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
+{
+       int i;
+
+       strbuf_reset(buf);
+       if (!read_oneliner(buf, rebase_path_strategy(), 0))
+               return;
+       opts->strategy = strbuf_detach(buf, NULL);
+       if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
+               return;
+
+       opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
+       for (i = 0; i < opts->xopts_nr; i++) {
+               const char *arg = opts->xopts[i];
+
+               skip_prefix(arg, "--", &arg);
+               opts->xopts[i] = xstrdup(arg);
+       }
+}
+
 static int read_populate_opts(struct replay_opts *opts)
 {
        if (is_rebase_i(opts)) {
@@ -1016,6 +1462,11 @@ static int read_populate_opts(struct replay_opts *opts)
                                opts->gpg_sign = xstrdup(buf.buf + 2);
                        }
                }
+
+               if (file_exists(rebase_path_verbose()))
+                       opts->verbose = 1;
+
+               read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
 
                return 0;
@@ -1040,7 +1491,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
 {
        enum todo_command command = opts->action == REPLAY_PICK ?
                TODO_PICK : TODO_REVERT;
-       const char *command_string = todo_command_strings[command];
+       const char *command_string = todo_command_info[command].str;
        struct commit *commit;
 
        if (prepare_revs(opts))
@@ -1071,8 +1522,7 @@ static int create_seq_dir(void)
                error(_("a cherry-pick or revert is already in progress"));
                advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
                return -1;
-       }
-       else if (mkdir(git_path_seq_dir(), 0777) < 0)
+       } else if (mkdir(git_path_seq_dir(), 0777) < 0)
                return error_errno(_("could not create sequencer directory '%s'"),
                                   git_path_seq_dir());
        return 0;
@@ -1205,6 +1655,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
        const char *todo_path = get_todo_path(opts);
        int next = todo_list->current, offset, fd;
 
+       /*
+        * rebase -i writes "git-rebase-todo" without the currently executing
+        * command, appending it to "done" instead.
+        */
+       if (is_rebase_i(opts))
+               next++;
+
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
                return error_errno(_("could not lock '%s'"), todo_path);
@@ -1215,6 +1672,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
                return error(_("failed to finalize '%s'."), todo_path);
+
+       if (is_rebase_i(opts)) {
+               const char *done_path = rebase_path_done();
+               int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+               int prev_offset = !next ? 0 :
+                       todo_list->items[next - 1].offset_in_buf;
+
+               if (fd >= 0 && offset > prev_offset &&
+                   write_in_full(fd, todo_list->buf.buf + prev_offset,
+                                 offset - prev_offset) < 0) {
+                       close(fd);
+                       return error_errno(_("could not write to '%s'"),
+                                          done_path);
+               }
+               if (fd >= 0)
+                       close(fd);
+       }
        return 0;
 }
 
@@ -1253,9 +1727,228 @@ static int save_opts(struct replay_opts *opts)
        return res;
 }
 
+static int make_patch(struct commit *commit, struct replay_opts *opts)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct rev_info log_tree_opt;
+       const char *subject, *p;
+       int res = 0;
+
+       p = short_commit_name(commit);
+       if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
+               return -1;
+
+       strbuf_addf(&buf, "%s/patch", get_dir(opts));
+       memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+       init_revisions(&log_tree_opt, NULL);
+       log_tree_opt.abbrev = 0;
+       log_tree_opt.diff = 1;
+       log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
+       log_tree_opt.disable_stdin = 1;
+       log_tree_opt.no_commit_id = 1;
+       log_tree_opt.diffopt.file = fopen(buf.buf, "w");
+       log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
+       if (!log_tree_opt.diffopt.file)
+               res |= error_errno(_("could not open '%s'"), buf.buf);
+       else {
+               res |= log_tree_commit(&log_tree_opt, commit);
+               fclose(log_tree_opt.diffopt.file);
+       }
+       strbuf_reset(&buf);
+
+       strbuf_addf(&buf, "%s/message", get_dir(opts));
+       if (!file_exists(buf.buf)) {
+               const char *commit_buffer = get_commit_buffer(commit, NULL);
+               find_commit_subject(commit_buffer, &subject);
+               res |= write_message(subject, strlen(subject), buf.buf, 1);
+               unuse_commit_buffer(commit, commit_buffer);
+       }
+       strbuf_release(&buf);
+
+       return res;
+}
+
+static int intend_to_amend(void)
+{
+       unsigned char head[20];
+       char *p;
+
+       if (get_sha1("HEAD", head))
+               return error(_("cannot read HEAD"));
+
+       p = sha1_to_hex(head);
+       return write_message(p, strlen(p), rebase_path_amend(), 1);
+}
+
+static int error_with_patch(struct commit *commit,
+       const char *subject, int subject_len,
+       struct replay_opts *opts, int exit_code, int to_amend)
+{
+       if (make_patch(commit, opts))
+               return -1;
+
+       if (to_amend) {
+               if (intend_to_amend())
+                       return -1;
+
+               fprintf(stderr, "You can amend the commit now, with\n"
+                       "\n"
+                       "  git commit --amend %s\n"
+                       "\n"
+                       "Once you are satisfied with your changes, run\n"
+                       "\n"
+                       "  git rebase --continue\n", gpg_sign_opt_quoted(opts));
+       } else if (exit_code)
+               fprintf(stderr, "Could not apply %s... %.*s\n",
+                       short_commit_name(commit), subject_len, subject);
+
+       return exit_code;
+}
+
+static int error_failed_squash(struct commit *commit,
+       struct replay_opts *opts, int subject_len, const char *subject)
+{
+       if (rename(rebase_path_squash_msg(), rebase_path_message()))
+               return error(_("could not rename '%s' to '%s'"),
+                       rebase_path_squash_msg(), rebase_path_message());
+       unlink(rebase_path_fixup_msg());
+       unlink(git_path("MERGE_MSG"));
+       if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666))
+               return error(_("could not copy '%s' to '%s'"),
+                            rebase_path_message(), git_path("MERGE_MSG"));
+       return error_with_patch(commit, subject, subject_len, opts, 1, 0);
+}
+
+static int do_exec(const char *command_line)
+{
+       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);
+
+       /* force re-reading of the cache */
+       if (discard_cache() < 0 || read_cache() < 0)
+               return error(_("could not read index"));
+
+       dirty = require_clean_work_tree("rebase", NULL, 1, 1);
+
+       if (status) {
+               warning(_("execution failed: %s\n%s"
+                         "You can fix the problem, and then run\n"
+                         "\n"
+                         "  git rebase --continue\n"
+                         "\n"),
+                       command_line,
+                       dirty ? N_("and made changes to the index and/or the "
+                               "working tree\n") : "");
+               if (status == 127)
+                       /* command not found */
+                       status = 1;
+       } else if (dirty) {
+               warning(_("execution succeeded: %s\nbut "
+                         "left changes to the index and/or the working tree\n"
+                         "Commit or stash your changes, and then run\n"
+                         "\n"
+                         "  git rebase --continue\n"
+                         "\n"), command_line);
+               status = 1;
+       }
+
+       return status;
+}
+
+static int is_final_fixup(struct todo_list *todo_list)
+{
+       int i = todo_list->current;
+
+       if (!is_fixup(todo_list->items[i].command))
+               return 0;
+
+       while (++i < todo_list->nr)
+               if (is_fixup(todo_list->items[i].command))
+                       return 0;
+               else if (!is_noop(todo_list->items[i].command))
+                       break;
+       return 1;
+}
+
+static enum todo_command peek_command(struct todo_list *todo_list, int offset)
+{
+       int i;
+
+       for (i = todo_list->current + offset; i < todo_list->nr; i++)
+               if (!is_noop(todo_list->items[i].command))
+                       return todo_list->items[i].command;
+
+       return -1;
+}
+
+static int apply_autostash(struct replay_opts *opts)
+{
+       struct strbuf stash_sha1 = STRBUF_INIT;
+       struct child_process child = CHILD_PROCESS_INIT;
+       int ret = 0;
+
+       if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
+               strbuf_release(&stash_sha1);
+               return 0;
+       }
+       strbuf_trim(&stash_sha1);
+
+       child.git_cmd = 1;
+       argv_array_push(&child.args, "stash");
+       argv_array_push(&child.args, "apply");
+       argv_array_push(&child.args, stash_sha1.buf);
+       if (!run_command(&child))
+               printf(_("Applied autostash."));
+       else {
+               struct child_process store = CHILD_PROCESS_INIT;
+
+               store.git_cmd = 1;
+               argv_array_push(&store.args, "stash");
+               argv_array_push(&store.args, "store");
+               argv_array_push(&store.args, "-m");
+               argv_array_push(&store.args, "autostash");
+               argv_array_push(&store.args, "-q");
+               argv_array_push(&store.args, stash_sha1.buf);
+               if (run_command(&store))
+                       ret = error(_("cannot store %s"), stash_sha1.buf);
+               else
+                       printf(_("Applying autostash resulted in conflicts.\n"
+                               "Your changes are safe in the stash.\n"
+                               "You can run \"git stash pop\" or"
+                               " \"git stash drop\" at any time.\n"));
+       }
+
+       strbuf_release(&stash_sha1);
+       return ret;
+}
+
+static const char *reflog_message(struct replay_opts *opts,
+       const char *sub_action, const char *fmt, ...)
+{
+       va_list ap;
+       static struct strbuf buf = STRBUF_INIT;
+
+       va_start(ap, fmt);
+       strbuf_reset(&buf);
+       strbuf_addstr(&buf, action_name(opts));
+       if (sub_action)
+               strbuf_addf(&buf, " (%s)", sub_action);
+       if (fmt) {
+               strbuf_addstr(&buf, ": ");
+               strbuf_vaddf(&buf, fmt, ap);
+       }
+       va_end(ap);
+
+       return buf.buf;
+}
+
 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 {
-       int res;
+       int res = 0;
 
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
@@ -1268,12 +1961,179 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                struct todo_item *item = todo_list->items + todo_list->current;
                if (save_todo(todo_list, opts))
                        return -1;
-               res = do_pick_commit(item->command, item->commit, opts);
+               if (is_rebase_i(opts)) {
+                       if (item->command != TODO_COMMENT) {
+                               FILE *f = fopen(rebase_path_msgnum(), "w");
+
+                               todo_list->done_nr++;
+
+                               if (f) {
+                                       fprintf(f, "%d\n", todo_list->done_nr);
+                                       fclose(f);
+                               }
+                               fprintf(stderr, "Rebasing (%d/%d)%s",
+                                       todo_list->done_nr,
+                                       todo_list->total_nr,
+                                       opts->verbose ? "\n" : "\r");
+                       }
+                       unlink(rebase_path_message());
+                       unlink(rebase_path_author_script());
+                       unlink(rebase_path_stopped_sha());
+                       unlink(rebase_path_amend());
+               }
+               if (item->command <= TODO_SQUASH) {
+                       if (is_rebase_i(opts))
+                               setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+                                       command_to_string(item->command), NULL),
+                                       1);
+                       res = do_pick_commit(item->command, item->commit,
+                                       opts, is_final_fixup(todo_list));
+                       if (is_rebase_i(opts) && res < 0) {
+                               /* Reschedule */
+                               todo_list->current--;
+                               if (save_todo(todo_list, opts))
+                                       return -1;
+                       }
+                       if (item->command == TODO_EDIT) {
+                               struct commit *commit = item->commit;
+                               if (!res)
+                                       warning(_("stopped at %s... %.*s"),
+                                               short_commit_name(commit),
+                                               item->arg_len, item->arg);
+                               return error_with_patch(commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       !res);
+                       }
+                       if (is_rebase_i(opts) && !res)
+                               record_in_rewritten(&item->commit->object.oid,
+                                       peek_command(todo_list, 1));
+                       if (res && is_fixup(item->command)) {
+                               if (res == 1)
+                                       intend_to_amend();
+                               return error_failed_squash(item->commit, opts,
+                                       item->arg_len, item->arg);
+                       } else if (res && is_rebase_i(opts))
+                               return res | error_with_patch(item->commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       item->command == TODO_REWORD);
+               } else if (item->command == TODO_EXEC) {
+                       char *end_of_arg = (char *)(item->arg + item->arg_len);
+                       int saved = *end_of_arg;
+
+                       *end_of_arg = '\0';
+                       res = do_exec(item->arg);
+                       *end_of_arg = saved;
+               } else if (!is_noop(item->command))
+                       return error(_("unknown command %d"), item->command);
+
                todo_list->current++;
                if (res)
                        return res;
        }
 
+       if (is_rebase_i(opts)) {
+               struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT;
+               struct stat st;
+
+               /* Stopped in the middle, as planned? */
+               if (todo_list->current < todo_list->nr)
+                       return 0;
+
+               if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
+                               starts_with(head_ref.buf, "refs/")) {
+                       const char *msg;
+                       unsigned char head[20], orig[20];
+                       int res;
+
+                       if (get_sha1("HEAD", head)) {
+                               res = error(_("cannot read HEAD"));
+cleanup_head_ref:
+                               strbuf_release(&head_ref);
+                               strbuf_release(&buf);
+                               return res;
+                       }
+                       if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
+                                       get_sha1_hex(buf.buf, orig)) {
+                               res = error(_("could not read orig-head"));
+                               goto cleanup_head_ref;
+                       }
+                       if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
+                               res = error(_("could not read 'onto'"));
+                               goto 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)) {
+                               res = error(_("could not update %s"),
+                                       head_ref.buf);
+                               goto cleanup_head_ref;
+                       }
+                       msg = reflog_message(opts, "finish", "returning to %s",
+                               head_ref.buf);
+                       if (create_symref("HEAD", head_ref.buf, msg)) {
+                               res = error(_("could not update HEAD to %s"),
+                                       head_ref.buf);
+                               goto cleanup_head_ref;
+                       }
+                       strbuf_reset(&buf);
+               }
+
+               if (opts->verbose) {
+                       struct rev_info log_tree_opt;
+                       struct object_id orig, head;
+
+                       memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+                       init_revisions(&log_tree_opt, NULL);
+                       log_tree_opt.diff = 1;
+                       log_tree_opt.diffopt.output_format =
+                               DIFF_FORMAT_DIFFSTAT;
+                       log_tree_opt.disable_stdin = 1;
+
+                       if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
+                           !get_sha1(buf.buf, orig.hash) &&
+                           !get_sha1("HEAD", head.hash)) {
+                               diff_tree_sha1(orig.hash, head.hash,
+                                              "", &log_tree_opt.diffopt);
+                               log_tree_diff_flush(&log_tree_opt);
+                       }
+               }
+               flush_rewritten_pending();
+               if (!stat(rebase_path_rewritten_list(), &st) &&
+                               st.st_size > 0) {
+                       struct child_process child = CHILD_PROCESS_INIT;
+                       const char *post_rewrite_hook =
+                               find_hook("post-rewrite");
+
+                       child.in = open(rebase_path_rewritten_list(), O_RDONLY);
+                       child.git_cmd = 1;
+                       argv_array_push(&child.args, "notes");
+                       argv_array_push(&child.args, "copy");
+                       argv_array_push(&child.args, "--for-rewrite=rebase");
+                       /* we don't care if this copying failed */
+                       run_command(&child);
+
+                       if (post_rewrite_hook) {
+                               struct child_process hook = CHILD_PROCESS_INIT;
+
+                               hook.in = open(rebase_path_rewritten_list(),
+                                       O_RDONLY);
+                               hook.stdout_to_stderr = 1;
+                               argv_array_push(&hook.args, post_rewrite_hook);
+                               argv_array_push(&hook.args, "rebase");
+                               /* we don't care if this hook failed */
+                               run_command(&hook);
+                       }
+               }
+               apply_autostash(opts);
+
+               fprintf(stderr, "Successfully rebased and updated %s.\n",
+                       head_ref.buf);
+
+               strbuf_release(&buf);
+               strbuf_release(&head_ref);
+       }
+
        /*
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory
@@ -1291,6 +2151,47 @@ static int continue_single_pick(void)
        return run_command_v_opt(argv, RUN_GIT_CMD);
 }
 
+static int commit_staged_changes(struct replay_opts *opts)
+{
+       int amend = 0;
+
+       if (has_unstaged_changes(1))
+               return error(_("cannot rebase: You have unstaged changes."));
+       if (!has_uncommitted_changes(0)) {
+               const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD");
+
+               if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
+                       return error(_("could not remove CHERRY_PICK_HEAD"));
+               return 0;
+       }
+
+       if (file_exists(rebase_path_amend())) {
+               struct strbuf rev = STRBUF_INIT;
+               unsigned char head[20], to_amend[20];
+
+               if (get_sha1("HEAD", head))
+                       return error(_("cannot amend non-existing commit"));
+               if (!read_oneliner(&rev, rebase_path_amend(), 0))
+                       return error(_("invalid file: '%s'"), rebase_path_amend());
+               if (get_sha1_hex(rev.buf, to_amend))
+                       return error(_("invalid contents: '%s'"),
+                               rebase_path_amend());
+               if (hashcmp(head, to_amend))
+                       return error(_("\nYou have uncommitted changes in your "
+                                      "working tree. Please, commit them\n"
+                                      "first and then run 'git rebase "
+                                      "--continue' again."));
+
+               strbuf_release(&rev);
+               amend = 1;
+       }
+
+       if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0))
+               return error(_("could not commit staged changes."));
+       unlink(rebase_path_amend());
+       return 0;
+}
+
 int sequencer_continue(struct replay_opts *opts)
 {
        struct todo_list todo_list = TODO_LIST_INIT;
@@ -1299,25 +2200,39 @@ int sequencer_continue(struct replay_opts *opts)
        if (read_and_refresh_cache(opts))
                return -1;
 
-       if (!file_exists(get_todo_path(opts)))
+       if (is_rebase_i(opts)) {
+               if (commit_staged_changes(opts))
+                       return -1;
+       } else if (!file_exists(get_todo_path(opts)))
                return continue_single_pick();
        if (read_populate_opts(opts))
                return -1;
        if ((res = read_populate_todo(&todo_list, opts)))
                goto release_todo_list;
 
-       /* Verify that the conflict has been resolved */
-       if (file_exists(git_path_cherry_pick_head()) ||
-           file_exists(git_path_revert_head())) {
-               res = continue_single_pick();
-               if (res)
+       if (!is_rebase_i(opts)) {
+               /* Verify that the conflict has been resolved */
+               if (file_exists(git_path_cherry_pick_head()) ||
+                   file_exists(git_path_revert_head())) {
+                       res = continue_single_pick();
+                       if (res)
+                               goto release_todo_list;
+               }
+               if (index_differs_from("HEAD", 0, 0)) {
+                       res = error_dirty_index(opts);
                        goto release_todo_list;
+               }
+               todo_list.current++;
+       } else if (file_exists(rebase_path_stopped_sha())) {
+               struct strbuf buf = STRBUF_INIT;
+               struct object_id oid;
+
+               if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
+                   !get_sha1_committish(buf.buf, oid.hash))
+                       record_in_rewritten(&oid, peek_command(&todo_list, 0));
+               strbuf_release(&buf);
        }
-       if (index_differs_from("HEAD", 0, 0)) {
-               res = error_dirty_index(opts);
-               goto release_todo_list;
-       }
-       todo_list.current++;
+
        res = pick_commits(&todo_list, opts);
 release_todo_list:
        todo_list_release(&todo_list);
@@ -1328,7 +2243,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts)
 {
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        return do_pick_commit(opts->action == REPLAY_PICK ?
-               TODO_PICK : TODO_REVERT, cmit, opts);
+               TODO_PICK : TODO_REVERT, cmit, opts, 0);
 }
 
 int sequencer_pick_revisions(struct replay_opts *opts)