]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'pw/rebase-i-show-HEAD-to-reword'
authorJunio C Hamano <gitster@pobox.com>
Fri, 11 Oct 2019 05:24:46 +0000 (14:24 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 11 Oct 2019 05:24:47 +0000 (14:24 +0900)
"git rebase -i" showed a wrong HEAD while "reword" open the editor.

* pw/rebase-i-show-HEAD-to-reword:
  sequencer: simplify root commit creation
  rebase -i: check for updated todo after squash and reword
  rebase -i: always update HEAD before rewording

1  2 
sequencer.c
t/t3404-rebase-interactive.sh

diff --combined sequencer.c
index ea368bcfe94491c2c9033869483f1e4020999a7b,2adcf5a639c3a7d0154f07cf07f420f5d4f54007..501921e08c8e050bb10a9772f8dad1a71858683d
@@@ -770,7 -770,7 +770,7 @@@ static int parse_key_value_squoted(cha
   *    GIT_AUTHOR_DATE='$author_date'
   *
   * where $author_name, $author_email and $author_date are quoted. We are strict
 - * with our parsing, as the file was meant to be eval'd in the old
 + * with our parsing, as the file was meant to be eval'd in the now-removed
   * git-am.sh/git-rebase--interactive.sh scripts, and thus if the file differs
   * from what this function expects, it is better to bail out than to do
   * something that the user does not expect.
@@@ -869,34 -869,6 +869,6 @@@ static char *get_author(const char *mes
        return NULL;
  }
  
- /* Read author-script and return an ident line (author <email> timestamp) */
- static const char *read_author_ident(struct strbuf *buf)
- {
-       struct strbuf out = STRBUF_INIT;
-       char *name, *email, *date;
-       if (read_author_script(rebase_path_author_script(),
-                              &name, &email, &date, 0))
-               return NULL;
-       /* validate date since fmt_ident() will die() on bad value */
-       if (parse_date(date, &out)){
-               warning(_("invalid date format '%s' in '%s'"),
-                       date, rebase_path_author_script());
-               strbuf_release(&out);
-               return NULL;
-       }
-       strbuf_reset(&out);
-       strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0));
-       strbuf_swap(buf, &out);
-       strbuf_release(&out);
-       free(name);
-       free(email);
-       free(date);
-       return buf->buf;
- }
  static const char staged_changes_advice[] =
  N_("you have staged changes in your working tree\n"
  "If these changes are meant to be squashed into the previous commit, run:\n"
@@@ -954,47 -926,6 +926,6 @@@ static int run_git_commit(struct reposi
  {
        struct child_process cmd = CHILD_PROCESS_INIT;
  
-       if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
-               struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
-               const char *author = NULL;
-               struct object_id root_commit, *cache_tree_oid;
-               int res = 0;
-               if (is_rebase_i(opts)) {
-                       author = read_author_ident(&script);
-                       if (!author) {
-                               strbuf_release(&script);
-                               return -1;
-                       }
-               }
-               if (!defmsg)
-                       BUG("root commit without message");
-               if (!(cache_tree_oid = get_cache_tree_oid(r->index)))
-                       res = -1;
-               if (!res)
-                       res = strbuf_read_file(&msg, defmsg, 0);
-               if (res <= 0)
-                       res = error_errno(_("could not read '%s'"), defmsg);
-               else
-                       res = commit_tree(msg.buf, msg.len, cache_tree_oid,
-                                         NULL, &root_commit, author,
-                                         opts->gpg_sign);
-               strbuf_release(&msg);
-               strbuf_release(&script);
-               if (!res) {
-                       update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
-                                  REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
-                       res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
-                                        UPDATE_REFS_MSG_ON_ERR);
-               }
-               return res < 0 ? error(_("writing root commit")) : 0;
-       }
        cmd.git_cmd = 1;
  
        if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
@@@ -1378,7 -1309,7 +1309,7 @@@ static int try_to_commit(struct reposit
                         struct object_id *oid)
  {
        struct object_id tree;
-       struct commit *current_head;
+       struct commit *current_head = NULL;
        struct commit_list *parents = NULL;
        struct commit_extra_header *extra = NULL;
        struct strbuf err = STRBUF_INIT;
                }
                parents = copy_commit_list(current_head->parents);
                extra = read_commit_extra_headers(current_head, exclude_gpgsig);
-       } else if (current_head) {
+       } else if (current_head &&
+                  (!(flags & CREATE_ROOT_COMMIT) || (flags & AMEND_MSG))) {
                commit_list_insert(current_head, &parents);
        }
  
@@@ -1490,8 -1422,7 +1422,7 @@@ static int do_commit(struct repository 
  {
        int res = 1;
  
-       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
-           !(flags & CREATE_ROOT_COMMIT)) {
+       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
                struct object_id oid;
                struct strbuf sb = STRBUF_INIT;
  
@@@ -1775,7 -1706,7 +1706,7 @@@ static int do_pick_commit(struct reposi
                          enum todo_command command,
                          struct commit *commit,
                          struct replay_opts *opts,
-                         int final_fixup)
+                         int final_fixup, int *check_todo)
  {
        unsigned int flags = opts->edit ? EDIT_MSG : 0;
        const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r);
        char *author = NULL;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0, allow;
+       int res, unborn = 0, reword = 0, allow;
  
        if (opts->no_commit) {
                /*
                        opts);
                if (res || command != TODO_REWORD)
                        goto leave;
-               flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG;
+               reword = 1;
                msg_file = NULL;
                goto fast_forward_edit;
        }
        }
  
        if (command == TODO_REWORD)
-               flags |= EDIT_MSG | VERIFY_MSG;
+               reword = 1;
        else if (is_fixup(command)) {
                if (update_squash_messages(r, command, commit, opts))
                        return -1;
        } else if (allow)
                flags |= ALLOW_EMPTY;
        if (!opts->no_commit) {
- fast_forward_edit:
                if (author || command == TODO_REVERT || (flags & AMEND_MSG))
                        res = do_commit(r, msg_file, author, opts, flags);
                else
                        res = error(_("unable to parse commit author"));
+               *check_todo = !!(flags & EDIT_MSG);
+               if (!res && reword) {
+ fast_forward_edit:
+                       res = run_git_commit(r, NULL, opts, EDIT_MSG |
+                                            VERIFY_MSG | AMEND_MSG |
+                                            (flags & ALLOW_EMPTY));
+                       *check_todo = 1;
+               }
        }
  
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
@@@ -2079,18 -2018,6 +2018,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 -2253,19 +2252,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 -2592,15 +2593,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 -2651,15 +2678,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 -2725,6 +2766,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 -3136,7 +3241,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,9 -3198,6 +3303,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");
                goto leave_merge;
        }
  
 -      write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
 +      write_message(oid_to_hex(&merge_commit->object.oid), the_hash_algo->hexsz,
                      git_path_merge_head(r), 0);
        write_message("no-ff", 5, git_path_merge_mode(r), 0);
  
                 * 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);
  
@@@ -3828,6 -3647,7 +3767,7 @@@ static int pick_commits(struct reposito
        while (todo_list->current < todo_list->nr) {
                struct todo_item *item = todo_list->items + todo_list->current;
                const char *arg = todo_item_get_arg(todo_list, item);
+               int check_todo = 0;
  
                if (save_todo(todo_list, opts))
                        return -1;
                        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) {
                                        command_to_string(item->command), NULL),
                                        1);
                        res = do_pick_commit(r, item->command, item->commit,
-                                       opts, is_final_fixup(todo_list));
+                                            opts, is_final_fixup(todo_list),
+                                            &check_todo);
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
                                advise(_(rescheduled_advice),
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(arg + item->arg_len);
                        int saved = *end_of_arg;
-                       struct stat st;
  
                        if (!opts->verbose)
                                term_clear_line();
                        if (res) {
                                if (opts->reschedule_failed_exec)
                                        reschedule = 1;
-                       } else if (stat(get_todo_path(opts), &st))
-                               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 */
-                               /* `current` will be incremented below */
-                               todo_list->current = -1;
                        }
+                       check_todo = 1;
                } else if (item->command == TODO_LABEL) {
                        if ((res = do_label(r, arg, item->arg_len)))
                                reschedule = 1;
                                                        item->commit,
                                                        arg, item->arg_len,
                                                        opts, res, 0);
+               } else if (check_todo && !res) {
+                       struct stat st;
+                       if (stat(get_todo_path(opts), &st)) {
+                               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 */
+                               /* `current` will be incremented below */
+                               todo_list->current = -1;
+                       }
                }
  
                todo_list->current++;
@@@ -4236,7 -4061,7 +4181,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());
@@@ -4306,9 -4131,12 +4251,12 @@@ static int single_pick(struct repositor
                       struct commit *cmit,
                       struct replay_opts *opts)
  {
+       int check_todo;
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        return do_pick_commit(r, opts->action == REPLAY_PICK ?
-               TODO_PICK : TODO_REVERT, cmit, opts, 0);
+                             TODO_PICK : TODO_REVERT, cmit, opts, 0,
+                             &check_todo);
  }
  
  int sequencer_pick_revisions(struct repository *r,
         */
  
        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"));
@@@ -4497,7 -4325,7 +4445,7 @@@ static const char *label_oid(struct obj
                char *p;
  
                strbuf_reset(&state->buf);
 -              strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
 +              strbuf_grow(&state->buf, GIT_MAX_HEXSZ);
                label = p = state->buf.buf;
  
                find_unique_abbrev_r(p, oid, default_abbrev);
                        size_t i = strlen(p) + 1;
  
                        oid_to_hex_r(p, oid);
 -                      for (; i < GIT_SHA1_HEXSZ; i++) {
 +                      for (; i < the_hash_algo->hexsz; i++) {
                                char save = p[i];
                                p[i] = '\0';
                                if (!hashmap_get_from_hash(&state->labels,
@@@ -4564,7 -4392,6 +4512,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;
  
index 29a35840ed03e948fa342a4bb82f093d7fbd8588,d2f1d5bd235fd529007e4c1f17231289af357bd6..d2dfbe46b9298719c41f35501d876c06c73235eb
@@@ -29,6 -29,9 +29,6 @@@ Initial setup
  
  . "$TEST_DIRECTORY"/lib-rebase.sh
  
 -# WARNING: Modifications to the initial repository can change the SHA ID used
 -# in the expect2 file for the 'stop on conflicting pick' test.
 -
  test_expect_success 'setup' '
        test_commit A file1 &&
        test_commit B file1 &&
@@@ -152,6 -155,8 +152,6 @@@ test_expect_success 'rebase -x with emp
        test_i18ncmp expected actual
  '
  
 -LF='
 -'
  test_expect_success 'rebase -x with newline in command fails' '
        test_when_finished "git rebase --abort ||:" &&
        test_must_fail env git rebase -x "a${LF}b" @ 2>actual &&
@@@ -228,28 -233,25 +228,28 @@@ test_expect_success 'exchange two commi
        set_fake_editor &&
        FAKE_LINES="2 1" git rebase -i HEAD~2 &&
        test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 -      test G = $(git cat-file commit HEAD | sed -ne \$p)
 +      test G = $(git cat-file commit HEAD | sed -ne \$p) &&
 +      blob1=$(git rev-parse --short HEAD^:file1) &&
 +      blob2=$(git rev-parse --short HEAD:file1) &&
 +      commit=$(git rev-parse --short HEAD)
  '
  
  test_expect_success 'stop on conflicting pick' '
 -      cat >expect <<-\EOF &&
 +      cat >expect <<-EOF &&
        diff --git a/file1 b/file1
 -      index f70f10e..fd79235 100644
 +      index $blob1..$blob2 100644
        --- a/file1
        +++ b/file1
        @@ -1 +1 @@
        -A
        +G
        EOF
 -      cat >expect2 <<-\EOF &&
 +      cat >expect2 <<-EOF &&
        <<<<<<< HEAD
        D
        =======
        G
 -      >>>>>>> 5d18e54... G
 +      >>>>>>> $commit... G
        EOF
        git tag new-branch1 &&
        set_fake_editor &&
@@@ -1001,7 -1003,7 +1001,7 @@@ test_expect_success 'rebase -i --root t
        git checkout B &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="2" git rebase -i --root &&
 -      git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
 +      git cat-file commit HEAD | grep "^tree $EMPTY_TREE" &&
        git rebase --abort
  '
  
@@@ -1014,9 -1016,9 +1014,9 @@@ test_expect_success 'rebase -i --root f
        test 0 = $(git cat-file commit HEAD | grep -c ^parent\ )
  '
  
- test_expect_success 'rebase -i --root reword root commit' '
+ test_expect_success 'rebase -i --root reword original root commit' '
        test_when_finished "test_might_fail git rebase --abort" &&
-       git checkout -b reword-root-branch master &&
+       git checkout -b reword-original-root-branch master &&
        set_fake_editor &&
        FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \
        git rebase -i --root &&
        test -z "$(git show -s --format=%p HEAD^)"
  '
  
+ test_expect_success 'rebase -i --root reword new root commit' '
+       test_when_finished "test_might_fail git rebase --abort" &&
+       git checkout -b reword-now-root-branch master &&
+       set_fake_editor &&
+       FAKE_LINES="reword 3 1" FAKE_COMMIT_MESSAGE="C changed" \
+       git rebase -i --root &&
+       git show HEAD^ | grep "C changed" &&
+       test -z "$(git show -s --format=%p HEAD^)"
+ '
  test_expect_success 'rebase -i --root when root has untracked file conflict' '
        test_when_finished "reset_rebase" &&
        git checkout -b failing-root-pick A &&
@@@ -1052,11 -1064,11 +1062,11 @@@ test_expect_success 'rebase -i --root r
  '
  
  test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' '
-       git checkout reword-root-branch &&
+       git checkout reword-original-root-branch &&
        git reset --hard &&
        git checkout conflict-branch &&
        set_fake_editor &&
 -      test_must_fail git rebase --onto HEAD~2 HEAD~ &&
 +      test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
        test_must_fail git rebase --edit-todo &&
        git rebase --abort
  '
@@@ -1159,7 -1171,7 +1169,7 @@@ test_expect_success 'rebase -i error o
        test_expect_code 1 grep  "      emp" error
  '
  
 -test_expect_success 'short SHA-1 setup' '
 +test_expect_success SHA1 'short SHA-1 setup' '
        test_when_finished "git checkout master" &&
        git checkout --orphan collide &&
        git rm -rf . &&
        )
  '
  
 -test_expect_success 'short SHA-1 collide' '
 +test_expect_success SHA1 'short SHA-1 collide' '
        test_when_finished "reset_rebase && git checkout master" &&
        git checkout collide &&
        (
@@@ -1417,6 -1429,7 +1427,6 @@@ test_expect_success 'editor saves as CR
        )
  '
  
 -SQ="'"
  test_expect_success 'rebase -i --gpg-sign=<key-id>' '
        test_when_finished "test_might_fail git rebase --abort" &&
        set_fake_editor &&