]> git.ipfire.org Git - thirdparty/git.git/blobdiff - sequencer.c
Start the 2.46 cycle
[thirdparty/git.git] / sequencer.c
index 4e14fa6541c7012c8549e84957c47f386efbc7ec..88de4dc20fbb8cb0e813afc79486cca04484aba5 100644 (file)
@@ -207,6 +207,46 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-res
 static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
 static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
 
+/*
+ * A 'struct replay_ctx' represents the private state of the sequencer.
+ */
+struct replay_ctx {
+       /*
+        * The commit message that will be used except at the end of a
+        * chain of fixup and squash commands.
+        */
+       struct strbuf message;
+       /*
+        * The list of completed fixup and squash commands in the
+        * current chain.
+        */
+       struct strbuf current_fixups;
+       /*
+        * Stores the reflog message that will be used when creating a
+        * commit. Points to a static buffer and should not be free()'d.
+        */
+       const char *reflog_message;
+       /*
+        * The number of completed fixup and squash commands in the
+        * current chain.
+        */
+       int current_fixup_count;
+       /*
+        * Whether message contains a commit message.
+        */
+       unsigned have_message :1;
+};
+
+struct replay_ctx* replay_ctx_new(void)
+{
+       struct replay_ctx *ctx = xcalloc(1, sizeof(*ctx));
+
+       strbuf_init(&ctx->current_fixups, 0);
+       strbuf_init(&ctx->message, 0);
+
+       return ctx;
+}
+
 /**
  * A 'struct update_refs_record' represents a value in the update-refs
  * list. We use a string_list to map refs to these (before, after) pairs.
@@ -366,17 +406,26 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
        return buf.buf;
 }
 
+static void replay_ctx_release(struct replay_ctx *ctx)
+{
+       strbuf_release(&ctx->current_fixups);
+       strbuf_release(&ctx->message);
+}
+
 void replay_opts_release(struct replay_opts *opts)
 {
+       struct replay_ctx *ctx = opts->ctx;
+
        free(opts->gpg_sign);
        free(opts->reflog_action);
        free(opts->default_strategy);
        free(opts->strategy);
        strvec_clear (&opts->xopts);
-       strbuf_release(&opts->current_fixups);
        if (opts->revs)
                release_revisions(opts->revs);
        free(opts->revs);
+       replay_ctx_release(ctx);
+       free(opts->ctx);
 }
 
 int sequencer_remove_state(struct replay_opts *opts)
@@ -479,7 +528,7 @@ static void print_advice(struct repository *r, int show_hint,
                msg = getenv("GIT_CHERRY_PICK_HELP");
 
        if (msg) {
-               advise("%s\n", msg);
+               advise_if_enabled(ADVICE_MERGE_CONFLICT, "%s", msg);
                /*
                 * A conflict has occurred but the porcelain
                 * (typically rebase --interactive) wants to take care
@@ -492,22 +541,25 @@ static void print_advice(struct repository *r, int show_hint,
 
        if (show_hint) {
                if (opts->no_commit)
-                       advise(_("after resolving the conflicts, mark the corrected paths\n"
-                                "with 'git add <paths>' or 'git rm <paths>'"));
+                       advise_if_enabled(ADVICE_MERGE_CONFLICT,
+                                         _("after resolving the conflicts, mark the corrected paths\n"
+                                           "with 'git add <paths>' or 'git rm <paths>'"));
                else if (opts->action == REPLAY_PICK)
-                       advise(_("After resolving the conflicts, mark them with\n"
-                                "\"git add/rm <pathspec>\", then run\n"
-                                "\"git cherry-pick --continue\".\n"
-                                "You can instead skip this commit with \"git cherry-pick --skip\".\n"
-                                "To abort and get back to the state before \"git cherry-pick\",\n"
-                                "run \"git cherry-pick --abort\"."));
+                       advise_if_enabled(ADVICE_MERGE_CONFLICT,
+                                         _("After resolving the conflicts, mark them with\n"
+                                           "\"git add/rm <pathspec>\", then run\n"
+                                           "\"git cherry-pick --continue\".\n"
+                                           "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+                                           "To abort and get back to the state before \"git cherry-pick\",\n"
+                                           "run \"git cherry-pick --abort\"."));
                else if (opts->action == REPLAY_REVERT)
-                       advise(_("After resolving the conflicts, mark them with\n"
-                                "\"git add/rm <pathspec>\", then run\n"
-                                "\"git revert --continue\".\n"
-                                "You can instead skip this commit with \"git revert --skip\".\n"
-                                "To abort and get back to the state before \"git revert\",\n"
-                                "run \"git revert --abort\"."));
+                       advise_if_enabled(ADVICE_MERGE_CONFLICT,
+                                         _("After resolving the conflicts, mark them with\n"
+                                           "\"git add/rm <pathspec>\", then run\n"
+                                           "\"git revert --continue\".\n"
+                                           "You can instead skip this commit with \"git revert --skip\".\n"
+                                           "To abort and get back to the state before \"git revert\",\n"
+                                           "run \"git revert --abort\"."));
                else
                        BUG("unexpected pick action in print_advice()");
        }
@@ -675,15 +727,15 @@ void append_conflicts_hint(struct index_state *istate,
        if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
                strbuf_addch(msgbuf, '\n');
                wt_status_append_cut_line(msgbuf);
-               strbuf_addch(msgbuf, comment_line_char);
+               strbuf_addstr(msgbuf, comment_line_str);
        }
 
        strbuf_addch(msgbuf, '\n');
-       strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
+       strbuf_commented_addf(msgbuf, comment_line_str, "Conflicts:\n");
        for (i = 0; i < istate->cache_nr;) {
                const struct cache_entry *ce = istate->cache[i++];
                if (ce_stage(ce)) {
-                       strbuf_commented_addf(msgbuf, comment_line_char,
+                       strbuf_commented_addf(msgbuf, comment_line_str,
                                              "\t%s\n", ce->name);
                        while (i < istate->cache_nr &&
                               !strcmp(ce->name, istate->cache[i]->name))
@@ -784,29 +836,42 @@ static struct object_id *get_cache_tree_oid(struct index_state *istate)
 static int is_index_unchanged(struct repository *r)
 {
        struct object_id head_oid, *cache_tree_oid;
+       const struct object_id *head_tree_oid;
        struct commit *head_commit;
        struct index_state *istate = r->index;
+       const char *head_name;
+
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
+               /* Check to see if this is an unborn branch */
+               head_name = resolve_ref_unsafe("HEAD",
+                       RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+                       &head_oid, NULL);
+               if (!head_name ||
+                       !starts_with(head_name, "refs/heads/") ||
+                       !is_null_oid(&head_oid))
+                       return error(_("could not resolve HEAD commit"));
+               head_tree_oid = the_hash_algo->empty_tree;
+       } else {
+               head_commit = lookup_commit(r, &head_oid);
 
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
-               return error(_("could not resolve HEAD commit"));
-
-       head_commit = lookup_commit(r, &head_oid);
+               /*
+                * If head_commit is NULL, check_commit, called from
+                * lookup_commit, would have indicated that head_commit is not
+                * a commit object already.  repo_parse_commit() will return failure
+                * without further complaints in such a case.  Otherwise, if
+                * the commit is invalid, repo_parse_commit() will complain.  So
+                * there is nothing for us to say here.  Just return failure.
+                */
+               if (repo_parse_commit(r, head_commit))
+                       return -1;
 
-       /*
-        * If head_commit is NULL, check_commit, called from
-        * lookup_commit, would have indicated that head_commit is not
-        * a commit object already.  repo_parse_commit() will return failure
-        * without further complaints in such a case.  Otherwise, if
-        * the commit is invalid, repo_parse_commit() will complain.  So
-        * there is nothing for us to say here.  Just return failure.
-        */
-       if (repo_parse_commit(r, head_commit))
-               return -1;
+               head_tree_oid = get_commit_tree_oid(head_commit);
+       }
 
        if (!(cache_tree_oid = get_cache_tree_oid(istate)))
                return -1;
 
-       return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
+       return oideq(cache_tree_oid, head_tree_oid);
 }
 
 static int write_author_script(const char *message)
@@ -1068,6 +1133,7 @@ static int run_git_commit(const char *defmsg,
                          struct replay_opts *opts,
                          unsigned int flags)
 {
+       struct replay_ctx *ctx = opts->ctx;
        struct child_process cmd = CHILD_PROCESS_INIT;
 
        if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
@@ -1085,7 +1151,7 @@ static int run_git_commit(const char *defmsg,
                             gpg_opt, gpg_opt);
        }
 
-       strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", opts->reflog_message);
+       strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", ctx->reflog_message);
 
        if (opts->committer_date_is_author_date)
                strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
@@ -1166,7 +1232,7 @@ void cleanup_message(struct strbuf *msgbuf,
                strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
        if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
                strbuf_stripspace(msgbuf,
-                 cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+                 cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
 }
 
 /*
@@ -1198,7 +1264,7 @@ int template_untouched(const struct strbuf *sb, const char *template_file,
                return 0;
 
        strbuf_stripspace(&tmpl,
-         cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+         cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
        if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
@@ -1471,6 +1537,7 @@ static int try_to_commit(struct repository *r,
                         struct replay_opts *opts, unsigned int flags,
                         struct object_id *oid)
 {
+       struct replay_ctx *ctx = opts->ctx;
        struct object_id tree;
        struct commit *current_head = NULL;
        struct commit_list *parents = NULL;
@@ -1571,7 +1638,7 @@ static int try_to_commit(struct repository *r,
 
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
                strbuf_stripspace(msg,
-                 cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
+                 cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_str : NULL);
        if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
@@ -1632,7 +1699,7 @@ static int try_to_commit(struct repository *r,
                goto out;
        }
 
-       if (update_head_with_reflog(current_head, oid, opts->reflog_message,
+       if (update_head_with_reflog(current_head, oid, ctx->reflog_message,
                                    msg, &err)) {
                res = error("%s", err.buf);
                goto out;
@@ -1733,34 +1800,25 @@ static int allow_empty(struct repository *r,
        int index_unchanged, originally_empty;
 
        /*
-        * Four cases:
+        * For a commit that is initially empty, allow_empty determines if it
+        * should be kept or not
         *
-        * (1) we do not allow empty at all and error out.
-        *
-        * (2) we allow ones that were initially empty, and
-        *     just drop the ones that become empty
-        *
-        * (3) we allow ones that were initially empty, but
-        *     halt for the ones that become empty;
-        *
-        * (4) we allow both.
+        * For a commit that becomes empty, keep_redundant_commits and
+        * drop_redundant_commits determine whether the commit should be kept or
+        * dropped. If neither is specified, halt.
         */
-       if (!opts->allow_empty)
-               return 0; /* let "git commit" barf as necessary */
-
        index_unchanged = is_index_unchanged(r);
        if (index_unchanged < 0)
                return index_unchanged;
        if (!index_unchanged)
                return 0; /* we do not have to say --allow-empty */
 
-       if (opts->keep_redundant_commits)
-               return 1;
-
        originally_empty = is_original_commit_empty(commit);
        if (originally_empty < 0)
                return originally_empty;
        if (originally_empty)
+               return opts->allow_empty;
+       else if (opts->keep_redundant_commits)
                return 1;
        else if (opts->drop_redundant_commits)
                return 2;
@@ -1793,6 +1851,8 @@ static const char *command_to_string(const enum todo_command command)
 {
        if (command < TODO_COMMENT)
                return todo_command_info[command].str;
+       if (command == TODO_COMMENT)
+               return comment_line_str;
        die(_("unknown command: %d"), command);
 }
 
@@ -1800,7 +1860,7 @@ static char command_to_char(const enum todo_command command)
 {
        if (command < TODO_COMMENT)
                return todo_command_info[command].c;
-       return comment_line_char;
+       return 0;
 }
 
 static int is_noop(const enum todo_command command)
@@ -1854,7 +1914,7 @@ static int is_fixup_flag(enum todo_command command, unsigned flag)
 static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
 {
        const char *s = str;
-       while (len > 0 && s[0] == comment_line_char) {
+       while (starts_with_mem(s, len, comment_line_str)) {
                size_t count;
                const char *n = memchr(s, '\n', len);
                if (!n)
@@ -1865,14 +1925,14 @@ static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
                s += count;
                len -= count;
        }
-       strbuf_add_commented_lines(buf, s, len, comment_line_char);
+       strbuf_add_commented_lines(buf, s, len, comment_line_str);
 }
 
 /* Does the current fixup chain contain a squash command? */
-static int seen_squash(struct replay_opts *opts)
+static int seen_squash(struct replay_ctx *ctx)
 {
-       return starts_with(opts->current_fixups.buf, "squash") ||
-               strstr(opts->current_fixups.buf, "\nsquash");
+       return starts_with(ctx->current_fixups.buf, "squash") ||
+               strstr(ctx->current_fixups.buf, "\nsquash");
 }
 
 static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
@@ -1948,6 +2008,7 @@ static int append_squash_message(struct strbuf *buf, const char *body,
                         enum todo_command command, struct replay_opts *opts,
                         unsigned flag)
 {
+       struct replay_ctx *ctx = opts->ctx;
        const char *fixup_msg;
        size_t commented_len = 0, fixup_off;
        /*
@@ -1956,21 +2017,21 @@ static int append_squash_message(struct strbuf *buf, const char *body,
         * squashing commit messages.
         */
        if (starts_with(body, "amend!") ||
-           ((command == TODO_SQUASH || seen_squash(opts)) &&
+           ((command == TODO_SQUASH || seen_squash(ctx)) &&
             (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
                commented_len = commit_subject_length(body);
 
-       strbuf_addf(buf, "\n%c ", comment_line_char);
+       strbuf_addf(buf, "\n%s ", comment_line_str);
        strbuf_addf(buf, _(nth_commit_msg_fmt),
-                   ++opts->current_fixup_count + 1);
+                   ++ctx->current_fixup_count + 1);
        strbuf_addstr(buf, "\n\n");
-       strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
+       strbuf_add_commented_lines(buf, body, commented_len, comment_line_str);
        /* buf->buf may be reallocated so store an offset into the buffer */
        fixup_off = buf->len;
        strbuf_addstr(buf, body + commented_len);
 
        /* fixup -C after squash behaves like squash */
-       if (is_fixup_flag(command, flag) && !seen_squash(opts)) {
+       if (is_fixup_flag(command, flag) && !seen_squash(ctx)) {
                /*
                 * We're replacing the commit message so we need to
                 * append the Signed-off-by: trailer if the user
@@ -2004,12 +2065,13 @@ static int update_squash_messages(struct repository *r,
                                  struct replay_opts *opts,
                                  unsigned flag)
 {
+       struct replay_ctx *ctx = opts->ctx;
        struct strbuf buf = STRBUF_INIT;
        int res = 0;
        const char *message, *body;
        const char *encoding = get_commit_output_encoding();
 
-       if (opts->current_fixup_count > 0) {
+       if (ctx->current_fixup_count > 0) {
                struct strbuf header = STRBUF_INIT;
                char *eol;
 
@@ -2017,15 +2079,15 @@ static int update_squash_messages(struct repository *r,
                        return error(_("could not read '%s'"),
                                rebase_path_squash_msg());
 
-               eol = buf.buf[0] != comment_line_char ?
+               eol = !starts_with(buf.buf, comment_line_str) ?
                        buf.buf : strchrnul(buf.buf, '\n');
 
-               strbuf_addf(&header, "%c ", comment_line_char);
+               strbuf_addf(&header, "%s ", comment_line_str);
                strbuf_addf(&header, _(combined_commit_msg_fmt),
-                           opts->current_fixup_count + 2);
+                           ctx->current_fixup_count + 2);
                strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
                strbuf_release(&header);
-               if (is_fixup_flag(command, flag) && !seen_squash(opts))
+               if (is_fixup_flag(command, flag) && !seen_squash(ctx))
                        update_squash_message_for_fixup(&buf);
        } else {
                struct object_id head;
@@ -2046,16 +2108,16 @@ static int update_squash_messages(struct repository *r,
                        repo_unuse_commit_buffer(r, head_commit, head_message);
                        return error(_("cannot write '%s'"), rebase_path_fixup_msg());
                }
-               strbuf_addf(&buf, "%c ", comment_line_char);
+               strbuf_addf(&buf, "%s ", comment_line_str);
                strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
-               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, "\n%s ", comment_line_str);
                strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
                              _(skip_first_commit_msg_str) :
                              _(first_commit_msg_str));
                strbuf_addstr(&buf, "\n\n");
                if (is_fixup_flag(command, flag))
                        strbuf_add_commented_lines(&buf, body, strlen(body),
-                                                  comment_line_char);
+                                                  comment_line_str);
                else
                        strbuf_addstr(&buf, body);
 
@@ -2070,12 +2132,12 @@ static int update_squash_messages(struct repository *r,
        if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
                res = append_squash_message(&buf, body, command, opts, flag);
        } else if (command == TODO_FIXUP) {
-               strbuf_addf(&buf, "\n%c ", comment_line_char);
+               strbuf_addf(&buf, "\n%s ", comment_line_str);
                strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
-                           ++opts->current_fixup_count + 1);
+                           ++ctx->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
                strbuf_add_commented_lines(&buf, body, strlen(body),
-                                          comment_line_char);
+                                          comment_line_str);
        } else
                return error(_("unknown command: %d"), command);
        repo_unuse_commit_buffer(r, commit, message);
@@ -2086,12 +2148,12 @@ static int update_squash_messages(struct repository *r,
        strbuf_release(&buf);
 
        if (!res) {
-               strbuf_addf(&opts->current_fixups, "%s%s %s",
-                           opts->current_fixups.len ? "\n" : "",
+               strbuf_addf(&ctx->current_fixups, "%s%s %s",
+                           ctx->current_fixups.len ? "\n" : "",
                            command_to_string(command),
                            oid_to_hex(&commit->object.oid));
-               res = write_message(opts->current_fixups.buf,
-                                   opts->current_fixups.len,
+               res = write_message(ctx->current_fixups.buf,
+                                   ctx->current_fixups.len,
                                    rebase_path_current_fixups(), 0);
        }
 
@@ -2169,6 +2231,7 @@ static int do_pick_commit(struct repository *r,
                          struct replay_opts *opts,
                          int final_fixup, int *check_todo)
 {
+       struct replay_ctx *ctx = opts->ctx;
        unsigned int flags = should_edit(opts) ? EDIT_MSG : 0;
        const char *msg_file = should_edit(opts) ? NULL : git_path_merge_msg(r);
        struct object_id head;
@@ -2176,7 +2239,6 @@ static int do_pick_commit(struct repository *r,
        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, reword = 0, allow, drop_commit;
        enum todo_command command = item->command;
        struct commit *commit = item->commit;
@@ -2275,7 +2337,7 @@ static int do_pick_commit(struct repository *r,
                next = parent;
                next_label = msg.parent_label;
                if (opts->commit_use_reference) {
-                       strbuf_addstr(&msgbuf,
+                       strbuf_addstr(&ctx->message,
                                "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***");
                } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) &&
                           /*
@@ -2284,21 +2346,21 @@ static int do_pick_commit(struct repository *r,
                            * thus requiring excessive complexity to deal with.
                            */
                           !starts_with(orig_subject, "Revert \"")) {
-                       strbuf_addstr(&msgbuf, "Reapply \"");
-                       strbuf_addstr(&msgbuf, orig_subject);
+                       strbuf_addstr(&ctx->message, "Reapply \"");
+                       strbuf_addstr(&ctx->message, orig_subject);
                } else {
-                       strbuf_addstr(&msgbuf, "Revert \"");
-                       strbuf_addstr(&msgbuf, msg.subject);
-                       strbuf_addstr(&msgbuf, "\"");
+                       strbuf_addstr(&ctx->message, "Revert \"");
+                       strbuf_addstr(&ctx->message, msg.subject);
+                       strbuf_addstr(&ctx->message, "\"");
                }
-               strbuf_addstr(&msgbuf, "\n\nThis reverts commit ");
-               refer_to_commit(opts, &msgbuf, commit);
+               strbuf_addstr(&ctx->message, "\n\nThis reverts commit ");
+               refer_to_commit(opts, &ctx->message, commit);
 
                if (commit->parents && commit->parents->next) {
-                       strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
-                       refer_to_commit(opts, &msgbuf, parent);
+                       strbuf_addstr(&ctx->message, ", reversing\nchanges made to ");
+                       refer_to_commit(opts, &ctx->message, parent);
                }
-               strbuf_addstr(&msgbuf, ".\n");
+               strbuf_addstr(&ctx->message, ".\n");
        } else {
                const char *p;
 
@@ -2307,21 +2369,22 @@ static int do_pick_commit(struct repository *r,
                next = commit;
                next_label = msg.label;
 
-               /* Append the commit log message to msgbuf. */
+               /* Append the commit log message to ctx->message. */
                if (find_commit_subject(msg.message, &p))
-                       strbuf_addstr(&msgbuf, p);
+                       strbuf_addstr(&ctx->message, p);
 
                if (opts->record_origin) {
-                       strbuf_complete_line(&msgbuf);
-                       if (!has_conforming_footer(&msgbuf, NULL, 0))
-                               strbuf_addch(&msgbuf, '\n');
-                       strbuf_addstr(&msgbuf, cherry_picked_prefix);
-                       strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
-                       strbuf_addstr(&msgbuf, ")\n");
+                       strbuf_complete_line(&ctx->message);
+                       if (!has_conforming_footer(&ctx->message, NULL, 0))
+                               strbuf_addch(&ctx->message, '\n');
+                       strbuf_addstr(&ctx->message, cherry_picked_prefix);
+                       strbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid));
+                       strbuf_addstr(&ctx->message, ")\n");
                }
                if (!is_fixup(command))
                        author = get_author(msg.message);
        }
+       ctx->have_message = 1;
 
        if (command == TODO_REWORD)
                reword = 1;
@@ -2352,7 +2415,7 @@ static int do_pick_commit(struct repository *r,
        }
 
        if (opts->signoff && !is_fixup(command))
-               append_signoff(&msgbuf, 0, 0);
+               append_signoff(&ctx->message, 0, 0);
 
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
                res = -1;
@@ -2361,17 +2424,17 @@ static int do_pick_commit(struct repository *r,
                 !strcmp(opts->strategy, "ort") ||
                 command == TODO_REVERT) {
                res = do_recursive_merge(r, base, next, base_label, next_label,
-                                        &head, &msgbuf, opts);
+                                        &head, &ctx->message, opts);
                if (res < 0)
                        goto leave;
 
-               res |= write_message(msgbuf.buf, msgbuf.len,
+               res |= write_message(ctx->message.buf, ctx->message.len,
                                     git_path_merge_msg(r), 0);
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
 
-               res = write_message(msgbuf.buf, msgbuf.len,
+               res = write_message(ctx->message.buf, ctx->message.len,
                                    git_path_merge_msg(r), 0);
 
                commit_list_insert(base, &common);
@@ -2449,14 +2512,13 @@ fast_forward_edit:
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
                unlink(rebase_path_current_fixups());
-               strbuf_reset(&opts->current_fixups);
-               opts->current_fixup_count = 0;
+               strbuf_reset(&ctx->current_fixups);
+               ctx->current_fixup_count = 0;
        }
 
 leave:
        free_message(commit, &msg);
        free(author);
-       strbuf_release(&msgbuf);
        update_abort_safety_file();
 
        return res;
@@ -2576,7 +2638,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
        /* left-trim */
        bol += strspn(bol, " \t");
 
-       if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
+       if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
                item->command = TODO_COMMENT;
                item->commit = NULL;
                item->arg_offset = bol - buf;
@@ -2837,12 +2899,14 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
                        NULL, REF_NO_DEREF);
 
        if (!need_cleanup)
-               return;
+               goto out;
 
        if (!have_finished_the_last_pick())
-               return;
+               goto out;
 
        sequencer_remove_state(&opts);
+out:
+       replay_opts_release(&opts);
 }
 
 static void todo_list_write_total_nr(struct todo_list *todo_list)
@@ -2940,6 +3004,9 @@ static int populate_opts_cb(const char *key, const char *value,
        else if (!strcmp(key, "options.allow-empty-message"))
                opts->allow_empty_message =
                        git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
+       else if (!strcmp(key, "options.drop-redundant-commits"))
+               opts->drop_redundant_commits =
+                       git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.keep-redundant-commits"))
                opts->keep_redundant_commits =
                        git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
@@ -3010,6 +3077,8 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
 
 static int read_populate_opts(struct replay_opts *opts)
 {
+       struct replay_ctx *ctx = opts->ctx;
+
        if (is_rebase_i(opts)) {
                struct strbuf buf = STRBUF_INIT;
                int ret = 0;
@@ -3069,13 +3138,13 @@ static int read_populate_opts(struct replay_opts *opts)
                read_strategy_opts(opts, &buf);
                strbuf_reset(&buf);
 
-               if (read_oneliner(&opts->current_fixups,
+               if (read_oneliner(&ctx->current_fixups,
                                  rebase_path_current_fixups(),
                                  READ_ONELINER_SKIP_IF_EMPTY)) {
-                       const char *p = opts->current_fixups.buf;
-                       opts->current_fixup_count = 1;
+                       const char *p = ctx->current_fixups.buf;
+                       ctx->current_fixup_count = 1;
                        while ((p = strchr(p, '\n'))) {
-                               opts->current_fixup_count++;
+                               ctx->current_fixup_count++;
                                p++;
                        }
                }
@@ -3474,54 +3543,57 @@ static int save_opts(struct replay_opts *opts)
 
        if (opts->no_commit)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.no-commit", "true");
+                                       "options.no-commit", NULL, "true");
        if (opts->edit >= 0)
-               res |= git_config_set_in_file_gently(opts_file, "options.edit",
+               res |= git_config_set_in_file_gently(opts_file, "options.edit", NULL,
                                                     opts->edit ? "true" : "false");
        if (opts->allow_empty)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.allow-empty", "true");
+                                       "options.allow-empty", NULL, "true");
        if (opts->allow_empty_message)
                res |= git_config_set_in_file_gently(opts_file,
-                               "options.allow-empty-message", "true");
+                               "options.allow-empty-message", NULL, "true");
+       if (opts->drop_redundant_commits)
+               res |= git_config_set_in_file_gently(opts_file,
+                               "options.drop-redundant-commits", NULL, "true");
        if (opts->keep_redundant_commits)
                res |= git_config_set_in_file_gently(opts_file,
-                               "options.keep-redundant-commits", "true");
+                               "options.keep-redundant-commits", NULL, "true");
        if (opts->signoff)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.signoff", "true");
+                                       "options.signoff", NULL, "true");
        if (opts->record_origin)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.record-origin", "true");
+                                       "options.record-origin", NULL, "true");
        if (opts->allow_ff)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.allow-ff", "true");
+                                       "options.allow-ff", NULL, "true");
        if (opts->mainline) {
                struct strbuf buf = STRBUF_INIT;
                strbuf_addf(&buf, "%d", opts->mainline);
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.mainline", buf.buf);
+                                       "options.mainline", NULL, buf.buf);
                strbuf_release(&buf);
        }
        if (opts->strategy)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.strategy", opts->strategy);
+                                       "options.strategy", NULL, opts->strategy);
        if (opts->gpg_sign)
                res |= git_config_set_in_file_gently(opts_file,
-                                       "options.gpg-sign", opts->gpg_sign);
+                                       "options.gpg-sign", NULL, opts->gpg_sign);
        for (size_t i = 0; i < opts->xopts.nr; i++)
                res |= git_config_set_multivar_in_file_gently(opts_file,
                                "options.strategy-option",
-                               opts->xopts.v[i], "^$", 0);
+                               opts->xopts.v[i], "^$", NULL, 0);
        if (opts->allow_rerere_auto)
                res |= git_config_set_in_file_gently(opts_file,
-                               "options.allow-rerere-auto",
+                               "options.allow-rerere-auto", NULL,
                                opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
                                "true" : "false");
 
        if (opts->explicit_cleanup)
                res |= git_config_set_in_file_gently(opts_file,
-                               "options.default-msg-cleanup",
+                               "options.default-msg-cleanup", NULL,
                                describe_cleanup_mode(opts->default_msg_cleanup));
        return res;
 }
@@ -3593,13 +3665,24 @@ static int error_with_patch(struct repository *r,
                            struct replay_opts *opts,
                            int exit_code, int to_amend)
 {
-       if (commit) {
-               if (make_patch(r, commit, opts))
+       struct replay_ctx *ctx = opts->ctx;
+
+       /*
+        * Write the commit message to be used by "git rebase
+        * --continue". If a "fixup" or "squash" command has conflicts
+        * then we will have already written rebase_path_message() in
+        * error_failed_squash(). If an "edit" command was
+        * fast-forwarded then we don't have a message in ctx->message
+        * and rely on make_patch() to write rebase_path_message()
+        * instead.
+        */
+       if (ctx->have_message && !file_exists(rebase_path_message()) &&
+           write_message(ctx->message.buf, ctx->message.len,
+                         rebase_path_message(), 0))
+               return error(_("could not write commit message file"));
+
+       if (commit && make_patch(r, commit, opts))
                        return -1;
-       } else if (copy_file(rebase_path_message(),
-                            git_path_merge_msg(r), 0666))
-               return error(_("unable to copy '%s' to '%s'"),
-                            git_path_merge_msg(r), rebase_path_message());
 
        if (to_amend) {
                if (intend_to_amend())
@@ -3921,6 +4004,7 @@ static int do_merge(struct repository *r,
                    const char *arg, int arg_len,
                    int flags, int *check_todo, struct replay_opts *opts)
 {
+       struct replay_ctx *ctx = opts->ctx;
        int run_commit_flags = 0;
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
@@ -4049,40 +4133,31 @@ static int do_merge(struct repository *r,
                write_author_script(message);
                find_commit_subject(message, &body);
                len = strlen(body);
-               ret = write_message(body, len, git_path_merge_msg(r), 0);
+               strbuf_add(&ctx->message, body, len);
                repo_unuse_commit_buffer(r, commit, message);
-               if (ret) {
-                       error_errno(_("could not write '%s'"),
-                                   git_path_merge_msg(r));
-                       goto leave_merge;
-               }
        } else {
                struct strbuf buf = STRBUF_INIT;
-               int len;
 
                strbuf_addf(&buf, "author %s", git_author_info(0));
                write_author_script(buf.buf);
-               strbuf_reset(&buf);
+               strbuf_release(&buf);
 
                if (oneline_offset < arg_len) {
-                       p = arg + oneline_offset;
-                       len = arg_len - oneline_offset;
+                       strbuf_add(&ctx->message, arg + oneline_offset,
+                                  arg_len - oneline_offset);
                } else {
-                       strbuf_addf(&buf, "Merge %s '%.*s'",
+                       strbuf_addf(&ctx->message, "Merge %s '%.*s'",
                                    to_merge->next ? "branches" : "branch",
                                    merge_arg_len, arg);
-                       p = buf.buf;
-                       len = buf.len;
-               }
-
-               ret = write_message(p, len, git_path_merge_msg(r), 0);
-               strbuf_release(&buf);
-               if (ret) {
-                       error_errno(_("could not write '%s'"),
-                                   git_path_merge_msg(r));
-                       goto leave_merge;
                }
        }
+       ctx->have_message = 1;
+       if (write_message(ctx->message.buf, ctx->message.len,
+                         git_path_merge_msg(r), 0)) {
+                   ret = error_errno(_("could not write '%s'"),
+                                     git_path_merge_msg(r));
+                   goto leave_merge;
+       }
 
        if (strategy || to_merge->next) {
                /* Octopus merge */
@@ -4743,11 +4818,12 @@ static int pick_one_commit(struct repository *r,
                           struct replay_opts *opts,
                           int *check_todo, int* reschedule)
 {
+       struct replay_ctx *ctx = opts->ctx;
        int res;
        struct todo_item *item = todo_list->items + todo_list->current;
        const char *arg = todo_item_get_arg(todo_list, item);
        if (is_rebase_i(opts))
-               opts->reflog_message = reflog_message(
+               ctx->reflog_message = reflog_message(
                        opts, command_to_string(item->command), NULL);
 
        res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
@@ -4804,9 +4880,10 @@ static int pick_commits(struct repository *r,
                        struct todo_list *todo_list,
                        struct replay_opts *opts)
 {
+       struct replay_ctx *ctx = opts->ctx;
        int res = 0, reschedule = 0;
 
-       opts->reflog_message = sequencer_reflog_action(opts);
+       ctx->reflog_message = sequencer_reflog_action(opts);
        if (opts->allow_ff)
                assert(!(opts->signoff || opts->no_commit ||
                         opts->record_origin || should_edit(opts) ||
@@ -4856,6 +4933,8 @@ static int pick_commits(struct repository *r,
                                return stopped_at_head(r);
                        }
                }
+               strbuf_reset(&ctx->message);
+               ctx->have_message = 0;
                if (item->command <= TODO_SQUASH) {
                        res = pick_one_commit(r, todo_list, opts, &check_todo,
                                              &reschedule);
@@ -5061,6 +5140,7 @@ static int commit_staged_changes(struct repository *r,
                                 struct replay_opts *opts,
                                 struct todo_list *todo_list)
 {
+       struct replay_ctx *ctx = opts->ctx;
        unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
        unsigned int final_fixup = 0, is_clean;
 
@@ -5097,7 +5177,7 @@ static int commit_staged_changes(struct repository *r,
                 * the commit message and if there was a squash, let the user
                 * edit it.
                 */
-               if (!is_clean || !opts->current_fixup_count)
+               if (!is_clean || !ctx->current_fixup_count)
                        ; /* this is not the final fixup */
                else if (!oideq(&head, &to_amend) ||
                         !file_exists(rebase_path_stopped_sha())) {
@@ -5106,20 +5186,20 @@ static int commit_staged_changes(struct repository *r,
                                unlink(rebase_path_fixup_msg());
                                unlink(rebase_path_squash_msg());
                                unlink(rebase_path_current_fixups());
-                               strbuf_reset(&opts->current_fixups);
-                               opts->current_fixup_count = 0;
+                               strbuf_reset(&ctx->current_fixups);
+                               ctx->current_fixup_count = 0;
                        }
                } else {
                        /* we are in a fixup/squash chain */
-                       const char *p = opts->current_fixups.buf;
-                       int len = opts->current_fixups.len;
+                       const char *p = ctx->current_fixups.buf;
+                       int len = ctx->current_fixups.len;
 
-                       opts->current_fixup_count--;
+                       ctx->current_fixup_count--;
                        if (!len)
                                BUG("Incorrect current_fixups:\n%s", p);
                        while (len && p[len - 1] != '\n')
                                len--;
-                       strbuf_setlen(&opts->current_fixups, len);
+                       strbuf_setlen(&ctx->current_fixups, len);
                        if (write_message(p, len, rebase_path_current_fixups(),
                                          0) < 0)
                                return error(_("could not write file: '%s'"),
@@ -5136,7 +5216,7 @@ static int commit_staged_changes(struct repository *r,
                         * actually need to re-commit with a cleaned up commit
                         * message.
                         */
-                       if (opts->current_fixup_count > 0 &&
+                       if (ctx->current_fixup_count > 0 &&
                            !is_fixup(peek_command(todo_list, 0))) {
                                final_fixup = 1;
                                /*
@@ -5209,20 +5289,21 @@ static int commit_staged_changes(struct repository *r,
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
        }
-       if (opts->current_fixup_count > 0) {
+       if (ctx->current_fixup_count > 0) {
                /*
                 * Whether final fixup or not, we just cleaned up the commit
                 * message...
                 */
                unlink(rebase_path_current_fixups());
-               strbuf_reset(&opts->current_fixups);
-               opts->current_fixup_count = 0;
+               strbuf_reset(&ctx->current_fixups);
+               ctx->current_fixup_count = 0;
        }
        return 0;
 }
 
 int sequencer_continue(struct repository *r, struct replay_opts *opts)
 {
+       struct replay_ctx *ctx = opts->ctx;
        struct todo_list todo_list = TODO_LIST_INIT;
        int res;
 
@@ -5242,7 +5323,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
                        unlink(rebase_path_dropped());
                }
 
-               opts->reflog_message = reflog_message(opts, "continue", NULL);
+               ctx->reflog_message = reflog_message(opts, "continue", NULL);
                if (commit_staged_changes(r, opts, &todo_list)) {
                        res = -1;
                        goto release_todo_list;
@@ -5294,7 +5375,7 @@ static int single_pick(struct repository *r,
                        TODO_PICK : TODO_REVERT;
        item.commit = cmit;
 
-       opts->reflog_message = sequencer_reflog_action(opts);
+       opts->ctx->reflog_message = sequencer_reflog_action(opts);
        return do_pick_commit(r, &item, opts, 0, &check_todo);
 }
 
@@ -5679,8 +5760,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                                    oid_to_hex(&commit->object.oid),
                                    oneline.buf);
                        if (is_empty)
-                               strbuf_addf(&buf, " %c empty",
-                                           comment_line_char);
+                               strbuf_addf(&buf, " %s empty",
+                                           comment_line_str);
 
                        FLEX_ALLOC_STR(entry, string, buf.buf);
                        oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -5770,7 +5851,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                entry = oidmap_get(&state.commit2label, &commit->object.oid);
 
                if (entry)
-                       strbuf_addf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
+                       strbuf_addf(out, "\n%s Branch %s\n", comment_line_str, entry->string);
                else
                        strbuf_addch(out, '\n');
 
@@ -5907,7 +5988,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                            oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, out);
                if (is_empty)
-                       strbuf_addf(out, " %c empty", comment_line_char);
+                       strbuf_addf(out, " %s empty", comment_line_str);
                strbuf_addch(out, '\n');
        }
        if (skipped_commit)