Merge branch 'pw/rebase-abort-clean-rewritten' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 Jul 2019 19:38:20 +0000 (12:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Jul 2019 19:38:20 +0000 (12:38 -0700)
"git rebase --abort" used to leave refs/rewritten/ when concluding
"git rebase -r", which has been corrected.

* pw/rebase-abort-clean-rewritten:
rebase --abort/--quit: cleanup refs/rewritten
sequencer: return errors from sequencer_remove_state()
rebase: warn if state directory cannot be removed
rebase: fix a memory leak

1  2 
builtin/rebase.c
sequencer.c
t/t3430-rebase-merges.sh
diff --combined builtin/rebase.c
index b6892bc77120b44fc33f9899b928ca4fbf84abe7,bb347203ba755e6c9b0ba2910c2e92c52c4f19b8..3d6219d0223e78218f400bd1f0504e62c2058070
@@@ -87,7 -87,6 +87,7 @@@ struct rebase_options 
        char *strategy, *strategy_opts;
        struct strbuf git_format_patch_opt;
        int reschedule_failed_exec;
 +      int use_legacy_rebase;
  };
  
  #define REBASE_OPTIONS_INIT {                         \
@@@ -521,6 -520,29 +521,6 @@@ int cmd_rebase__interactive(int argc, c
        return !!run_rebase_interactive(&opts, command);
  }
  
 -static int use_builtin_rebase(void)
 -{
 -      struct child_process cp = CHILD_PROCESS_INIT;
 -      struct strbuf out = STRBUF_INIT;
 -      int ret, env = git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1);
 -
 -      if (env != -1)
 -              return env;
 -
 -      argv_array_pushl(&cp.args,
 -                       "config", "--bool", "rebase.usebuiltin", NULL);
 -      cp.git_cmd = 1;
 -      if (capture_command(&cp, &out, 6)) {
 -              strbuf_release(&out);
 -              return 1;
 -      }
 -
 -      strbuf_trim(&out);
 -      ret = !strcmp("true", out.buf);
 -      strbuf_release(&out);
 -      return ret;
 -}
 -
  static int is_interactive(struct rebase_options *opts)
  {
        return opts->type == REBASE_INTERACTIVE ||
@@@ -738,6 -760,7 +738,7 @@@ static int finish_rebase(struct rebase_
  {
        struct strbuf dir = STRBUF_INIT;
        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+       int ret = 0;
  
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
        apply_autostash(opts);
         * user should see them.
         */
        run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
-       strbuf_addstr(&dir, opts->state_dir);
-       remove_dir_recursively(&dir, 0);
-       strbuf_release(&dir);
+       if (opts->type == REBASE_INTERACTIVE) {
+               struct replay_opts replay = REPLAY_OPTS_INIT;
  
-       return 0;
+               replay.action = REPLAY_INTERACTIVE_REBASE;
+               ret = sequencer_remove_state(&replay);
+       } else {
+               strbuf_addstr(&dir, opts->state_dir);
+               if (remove_dir_recursively(&dir, 0))
+                       ret = error(_("could not remove '%s'"),
+                                   opts->state_dir);
+               strbuf_release(&dir);
+       }
+       return ret;
  }
  
  static struct commit *peel_committish(const char *name)
@@@ -782,7 -814,6 +792,7 @@@ static void add_var(struct strbuf *buf
  #define RESET_HEAD_HARD (1<<1)
  #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
  #define RESET_HEAD_REFS_ONLY (1<<3)
 +#define RESET_ORIG_HEAD (1<<4)
  
  static int reset_head(struct object_id *oid, const char *action,
                      const char *switch_to_branch, unsigned flags,
        unsigned reset_hard = flags & RESET_HEAD_HARD;
        unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
        unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
 +      unsigned update_orig_head = flags & RESET_ORIG_HEAD;
        struct object_id head_oid;
        struct tree_desc desc[2] = { { NULL }, { NULL } };
        struct lock_file lock = LOCK_INIT;
@@@ -869,21 -899,18 +879,21 @@@ reset_head_refs
        strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
        prefix_len = msg.len;
  
 -      if (!get_oid("ORIG_HEAD", &oid_old_orig))
 -              old_orig = &oid_old_orig;
 -      if (!get_oid("HEAD", &oid_orig)) {
 -              orig = &oid_orig;
 -              if (!reflog_orig_head) {
 -                      strbuf_addstr(&msg, "updating ORIG_HEAD");
 -                      reflog_orig_head = msg.buf;
 -              }
 -              update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0,
 -                         UPDATE_REFS_MSG_ON_ERR);
 -      } else if (old_orig)
 -              delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
 +      if (update_orig_head) {
 +              if (!get_oid("ORIG_HEAD", &oid_old_orig))
 +                      old_orig = &oid_old_orig;
 +              if (!get_oid("HEAD", &oid_orig)) {
 +                      orig = &oid_orig;
 +                      if (!reflog_orig_head) {
 +                              strbuf_addstr(&msg, "updating ORIG_HEAD");
 +                              reflog_orig_head = msg.buf;
 +                      }
 +                      update_ref(reflog_orig_head, "ORIG_HEAD", orig,
 +                                 old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
 +              } else if (old_orig)
 +                      delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
 +      }
 +
        if (!reflog_head) {
                strbuf_setlen(&msg, prefix_len);
                strbuf_addstr(&msg, "updating HEAD");
                                 detach_head ? REF_NO_DEREF : 0,
                                 UPDATE_REFS_MSG_ON_ERR);
        else {
 -              ret = update_ref(reflog_orig_head, switch_to_branch, oid,
 +              ret = update_ref(reflog_head, switch_to_branch, oid,
                                 NULL, 0, UPDATE_REFS_MSG_ON_ERR);
                if (!ret)
                        ret = create_symref("HEAD", switch_to_branch,
@@@ -1203,7 -1230,7 +1213,7 @@@ static int rebase_config(const char *va
                if (git_config_bool(var, value))
                        opts->flags |= REBASE_DIFFSTAT;
                else
 -                      opts->flags &= !REBASE_DIFFSTAT;
 +                      opts->flags &= ~REBASE_DIFFSTAT;
                return 0;
        }
  
                return 0;
        }
  
 +      if (!strcmp(var, "rebase.usebuiltin")) {
 +              opts->use_legacy_rebase = !git_config_bool(var, value);
 +              return 0;
 +      }
 +
        return git_default_config(var, value, data);
  }
  
@@@ -1384,7 -1406,6 +1394,7 @@@ int cmd_rebase(int argc, const char **a
        struct string_list strategy_options = STRING_LIST_INIT_NODUP;
        struct object_id squash_onto;
        char *squash_onto_name = NULL;
 +      int reschedule_failed_exec = -1;
        struct option builtin_rebase_options[] = {
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        parse_opt_interactive },
                OPT_SET_INT('p', "preserve-merges", &options.type,
 -                          N_("try to recreate merges instead of ignoring "
 -                             "them"), REBASE_PRESERVE_MERGES),
 +                          N_("(DEPRECATED) try to recreate merges instead of "
 +                             "ignoring them"), REBASE_PRESERVE_MERGES),
                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
                OPT_BOOL('k', "keep-empty", &options.keep_empty,
                         N_("preserve empty commits during rebase")),
                OPT_BOOL(0, "root", &options.root,
                         N_("rebase all reachable commits up to the root(s)")),
                OPT_BOOL(0, "reschedule-failed-exec",
 -                       &options.reschedule_failed_exec,
 +                       &reschedule_failed_exec,
                         N_("automatically re-schedule any `exec` that fails")),
                OPT_END(),
        };
        int i;
  
 -      /*
 -       * NEEDSWORK: Once the builtin rebase has been tested enough
 -       * and git-legacy-rebase.sh is retired to contrib/, this preamble
 -       * can be removed.
 -       */
 -
 -      if (!use_builtin_rebase()) {
 -              const char *path = mkpath("%s/git-legacy-rebase",
 -                                        git_exec_path());
 -
 -              if (sane_execvp(path, (char **)argv) < 0)
 -                      die_errno(_("could not exec %s"), path);
 -              else
 -                      BUG("sane_execvp() returned???");
 -      }
 -
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
        options.allow_empty_message = 1;
        git_config(rebase_config, &options);
  
 +      if (options.use_legacy_rebase ||
 +          !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
 +              warning(_("the rebase.useBuiltin support has been removed!\n"
 +                        "See its entry in 'git help config' for details."));
 +
        strbuf_reset(&buf);
        strbuf_addf(&buf, "%s/applying", apply_dir());
        if(file_exists(buf.buf))
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
  
 +      if (options.type == REBASE_PRESERVE_MERGES)
 +              warning(_("git rebase --preserve-merges is deprecated. "
 +                        "Use --rebase-merges instead."));
 +
        if (action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
                remove_branch_state(the_repository);
-               ret = finish_rebase(&options);
+               ret = !!finish_rebase(&options);
                goto cleanup;
        }
        case ACTION_QUIT: {
-               strbuf_reset(&buf);
-               strbuf_addstr(&buf, options.state_dir);
-               ret = !!remove_dir_recursively(&buf, 0);
-               if (ret)
-                       die(_("could not remove '%s'"), options.state_dir);
+               if (options.type == REBASE_INTERACTIVE) {
+                       struct replay_opts replay = REPLAY_OPTS_INIT;
+                       replay.action = REPLAY_INTERACTIVE_REBASE;
+                       ret = !!sequencer_remove_state(&replay);
+               } else {
+                       strbuf_reset(&buf);
+                       strbuf_addstr(&buf, options.state_dir);
+                       ret = !!remove_dir_recursively(&buf, 0);
+                       if (ret)
+                               error(_("could not remove '%s'"),
+                                      options.state_dir);
+               }
                goto cleanup;
        }
        case ACTION_EDIT_TODO:
                break;
        }
  
 -      if (options.reschedule_failed_exec && !is_interactive(&options))
 -              die(_("%s requires an interactive rebase"), "--reschedule-failed-exec");
 +      if (reschedule_failed_exec > 0 && !is_interactive(&options))
 +              die(_("--reschedule-failed-exec requires "
 +                    "--exec or --interactive"));
 +      if (reschedule_failed_exec >= 0)
 +              options.reschedule_failed_exec = reschedule_failed_exec;
  
        if (options.git_am_opts.argc) {
                /* all am options except -q are compatible only with --am */
                                branch_name = options.head_name;
  
                } else {
 -                      free(options.head_name);
 -                      options.head_name = NULL;
 +                      FREE_AND_NULL(options.head_name);
                        branch_name = "HEAD";
                }
                if (get_oid("HEAD", &options.orig_head))
        strbuf_addf(&msg, "%s: checkout %s",
                    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
        if (reset_head(&options.onto->object.oid, "checkout", NULL,
 -                     RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
 +                     RESET_HEAD_DETACH | RESET_ORIG_HEAD | 
 +                     RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                       NULL, msg.buf))
                die(_("Could not detach HEAD"));
        strbuf_release(&msg);
         * we just fast-forwarded.
         */
        strbuf_reset(&msg);
 -      if (!oidcmp(&merge_base, &options.orig_head)) {
 +      if (oideq(&merge_base, &options.orig_head)) {
                printf(_("Fast-forwarded %s to %s.\n"),
                        branch_name, options.onto_name);
                strbuf_addf(&msg, "rebase finished: %s onto %s",
                        options.head_name ? options.head_name : "detached HEAD",
                        oid_to_hex(&options.onto->object.oid));
 -              reset_head(NULL, "Fast-forwarded", options.head_name, 0,
 -                         "HEAD", msg.buf);
 +              reset_head(NULL, "Fast-forwarded", options.head_name,
 +                         RESET_HEAD_REFS_ONLY, "HEAD", msg.buf);
                strbuf_release(&msg);
                ret = !!finish_rebase(&options);
                goto cleanup;
@@@ -2150,6 -2183,7 +2168,7 @@@ run_rebase
        ret = !!run_specific_rebase(&options, action);
  
  cleanup:
+       strbuf_release(&buf);
        strbuf_release(&revisions);
        free(options.head_name);
        free(options.gpg_sign_opt);
diff --combined sequencer.c
index 63b09cfceb5d3d4c685bebad6dcc24fe1d72bc61,258e5831565cb0782344eeffd2aa71eb161317df..1d206fd22456cce12f7496321df8a47fcb94db9e
@@@ -171,22 -171,17 +171,22 @@@ static int git_sequencer_config(const c
                if (status)
                        return status;
  
 -              if (!strcmp(s, "verbatim"))
 +              if (!strcmp(s, "verbatim")) {
                        opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
 -              else if (!strcmp(s, "whitespace"))
 +                      opts->explicit_cleanup = 1;
 +              } else if (!strcmp(s, "whitespace")) {
                        opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
 -              else if (!strcmp(s, "strip"))
 +                      opts->explicit_cleanup = 1;
 +              } else if (!strcmp(s, "strip")) {
                        opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
 -              else if (!strcmp(s, "scissors"))
 -                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
 -              else
 +                      opts->explicit_cleanup = 1;
 +              } else if (!strcmp(s, "scissors")) {
 +                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SCISSORS;
 +                      opts->explicit_cleanup = 1;
 +              } else {
                        warning(_("invalid commit message cleanup mode '%s'"),
                                  s);
 +              }
  
                free((char *)s);
                return status;
@@@ -279,7 -274,7 +279,7 @@@ static const char *gpg_sign_opt_quoted(
  int sequencer_remove_state(struct replay_opts *opts)
  {
        struct strbuf buf = STRBUF_INIT;
-       int i;
+       int i, ret = 0;
  
        if (is_rebase_i(opts) &&
            strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
                        char *eol = strchr(p, '\n');
                        if (eol)
                                *eol = '\0';
-                       if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
+                       if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) {
                                warning(_("could not delete '%s'"), p);
+                               ret = -1;
+                       }
                        if (!eol)
                                break;
                        p = eol + 1;
  
        strbuf_reset(&buf);
        strbuf_addstr(&buf, get_dir(opts));
-       remove_dir_recursively(&buf, 0);
+       if (remove_dir_recursively(&buf, 0))
+               ret = error(_("could not remove '%s'"), buf.buf);
        strbuf_release(&buf);
  
-       return 0;
+       return ret;
  }
  
  static const char *action_name(const struct replay_opts *opts)
@@@ -515,54 -513,11 +518,54 @@@ static int fast_forward_to(struct repos
        return 0;
  }
  
 +enum commit_msg_cleanup_mode get_cleanup_mode(const char *cleanup_arg,
 +      int use_editor)
 +{
 +      if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
 +              return use_editor ? COMMIT_MSG_CLEANUP_ALL :
 +                                  COMMIT_MSG_CLEANUP_SPACE;
 +      else if (!strcmp(cleanup_arg, "verbatim"))
 +              return COMMIT_MSG_CLEANUP_NONE;
 +      else if (!strcmp(cleanup_arg, "whitespace"))
 +              return COMMIT_MSG_CLEANUP_SPACE;
 +      else if (!strcmp(cleanup_arg, "strip"))
 +              return COMMIT_MSG_CLEANUP_ALL;
 +      else if (!strcmp(cleanup_arg, "scissors"))
 +              return use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
 +                                  COMMIT_MSG_CLEANUP_SPACE;
 +      else
 +              die(_("Invalid cleanup mode %s"), cleanup_arg);
 +}
 +
 +/*
 + * NB using int rather than enum cleanup_mode to stop clang's
 + * -Wtautological-constant-out-of-range-compare complaining that the comparison
 + * is always true.
 + */
 +static const char *describe_cleanup_mode(int cleanup_mode)
 +{
 +      static const char *modes[] = { "whitespace",
 +                                     "verbatim",
 +                                     "scissors",
 +                                     "strip" };
 +
 +      if (cleanup_mode < ARRAY_SIZE(modes))
 +              return modes[cleanup_mode];
 +
 +      BUG("invalid cleanup_mode provided (%d)", cleanup_mode);
 +}
 +
  void append_conflicts_hint(struct index_state *istate,
 -                         struct strbuf *msgbuf)
 +      struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode)
  {
        int i;
  
 +      if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
 +              strbuf_addch(msgbuf, '\n');
 +              wt_status_append_cut_line(msgbuf);
 +              strbuf_addch(msgbuf, comment_line_char);
 +      }
 +
        strbuf_addch(msgbuf, '\n');
        strbuf_commented_addf(msgbuf, "Conflicts:\n");
        for (i = 0; i < istate->cache_nr;) {
@@@ -630,8 -585,7 +633,8 @@@ static int do_recursive_merge(struct re
                        _(action_name(opts)));
  
        if (!clean)
 -              append_conflicts_hint(r->index, msgbuf);
 +              append_conflicts_hint(r->index, msgbuf,
 +                                    opts->default_msg_cleanup);
  
        return !clean;
  }
@@@ -950,6 -904,7 +953,6 @@@ static int run_git_commit(struct reposi
                          unsigned int flags)
  {
        struct child_process cmd = CHILD_PROCESS_INIT;
 -      const char *value;
  
        if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
                struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
                argv_array_push(&cmd.args, "-e");
        else if (!(flags & CLEANUP_MSG) &&
                 !opts->signoff && !opts->record_origin &&
 -               git_config_get_value("commit.cleanup", &value))
 +               !opts->explicit_cleanup)
                argv_array_push(&cmd.args, "--cleanup=verbatim");
  
        if ((flags & ALLOW_EMPTY))
@@@ -1060,16 -1015,6 +1063,16 @@@ static int rest_is_empty(const struct s
        return 1;
  }
  
 +void cleanup_message(struct strbuf *msgbuf,
 +      enum commit_msg_cleanup_mode cleanup_mode, int verbose)
 +{
 +      if (verbose || /* Truncate the message just before the diff, if any. */
 +          cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
 +              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);
 +}
 +
  /*
   * Find out if the message in the strbuf contains only whitespace and
   * Signed-off-by lines.
@@@ -1440,13 -1385,8 +1443,13 @@@ static int try_to_commit(struct reposit
                msg = &commit_msg;
        }
  
 -      cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
 -                                        opts->default_msg_cleanup;
 +      if (flags & CLEANUP_MSG)
 +              cleanup = COMMIT_MSG_CLEANUP_ALL;
 +      else if ((opts->signoff || opts->record_origin) &&
 +               !opts->explicit_cleanup)
 +              cleanup = COMMIT_MSG_CLEANUP_SPACE;
 +      else
 +              cleanup = opts->default_msg_cleanup;
  
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
                strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
@@@ -2161,48 -2101,12 +2164,48 @@@ static int parse_insn_line(struct repos
        item->arg_len = (int)(eol - bol);
  
        if (status < 0)
 -              return -1;
 +              return error(_("could not parse '%.*s'"),
 +                           (int)(end_of_object_name - bol), bol);
  
        item->commit = lookup_commit_reference(r, &commit_oid);
        return !item->commit;
  }
  
 +int sequencer_get_last_command(struct repository *r, enum replay_action *action)
 +{
 +      struct todo_item item;
 +      char *eol;
 +      const char *todo_file;
 +      struct strbuf buf = STRBUF_INIT;
 +      int ret = -1;
 +
 +      todo_file = git_path_todo_file();
 +      if (strbuf_read_file(&buf, todo_file, 0) < 0) {
 +              if (errno == ENOENT)
 +                      return -1;
 +              else
 +                      return error_errno("unable to open '%s'", todo_file);
 +      }
 +      eol = strchrnul(buf.buf, '\n');
 +      if (buf.buf != eol && eol[-1] == '\r')
 +              eol--; /* strip Carriage Return */
 +      if (parse_insn_line(r, &item, buf.buf, buf.buf, eol))
 +              goto fail;
 +      if (item.command == TODO_PICK)
 +              *action = REPLAY_PICK;
 +      else if (item.command == TODO_REVERT)
 +              *action = REPLAY_REVERT;
 +      else
 +              goto fail;
 +
 +      ret = 0;
 +
 + fail:
 +      strbuf_release(&buf);
 +
 +      return ret;
 +}
 +
  int todo_list_parse_insn_buffer(struct repository *r, char *buf,
                                struct todo_list *todo_list)
  {
@@@ -2286,57 -2190,6 +2289,57 @@@ static ssize_t strbuf_read_file_or_whin
        return len;
  }
  
 +static int have_finished_the_last_pick(void)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *eol;
 +      const char *todo_path = git_path_todo_file();
 +      int ret = 0;
 +
 +      if (strbuf_read_file(&buf, todo_path, 0) < 0) {
 +              if (errno == ENOENT) {
 +                      return 0;
 +              } else {
 +                      error_errno("unable to open '%s'", todo_path);
 +                      return 0;
 +              }
 +      }
 +      /* If there is only one line then we are done */
 +      eol = strchr(buf.buf, '\n');
 +      if (!eol || !eol[1])
 +              ret = 1;
 +
 +      strbuf_release(&buf);
 +
 +      return ret;
 +}
 +
 +void sequencer_post_commit_cleanup(struct repository *r)
 +{
 +      struct replay_opts opts = REPLAY_OPTS_INIT;
 +      int need_cleanup = 0;
 +
 +      if (file_exists(git_path_cherry_pick_head(r))) {
 +              unlink(git_path_cherry_pick_head(r));
 +              opts.action = REPLAY_PICK;
 +              need_cleanup = 1;
 +      }
 +
 +      if (file_exists(git_path_revert_head(r))) {
 +              unlink(git_path_revert_head(r));
 +              opts.action = REPLAY_REVERT;
 +              need_cleanup = 1;
 +      }
 +
 +      if (!need_cleanup)
 +              return;
 +
 +      if (!have_finished_the_last_pick())
 +              return;
 +
 +      sequencer_remove_state(&opts);
 +}
 +
  static int read_populate_todo(struct repository *r,
                              struct todo_list *todo_list,
                              struct replay_opts *opts)
@@@ -2424,15 -2277,6 +2427,15 @@@ static int populate_opts_cb(const char 
                opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
        else if (!strcmp(key, "options.edit"))
                opts->edit = git_config_bool_or_int(key, value, &error_flag);
 +      else if (!strcmp(key, "options.allow-empty"))
 +              opts->allow_empty =
 +                      git_config_bool_or_int(key, value, &error_flag);
 +      else if (!strcmp(key, "options.allow-empty-message"))
 +              opts->allow_empty_message =
 +                      git_config_bool_or_int(key, value, &error_flag);
 +      else if (!strcmp(key, "options.keep-redundant-commits"))
 +              opts->keep_redundant_commits =
 +                      git_config_bool_or_int(key, value, &error_flag);
        else if (!strcmp(key, "options.signoff"))
                opts->signoff = git_config_bool_or_int(key, value, &error_flag);
        else if (!strcmp(key, "options.record-origin"))
                opts->allow_rerere_auto =
                        git_config_bool_or_int(key, value, &error_flag) ?
                                RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
 -      else
 +      else if (!strcmp(key, "options.default-msg-cleanup")) {
 +              opts->explicit_cleanup = 1;
 +              opts->default_msg_cleanup = get_cleanup_mode(value, 1);
 +      } else
                return error(_("invalid key: %s"), key);
  
        if (!error_flag)
@@@ -2830,59 -2671,36 +2833,59 @@@ static int save_opts(struct replay_opt
        int res = 0;
  
        if (opts->no_commit)
 -              res |= git_config_set_in_file_gently(opts_file, "options.no-commit", "true");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.no-commit", "true");
        if (opts->edit)
 -              res |= git_config_set_in_file_gently(opts_file, "options.edit", "true");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.edit", "true");
 +      if (opts->allow_empty)
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.allow-empty", "true");
 +      if (opts->allow_empty_message)
 +              res |= git_config_set_in_file_gently(opts_file,
 +                              "options.allow-empty-message", "true");
 +      if (opts->keep_redundant_commits)
 +              res |= git_config_set_in_file_gently(opts_file,
 +                              "options.keep-redundant-commits", "true");
        if (opts->signoff)
 -              res |= git_config_set_in_file_gently(opts_file, "options.signoff", "true");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.signoff", "true");
        if (opts->record_origin)
 -              res |= git_config_set_in_file_gently(opts_file, "options.record-origin", "true");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.record-origin", "true");
        if (opts->allow_ff)
 -              res |= git_config_set_in_file_gently(opts_file, "options.allow-ff", "true");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.allow-ff", "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);
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.mainline", buf.buf);
                strbuf_release(&buf);
        }
        if (opts->strategy)
 -              res |= git_config_set_in_file_gently(opts_file, "options.strategy", opts->strategy);
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.strategy", opts->strategy);
        if (opts->gpg_sign)
 -              res |= git_config_set_in_file_gently(opts_file, "options.gpg-sign", opts->gpg_sign);
 +              res |= git_config_set_in_file_gently(opts_file,
 +                                      "options.gpg-sign", opts->gpg_sign);
        if (opts->xopts) {
                int i;
                for (i = 0; i < opts->xopts_nr; i++)
                        res |= git_config_set_multivar_in_file_gently(opts_file,
 -                                                      "options.strategy-option",
 -                                                      opts->xopts[i], "^$", 0);
 +                                      "options.strategy-option",
 +                                      opts->xopts[i], "^$", 0);
        }
        if (opts->allow_rerere_auto)
 -              res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto",
 -                                                   opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
 -                                                   "true" : "false");
 +              res |= git_config_set_in_file_gently(opts_file,
 +                              "options.allow-rerere-auto",
 +                              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",
 +                              describe_cleanup_mode(opts->default_msg_cleanup));
        return res;
  }
  
@@@ -3731,11 -3549,8 +3734,11 @@@ static int pick_commits(struct reposito
                        unlink(git_path_merge_head(the_repository));
                        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
  
 -                      if (item->command == TODO_BREAK)
 +                      if (item->command == TODO_BREAK) {
 +                              if (!opts->verbose)
 +                                      term_clear_line();
                                return stopped_at_head(r);
 +                      }
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
                        }
                        if (item->command == TODO_EDIT) {
                                struct commit *commit = item->commit;
 -                              if (!res)
 +                              if (!res) {
 +                                      if (!opts->verbose)
 +                                              term_clear_line();
                                        fprintf(stderr,
                                                _("Stopped at %s...  %.*s\n"),
                                                short_commit_name(commit),
                                                item->arg_len, arg);
 +                              }
                                return error_with_patch(r, commit,
                                        arg, item->arg_len, opts, res, !res);
                        }
                        int saved = *end_of_arg;
                        struct stat st;
  
 +                      if (!opts->verbose)
 +                              term_clear_line();
                        *end_of_arg = '\0';
                        res = do_exec(r, arg);
                        *end_of_arg = saved;
  
 -                      /* Reread the todo file if it has changed. */
                        if (res) {
                                if (opts->reschedule_failed_exec)
                                        reschedule = 1;
                                res = error_errno(_("could not stat '%s'"),
                                                  get_todo_path(opts));
                        else if (match_stat_data(&todo_list->stat, &st)) {
 +                              /* Reread the todo file if it has changed. */
                                todo_list_release(todo_list);
                                if (read_populate_todo(r, todo_list, opts))
                                        res = -1; /* message was printed */
@@@ -3962,13 -3772,10 +3965,13 @@@ cleanup_head_ref
                }
                apply_autostash(opts);
  
 -              if (!opts->quiet)
 +              if (!opts->quiet) {
 +                      if (!opts->verbose)
 +                              term_clear_line();
                        fprintf(stderr,
                                "Successfully rebased and updated %s.\n",
                                head_ref.buf);
 +              }
  
                strbuf_release(&buf);
                strbuf_release(&head_ref);
diff --combined t/t3430-rebase-merges.sh
index 42ba5b9f0981b2b7ce98e1c5c55012ef61757bab,e63576d334292185b97768a5e2b06e70fa43aba4..f0814d5280c6755ad1f9f8378a6a32d32ffaee1d
@@@ -224,8 -224,24 +224,24 @@@ test_expect_success 'refs/rewritten/* i
        test_cmp_rev HEAD "$(cat wt/b)"
  '
  
+ test_expect_success '--abort cleans up refs/rewritten' '
+       git checkout -b abort-cleans-refs-rewritten H &&
+       GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
+       git rev-parse --verify refs/rewritten/onto &&
+       git rebase --abort &&
+       test_must_fail git rev-parse --verify refs/rewritten/onto
+ '
+ test_expect_success '--quit cleans up refs/rewritten' '
+       git checkout -b quit-cleans-refs-rewritten H &&
+       GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
+       git rev-parse --verify refs/rewritten/onto &&
+       git rebase --quit &&
+       test_must_fail git rev-parse --verify refs/rewritten/onto
+ '
  test_expect_success 'post-rewrite hook and fixups work for merges' '
-       git checkout -b post-rewrite &&
+       git checkout -b post-rewrite &&
        test_commit same1 &&
        git reset --hard HEAD^ &&
        test_commit same2 &&
@@@ -271,7 -287,7 +287,7 @@@ test_expect_success 'root commits' 
        EOF
        test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
        test_tick &&
 -      git rebase -i --force --root -r &&
 +      git rebase -i --force-rebase --root -r &&
        test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
        test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
        test $(git rev-parse second-root:second-root.t) = \
@@@ -364,7 -380,7 +380,7 @@@ test_expect_success 'octopus merges' 
        test_cmp_rev HEAD $before &&
  
        test_tick &&
 -      git rebase -i --force -r HEAD^^ &&
 +      git rebase -i --force-rebase -r HEAD^^ &&
        test "Hank" = "$(git show -s --format=%an HEAD)" &&
        test "$before" != $(git rev-parse HEAD) &&
        test_cmp_graph HEAD^^.. <<-\EOF