]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'en/sequencer-empty-edit-result-aborts' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:45 +0000 (22:57 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:45 +0000 (22:57 +0900)
"git rebase" etc. in Git 2.19 fails to abort when given an empty
commit log message as result of editing, which has been corrected.

* en/sequencer-empty-edit-result-aborts:
  sequencer: fix --allow-empty-message behavior, make it smarter

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

diff --combined sequencer.c
index c758c1cdcb19e094fa90270c226faa43f76d08f9,61548952e3e140e785dcb9543126ae3ce892296b..f388405b4e0d2c260f74777323a738ac845195e0
@@@ -2,7 -2,6 +2,7 @@@
  #include "config.h"
  #include "lockfile.h"
  #include "dir.h"
 +#include "object-store.h"
  #include "object.h"
  #include "commit.h"
  #include "sequencer.h"
@@@ -28,7 -27,6 +28,7 @@@
  #include "worktree.h"
  #include "oidmap.h"
  #include "oidset.h"
 +#include "commit-slab.h"
  #include "alias.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@@ -63,12 -61,12 +63,12 @@@ static GIT_PATH_FUNC(rebase_path_done, 
   * The file to keep track of how many commands were already processed (e.g.
   * for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
 +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum")
  /*
   * The file to keep track of how many commands are to be processed in total
   * (e.g. for the prompt).
   */
 -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
 +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end")
  /*
   * The commit message that is planned to be used for any changes that
   * need to be committed following a user interaction.
@@@ -177,7 -175,6 +177,7 @@@ static int git_sequencer_config(const c
                        warning(_("invalid commit message cleanup mode '%s'"),
                                  s);
  
 +              free((char *)s);
                return status;
        }
  
@@@ -225,16 -222,13 +225,16 @@@ static const char *get_todo_path(const 
   * Returns 3 when sob exists within conforming footer as last entry
   */
  static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
 -      int ignore_footer)
 +      size_t ignore_footer)
  {
 +      struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
        struct trailer_info info;
 -      int i;
 +      size_t i;
        int found_sob = 0, found_sob_last = 0;
  
 -      trailer_info_get(&info, sb->buf);
 +      opts.no_divider = 1;
 +
 +      trailer_info_get(&info, sb->buf, &opts);
  
        if (info.trailer_start == info.trailer_end)
                return 0;
@@@ -310,7 -304,7 +310,7 @@@ static const char *action_name(const st
        case REPLAY_INTERACTIVE_REBASE:
                return N_("rebase -i");
        }
 -      die(_("Unknown action: %d"), opts->action);
 +      die(_("unknown action: %d"), opts->action);
  }
  
  struct commit_message {
@@@ -361,7 -355,7 +361,7 @@@ static void print_advice(int show_hint
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
 -              unlink(git_path_cherry_pick_head());
 +              unlink(git_path_cherry_pick_head(the_repository));
                return;
        }
  
@@@ -436,7 -430,7 +436,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(the_hash_algo->empty_tree);
 +      return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -597,7 -591,7 +597,7 @@@ static int is_index_unchanged(void
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
                return error(_("could not resolve HEAD commit"));
  
 -      head_commit = lookup_commit(&head_oid);
 +      head_commit = lookup_commit(the_repository, &head_oid);
  
        /*
         * If head_commit is NULL, check_commit, called from
@@@ -642,7 -636,7 +642,7 @@@ missing_author
                else if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
        strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
        while (*message && *message != '\n' && *message != '\r')
                if (skip_prefix(message, "> ", &message))
                else if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
        strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
        while (*message && *message != '\n' && *message != '\r')
                if (*message != '\'')
                        strbuf_addch(&buf, *(message++));
                else
 -                      strbuf_addf(&buf, "'\\\\%c'", *(message++));
 +                      strbuf_addf(&buf, "'\\%c'", *(message++));
 +      strbuf_addch(&buf, '\'');
        res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
        strbuf_release(&buf);
        return res;
  }
  
 +
 +/*
 + * write_author_script() used to fail to terminate the last line with a "'" and
 + * also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". We check for
 + * the terminating "'" on the last line to see how "'" has been escaped in case
 + * git was upgraded while rebase was stopped.
 + */
 +static int quoting_is_broken(const char *s, size_t n)
 +{
 +      /* Skip any empty lines in case the file was hand edited */
 +      while (n > 0 && s[--n] == '\n')
 +              ; /* empty */
 +      if (n > 0 && s[n] != '\'')
 +              return 1;
 +
 +      return 0;
 +}
 +
  /*
   * Read a list of environment variable assignments (such as the author-script
   * file) into an environment block. Returns -1 on error, 0 otherwise.
  static int read_env_script(struct argv_array *env)
  {
        struct strbuf script = STRBUF_INIT;
 -      int i, count = 0;
 -      char *p, *p2;
 +      int i, count = 0, sq_bug;
 +      const char *p2;
 +      char *p;
  
        if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
                return -1;
 -
 +      /* write_author_script() used to quote incorrectly */
 +      sq_bug = quoting_is_broken(script.buf, script.len);
        for (p = script.buf; *p; p++)
 -              if (skip_prefix(p, "'\\\\''", (const char **)&p2))
 +              if (sq_bug && skip_prefix(p, "'\\\\''", &p2))
 +                      strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
 +              else if (skip_prefix(p, "'\\''", &p2))
                        strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
                else if (*p == '\'')
                        strbuf_splice(&script, p-- - script.buf, 1, "", 0);
@@@ -734,51 -705,43 +734,51 @@@ static const char *read_author_ident(st
        const char *keys[] = {
                "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
        };
 -      char *in, *out, *eol;
 -      int i = 0, len;
 +      struct strbuf out = STRBUF_INIT;
 +      char *in, *eol;
 +      const char *val[3];
 +      int i = 0;
  
        if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
                return NULL;
  
        /* dequote values and construct ident line in-place */
 -      for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
 +      for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
                if (!skip_prefix(in, keys[i], (const char **)&in)) {
 -                      warning("could not parse '%s' (looking for '%s'",
 +                      warning(_("could not parse '%s' (looking for '%s')"),
                                rebase_path_author_script(), keys[i]);
                        return NULL;
                }
  
                eol = strchrnul(in, '\n');
                *eol = '\0';
 -              sq_dequote(in);
 -              len = strlen(in);
 -
 -              if (i > 0) /* separate values by spaces */
 -                      *(out++) = ' ';
 -              if (i == 1) /* email needs to be surrounded by <...> */
 -                      *(out++) = '<';
 -              memmove(out, in, len);
 -              out += len;
 -              if (i == 1) /* email needs to be surrounded by <...> */
 -                      *(out++) = '>';
 +              if (!sq_dequote(in)) {
 +                      warning(_("bad quoting on %s value in '%s'"),
 +                              keys[i], rebase_path_author_script());
 +                      return NULL;
 +              }
 +              val[i] = in;
                in = eol + 1;
        }
  
        if (i < 3) {
 -              warning("could not parse '%s' (looking for '%s')",
 +              warning(_("could not parse '%s' (looking for '%s')"),
                        rebase_path_author_script(), keys[i]);
                return NULL;
        }
  
 -      buf->len = out - buf->buf;
 +      /* validate date since fmt_ident() will die() on bad value */
 +      if (parse_date(val[2], &out)){
 +              warning(_("invalid date format '%s' in '%s'"),
 +                      val[2], rebase_path_author_script());
 +              strbuf_release(&out);
 +              return NULL;
 +      }
 +
 +      strbuf_reset(&out);
 +      strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0));
 +      strbuf_swap(buf, &out);
 +      strbuf_release(&out);
        return buf->buf;
  }
  
@@@ -823,18 -786,11 +823,18 @@@ static int run_git_commit(const char *d
  
        if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
                struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
 -              const char *author = is_rebase_i(opts) ?
 -                      read_author_ident(&script) : NULL;
 +              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 ((flags & ALLOW_EMPTY))
                argv_array_push(&cmd.args, "--allow-empty");
  
-       if (opts->allow_empty_message)
+       if (!(flags & EDIT_MSG))
                argv_array_push(&cmd.args, "--allow-empty-message");
  
        if (cmd.err == -1) {
@@@ -1142,7 -1098,7 +1142,7 @@@ void print_commit_summary(const char *p
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
  
 -      commit = lookup_commit(oid);
 +      commit = lookup_commit(the_repository, oid);
        if (!commit)
                die(_("couldn't look up newly created commit"));
        if (parse_commit(commit))
@@@ -1217,7 -1173,7 +1217,7 @@@ static int parse_head(struct commit **h
        if (get_oid("HEAD", &oid)) {
                current_head = NULL;
        } else {
 -              current_head = lookup_commit_reference(&oid);
 +              current_head = lookup_commit_reference(the_repository, &oid);
                if (!current_head)
                        return error(_("could not parse HEAD"));
                if (oidcmp(&oid, &current_head->object.oid)) {
@@@ -1285,7 -1241,7 +1285,7 @@@ static int try_to_commit(struct strbuf 
                commit_list_insert(current_head, &parents);
        }
  
 -      if (write_cache_as_tree(&tree, 0, NULL)) {
 +      if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) {
                res = error(_("git write-tree failed to write a tree"));
                goto out;
        }
  
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
                strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
-       if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+       if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
        }
@@@ -1366,8 -1322,8 +1366,8 @@@ static int do_commit(const char *msg_fi
                                    &oid);
                strbuf_release(&sb);
                if (!res) {
 -                      unlink(git_path_cherry_pick_head());
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_cherry_pick_head(the_repository));
 +                      unlink(git_path_merge_msg(the_repository));
                        if (!is_rebase_i(opts))
                                print_commit_summary(NULL, &oid,
                                                SUMMARY_SHOW_AUTHOR_DATE);
@@@ -1486,7 -1442,7 +1486,7 @@@ static const char *command_to_string(co
  {
        if (command < TODO_COMMENT)
                return todo_command_info[command].str;
 -      die("Unknown command: %d", command);
 +      die(_("unknown command: %d"), command);
  }
  
  static char command_to_char(const enum todo_command command)
@@@ -1552,7 -1508,7 +1552,7 @@@ static int update_squash_messages(enum 
  
                if (get_oid("HEAD", &head))
                        return error(_("need a HEAD to fixup"));
 -              if (!(head_commit = lookup_commit_reference(&head)))
 +              if (!(head_commit = lookup_commit_reference(the_repository, &head)))
                        return error(_("could not read HEAD"));
                if (!(head_message = get_commit_buffer(head_commit, NULL)))
                        return error(_("could not read HEAD's commit message"));
                unlink(rebase_path_fixup_msg());
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("This is the commit message #%d:"),
 -                          ++opts->current_fixup_count);
 +                          ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
                strbuf_addstr(&buf, body);
        } else if (command == TODO_FIXUP) {
                strbuf_addf(&buf, "\n%c ", comment_line_char);
                strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
 -                          ++opts->current_fixup_count);
 +                          ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
                strbuf_add_commented_lines(&buf, body, strlen(body));
        } else
@@@ -1655,7 -1611,7 +1655,7 @@@ static int do_pick_commit(enum todo_com
                struct replay_opts *opts, int final_fixup)
  {
        unsigned int flags = opts->edit ? EDIT_MSG : 0;
 -      const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
 +      const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
        struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
                 * that represents the "current" state for merge-recursive
                 * to work on.
                 */
 -              if (write_cache_as_tree(&head, 0, NULL))
 +              if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL))
                        return error(_("your index file is unmerged."));
        } else {
                unborn = get_oid("HEAD", &head);
                        flags |= CLEANUP_MSG;
                        msg_file = rebase_path_fixup_msg();
                } else {
 -                      const char *dest = git_path_squash_msg();
 +                      const char *dest = git_path_squash_msg(the_repository);
                        unlink(dest);
                        if (copy_file(dest, rebase_path_squash_msg(), 0666))
                                return error(_("could not rename '%s' to '%s'"),
                                             rebase_path_squash_msg(), dest);
 -                      unlink(git_path_merge_msg());
 +                      unlink(git_path_merge_msg(the_repository));
                        msg_file = dest;
                        flags |= EDIT_MSG;
                }
                res = do_recursive_merge(base, next, base_label, next_label,
                                         &head, &msgbuf, opts);
                if (res < 0)
 -                      return res;
 +                      goto leave;
 +
                res |= write_message(msgbuf.buf, msgbuf.len,
 -                                   git_path_merge_msg(), 0);
 +                                   git_path_merge_msg(the_repository), 0);
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
  
                res = write_message(msgbuf.buf, msgbuf.len,
 -                                  git_path_merge_msg(), 0);
 +                                  git_path_merge_msg(the_repository), 0);
  
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
@@@ -1905,6 -1860,8 +1905,6 @@@ static int prepare_revs(struct replay_o
        if (prepare_revision_walk(opts->revs))
                return error(_("revision walk setup failed"));
  
 -      if (!opts->revs->commits)
 -              return error(_("empty commit set passed"));
        return 0;
  }
  
@@@ -2048,7 -2005,7 +2048,7 @@@ static int parse_insn_line(struct todo_
        if (status < 0)
                return -1;
  
 -      item->commit = lookup_commit_reference(&commit_oid);
 +      item->commit = lookup_commit_reference(the_repository, &commit_oid);
        return !item->commit;
  }
  
@@@ -2246,7 -2203,6 +2246,7 @@@ static int populate_opts_cb(const char 
  static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
  {
        int i;
 +      char *strategy_opts_string;
  
        strbuf_reset(buf);
        if (!read_oneliner(buf, rebase_path_strategy(), 0))
        if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
                return;
  
 -      opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
 +      strategy_opts_string = buf->buf;
 +      if (*strategy_opts_string == ' ')
 +              strategy_opts_string++;
 +      opts->xopts_nr = split_cmdline(strategy_opts_string,
 +                                     (const char ***)&opts->xopts);
        for (i = 0; i < opts->xopts_nr; i++) {
                const char *arg = opts->xopts[i];
  
@@@ -2362,10 -2314,6 +2362,10 @@@ static int walk_revs_populate_todo(stru
                        short_commit_name(commit), subject_len, subject);
                unuse_commit_buffer(commit, commit_buffer);
        }
 +
 +      if (!todo_list->nr)
 +              return error(_("empty commit set passed"));
 +
        return 0;
  }
  
@@@ -2443,8 -2391,8 +2443,8 @@@ static int rollback_single_pick(void
  {
        struct object_id head_oid;
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
@@@ -2642,39 -2590,23 +2642,39 @@@ static int error_with_patch(struct comm
        const char *subject, int subject_len,
        struct replay_opts *opts, int exit_code, int to_amend)
  {
 -      if (make_patch(commit, opts))
 -              return -1;
 +      if (commit) {
 +              if (make_patch(commit, opts))
 +                      return -1;
 +      } else if (copy_file(rebase_path_message(),
 +                           git_path_merge_msg(the_repository), 0666))
 +              return error(_("unable to copy '%s' to '%s'"),
 +                           git_path_merge_msg(the_repository), rebase_path_message());
  
        if (to_amend) {
                if (intend_to_amend())
                        return -1;
  
 -              fprintf(stderr, "You can amend the commit now, with\n"
 -                      "\n"
 -                      "  git commit --amend %s\n"
 -                      "\n"
 -                      "Once you are satisfied with your changes, run\n"
 -                      "\n"
 -                      "  git rebase --continue\n", gpg_sign_opt_quoted(opts));
 -      } else if (exit_code)
 -              fprintf(stderr, "Could not apply %s... %.*s\n",
 -                      short_commit_name(commit), subject_len, subject);
 +              fprintf(stderr,
 +                      _("You can amend the commit now, with\n"
 +                        "\n"
 +                        "  git commit --amend %s\n"
 +                        "\n"
 +                        "Once you are satisfied with your changes, run\n"
 +                        "\n"
 +                        "  git rebase --continue\n"),
 +                      gpg_sign_opt_quoted(opts));
 +      } else if (exit_code) {
 +              if (commit)
 +                      fprintf_ln(stderr, _("Could not apply %s... %.*s"),
 +                                 short_commit_name(commit), subject_len, subject);
 +              else
 +                      /*
 +                       * We don't have the hash of the parent so
 +                       * just print the line from the todo file.
 +                       */
 +                      fprintf_ln(stderr, _("Could not merge %.*s"),
 +                                 subject_len, subject);
 +      }
  
        return exit_code;
  }
@@@ -2685,11 -2617,10 +2685,11 @@@ static int error_failed_squash(struct c
        if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
                return error(_("could not copy '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
 -      unlink(git_path_merge_msg());
 -      if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
 +      unlink(git_path_merge_msg(the_repository));
 +      if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
                return error(_("could not copy '%s' to '%s'"),
 -                           rebase_path_message(), git_path_merge_msg());
 +                           rebase_path_message(),
 +                           git_path_merge_msg(the_repository));
        return error_with_patch(commit, subject, subject_len, opts, 1, 0);
  }
  
@@@ -2702,8 -2633,6 +2702,8 @@@ static int do_exec(const char *command_
        fprintf(stderr, "Executing: %s\n", command_line);
        child_argv[0] = command_line;
        argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
 +      argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
 +                       absolute_path(get_git_work_tree()));
        status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
                                          child_env.argv);
  
@@@ -2787,7 -2716,7 +2787,7 @@@ static int do_label(const char *name, i
        struct object_id head_oid;
  
        if (len == 1 && *name == '#')
 -              return error("Illegal label name: '%.*s'", len, name);
 +              return error(_("illegal label name: '%.*s'"), len, name);
  
        strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
        strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
@@@ -2909,26 -2838,6 +2909,26 @@@ static int do_reset(const char *name, i
        return ret;
  }
  
 +static struct commit *lookup_label(const char *label, int len,
 +                                 struct strbuf *buf)
 +{
 +      struct commit *commit;
 +
 +      strbuf_reset(buf);
 +      strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
 +      commit = lookup_commit_reference_by_name(buf->buf);
 +      if (!commit) {
 +              /* fall back to non-rewritten ref or commit */
 +              strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
 +              commit = lookup_commit_reference_by_name(buf->buf);
 +      }
 +
 +      if (!commit)
 +              error(_("could not resolve '%s'"), buf->buf);
 +
 +      return commit;
 +}
 +
  static int do_merge(struct commit *commit, const char *arg, int arg_len,
                    int flags, struct replay_opts *opts)
  {
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
 +      struct commit_list *to_merge = NULL, **tail = &to_merge;
        struct merge_options o;
 -      int merge_arg_len, oneline_offset, can_fast_forward, ret;
 +      int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
        const char *p;
  
                goto leave_merge;
        }
  
 -      oneline_offset = arg_len;
 -      merge_arg_len = strcspn(arg, " \t\n");
 -      p = arg + merge_arg_len;
 -      p += strspn(p, " \t\n");
 -      if (*p == '#' && (!p[1] || isspace(p[1]))) {
 -              p += 1 + strspn(p + 1, " \t\n");
 -              oneline_offset = p - arg;
 -      } else if (p - arg < arg_len)
 -              BUG("octopus merges are not supported yet: '%s'", p);
 -
 -      strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
 -      merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 -      if (!merge_commit) {
 -              /* fall back to non-rewritten ref or commit */
 -              strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
 -              merge_commit = lookup_commit_reference_by_name(ref_name.buf);
 +      /*
 +       * For octopus merges, the arg starts with the list of revisions to be
 +       * merged. The list is optionally followed by '#' and the oneline.
 +       */
 +      merge_arg_len = oneline_offset = arg_len;
 +      for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
 +              if (!*p)
 +                      break;
 +              if (*p == '#' && (!p[1] || isspace(p[1]))) {
 +                      p += 1 + strspn(p + 1, " \t\n");
 +                      oneline_offset = p - arg;
 +                      break;
 +              }
 +              k = strcspn(p, " \t\n");
 +              if (!k)
 +                      continue;
 +              merge_commit = lookup_label(p, k, &ref_name);
 +              if (!merge_commit) {
 +                      ret = error(_("unable to parse '%.*s'"), k, p);
 +                      goto leave_merge;
 +              }
 +              tail = &commit_list_insert(merge_commit, tail)->next;
 +              p += k;
 +              merge_arg_len = p - arg;
        }
  
 -      if (!merge_commit) {
 -              ret = error(_("could not resolve '%s'"), ref_name.buf);
 +      if (!to_merge) {
 +              ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
                goto leave_merge;
        }
  
                 * "[new root]", let's simply fast-forward to the merge head.
                 */
                rollback_lock_file(&lock);
 -              ret = fast_forward_to(&merge_commit->object.oid,
 -                                     &head_commit->object.oid, 0, opts);
 +              if (to_merge->next)
 +                      ret = error(_("octopus merge cannot be executed on "
 +                                    "top of a [new root]"));
 +              else
 +                      ret = fast_forward_to(&to_merge->item->object.oid,
 +                                            &head_commit->object.oid, 0,
 +                                            opts);
                goto leave_merge;
        }
  
                write_author_script(message);
                find_commit_subject(message, &body);
                len = strlen(body);
 -              ret = write_message(body, len, git_path_merge_msg(), 0);
 +              ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
                unuse_commit_buffer(commit, message);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        } else {
                        p = arg + oneline_offset;
                        len = arg_len - oneline_offset;
                } else {
 -                      strbuf_addf(&buf, "Merge branch '%.*s'",
 +                      strbuf_addf(&buf, "Merge %s '%.*s'",
 +                                  to_merge->next ? "branches" : "branch",
                                    merge_arg_len, arg);
                        p = buf.buf;
                        len = buf.len;
                }
  
 -              ret = write_message(p, len, git_path_merge_msg(), 0);
 +              ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
                strbuf_release(&buf);
                if (ret) {
                        error_errno(_("could not write '%s'"),
 -                                  git_path_merge_msg());
 +                                  git_path_merge_msg(the_repository));
                        goto leave_merge;
                }
        }
                        &head_commit->object.oid);
  
        /*
 -       * If the merge head is different from the original one, we cannot
 +       * If any merge head is different from the original one, we cannot
         * fast-forward.
         */
        if (can_fast_forward) {
 -              struct commit_list *second_parent = commit->parents->next;
 +              struct commit_list *p = commit->parents->next;
  
 -              if (second_parent && !second_parent->next &&
 -                  oidcmp(&merge_commit->object.oid,
 -                         &second_parent->item->object.oid))
 +              for (j = to_merge; j && p; j = j->next, p = p->next)
 +                      if (oidcmp(&j->item->object.oid,
 +                                 &p->item->object.oid)) {
 +                              can_fast_forward = 0;
 +                              break;
 +                      }
 +              /*
 +               * If the number of merge heads differs from the original merge
 +               * commit, we cannot fast-forward.
 +               */
 +              if (j || p)
                        can_fast_forward = 0;
        }
  
 -      if (can_fast_forward && commit->parents->next &&
 -          !commit->parents->next->next &&
 -          !oidcmp(&commit->parents->next->item->object.oid,
 -                  &merge_commit->object.oid)) {
 +      if (can_fast_forward) {
                rollback_lock_file(&lock);
                ret = fast_forward_to(&commit->object.oid,
                                      &head_commit->object.oid, 0, opts);
                goto leave_merge;
        }
  
 +      if (to_merge->next) {
 +              /* Octopus merge */
 +              struct child_process cmd = CHILD_PROCESS_INIT;
 +
 +              if (read_env_script(&cmd.env_array)) {
 +                      const char *gpg_opt = gpg_sign_opt_quoted(opts);
 +
 +                      ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
 +                      goto leave_merge;
 +              }
 +
 +              cmd.git_cmd = 1;
 +              argv_array_push(&cmd.args, "merge");
 +              argv_array_push(&cmd.args, "-s");
 +              argv_array_push(&cmd.args, "octopus");
 +              argv_array_push(&cmd.args, "--no-edit");
 +              argv_array_push(&cmd.args, "--no-ff");
 +              argv_array_push(&cmd.args, "--no-log");
 +              argv_array_push(&cmd.args, "--no-stat");
 +              argv_array_push(&cmd.args, "-F");
 +              argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
 +              if (opts->gpg_sign)
 +                      argv_array_push(&cmd.args, opts->gpg_sign);
 +
 +              /* Add the tips to be merged */
 +              for (j = to_merge; j; j = j->next)
 +                      argv_array_push(&cmd.args,
 +                                      oid_to_hex(&j->item->object.oid));
 +
 +              strbuf_release(&ref_name);
 +              unlink(git_path_cherry_pick_head(the_repository));
 +              rollback_lock_file(&lock);
 +
 +              rollback_lock_file(&lock);
 +              ret = run_command(&cmd);
 +
 +              /* force re-reading of the cache */
 +              if (!ret && (discard_cache() < 0 || read_cache() < 0))
 +                      ret = error(_("could not read index"));
 +              goto leave_merge;
 +      }
 +
 +      merge_commit = to_merge->item;
        write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
 -                    git_path_merge_head(), 0);
 -      write_message("no-ff", 5, git_path_merge_mode(), 0);
 +                    git_path_merge_head(the_repository), 0);
 +      write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
  
        bases = get_merge_bases(head_commit, merge_commit);
        if (bases && !oidcmp(&merge_commit->object.oid,
                 * value (a negative one would indicate that the `merge`
                 * command needs to be rescheduled).
                 */
 -              ret = !!run_git_commit(git_path_merge_msg(), opts,
 +              ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
                                     run_commit_flags);
  
  leave_merge:
        strbuf_release(&ref_name);
        rollback_lock_file(&lock);
 +      free_commit_list(to_merge);
        return ret;
  }
  
@@@ -3369,27 -3214,10 +3369,27 @@@ static int pick_commits(struct todo_lis
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
 -                      } else if (res && is_rebase_i(opts) && item->commit)
 +                      } else if (res && is_rebase_i(opts) && item->commit) {
 +                              int to_amend = 0;
 +                              struct object_id oid;
 +
 +                              /*
 +                               * If we are rewording and have either
 +                               * fast-forwarded already, or are about to
 +                               * create a new root commit, we want to amend,
 +                               * otherwise we do not.
 +                               */
 +                              if (item->command == TODO_REWORD &&
 +                                  !get_oid("HEAD", &oid) &&
 +                                  (!oidcmp(&item->commit->object.oid, &oid) ||
 +                                   (opts->have_squash_onto &&
 +                                    !oidcmp(&opts->squash_onto, &oid))))
 +                                      to_amend = 1;
 +
                                return res | error_with_patch(item->commit,
 -                                      item->arg, item->arg_len, opts, res,
 -                                      item->command == TODO_REWORD);
 +                                              item->arg, item->arg_len, opts,
 +                                              res, to_amend);
 +                      }
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(item->arg + item->arg_len);
                        int saved = *end_of_arg;
@@@ -3570,8 -3398,8 +3570,8 @@@ static int continue_single_pick(void
  {
        const char *argv[] = { "commit", NULL };
  
 -      if (!file_exists(git_path_cherry_pick_head()) &&
 -          !file_exists(git_path_revert_head()))
 +      if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
 +          !file_exists(git_path_revert_head(the_repository)))
                return error(_("no cherry-pick or revert in progress"));
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
@@@ -3610,20 -3438,9 +3610,20 @@@ static int commit_staged_changes(struc
                 * the commit message and if there was a squash, let the user
                 * edit it.
                 */
 -              if (is_clean && !oidcmp(&head, &to_amend) &&
 -                  opts->current_fixup_count > 0 &&
 -                  file_exists(rebase_path_stopped_sha())) {
 +              if (!is_clean || !opts->current_fixup_count)
 +                      ; /* this is not the final fixup */
 +              else if (oidcmp(&head, &to_amend) ||
 +                       !file_exists(rebase_path_stopped_sha())) {
 +                      /* was a final fixup or squash done manually? */
 +                      if (!is_fixup(peek_command(todo_list, 0))) {
 +                              unlink(rebase_path_fixup_msg());
 +                              unlink(rebase_path_squash_msg());
 +                              unlink(rebase_path_current_fixups());
 +                              strbuf_reset(&opts->current_fixups);
 +                              opts->current_fixup_count = 0;
 +                      }
 +              } else {
 +                      /* we are in a fixup/squash chain */
                        const char *p = opts->current_fixups.buf;
                        int len = opts->current_fixups.len;
  
        }
  
        if (is_clean) {
 -              const char *cherry_pick_head = git_path_cherry_pick_head();
 +              const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
  
                if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
@@@ -3735,8 -3552,8 +3735,8 @@@ int sequencer_continue(struct replay_op
  
        if (!is_rebase_i(opts)) {
                /* Verify that the conflict has been resolved */
 -              if (file_exists(git_path_cherry_pick_head()) ||
 -                  file_exists(git_path_revert_head())) {
 +              if (file_exists(git_path_cherry_pick_head(the_repository)) ||
 +                  file_exists(git_path_revert_head(the_repository))) {
                        res = continue_single_pick();
                        if (res)
                                goto release_todo_list;
@@@ -3788,7 -3605,7 +3788,7 @@@ int sequencer_pick_revisions(struct rep
                        continue;
  
                if (!get_oid(name, &oid)) {
 -                      if (!lookup_commit_reference_gently(&oid, 1)) {
 +                      if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
                                enum object_type type = oid_object_info(the_repository,
                                                                        &oid,
                                                                        NULL);
                if (prepare_revision_walk(opts->revs))
                        return error(_("revision walk setup failed"));
                cmit = get_revision(opts->revs);
 -              if (!cmit || get_revision(opts->revs))
 -                      return error("BUG: expected exactly one commit from walk");
 +              if (!cmit)
 +                      return error(_("empty commit set passed"));
 +              if (get_revision(opts->revs))
 +                      BUG("unexpected extra commit from walk");
                return single_pick(cmit, opts);
        }
  
        return res;
  }
  
 -void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
 +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
  {
        unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
        struct strbuf sob = STRBUF_INIT;
@@@ -4059,6 -3874,7 +4059,6 @@@ static int make_script_with_merges(stru
         */
        while ((commit = get_revision(revs))) {
                struct commit_list *to_merge;
 -              int is_octopus;
                const char *p1, *p2;
                struct object_id *oid;
                int is_empty;
                        continue;
                }
  
 -              is_octopus = to_merge && to_merge->next;
 -
 -              if (is_octopus)
 -                      BUG("Octopus merges not yet supported");
 -
                /* Create a label */
                strbuf_reset(&label);
                if (skip_prefix(oneline.buf, "Merge ", &p1) &&
                strbuf_addf(&buf, "%s -C %s",
                            cmd_merge, oid_to_hex(&commit->object.oid));
  
 -              /* label the tip of merged branch */
 -              oid = &to_merge->item->object.oid;
 -              strbuf_addch(&buf, ' ');
 +              /* label the tips of merged branches */
 +              for (; to_merge; to_merge = to_merge->next) {
 +                      oid = &to_merge->item->object.oid;
 +                      strbuf_addch(&buf, ' ');
 +
 +                      if (!oidset_contains(&interesting, oid)) {
 +                              strbuf_addstr(&buf, label_oid(oid, NULL,
 +                                                            &state));
 +                              continue;
 +                      }
  
 -              if (!oidset_contains(&interesting, oid))
 -                      strbuf_addstr(&buf, label_oid(oid, NULL, &state));
 -              else {
                        tips_tail = &commit_list_insert(to_merge->item,
                                                        tips_tail)->next;
  
                entry = oidmap_get(&state.commit2label, &commit->object.oid);
  
                if (entry)
 -                      fprintf(out, "\n# Branch %s\n", entry->string);
 +                      fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
                else
                        fprintf(out, "\n");
  
@@@ -4312,9 -4129,10 +4312,9 @@@ int sequencer_add_exec_commands(const c
  {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
 -      struct todo_item *item;
        struct strbuf *buf = &todo_list.buf;
        size_t offset = 0, commands_len = strlen(commands);
 -      int i, first;
 +      int i, insert;
  
        if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
  
 -      first = 1;
 -      /* insert <commands> before every pick except the first one */
 -      for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
 -              if (item->command == TODO_PICK && !first) {
 -                      strbuf_insert(buf, item->offset_in_buf + offset,
 -                                    commands, commands_len);
 +      /*
 +       * Insert <commands> after every pick. Here, fixup/squash chains
 +       * are considered part of the pick, so we insert the commands *after*
 +       * those chains if there are any.
 +       */
 +      insert = -1;
 +      for (i = 0; i < todo_list.nr; i++) {
 +              enum todo_command command = todo_list.items[i].command;
 +
 +              if (insert >= 0) {
 +                      /* skip fixup/squash chains */
 +                      if (command == TODO_COMMENT)
 +                              continue;
 +                      else if (is_fixup(command)) {
 +                              insert = i + 1;
 +                              continue;
 +                      }
 +                      strbuf_insert(buf,
 +                                    todo_list.items[insert].offset_in_buf +
 +                                    offset, commands, commands_len);
                        offset += commands_len;
 +                      insert = -1;
                }
 -              first = 0;
 +
 +              if (command == TODO_PICK || command == TODO_MERGE)
 +                      insert = i + 1;
        }
  
 -      /* append final <commands> */
 -      strbuf_add(buf, commands, commands_len);
 +      /* insert or append final <commands> */
 +      if (insert >= 0 && insert < todo_list.nr)
 +              strbuf_insert(buf, todo_list.items[insert].offset_in_buf +
 +                            offset, commands, commands_len);
 +      else if (insert >= 0 || !offset)
 +              strbuf_add(buf, commands, commands_len);
  
        i = write_message(buf->buf, buf->len, todo_file, 0);
        todo_list_release(&todo_list);
@@@ -4441,7 -4238,6 +4441,7 @@@ static enum check_level get_missing_com
        return CHECK_IGNORE;
  }
  
 +define_commit_slab(commit_seen, unsigned char);
  /*
   * Check if the user dropped some commits by mistake
   * Behaviour determined by rebase.missingCommitsCheck.
@@@ -4455,9 -4251,6 +4455,9 @@@ int check_todo_list(void
        struct todo_list todo_list = TODO_LIST_INIT;
        struct strbuf missing = STRBUF_INIT;
        int advise_to_edit_todo = 0, res = 0, i;
 +      struct commit_seen commit_seen;
 +
 +      init_commit_seen(&commit_seen);
  
        strbuf_addstr(&todo_file, rebase_path_todo());
        if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
        for (i = 0; i < todo_list.nr; i++) {
                struct commit *commit = todo_list.items[i].commit;
                if (commit)
 -                      commit->util = (void *)1;
 +                      *commit_seen_at(&commit_seen, commit) = 1;
        }
  
        todo_list_release(&todo_list);
        for (i = todo_list.nr - 1; i >= 0; i--) {
                struct todo_item *item = todo_list.items + i;
                struct commit *commit = item->commit;
 -              if (commit && !commit->util) {
 +              if (commit && !*commit_seen_at(&commit_seen, commit)) {
                        strbuf_addf(&missing, " - %s %.*s\n",
                                    short_commit_name(commit),
                                    item->arg_len, item->arg);
 -                      commit->util = (void *)1;
 +                      *commit_seen_at(&commit_seen, commit) = 1;
                }
        }
  
                "The possible behaviours are: ignore, warn, error.\n\n"));
  
  leave_check:
 +      clear_commit_seen(&commit_seen);
        strbuf_release(&todo_file);
        todo_list_release(&todo_list);
  
@@@ -4641,8 -4433,6 +4641,8 @@@ static int subject2item_cmp(const void 
        return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
  }
  
 +define_commit_slab(commit_todo_item, struct todo_item *);
 +
  /*
   * Rearrange the todo list that has both "pick commit-id msg" and "pick
   * commit-id fixup!/squash! msg" in it so that the latter is put immediately
@@@ -4659,7 -4449,6 +4659,7 @@@ int rearrange_squash(void
        struct hashmap subject2item;
        int res = 0, rearranged = 0, *next, *tail, i;
        char **subjects;
 +      struct commit_todo_item commit_todo;
  
        if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
                return -1;
                return -1;
        }
  
 +      init_commit_todo_item(&commit_todo);
        /*
         * The hashmap maps onelines to the respective todo list index.
         *
  
                if (is_fixup(item->command)) {
                        todo_list_release(&todo_list);
 +                      clear_commit_todo_item(&commit_todo);
                        return error(_("the script was already rearranged."));
                }
  
 -              item->commit->util = item;
 +              *commit_todo_item_at(&commit_todo, item->commit) = item;
  
                parse_commit(item->commit);
                commit_buffer = get_commit_buffer(item->commit, NULL);
                        else if (!strchr(p, ' ') &&
                                 (commit2 =
                                  lookup_commit_reference_by_name(p)) &&
 -                               commit2->util)
 +                               *commit_todo_item_at(&commit_todo, commit2))
                                /* found by commit name */
 -                              i2 = (struct todo_item *)commit2->util
 +                              i2 = *commit_todo_item_at(&commit_todo, commit2)
                                        - todo_list.items;
                        else {
                                /* copy can be a prefix of the commit subject */
        hashmap_free(&subject2item, 1);
        todo_list_release(&todo_list);
  
 +      clear_commit_todo_item(&commit_todo);
        return res;
  }
index 86bba5ed7c23f78065521a9042244ea978a3a80f,352a52e59d1a25d6fe50c5001ba3c84e2fc4a30c..ff89b6341a6fc19801959058e35039778bc6577b
@@@ -119,15 -119,6 +119,15 @@@ test_expect_success 'rebase -i with exe
        )
  '
  
 +test_expect_success 'rebase -i sets work tree properly' '
 +      test_when_finished "rm -rf subdir" &&
 +      test_when_finished "test_might_fail git rebase --abort" &&
 +      mkdir subdir &&
 +      git rebase -x "(cd subdir && git rev-parse --show-toplevel)" HEAD^ \
 +              >actual &&
 +      ! grep "/subdir$" actual
 +'
 +
  test_expect_success 'rebase -i with the exec command checks tree cleanness' '
        git checkout master &&
        set_fake_editor &&
@@@ -273,18 -264,11 +273,18 @@@ test_expect_success 'retain authorship
  '
  
  test_expect_success 'retain authorship w/ conflicts' '
 +      oGIT_AUTHOR_NAME=$GIT_AUTHOR_NAME &&
 +      test_when_finished "GIT_AUTHOR_NAME=\$oGIT_AUTHOR_NAME" &&
 +
        git reset --hard twerp &&
        test_commit a conflict a conflict-a &&
        git reset --hard twerp &&
 -      GIT_AUTHOR_NAME=AttributeMe \
 +
 +      GIT_AUTHOR_NAME=AttributeMe &&
 +      export GIT_AUTHOR_NAME &&
        test_commit b conflict b conflict-b &&
 +      GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME &&
 +
        set_fake_editor &&
        test_must_fail git rebase -i conflict-a &&
        echo resolved >conflict &&
@@@ -525,7 -509,7 +525,7 @@@ test_expect_success 'interrupted squas
        one=$(git rev-parse HEAD~3) &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
 -      (echo one; echo two; echo four) > conflict &&
 +      test_write_lines one two four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
        echo resolved > conflict &&
@@@ -539,10 -523,10 +539,10 @@@ test_expect_success 'interrupted squas
        one=$(git rev-parse HEAD~3) &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 &&
 -      (echo one; echo four) > conflict &&
 +      test_write_lines one four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
 -      (echo one; echo two; echo four) > conflict &&
 +      test_write_lines one two four > conflict &&
        git add conflict &&
        test_must_fail git rebase --continue &&
        echo resolved > conflict &&
@@@ -569,16 -553,15 +569,15 @@@ test_expect_success '--continue tries t
  '
  
  test_expect_success 'aborted --continue does not squash commits after "edit"' '
-       test_when_finished "git rebase --abort" &&
        old=$(git rev-parse HEAD) &&
        test_tick &&
        set_fake_editor &&
        FAKE_LINES="edit 1" git rebase -i HEAD^ &&
        echo "edited again" > file7 &&
        git add file7 &&
-       echo all the things >>conflict &&
-       test_must_fail git rebase --continue &&
-       test $old = $(git rev-parse HEAD)
+       test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue &&
+       test $old = $(git rev-parse HEAD) &&
+       git rebase --abort
  '
  
  test_expect_success 'auto-amend only edited commits after "edit"' '
@@@ -796,15 -779,16 +795,15 @@@ test_expect_success 'always cherry-pic
        git tag original-no-ff-branch &&
        set_fake_editor &&
        git rebase -i --no-ff A &&
 -      touch empty &&
        for p in 0 1 2
        do
                test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
                git diff HEAD~$p original-no-ff-branch~$p > out &&
 -              test_cmp empty out
 +              test_must_be_empty out
        done &&
        test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
        git diff HEAD~3 original-no-ff-branch~3 > out &&
 -      test_cmp empty out
 +      test_must_be_empty out
  '
  
  test_expect_success 'set up commits with funny messages' '
@@@ -997,35 -981,7 +996,35 @@@ test_expect_success 'rebase -i --root r
        test -z "$(git show -s --format=%p HEAD^)"
  '
  
 +test_expect_success 'rebase -i --root when root has untracked file confilct' '
 +      test_when_finished "reset_rebase" &&
 +      git checkout -b failing-root-pick A &&
 +      echo x >file2 &&
 +      git rm file1 &&
 +      git commit -m "remove file 1 add file 2" &&
 +      echo z >file1 &&
 +      set_fake_editor &&
 +      test_must_fail env FAKE_LINES="1 2" git rebase -i --root &&
 +      rm file1 &&
 +      git rebase --continue &&
 +      test "$(git log -1 --format=%B)" = "remove file 1 add file 2" &&
 +      test "$(git rev-list --count HEAD)" = 2
 +'
 +
 +test_expect_success 'rebase -i --root reword root when root has untracked file conflict' '
 +      test_when_finished "reset_rebase" &&
 +      echo z>file1 &&
 +      set_fake_editor &&
 +      test_must_fail env FAKE_LINES="reword 1 2" \
 +              FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root &&
 +      rm file1 &&
 +      FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue &&
 +      test "$(git log -1 --format=%B HEAD^)" = "Reworded A" &&
 +      test "$(git rev-list --count HEAD)" = 2
 +'
 +
  test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' '
 +      git checkout reword-root-branch &&
        git reset --hard &&
        git checkout conflict-branch &&
        set_fake_editor &&
@@@ -1246,7 -1202,7 +1245,7 @@@ rebase_setup_and_clean () 
                test_might_fail git branch -D $1 &&
                test_might_fail git rebase --abort
        " &&
 -      git checkout -b $1 master
 +      git checkout -b $1 ${2:-master}
  }
  
  test_expect_success 'drop' '
@@@ -1423,24 -1379,4 +1422,24 @@@ test_expect_success 'rebase -i --gpg-si
        test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err
  '
  
 +test_expect_success 'valid author header after --root swap' '
 +      rebase_setup_and_clean author-header no-conflict-branch &&
 +      set_fake_editor &&
 +      git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
 +      git cat-file commit HEAD | grep ^author >expected &&
 +      FAKE_LINES="5 1" git rebase -i --root &&
 +      git cat-file commit HEAD^ | grep ^author >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'valid author header when author contains single quote' '
 +      rebase_setup_and_clean author-header no-conflict-branch &&
 +      set_fake_editor &&
 +      git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
 +      git cat-file commit HEAD | grep ^author >expected &&
 +      FAKE_LINES="2" git rebase -i HEAD~2 &&
 +      git cat-file commit HEAD | grep ^author >actual &&
 +      test_cmp expected actual
 +'
 +
  test_done