Merge branch 'js/rebase-r-strategy'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Sep 2019 18:50:07 +0000 (11:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Sep 2019 18:50:07 +0000 (11:50 -0700)
"git rebase --rebase-merges" learned to drive different merge
strategies and pass strategy specific options to them.

* js/rebase-r-strategy:
t3427: accelerate this test by using fast-export and fast-import
rebase -r: do not (re-)generate root commits with `--root` *and* `--onto`
t3418: test `rebase -r` with merge strategies
t/lib-rebase: prepare for testing `git rebase --rebase-merges`
rebase -r: support merge strategies other than `recursive`
t3427: fix another incorrect assumption
t3427: accommodate for the `rebase --merge` backend having been replaced
t3427: fix erroneous assumption
t3427: condense the unnecessarily repetitive test cases into three
t3427: move the `filter-branch` invocation into the `setup` case
t3427: simplify the `setup` test case significantly
t3427: add a clarifying comment
rebase: fold git-rebase--common into the -p backend
sequencer: the `am` and `rebase--interactive` scripts are gone
.gitignore: there is no longer a built-in `git-rebase--interactive`
t3400: stop referring to the scripted rebase
Drop unused git-rebase--am.sh

1  2 
Documentation/git-rebase.txt
builtin/rebase.c
sequencer.c
sequencer.h
t/t3418-rebase-continue.sh
t/t3430-rebase-merges.sh
index 6156609cf7149ccf5c1f79df2d9807cdbcc609ba,bc620c44e93916cc48d9154ab344a0050e62e125..3136c19fb4ce578ee37c4e3b9b86ab2e47294dc8
@@@ -12,12 -12,12 +12,12 @@@ SYNOPSI
        [<upstream> [<branch>]]
  'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
 -'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch
 +'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
  
  DESCRIPTION
  -----------
  If <branch> is specified, 'git rebase' will perform an automatic
 -`git checkout <branch>` before doing anything else.  Otherwise
 +`git switch <branch>` before doing anything else.  Otherwise
  it remains on the current branch.
  
  If <upstream> is not specified, the upstream configured in
@@@ -543,8 -543,6 +543,6 @@@ In addition, the following pairs of opt
   * --preserve-merges and --interactive
   * --preserve-merges and --signoff
   * --preserve-merges and --rebase-merges
-  * --rebase-merges and --strategy
-  * --rebase-merges and --strategy-option
  
  BEHAVIORAL DIFFERENCES
  -----------------------
diff --combined builtin/rebase.c
index 670096c065f5f5fa8cb57c642cd759111b010988,ee2bc8b032a2fad1bf016314d31a655948683cd4..e8319d594639dcd85bb47b5f1ece1a4fdd5c1a4c
@@@ -62,7 -62,7 +62,7 @@@ struct rebase_options 
        const char *onto_name;
        const char *revisions;
        const char *switch_to;
-       int root;
+       int root, root_with_onto;
        struct object_id *squash_onto;
        struct commit *restrict_revision;
        int dont_finish_rebase;
@@@ -374,6 -374,7 +374,7 @@@ static int run_rebase_interactive(struc
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
        flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
+       flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
        flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
  
        switch (command) {
@@@ -508,7 -509,7 +509,7 @@@ int cmd_rebase__interactive(int argc, c
        if (argc == 1)
                usage_with_options(builtin_rebase_interactive_usage, options);
  
 -      argc = parse_options(argc, argv, NULL, options,
 +      argc = parse_options(argc, argv, prefix, options,
                        builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
  
        if (!is_null_oid(&squash_onto))
@@@ -738,30 -739,20 +739,30 @@@ 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);
 -      close_all_packs(the_repository->objects);
 +      close_object_store(the_repository->objects);
        /*
         * We ignore errors in 'gc --auto', since the
         * 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)
@@@ -850,13 -841,13 +851,13 @@@ static int reset_head(struct object_id 
                goto leave_reset_head;
        }
  
 -      if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) {
 +      if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) {
                ret = error(_("failed to find tree of %s"),
                            oid_to_hex(&head_oid));
                goto leave_reset_head;
        }
  
 -      if (!fill_tree_descriptor(&desc[nr++], oid)) {
 +      if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) {
                ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
                goto leave_reset_head;
        }
@@@ -1389,7 -1380,6 +1390,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"),
                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(),
        };
                if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
                               NULL, NULL) < 0)
                        die(_("could not discard worktree changes"));
 -              remove_branch_state(the_repository);
 +              remove_branch_state(the_repository, 0);
                if (read_basic_state(&options))
                        exit(1);
                goto run_rebase;
                               NULL, NULL) < 0)
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
 -              remove_branch_state(the_repository);
 -              ret = finish_rebase(&options);
 +              remove_branch_state(the_repository, 0);
 +              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 */
                              "'--reschedule-failed-exec'"));
        }
  
-       if (options.rebase_merges) {
-               if (strategy_options.nr)
-                       die(_("cannot combine '--rebase-merges' with "
-                             "'--strategy-option'"));
-               if (options.strategy)
-                       die(_("cannot combine '--rebase-merges' with "
-                             "'--strategy'"));
-       }
        if (!options.root) {
                if (argc < 1) {
                        struct branch *branch;
                        options.squash_onto = &squash_onto;
                        options.onto_name = squash_onto_name =
                                xstrdup(oid_to_hex(&squash_onto));
-               }
+               } else
+                       options.root_with_onto = 1;
                options.upstream_name = NULL;
                options.upstream = NULL;
                if (argc > 1)
@@@ -2159,7 -2131,6 +2153,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 34ebf8ed94ad7d8df6773337d31ff7d9c2c84c4a,ca119c84e577c958a34ea69128dc9a22cc15fb56..d648aaf416510e656a372abe54d1a28d995de889
@@@ -279,7 -279,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)
@@@ -2079,18 -2076,6 +2079,18 @@@ const char *todo_item_get_arg(struct to
        return todo_list->buf.buf + item->arg_offset;
  }
  
 +static int is_command(enum todo_command command, const char **bol)
 +{
 +      const char *str = todo_command_info[command].str;
 +      const char nick = todo_command_info[command].c;
 +      const char *p = *bol + 1;
 +
 +      return skip_prefix(*bol, str, bol) ||
 +              ((nick && **bol == nick) &&
 +               (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
 +               (*bol = p));
 +}
 +
  static int parse_insn_line(struct repository *r, struct todo_item *item,
                           const char *buf, const char *bol, char *eol)
  {
        }
  
        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 == eol || bol[1] == ' ') &&
 -                         *bol == todo_command_info[i].c) {
 -                      bol++;
 +              if (is_command(i, &bol)) {
                        item->command = i;
                        break;
                }
  
  int sequencer_get_last_command(struct repository *r, enum replay_action *action)
  {
 -      struct todo_item item;
 -      char *eol;
 -      const char *todo_file;
 +      const char *todo_file, *bol;
        struct strbuf buf = STRBUF_INIT;
 -      int ret = -1;
 +      int ret = 0;
  
        todo_file = git_path_todo_file();
        if (strbuf_read_file(&buf, todo_file, 0) < 0) {
 -              if (errno == ENOENT)
 +              if (errno == ENOENT || errno == ENOTDIR)
                        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)
 +      bol = buf.buf + strspn(buf.buf, " \t\r\n");
 +      if (is_command(TODO_PICK, &bol) && (*bol == ' ' || *bol == '\t'))
                *action = REPLAY_PICK;
 -      else if (item.command == TODO_REVERT)
 +      else if (is_command(TODO_REVERT, &bol) &&
 +               (*bol == ' ' || *bol == '\t'))
                *action = REPLAY_REVERT;
        else
 -              goto fail;
 -
 -      ret = 0;
 +              ret = -1;
  
 - fail:
        strbuf_release(&buf);
  
        return ret;
@@@ -2313,21 -2311,19 +2313,21 @@@ static int have_finished_the_last_pick(
        return ret;
  }
  
 -void sequencer_post_commit_cleanup(struct repository *r)
 +void sequencer_post_commit_cleanup(struct repository *r, int verbose)
  {
        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));
 +              if (!unlink(git_path_cherry_pick_head(r)) && verbose)
 +                      warning(_("cancelling a cherry picking in progress"));
                opts.action = REPLAY_PICK;
                need_cleanup = 1;
        }
  
        if (file_exists(git_path_revert_head(r))) {
 -              unlink(git_path_revert_head(r));
 +              if (!unlink(git_path_revert_head(r)) && verbose)
 +                      warning(_("cancelling a revert in progress"));
                opts.action = REPLAY_REVERT;
                need_cleanup = 1;
        }
@@@ -2654,41 -2650,15 +2654,41 @@@ static int walk_revs_populate_todo(stru
        return 0;
  }
  
 -static int create_seq_dir(void)
 +static int create_seq_dir(struct repository *r)
  {
 -      if (file_exists(git_path_seq_dir())) {
 -              error(_("a cherry-pick or revert is already in progress"));
 -              advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
 +      enum replay_action action;
 +      const char *in_progress_error = NULL;
 +      const char *in_progress_advice = NULL;
 +      unsigned int advise_skip = file_exists(git_path_revert_head(r)) ||
 +                              file_exists(git_path_cherry_pick_head(r));
 +
 +      if (!sequencer_get_last_command(r, &action)) {
 +              switch (action) {
 +              case REPLAY_REVERT:
 +                      in_progress_error = _("revert is already in progress");
 +                      in_progress_advice =
 +                      _("try \"git revert (--continue | %s--abort | --quit)\"");
 +                      break;
 +              case REPLAY_PICK:
 +                      in_progress_error = _("cherry-pick is already in progress");
 +                      in_progress_advice =
 +                      _("try \"git cherry-pick (--continue | %s--abort | --quit)\"");
 +                      break;
 +              default:
 +                      BUG("unexpected action in create_seq_dir");
 +              }
 +      }
 +      if (in_progress_error) {
 +              error("%s", in_progress_error);
 +              if (advice_sequencer_in_use)
 +                      advise(in_progress_advice,
 +                              advise_skip ? "--skip | " : "");
                return -1;
 -      } else if (mkdir(git_path_seq_dir(), 0777) < 0)
 +      }
 +      if (mkdir(git_path_seq_dir(), 0777) < 0)
                return error_errno(_("could not create sequencer directory '%s'"),
                                   git_path_seq_dir());
 +
        return 0;
  }
  
@@@ -2739,20 -2709,15 +2739,20 @@@ static int rollback_is_safe(void
        return oideq(&actual_head, &expected_head);
  }
  
 -static int reset_for_rollback(const struct object_id *oid)
 +static int reset_merge(const struct object_id *oid)
  {
 -      const char *argv[4];    /* reset --merge <arg> + NULL */
 +      int ret;
 +      struct argv_array argv = ARGV_ARRAY_INIT;
  
 -      argv[0] = "reset";
 -      argv[1] = "--merge";
 -      argv[2] = oid_to_hex(oid);
 -      argv[3] = NULL;
 -      return run_command_v_opt(argv, RUN_GIT_CMD);
 +      argv_array_pushl(&argv, "reset", "--merge", NULL);
 +
 +      if (!is_null_oid(oid))
 +              argv_array_push(&argv, oid_to_hex(oid));
 +
 +      ret = run_command_v_opt(argv.argv, RUN_GIT_CMD);
 +      argv_array_clear(&argv);
 +
 +      return ret;
  }
  
  static int rollback_single_pick(struct repository *r)
                return error(_("cannot resolve HEAD"));
        if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
 -      return reset_for_rollback(&head_oid);
 +      return reset_merge(&head_oid);
 +}
 +
 +static int skip_single_pick(void)
 +{
 +      struct object_id head;
 +
 +      if (read_ref_full("HEAD", 0, &head, NULL))
 +              return error(_("cannot resolve HEAD"));
 +      return reset_merge(&head);
  }
  
  int sequencer_rollback(struct repository *r, struct replay_opts *opts)
                warning(_("You seem to have moved HEAD. "
                          "Not rewinding, check your HEAD!"));
        } else
 -      if (reset_for_rollback(&oid))
 +      if (reset_merge(&oid))
                goto fail;
        strbuf_release(&buf);
        return sequencer_remove_state(opts);
@@@ -2827,70 -2783,6 +2827,70 @@@ fail
        return -1;
  }
  
 +int sequencer_skip(struct repository *r, struct replay_opts *opts)
 +{
 +      enum replay_action action = -1;
 +      sequencer_get_last_command(r, &action);
 +
 +      /*
 +       * Check whether the subcommand requested to skip the commit is actually
 +       * in progress and that it's safe to skip the commit.
 +       *
 +       * opts->action tells us which subcommand requested to skip the commit.
 +       * If the corresponding .git/<ACTION>_HEAD exists, we know that the
 +       * action is in progress and we can skip the commit.
 +       *
 +       * Otherwise we check that the last instruction was related to the
 +       * particular subcommand we're trying to execute and barf if that's not
 +       * the case.
 +       *
 +       * Finally we check that the rollback is "safe", i.e., has the HEAD
 +       * moved? In this case, it doesn't make sense to "reset the merge" and
 +       * "skip the commit" as the user already handled this by committing. But
 +       * we'd not want to barf here, instead give advice on how to proceed. We
 +       * only need to check that when .git/<ACTION>_HEAD doesn't exist because
 +       * it gets removed when the user commits, so if it still exists we're
 +       * sure the user can't have committed before.
 +       */
 +      switch (opts->action) {
 +      case REPLAY_REVERT:
 +              if (!file_exists(git_path_revert_head(r))) {
 +                      if (action != REPLAY_REVERT)
 +                              return error(_("no revert in progress"));
 +                      if (!rollback_is_safe())
 +                              goto give_advice;
 +              }
 +              break;
 +      case REPLAY_PICK:
 +              if (!file_exists(git_path_cherry_pick_head(r))) {
 +                      if (action != REPLAY_PICK)
 +                              return error(_("no cherry-pick in progress"));
 +                      if (!rollback_is_safe())
 +                              goto give_advice;
 +              }
 +              break;
 +      default:
 +              BUG("unexpected action in sequencer_skip");
 +      }
 +
 +      if (skip_single_pick())
 +              return error(_("failed to skip the commit"));
 +      if (!is_directory(git_path_seq_dir()))
 +              return 0;
 +
 +      return sequencer_continue(r, opts);
 +
 +give_advice:
 +      error(_("there is nothing to skip"));
 +
 +      if (advice_resolve_conflict) {
 +              advise(_("have you committed already?\n"
 +                       "try \"git %s --continue\""),
 +                       action == REPLAY_REVERT ? "revert" : "cherry-pick");
 +      }
 +      return -1;
 +}
 +
  static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
  {
        struct lock_file todo_lock = LOCK_INIT;
@@@ -3302,7 -3194,7 +3302,7 @@@ static int do_reset(struct repository *
                return error_resolve_conflict(_(action_name(opts)));
        }
  
 -      if (!fill_tree_descriptor(&desc, &oid)) {
 +      if (!fill_tree_descriptor(r, &desc, &oid)) {
                error(_("failed to find tree of %s"), oid_to_hex(&oid));
                rollback_lock_file(&lock);
                free((void *)desc.buffer);
@@@ -3364,6 -3256,9 +3364,9 @@@ static int do_merge(struct repository *
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
        struct commit_list *to_merge = NULL, **tail = &to_merge;
+       const char *strategy = !opts->xopts_nr &&
+               (!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
+               NULL : opts->strategy;
        struct merge_options o;
        int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
                rollback_lock_file(&lock);
                ret = fast_forward_to(r, &commit->object.oid,
                                      &head_commit->object.oid, 0, opts);
 +              if (flags & TODO_EDIT_MERGE_MSG) {
 +                      run_commit_flags |= AMEND_MSG;
 +                      goto fast_forward_edit;
 +              }
                goto leave_merge;
        }
  
-       if (to_merge->next) {
+       if (strategy || to_merge->next) {
                /* Octopus merge */
                struct child_process cmd = CHILD_PROCESS_INIT;
  
                cmd.git_cmd = 1;
                argv_array_push(&cmd.args, "merge");
                argv_array_push(&cmd.args, "-s");
-               argv_array_push(&cmd.args, "octopus");
+               if (!strategy)
+                       argv_array_push(&cmd.args, "octopus");
+               else {
+                       argv_array_push(&cmd.args, strategy);
+                       for (k = 0; k < opts->xopts_nr; k++)
+                               argv_array_pushf(&cmd.args,
+                                                "-X%s", opts->xopts[k]);
+               }
                argv_array_push(&cmd.args, "--no-edit");
                argv_array_push(&cmd.args, "--no-ff");
                argv_array_push(&cmd.args, "--no-log");
                 * value (a negative one would indicate that the `merge`
                 * command needs to be rescheduled).
                 */
 +      fast_forward_edit:
                ret = !!run_git_commit(r, git_path_merge_msg(r), opts,
                                       run_commit_flags);
  
@@@ -3841,14 -3738,11 +3851,14 @@@ static int pick_commits(struct reposito
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
 -                      unlink(git_path_merge_head(the_repository));
 +                      unlink(git_path_merge_head(r));
                        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;
@@@ -4075,13 -3964,10 +4085,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);
@@@ -4226,7 -4112,7 +4236,7 @@@ static int commit_staged_changes(struc
                           opts, flags))
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
 -      unlink(git_path_merge_head(the_repository));
 +      unlink(git_path_merge_head(r));
        if (final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
@@@ -4361,7 -4247,7 +4371,7 @@@ int sequencer_pick_revisions(struct rep
         */
  
        if (walk_revs_populate_todo(&todo_list, opts) ||
 -                      create_seq_dir() < 0)
 +                      create_seq_dir(r) < 0)
                return -1;
        if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
                return error(_("can't revert as initial commit"));
@@@ -4554,6 -4440,7 +4564,7 @@@ static int make_script_with_merges(stru
  {
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+       int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
        struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
        struct strbuf label = STRBUF_INIT;
        struct commit_list *commits = NULL, **tail = &commits, *iter;
  
                if (!commit)
                        strbuf_addf(out, "%s %s\n", cmd_reset,
-                                   rebase_cousins ? "onto" : "[new root]");
+                                   rebase_cousins || root_with_onto ?
+                                   "onto" : "[new root]");
                else {
                        const char *to = NULL;
  
diff --combined sequencer.h
index 6704acbb9c93a55cb7ec69d2e045d67850bc4049,d506081d3c8656d0b290f0b39492271dbb0917f0..574260f6215f60e8c1aedb227c80a26c34da6c94
@@@ -129,7 -129,6 +129,7 @@@ int sequencer_pick_revisions(struct rep
                             struct replay_opts *opts);
  int sequencer_continue(struct repository *repo, struct replay_opts *opts);
  int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 +int sequencer_skip(struct repository *repo, struct replay_opts *opts);
  int sequencer_remove_state(struct replay_opts *opts);
  
  #define TODO_LIST_KEEP_EMPTY (1U << 0)
   */
  #define TODO_LIST_REBASE_COUSINS (1U << 4)
  #define TODO_LIST_APPEND_TODO_HELP (1U << 5)
+ /*
+  * When generating a script that rebases merges with `--root` *and* with
+  * `--onto`, we do not want to re-generate the root commits.
+  */
+ #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
  
  int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                          const char **argv, unsigned flags);
@@@ -201,6 -206,6 +207,6 @@@ int read_author_script(const char *path
  void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);
  int write_basic_state(struct replay_opts *opts, const char *head_name,
                      struct commit *onto, const char *orig_head);
 -void sequencer_post_commit_cleanup(struct repository *r);
 +void sequencer_post_commit_cleanup(struct repository *r, int verbose);
  int sequencer_get_last_command(struct repository* r,
                               enum replay_action *action);
index 4eff14dae53223fb432ff5d9147543850a2c9ad5,fbf9addfd137b9df4e2683f4f055034f057f18e6..7a2da972fd373cde3cc233472acadc0ae803bd29
@@@ -118,6 -118,20 +118,20 @@@ test_expect_success REBASE_P 'rebase pa
                -s recursive --strategy-option=theirs HEAD~2 &&
        test_commit force-change &&
        git rebase --continue
+ '
+ test_expect_success 'rebase -r passes merge strategy options correctly' '
+       rm -fr .git/rebase-* &&
+       git reset --hard commit-new-file-F3-on-topic-branch &&
+       test_commit merge-theirs &&
+       git reset --hard HEAD^ &&
+       test_commit some-other-commit &&
+       test_tick &&
+       git merge --no-ff merge-theirs &&
+       FAKE_LINES="1 3 edit 4 5 7 8 9" git rebase -i -f -r -m \
+               -s recursive --strategy-option=theirs HEAD~2 &&
+       test_commit force-change-ours &&
+       git rebase --continue
  '
  
  test_expect_success '--skip after failed fixup cleans commit message' '
@@@ -265,12 -279,4 +279,12 @@@ test_expect_success '--reschedule-faile
        test_i18ngrep "has been rescheduled" err
  '
  
 +test_expect_success 'rebase.reschedulefailedexec only affects `rebase -i`' '
 +      test_config rebase.reschedulefailedexec true &&
 +      test_must_fail git rebase -x false HEAD^ &&
 +      grep "^exec false" .git/rebase-merge/git-rebase-todo &&
 +      git rebase --abort &&
 +      git rebase HEAD^
 +'
 +
  test_done
diff --combined t/t3430-rebase-merges.sh
index 7b6c4847ad6b0993bc192f53dec4671edfdf7f91,8ea6ff3548444c03209771dba7768812854430d0..fe6489fed6339f0a5144274a87c1afefa9ec3b4e
@@@ -164,19 -164,6 +164,19 @@@ test_expect_success 'failed `merge <bra
        grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
  '
  
 +test_expect_success 'fast-forward merge -c still rewords' '
 +      git checkout -b fast-forward-merge-c H &&
 +      (
 +              set_fake_editor &&
 +              FAKE_COMMIT_MESSAGE=edited \
 +                      GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
 +                      git rebase -ir @^
 +      ) &&
 +      echo edited >expected &&
 +      git log --pretty=format:%B -1 >actual &&
 +      test_cmp expected actual
 +'
 +
  test_expect_success 'with a branch tip that was cherry-picked already' '
        git checkout -b already-upstream master &&
        base="$(git rev-parse --verify HEAD)" &&
@@@ -237,24 -224,8 +237,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 &&
@@@ -441,4 -412,25 +441,25 @@@ test_expect_success '--continue after r
        test_path_is_missing .git/MERGE_HEAD
  '
  
+ test_expect_success '--rebase-merges with strategies' '
+       git checkout -b with-a-strategy F &&
+       test_tick &&
+       git merge -m "Merge conflicting-G" conflicting-G &&
+       : first, test with a merge strategy option &&
+       git rebase -ir -Xtheirs G &&
+       echo conflicting-G >expect &&
+       test_cmp expect G.t &&
+       : now, try with a merge strategy other than recursive &&
+       git reset --hard @{1} &&
+       write_script git-merge-override <<-\EOF &&
+       echo overridden$1 >>G.t
+       git add G.t
+       EOF
+       PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
+       test_write_lines G overridden--xopt >expect &&
+       test_cmp expect G.t
+ '
  test_done