Merge branch 'js/rebase-i-redo-exec-fix'
authorJunio C Hamano <gitster@pobox.com>
Sat, 9 Feb 2019 04:44:52 +0000 (20:44 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 9 Feb 2019 04:44:52 +0000 (20:44 -0800)
For "rebase -i --reschedule-failed-exec", we do not want the "-y"
shortcut after all.

* js/rebase-i-redo-exec-fix:
Revert "rebase: introduce a shortcut for --reschedule-failed-exec"

1  2 
Documentation/git-rebase.txt
builtin/rebase.c
git-legacy-rebase.sh
index 7e695b30e455245d8c187b7be0402e35bb83362c,174be5b1f0a4963cdb954df1b9cba7f01ed0b286..daa16403ece9f0f050da5a24086e89b181d9c0fa
@@@ -462,12 -462,6 +462,6 @@@ without an explicit `--interactive`
  +
  See also INCOMPATIBLE OPTIONS below.
  
- -y <cmd>::
-       This is the same as passing `--reschedule-failed-exec` before
-       `-x <cmd>`, i.e. it appends the specified `exec` command and
-       turns on the mode where failed `exec` commands are automatically
-       rescheduled.
  --root::
        Rebase all commits reachable from <branch>, instead of
        limiting them with an <upstream>.  This allows you to rebase
@@@ -515,7 -509,15 +509,7 @@@ See also INCOMPATIBLE OPTIONS below
  INCOMPATIBLE OPTIONS
  --------------------
  
 -git-rebase has many flags that are incompatible with each other,
 -predominantly due to the fact that it has three different underlying
 -implementations:
 -
 - * one based on linkgit:git-am[1] (the default)
 - * one based on git-merge-recursive (merge backend)
 - * one based on linkgit:git-cherry-pick[1] (interactive backend)
 -
 -Flags only understood by the am backend:
 +The following options:
  
   * --committer-date-is-author-date
   * --ignore-date
   * --ignore-whitespace
   * -C
  
 -Flags understood by both merge and interactive backends:
 +are incompatible with the following options:
  
   * --merge
   * --strategy
   * --strategy-option
   * --allow-empty-message
 -
 -Flags only understood by the interactive backend:
 -
   * --[no-]autosquash
   * --rebase-merges
   * --preserve-merges
   * --edit-todo
   * --root when used in combination with --onto
  
 -Other incompatible flag pairs:
 +In addition, the following pairs of options are incompatible:
  
   * --preserve-merges and --interactive
   * --preserve-merges and --signoff
@@@ -570,9 -575,8 +564,9 @@@ it to keep commits that started empty
  Directory rename detection
  ~~~~~~~~~~~~~~~~~~~~~~~~~~
  
 -The merge and interactive backends work fine with
 -directory rename detection.  The am backend sometimes does not.
 +Directory rename heuristics are enabled in the merge and interactive
 +backends.  Due to the lack of accurate tree information, directory
 +rename detection is disabled in the am backend.
  
  include::merge-strategies.txt[]
  
@@@ -980,7 -984,7 +974,7 @@@ when the merge operation did not even s
  
  At this time, the `merge` command will *always* use the `recursive`
  merge strategy for regular merges, and `octopus` for octopus merges,
 -strategy, with no way to choose a different one. To work around
 +with no way to choose a different one. To work around
  this, an `exec` command can be used to call `git merge` explicitly,
  using the fact that the labels are worktree-local refs (the ref
  `refs/rewritten/onto` would correspond to the label `onto`, for example).
diff --combined builtin/rebase.c
index b9d61771ab3c17f97bce90f3f847c9284eb14a9a,4839e525558bfea819d710cc40ab483eb80fcdf6..96efd40901dba9424e61775e3033622ceaaba388
@@@ -4,7 -4,6 +4,7 @@@
   * Copyright (c) 2018 Pratik Karki
   */
  
 +#define USE_THE_INDEX_COMPATIBILITY_MACROS
  #include "builtin.h"
  #include "run-command.h"
  #include "exec-cmd.h"
@@@ -124,7 -123,7 +124,7 @@@ static void imply_interactive(struct re
        case REBASE_PRESERVE_MERGES:
                break;
        case REBASE_MERGE:
 -              /* we silently *upgrade* --merge to --interactive if needed */
 +              /* we now implement --merge via --interactive */
        default:
                opts->type = REBASE_INTERACTIVE; /* implied */
                break;
@@@ -187,7 -186,10 +187,7 @@@ static int read_basic_state(struct reba
        if (get_oid(buf.buf, &opts->orig_head))
                return error(_("invalid orig-head: '%s'"), buf.buf);
  
 -      strbuf_reset(&buf);
 -      if (read_one(state_dir_path("quiet", opts), &buf))
 -              return -1;
 -      if (buf.len)
 +      if (file_exists(state_dir_path("quiet", opts)))
                opts->flags &= ~REBASE_NO_QUIET;
        else
                opts->flags |= REBASE_NO_QUIET;
        return 0;
  }
  
 +static int write_basic_state(struct rebase_options *opts)
 +{
 +      write_file(state_dir_path("head-name", opts), "%s",
 +                 opts->head_name ? opts->head_name : "detached HEAD");
 +      write_file(state_dir_path("onto", opts), "%s",
 +                 opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 +      write_file(state_dir_path("orig-head", opts), "%s",
 +                 oid_to_hex(&opts->orig_head));
 +      write_file(state_dir_path("quiet", opts), "%s",
 +                 opts->flags & REBASE_NO_QUIET ? "" : "t");
 +      if (opts->flags & REBASE_VERBOSE)
 +              write_file(state_dir_path("verbose", opts), "%s", "");
 +      if (opts->strategy)
 +              write_file(state_dir_path("strategy", opts), "%s",
 +                         opts->strategy);
 +      if (opts->strategy_opts)
 +              write_file(state_dir_path("strategy_opts", opts), "%s",
 +                         opts->strategy_opts);
 +      if (opts->allow_rerere_autoupdate >= 0)
 +              write_file(state_dir_path("allow_rerere_autoupdate", opts),
 +                         "-%s-rerere-autoupdate",
 +                         opts->allow_rerere_autoupdate ? "" : "-no");
 +      if (opts->gpg_sign_opt)
 +              write_file(state_dir_path("gpg_sign_opt", opts), "%s",
 +                         opts->gpg_sign_opt);
 +      if (opts->signoff)
 +              write_file(state_dir_path("strategy", opts), "--signoff");
 +
 +      return 0;
 +}
 +
  static int apply_autostash(struct rebase_options *opts)
  {
        const char *path = state_dir_path("autostash", opts);
@@@ -363,161 -334,6 +363,161 @@@ static void add_var(struct strbuf *buf
        }
  }
  
 +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
 +
 +#define RESET_HEAD_DETACH (1<<0)
 +#define RESET_HEAD_HARD (1<<1)
 +#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
 +#define RESET_HEAD_REFS_ONLY (1<<3)
 +
 +static int reset_head(struct object_id *oid, const char *action,
 +                    const char *switch_to_branch, unsigned flags,
 +                    const char *reflog_orig_head, const char *reflog_head)
 +{
 +      unsigned detach_head = flags & RESET_HEAD_DETACH;
 +      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;
 +      struct object_id head_oid;
 +      struct tree_desc desc[2] = { { NULL }, { NULL } };
 +      struct lock_file lock = LOCK_INIT;
 +      struct unpack_trees_options unpack_tree_opts;
 +      struct tree *tree;
 +      const char *reflog_action;
 +      struct strbuf msg = STRBUF_INIT;
 +      size_t prefix_len;
 +      struct object_id *orig = NULL, oid_orig,
 +              *old_orig = NULL, oid_old_orig;
 +      int ret = 0, nr = 0;
 +
 +      if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
 +              BUG("Not a fully qualified branch: '%s'", switch_to_branch);
 +
 +      if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
 +              ret = -1;
 +              goto leave_reset_head;
 +      }
 +
 +      if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
 +              ret = error(_("could not determine HEAD revision"));
 +              goto leave_reset_head;
 +      }
 +
 +      if (!oid)
 +              oid = &head_oid;
 +
 +      if (refs_only)
 +              goto reset_head_refs;
 +
 +      memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
 +      setup_unpack_trees_porcelain(&unpack_tree_opts, action);
 +      unpack_tree_opts.head_idx = 1;
 +      unpack_tree_opts.src_index = the_repository->index;
 +      unpack_tree_opts.dst_index = the_repository->index;
 +      unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
 +      unpack_tree_opts.update = 1;
 +      unpack_tree_opts.merge = 1;
 +      if (!detach_head)
 +              unpack_tree_opts.reset = 1;
 +
 +      if (repo_read_index_unmerged(the_repository) < 0) {
 +              ret = error(_("could not read index"));
 +              goto leave_reset_head;
 +      }
 +
 +      if (!reset_hard && !fill_tree_descriptor(&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)) {
 +              ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
 +              goto leave_reset_head;
 +      }
 +
 +      if (unpack_trees(nr, desc, &unpack_tree_opts)) {
 +              ret = -1;
 +              goto leave_reset_head;
 +      }
 +
 +      tree = parse_tree_indirect(oid);
 +      prime_cache_tree(the_repository, the_repository->index, tree);
 +
 +      if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
 +              ret = error(_("could not write index"));
 +              goto leave_reset_head;
 +      }
 +
 +reset_head_refs:
 +      reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
 +      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 (!reflog_head) {
 +              strbuf_setlen(&msg, prefix_len);
 +              strbuf_addstr(&msg, "updating HEAD");
 +              reflog_head = msg.buf;
 +      }
 +      if (!switch_to_branch)
 +              ret = update_ref(reflog_head, "HEAD", oid, orig,
 +                               detach_head ? REF_NO_DEREF : 0,
 +                               UPDATE_REFS_MSG_ON_ERR);
 +      else {
 +              ret = update_ref(reflog_orig_head, switch_to_branch, oid,
 +                               NULL, 0, UPDATE_REFS_MSG_ON_ERR);
 +              if (!ret)
 +                      ret = create_symref("HEAD", switch_to_branch,
 +                                          reflog_head);
 +      }
 +      if (run_hook)
 +              run_hook_le(NULL, "post-checkout",
 +                          oid_to_hex(orig ? orig : &null_oid),
 +                          oid_to_hex(oid), "1", NULL);
 +
 +leave_reset_head:
 +      strbuf_release(&msg);
 +      rollback_lock_file(&lock);
 +      while (nr)
 +              free((void *)desc[--nr].buffer);
 +      return ret;
 +}
 +
 +static int move_to_original_branch(struct rebase_options *opts)
 +{
 +      struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
 +      int ret;
 +
 +      if (!opts->head_name)
 +              return 0; /* nothing to move back to */
 +
 +      if (!opts->onto)
 +              BUG("move_to_original_branch without onto");
 +
 +      strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
 +                  opts->head_name, oid_to_hex(&opts->onto->object.oid));
 +      strbuf_addf(&head_reflog, "rebase finished: returning to %s",
 +                  opts->head_name);
 +      ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
 +                       orig_head_reflog.buf, head_reflog.buf);
 +
 +      strbuf_release(&orig_head_reflog);
 +      strbuf_release(&head_reflog);
 +      return ret;
 +}
 +
  static const char *resolvemsg =
  N_("Resolve all conflicts manually, mark them as resolved with\n"
  "\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
  "To abort and get back to the state before \"git rebase\", run "
  "\"git rebase --abort\".");
  
 +static int run_am(struct rebase_options *opts)
 +{
 +      struct child_process am = CHILD_PROCESS_INIT;
 +      struct child_process format_patch = CHILD_PROCESS_INIT;
 +      struct strbuf revisions = STRBUF_INIT;
 +      int status;
 +      char *rebased_patches;
 +
 +      am.git_cmd = 1;
 +      argv_array_push(&am.args, "am");
 +
 +      if (opts->action && !strcmp("continue", opts->action)) {
 +              argv_array_push(&am.args, "--resolved");
 +              argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
 +              if (opts->gpg_sign_opt)
 +                      argv_array_push(&am.args, opts->gpg_sign_opt);
 +              status = run_command(&am);
 +              if (status)
 +                      return status;
 +
 +              return move_to_original_branch(opts);
 +      }
 +      if (opts->action && !strcmp("skip", opts->action)) {
 +              argv_array_push(&am.args, "--skip");
 +              argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
 +              status = run_command(&am);
 +              if (status)
 +                      return status;
 +
 +              return move_to_original_branch(opts);
 +      }
 +      if (opts->action && !strcmp("show-current-patch", opts->action)) {
 +              argv_array_push(&am.args, "--show-current-patch");
 +              return run_command(&am);
 +      }
 +
 +      strbuf_addf(&revisions, "%s...%s",
 +                  oid_to_hex(opts->root ?
 +                             /* this is now equivalent to !opts->upstream */
 +                             &opts->onto->object.oid :
 +                             &opts->upstream->object.oid),
 +                  oid_to_hex(&opts->orig_head));
 +
 +      rebased_patches = xstrdup(git_path("rebased-patches"));
 +      format_patch.out = open(rebased_patches,
 +                              O_WRONLY | O_CREAT | O_TRUNC, 0666);
 +      if (format_patch.out < 0) {
 +              status = error_errno(_("could not open '%s' for writing"),
 +                                   rebased_patches);
 +              free(rebased_patches);
 +              argv_array_clear(&am.args);
 +              return status;
 +      }
 +
 +      format_patch.git_cmd = 1;
 +      argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
 +                       "--full-index", "--cherry-pick", "--right-only",
 +                       "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
 +                       "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL);
 +      if (opts->git_format_patch_opt.len)
 +              argv_array_split(&format_patch.args,
 +                               opts->git_format_patch_opt.buf);
 +      argv_array_push(&format_patch.args, revisions.buf);
 +      if (opts->restrict_revision)
 +              argv_array_pushf(&format_patch.args, "^%s",
 +                               oid_to_hex(&opts->restrict_revision->object.oid));
 +
 +      status = run_command(&format_patch);
 +      if (status) {
 +              unlink(rebased_patches);
 +              free(rebased_patches);
 +              argv_array_clear(&am.args);
 +
 +              reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
 +                         "HEAD", NULL);
 +              error(_("\ngit encountered an error while preparing the "
 +                      "patches to replay\n"
 +                      "these revisions:\n"
 +                      "\n    %s\n\n"
 +                      "As a result, git cannot rebase them."),
 +                    opts->revisions);
 +
 +              strbuf_release(&revisions);
 +              return status;
 +      }
 +      strbuf_release(&revisions);
 +
 +      am.in = open(rebased_patches, O_RDONLY);
 +      if (am.in < 0) {
 +              status = error_errno(_("could not open '%s' for reading"),
 +                                   rebased_patches);
 +              free(rebased_patches);
 +              argv_array_clear(&am.args);
 +              return status;
 +      }
 +
 +      argv_array_pushv(&am.args, opts->git_am_opts.argv);
 +      argv_array_push(&am.args, "--rebasing");
 +      argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
 +      argv_array_push(&am.args, "--patch-format=mboxrd");
 +      if (opts->allow_rerere_autoupdate > 0)
 +              argv_array_push(&am.args, "--rerere-autoupdate");
 +      else if (opts->allow_rerere_autoupdate == 0)
 +              argv_array_push(&am.args, "--no-rerere-autoupdate");
 +      if (opts->gpg_sign_opt)
 +              argv_array_push(&am.args, opts->gpg_sign_opt);
 +      status = run_command(&am);
 +      unlink(rebased_patches);
 +      free(rebased_patches);
 +
 +      if (!status) {
 +              return move_to_original_branch(opts);
 +      }
 +
 +      if (is_directory(opts->state_dir))
 +              write_basic_state(opts);
 +
 +      return status;
 +}
 +
  static int run_specific_rebase(struct rebase_options *opts)
  {
        const char *argv[] = { NULL, NULL };
                argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s",
                                 resolvemsg);
                if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
 -                      argv_array_push(&child.env_array, "GIT_EDITOR=:");
 +                      argv_array_push(&child.env_array,
 +                                      "GIT_SEQUENCE_EDITOR=:");
                        opts->autosquash = 0;
                }
  
                goto finished_rebase;
        }
  
 +      if (opts->type == REBASE_AM) {
 +              status = run_am(opts);
 +              goto finished_rebase;
 +      }
 +
        add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
        add_var(&script_snippet, "state_dir", opts->state_dir);
  
        if (is_interactive(opts) &&
            !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
                strbuf_addstr(&script_snippet,
 -                            "GIT_EDITOR=:; export GIT_EDITOR; ");
 +                            "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
                opts->autosquash = 0;
        }
  
                backend = "git-rebase--am";
                backend_func = "git_rebase__am";
                break;
 -      case REBASE_MERGE:
 -              backend = "git-rebase--merge";
 -              backend_func = "git_rebase__merge";
 -              break;
        case REBASE_PRESERVE_MERGES:
                backend = "git-rebase--preserve-merges";
                backend_func = "git_rebase__preserve_merges";
@@@ -835,6 -529,125 +835,6 @@@ finished_rebase
        return status ? -1 : 0;
  }
  
 -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
 -
 -#define RESET_HEAD_DETACH (1<<0)
 -#define RESET_HEAD_HARD (1<<1)
 -
 -static int reset_head(struct object_id *oid, const char *action,
 -                    const char *switch_to_branch, unsigned flags,
 -                    const char *reflog_orig_head, const char *reflog_head)
 -{
 -      unsigned detach_head = flags & RESET_HEAD_DETACH;
 -      unsigned reset_hard = flags & RESET_HEAD_HARD;
 -      struct object_id head_oid;
 -      struct tree_desc desc[2] = { { NULL }, { NULL } };
 -      struct lock_file lock = LOCK_INIT;
 -      struct unpack_trees_options unpack_tree_opts;
 -      struct tree *tree;
 -      const char *reflog_action;
 -      struct strbuf msg = STRBUF_INIT;
 -      size_t prefix_len;
 -      struct object_id *orig = NULL, oid_orig,
 -              *old_orig = NULL, oid_old_orig;
 -      int ret = 0, nr = 0;
 -
 -      if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
 -              BUG("Not a fully qualified branch: '%s'", switch_to_branch);
 -
 -      if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
 -              ret = -1;
 -              goto leave_reset_head;
 -      }
 -
 -      if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
 -              ret = error(_("could not determine HEAD revision"));
 -              goto leave_reset_head;
 -      }
 -
 -      if (!oid)
 -              oid = &head_oid;
 -
 -      memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
 -      setup_unpack_trees_porcelain(&unpack_tree_opts, action);
 -      unpack_tree_opts.head_idx = 1;
 -      unpack_tree_opts.src_index = the_repository->index;
 -      unpack_tree_opts.dst_index = the_repository->index;
 -      unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
 -      unpack_tree_opts.update = 1;
 -      unpack_tree_opts.merge = 1;
 -      if (!detach_head)
 -              unpack_tree_opts.reset = 1;
 -
 -      if (read_index_unmerged(the_repository->index) < 0) {
 -              ret = error(_("could not read index"));
 -              goto leave_reset_head;
 -      }
 -
 -      if (!reset_hard && !fill_tree_descriptor(&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)) {
 -              ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
 -              goto leave_reset_head;
 -      }
 -
 -      if (unpack_trees(nr, desc, &unpack_tree_opts)) {
 -              ret = -1;
 -              goto leave_reset_head;
 -      }
 -
 -      tree = parse_tree_indirect(oid);
 -      prime_cache_tree(the_repository->index, tree);
 -
 -      if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
 -              ret = error(_("could not write index"));
 -              goto leave_reset_head;
 -      }
 -
 -      reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
 -      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 (!reflog_head) {
 -              strbuf_setlen(&msg, prefix_len);
 -              strbuf_addstr(&msg, "updating HEAD");
 -              reflog_head = msg.buf;
 -      }
 -      if (!switch_to_branch)
 -              ret = update_ref(reflog_head, "HEAD", oid, orig,
 -                               detach_head ? REF_NO_DEREF : 0,
 -                               UPDATE_REFS_MSG_ON_ERR);
 -      else {
 -              ret = create_symref("HEAD", switch_to_branch, msg.buf);
 -              if (!ret)
 -                      ret = update_ref(reflog_head, "HEAD", oid, NULL, 0,
 -                                       UPDATE_REFS_MSG_ON_ERR);
 -      }
 -
 -leave_reset_head:
 -      strbuf_release(&msg);
 -      rollback_lock_file(&lock);
 -      while (nr)
 -              free((void *)desc[--nr].buffer);
 -      return ret;
 -}
 -
  static int rebase_config(const char *var, const char *value, void *data)
  {
        struct rebase_options *opts = data;
@@@ -941,23 -754,6 +941,6 @@@ static int parse_opt_interactive(const 
        return 0;
  }
  
- struct opt_y {
-       struct string_list *list;
-       struct rebase_options *options;
- };
- static int parse_opt_y(const struct option *opt, const char *arg, int unset)
- {
-       struct opt_y *o = opt->value;
-       if (unset || !arg)
-               return -1;
-       o->options->reschedule_failed_exec = 1;
-       string_list_append(o->list, arg);
-       return 0;
- }
  static void NORETURN error_on_missing_default_upstream(void)
  {
        struct branch *current_branch = branch_get(NULL);
@@@ -1005,19 -801,6 +988,19 @@@ static void set_reflog_action(struct re
        strbuf_release(&buf);
  }
  
 +static int check_exec_cmd(const char *cmd)
 +{
 +      if (strchr(cmd, '\n'))
 +              return error(_("exec commands cannot contain newlines"));
 +
 +      /* Does the command consist purely of whitespace? */
 +      if (!cmd[strspn(cmd, " \t\r\f\v")])
 +              return error(_("empty exec command"));
 +
 +      return 0;
 +}
 +
 +
  int cmd_rebase(int argc, const char **argv, const char *prefix)
  {
        struct rebase_options options = {
        struct string_list strategy_options = STRING_LIST_INIT_NODUP;
        struct object_id squash_onto;
        char *squash_onto_name = NULL;
-       struct opt_y opt_y = { .list = &exec, .options = &options };
        struct option builtin_rebase_options[] = {
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
                OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
                                N_("add exec lines after each commit of the "
                                   "editable list")),
-               { OPTION_CALLBACK, 'y', NULL, &opt_y, N_("<cmd>"),
-                       N_("same as --reschedule-failed-exec -x <cmd>"),
-                       PARSE_OPT_NONEG, parse_opt_y },
                OPT_BOOL(0, "allow-empty-message",
                         &options.allow_empty_message,
                         N_("allow rebasing commits with empty messages")),
                        die(_("Cannot read HEAD"));
  
                fd = hold_locked_index(&lock_file, 0);
 -              if (read_index(the_repository->index) < 0)
 +              if (repo_read_index(the_repository) < 0)
                        die(_("could not read index"));
                refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
                              NULL);
                if (0 <= fd)
 -                      update_index_if_able(the_repository->index,
 -                                           &lock_file);
 +                      repo_update_index_if_able(the_repository, &lock_file);
                rollback_lock_file(&lock_file);
  
 -              if (has_unstaged_changes(1)) {
 +              if (has_unstaged_changes(the_repository, 1)) {
                        puts(_("You must edit all merge conflicts and then\n"
                               "mark them as resolved using git add"));
                        exit(1);
                options.action = "skip";
                set_reflog_action(&options);
  
 -              rerere_clear(&merge_rr);
 +              rerere_clear(the_repository, &merge_rr);
                string_list_clear(&merge_rr, 1);
  
                if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
                               NULL, NULL) < 0)
                        die(_("could not discard worktree changes"));
 -              remove_branch_state();
 +              remove_branch_state(the_repository);
                if (read_basic_state(&options))
                        exit(1);
                goto run_rebase;
                options.action = "abort";
                set_reflog_action(&options);
  
 -              rerere_clear(&merge_rr);
 +              rerere_clear(the_repository, &merge_rr);
                string_list_clear(&merge_rr, 1);
  
                if (read_basic_state(&options))
                               NULL, NULL) < 0)
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
 -              remove_branch_state();
 +              remove_branch_state(the_repository);
                ret = finish_rebase(&options);
                goto cleanup;
        }
                }
        }
  
 +      for (i = 0; i < exec.nr; i++)
 +              if (check_exec_cmd(exec.items[i].string))
 +                      exit(1);
 +
        if (!(options.flags & REBASE_NO_QUIET))
                argv_array_push(&options.git_am_opts, "-q");
  
                }
        }
  
 +      if (options.type == REBASE_MERGE)
 +              imply_interactive(&options, "--merge");
 +
        if (options.root && !options.onto_name)
                imply_interactive(&options, "--root without --onto");
  
                                break;
  
                if (is_interactive(&options) && i >= 0)
 -                      die(_("error: cannot combine interactive options "
 -                            "(--interactive, --exec, --rebase-merges, "
 -                            "--preserve-merges, --keep-empty, --root + "
 -                            "--onto) with am options (%s)"), buf.buf);
 -              if (options.type == REBASE_MERGE && i >= 0)
 -                      die(_("error: cannot combine merge options (--merge, "
 -                            "--strategy, --strategy-option) with am options "
 -                            "(%s)"), buf.buf);
 +                      die(_("cannot combine am options with either "
 +                            "interactive or merge options"));
        }
  
        if (options.signoff) {
                 *       git-rebase.txt caveats with "unless you know what you are doing"
                 */
                if (options.rebase_merges)
 -                      die(_("error: cannot combine '--preserve-merges' with "
 +                      die(_("cannot combine '--preserve-merges' with "
                              "'--rebase-merges'"));
  
                if (options.reschedule_failed_exec)
  
        if (options.rebase_merges) {
                if (strategy_options.nr)
 -                      die(_("error: cannot combine '--rebase-merges' with "
 +                      die(_("cannot combine '--rebase-merges' with "
                              "'--strategy-option'"));
                if (options.strategy)
 -                      die(_("error: cannot combine '--rebase-merges' with "
 +                      die(_("cannot combine '--rebase-merges' with "
                              "'--strategy'"));
        }
  
                        get_fork_point(options.upstream_name, head);
        }
  
 -      if (read_index(the_repository->index) < 0)
 +      if (repo_read_index(the_repository) < 0)
                die(_("could not read index"));
  
        if (options.autostash) {
                fd = hold_locked_index(&lock_file, 0);
                refresh_cache(REFRESH_QUIET);
                if (0 <= fd)
 -                      update_index_if_able(&the_index, &lock_file);
 +                      repo_update_index_if_able(the_repository, &lock_file);
                rollback_lock_file(&lock_file);
  
 -              if (has_unstaged_changes(1) || has_uncommitted_changes(1)) {
 +              if (has_unstaged_changes(the_repository, 1) ||
 +                  has_uncommitted_changes(the_repository, 1)) {
                        const char *autostash =
                                state_dir_path("autostash", &options);
                        struct child_process stash = CHILD_PROCESS_INIT;
                        putchar('\n');
  
                        if (discard_index(the_repository->index) < 0 ||
 -                              read_index(the_repository->index) < 0)
 +                              repo_read_index(the_repository) < 0)
                                die(_("could not read index"));
                }
        }
  
 -      if (require_clean_work_tree("rebase",
 +      if (require_clean_work_tree(the_repository, "rebase",
                                    _("Please commit or stash them."), 1, 1)) {
                ret = 1;
                goto cleanup;
                                            getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
                                            options.switch_to);
                                if (reset_head(&oid, "checkout",
 -                                             options.head_name, 0,
 +                                             options.head_name,
 +                                             RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                                               NULL, buf.buf) < 0) {
                                        ret = !!error(_("could not switch to "
                                                        "%s"),
        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, NULL, msg.buf))
 +                     RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
 +                     NULL, msg.buf))
                die(_("Could not detach HEAD"));
        strbuf_release(&msg);
  
diff --combined git-legacy-rebase.sh
index 8d6c9aca6584f76689246604abe047a85504671e,37db5a7ca4618be80009c7a691c02c6044ce5dd4..5c2c4e5276d17f257b255e85db112557d9d7fc61
@@@ -26,7 -26,6 +26,6 @@@ f,force-rebase!    cherry-pick all comm
  m,merge!           use merging strategies to rebase
  i,interactive!     let the user edit the list of commits to rebase
  x,exec=!           add exec lines after each commit of the editable list
- y=!                same as --reschedule-failed-exec -x
  k,keep-empty     preserve empty commits during rebase
  allow-empty-message allow rebasing commits with empty messages
  stat!              display a diffstat of what changed upstream
@@@ -118,7 -117,7 +117,7 @@@ read_basic_state () 
        else
                orig_head=$(cat "$state_dir"/head)
        fi &&
 -      GIT_QUIET=$(cat "$state_dir"/quiet) &&
 +      test -f "$state_dir"/quiet && GIT_QUIET=t
        test -f "$state_dir"/verbose && verbose=t
        test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
        test -f "$state_dir"/strategy_opts &&
@@@ -176,8 -175,8 +175,8 @@@ run_interactive () 
  
  run_specific_rebase () {
        if [ "$interactive_rebase" = implied ]; then
 -              GIT_EDITOR=:
 -              export GIT_EDITOR
 +              GIT_SEQUENCE_EDITOR=:
 +              export GIT_SEQUENCE_EDITOR
                autosquash=
        fi
  
@@@ -226,7 -225,6 +225,7 @@@ the
        state_dir="$apply_dir"
  elif test -d "$merge_dir"
  then
 +      type=interactive
        if test -d "$merge_dir"/rewritten
        then
                type=preserve-merges
                preserve_merges=t
        elif test -f "$merge_dir"/interactive
        then
 -              type=interactive
                interactive_rebase=explicit
 -      else
 -              type=merge
        fi
        state_dir="$merge_dir"
  fi
@@@ -260,11 -261,6 +259,6 @@@ d
        --exec=*)
                cmd="${cmd}exec ${1#--exec=}${LF}"
                test -z "$interactive_rebase" && interactive_rebase=implied
-               ;;
-       -y*)
-               reschedule_failed_exec=--reschedule-failed-exec
-               cmd="${cmd}exec ${1#-y}${LF}"
-               test -z "$interactive_rebase" && interactive_rebase=implied
                ;;
        --interactive)
                interactive_rebase=explicit
@@@ -494,7 -490,6 +488,7 @@@ the
        test -z "$interactive_rebase" && interactive_rebase=implied
  fi
  
 +actually_interactive=
  if test -n "$interactive_rebase"
  then
        if test -z "$preserve_merges"
        else
                type=preserve-merges
        fi
 -
 +      actually_interactive=t
        state_dir="$merge_dir"
  elif test -n "$do_merge"
  then
 -      type=merge
 +      interactive_rebase=implied
 +      type=interactive
        state_dir="$merge_dir"
  else
        type=am
@@@ -520,20 -514,28 +514,20 @@@ the
        git_format_patch_opt="$git_format_patch_opt --progress"
  fi
  
 -if test -n "$git_am_opt"; then
 -      incompatible_opts=$(echo " $git_am_opt " | \
 -                          sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
 -      if test -n "$interactive_rebase"
 +incompatible_opts=$(echo " $git_am_opt " | \
 +                  sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
 +if test -n "$incompatible_opts"
 +then
 +      if test -n "$actually_interactive" || test "$do_merge"
        then
 -              if test -n "$incompatible_opts"
 -              then
 -                      die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")"
 -              fi
 -      fi
 -      if test -n "$do_merge"; then
 -              if test -n "$incompatible_opts"
 -              then
 -                      die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")"
 -              fi
 +              die "$(gettext "fatal: cannot combine am options with either interactive or merge options")"
        fi
  fi
  
  if test -n "$signoff"
  then
        test -n "$preserve_merges" &&
 -              die "$(gettext "error: cannot combine '--signoff' with '--preserve-merges'")"
 +              die "$(gettext "fatal: cannot combine '--signoff' with '--preserve-merges'")"
        git_am_opt="$git_am_opt $signoff"
        force_rebase=t
  fi
@@@ -544,7 -546,7 +538,7 @@@ the
        # Note: incompatibility with --interactive is just a strong warning;
        #       git-rebase.txt caveats with "unless you know what you are doing"
        test -n "$rebase_merges" &&
 -              die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")"
 +              die "$(gettext "fatal: cannot combine '--preserve-merges' with '--rebase-merges'")"
  
        test -n "$reschedule_failed_exec" &&
                die "$(gettext "error: cannot combine '--preserve-merges' with '--reschedule-failed-exec'")"
@@@ -553,9 -555,9 +547,9 @@@ f
  if test -n "$rebase_merges"
  then
        test -n "$strategy_opts" &&
 -              die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy-option'")"
 +              die "$(gettext "fatal: cannot combine '--rebase-merges' with '--strategy-option'")"
        test -n "$strategy" &&
 -              die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy'")"
 +              die "$(gettext "fatal: cannot combine '--rebase-merges' with '--strategy'")"
  fi
  
  if test -z "$rebase_root"
@@@ -694,7 -696,7 +688,7 @@@ require_clean_work_tree "rebase" "$(get
  # but this should be done only when upstream and onto are the same
  # and if this is not an interactive rebase.
  mb=$(git merge-base "$onto" "$orig_head")
 -if test -z "$interactive_rebase" && test "$upstream" = "$onto" &&
 +if test -z "$actually_interactive" && test "$upstream" = "$onto" &&
        test "$mb" = "$onto" && test -z "$restrict_revision" &&
        # linear history?
        ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
@@@ -744,19 -746,6 +738,19 @@@ the
        GIT_PAGER='' git diff --stat --summary "$mb_tree" "$onto"
  fi
  
 +if test -z "$actually_interactive" && test "$mb" = "$orig_head"
 +then
 +      say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
 +      GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
 +              git checkout -q "$onto^0" || die "could not detach HEAD"
 +      # If the $onto is a proper descendant of the tip of the branch, then
 +      # we just fast-forwarded.
 +      git update-ref ORIG_HEAD $orig_head
 +      move_to_original_branch
 +      finish_rebase
 +      exit 0
 +fi
 +
  test -n "$interactive_rebase" && run_specific_rebase
  
  # Detach HEAD and reset the tree
@@@ -766,6 -755,16 +760,6 @@@ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: 
        git checkout -q "$onto^0" || die "could not detach HEAD"
  git update-ref ORIG_HEAD $orig_head
  
 -# If the $onto is a proper descendant of the tip of the branch, then
 -# we just fast-forwarded.
 -if test "$mb" = "$orig_head"
 -then
 -      say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
 -      move_to_original_branch
 -      finish_rebase
 -      exit 0
 -fi
 -
  if test -n "$rebase_root"
  then
        revisions="$onto..$orig_head"