Merge branch 'pw/rebase-i-squash-number-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 19:41:33 +0000 (12:41 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 19:41:33 +0000 (12:41 -0700)
When "git rebase -i" is told to squash two or more commits into
one, it labeled the log message for each commit with its number.
It correctly called the first one "1st commit", but the next one
was "commit #1", which was off-by-one. This has been corrected.

* pw/rebase-i-squash-number-fix:
rebase -i: fix numbering in squash message

1  2 
sequencer.c
t/t3418-rebase-continue.sh
diff --combined sequencer.c
index c00eedd8568519433462b7d94dd5ef4d46fef96f,77d3c2346f89ce62d6c7aeaeec2ad017b31e61dc..2db52fe8004b3147febace813ac6333fe8f44c55
@@@ -2,13 -2,12 +2,13 @@@
  #include "config.h"
  #include "lockfile.h"
  #include "dir.h"
 +#include "object-store.h"
  #include "object.h"
  #include "commit.h"
  #include "sequencer.h"
  #include "tag.h"
  #include "run-command.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "utf8.h"
  #include "cache-tree.h"
  #include "diff.h"
  #include "hashmap.h"
  #include "notes-utils.h"
  #include "sigchain.h"
 +#include "unpack-trees.h"
 +#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 -56,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.
@@@ -125,19 -118,6 +125,19 @@@ static GIT_PATH_FUNC(rebase_path_stoppe
  static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
  static GIT_PATH_FUNC(rebase_path_rewritten_pending,
        "rebase-merge/rewritten-pending")
 +
 +/*
 + * The path of the file containig the OID of the "squash onto" commit, i.e.
 + * the dummy commit used for `reset [new root]`.
 + */
 +static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
 +
 +/*
 + * The path of the file listing refs that need to be deleted after the rebase
 + * finishes. This is used by the `label` command to record the need for cleanup.
 + */
 +static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
 +
  /*
   * The following files are written by git-rebase just after parsing the
   * command-line (and are only consumed, not modified, by the sequencer).
  static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
  static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
  static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
 +static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff")
  static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
  static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
  static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
@@@ -177,7 -156,6 +177,7 @@@ static int git_sequencer_config(const c
                        warning(_("invalid commit message cleanup mode '%s'"),
                                  s);
  
 +              free((char *)s);
                return status;
        }
  
@@@ -264,24 -242,9 +264,24 @@@ static const char *gpg_sign_opt_quoted(
  
  int sequencer_remove_state(struct replay_opts *opts)
  {
 -      struct strbuf dir = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT;
        int i;
  
 +      if (is_rebase_i(opts) &&
 +          strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
 +              char *p = buf.buf;
 +              while (*p) {
 +                      char *eol = strchr(p, '\n');
 +                      if (eol)
 +                              *eol = '\0';
 +                      if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
 +                              warning(_("could not delete '%s'"), p);
 +                      if (!eol)
 +                              break;
 +                      p = eol + 1;
 +              }
 +      }
 +
        free(opts->gpg_sign);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
        free(opts->xopts);
        strbuf_release(&opts->current_fixups);
  
 -      strbuf_addstr(&dir, get_dir(opts));
 -      remove_dir_recursively(&dir, 0);
 -      strbuf_release(&dir);
 +      strbuf_reset(&buf);
 +      strbuf_addstr(&buf, get_dir(opts));
 +      remove_dir_recursively(&buf, 0);
 +      strbuf_release(&buf);
  
        return 0;
  }
@@@ -307,7 -269,7 +307,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 {
  
  static const char *short_commit_name(struct commit *commit)
  {
 -      return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
 +      return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
  }
  
  static int get_message(struct commit *commit, struct commit_message *out)
@@@ -358,7 -320,7 +358,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;
        }
  
@@@ -382,14 -344,12 +382,14 @@@ static int write_message(const void *bu
        if (msg_fd < 0)
                return error_errno(_("could not lock '%s'"), filename);
        if (write_in_full(msg_fd, buf, len) < 0) {
 +              error_errno(_("could not write to '%s'"), filename);
                rollback_lock_file(&msg_file);
 -              return error_errno(_("could not write to '%s'"), filename);
 +              return -1;
        }
        if (append_eol && write(msg_fd, "\n", 1) < 0) {
 +              error_errno(_("could not write eol to '%s'"), filename);
                rollback_lock_file(&msg_file);
 -              return error_errno(_("could not write eol to '%s'"), filename);
 +              return -1;
        }
        if (commit_lock_file(&msg_file) < 0)
                return error(_("failed to finalize '%s'"), filename);
@@@ -433,7 -393,7 +433,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)
@@@ -479,8 -439,7 +479,8 @@@ static int fast_forward_to(const struc
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
 -                                 to, unborn ? &null_oid : from,
 +                                 to, unborn && !is_rebase_i(opts) ?
 +                                 &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@@ -539,8 -498,8 +539,8 @@@ static int do_recursive_merge(struct co
        o.show_rename_progress = 1;
  
        head_tree = parse_tree_indirect(head);
 -      next_tree = next ? next->tree : empty_tree();
 -      base_tree = base ? base->tree : empty_tree();
 +      next_tree = next ? get_commit_tree(next) : empty_tree();
 +      base_tree = base ? get_commit_tree(base) : empty_tree();
  
        for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
                parse_merge_opt(&o, *xopt);
        return !clean;
  }
  
 +static struct object_id *get_cache_tree_oid(void)
 +{
 +      if (!active_cache_tree)
 +              active_cache_tree = cache_tree();
 +
 +      if (!cache_tree_fully_valid(active_cache_tree))
 +              if (cache_tree_update(&the_index, 0)) {
 +                      error(_("unable to update cache tree"));
 +                      return NULL;
 +              }
 +
 +      return &active_cache_tree->oid;
 +}
 +
  static int is_index_unchanged(void)
  {
 -      struct object_id head_oid;
 +      struct object_id head_oid, *cache_tree_oid;
        struct commit *head_commit;
  
        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
        if (parse_commit(head_commit))
                return -1;
  
 -      if (!active_cache_tree)
 -              active_cache_tree = cache_tree();
 -
 -      if (!cache_tree_fully_valid(active_cache_tree))
 -              if (cache_tree_update(&the_index, 0))
 -                      return error(_("unable to update cache tree"));
 +      if (!(cache_tree_oid = get_cache_tree_oid()))
 +              return -1;
  
 -      return !oidcmp(&active_cache_tree->oid,
 -                     &head_commit->tree->object.oid);
 +      return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
  }
  
  static int write_author_script(const char *message)
@@@ -654,7 -604,6 +654,7 @@@ missing_author
                        strbuf_addch(&buf, *(message++));
                else
                        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;
@@@ -703,60 -652,6 +703,60 @@@ 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)
 +{
 +      const char *keys[] = {
 +              "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
 +      };
 +      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 = 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'"),
 +                              rebase_path_author_script(), keys[i]);
 +                      return NULL;
 +              }
 +
 +              eol = strchrnul(in, '\n');
 +              *eol = '\0';
 +              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')"),
 +                      rebase_path_author_script(), keys[i]);
 +              return NULL;
 +      }
 +
 +      /* 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;
 +}
 +
  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"
  #define AMEND_MSG   (1<<2)
  #define CLEANUP_MSG (1<<3)
  #define VERIFY_MSG  (1<<4)
 +#define CREATE_ROOT_COMMIT (1<<5)
  
  /*
   * If we are cherry-pick, and if the merge did not result in
@@@ -796,40 -690,6 +796,40 @@@ static int run_git_commit(const char *d
        struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
  
 +      if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
 +              struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
 +              const char *author = is_rebase_i(opts) ?
 +                      read_author_ident(&script) : NULL;
 +              struct object_id root_commit, *cache_tree_oid;
 +              int res = 0;
 +
 +              if (!defmsg)
 +                      BUG("root commit without message");
 +
 +              if (!(cache_tree_oid = get_cache_tree_oid()))
 +                      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)) {
@@@ -1110,7 -970,7 +1110,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))
@@@ -1185,7 -1045,7 +1185,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)) {
@@@ -1253,14 -1113,14 +1253,14 @@@ static int try_to_commit(struct strbuf 
                commit_list_insert(current_head, &parents);
        }
  
 -      if (write_cache_as_tree(tree.hash, 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 (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
 -                                            &current_head->tree->object.oid :
 -                                            &empty_tree_oid, &tree)) {
 +                                            get_commit_tree_oid(current_head) :
 +                                            the_hash_algo->empty_tree, &tree)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
        }
                goto out;
        }
  
 +      reset_ident_date();
 +
        if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
                                 oid, author, opts->gpg_sign, extra)) {
                res = error(_("failed to write commit object"));
@@@ -1320,8 -1178,7 +1320,8 @@@ static int do_commit(const char *msg_fi
  {
        int res = 1;
  
 -      if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
 +      if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
 +          !(flags & CREATE_ROOT_COMMIT)) {
                struct object_id oid;
                struct strbuf sb = STRBUF_INIT;
  
                                    &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);
@@@ -1360,12 -1217,12 +1360,12 @@@ static int is_original_commit_empty(str
                if (parse_commit(parent))
                        return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
 -              ptree_oid = &parent->tree->object.oid;
 +              ptree_oid = get_commit_tree_oid(parent);
        } else {
                ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
  
 -      return !oidcmp(ptree_oid, &commit->tree->object.oid);
 +      return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
  }
  
  /*
@@@ -1421,9 -1278,6 +1421,9 @@@ enum todo_command 
        TODO_SQUASH,
        /* commands that do something else than handling a single commit */
        TODO_EXEC,
 +      TODO_LABEL,
 +      TODO_RESET,
 +      TODO_MERGE,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP,
        TODO_DROP,
@@@ -1442,9 -1296,6 +1442,9 @@@ static struct 
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
 +      { 'l', "label" },
 +      { 't', "reset" },
 +      { 'm', "merge" },
        { 0,   "noop" },
        { 'd', "drop" },
        { 0,   NULL }
@@@ -1454,7 -1305,7 +1454,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)
@@@ -1474,22 -1325,6 +1474,22 @@@ static int is_fixup(enum todo_command c
        return command == TODO_FIXUP || command == TODO_SQUASH;
  }
  
 +/* Does this command create a (non-merge) commit? */
 +static int is_pick_or_similar(enum todo_command command)
 +{
 +      switch (command) {
 +      case TODO_PICK:
 +      case TODO_REVERT:
 +      case TODO_EDIT:
 +      case TODO_REWORD:
 +      case TODO_FIXUP:
 +      case TODO_SQUASH:
 +              return 1;
 +      default:
 +              return 0;
 +      }
 +}
 +
  static int update_squash_messages(enum todo_command command,
                struct commit *commit, struct replay_opts *opts)
  {
  
                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
@@@ -1623,7 -1458,7 +1623,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.hash, 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);
 -              if (unborn)
 +              /* Do we want to generate a root commit? */
 +              if (is_pick_or_similar(command) && opts->have_squash_onto &&
 +                  !oidcmp(&head, &opts->squash_onto)) {
 +                      if (is_fixup(command))
 +                              return error(_("cannot fixup root commit"));
 +                      flags |= CREATE_ROOT_COMMIT;
 +                      unborn = 1;
 +              } else if (unborn)
                        oidcpy(&head, the_hash_algo->empty_tree);
 -              if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
 +              if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD",
                                       NULL, 0))
                        return error_dirty_index(opts);
        }
                        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;
                }
        }
  
 -      if (opts->signoff)
 +      if (opts->signoff && !is_fixup(command))
                append_signoff(&msgbuf, 0, 0);
  
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
                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);
@@@ -1873,6 -1700,8 +1873,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;
  }
  
@@@ -1896,14 -1725,9 +1896,14 @@@ static int read_and_refresh_cache(struc
        return 0;
  }
  
 +enum todo_item_flags {
 +      TODO_EDIT_MERGE_MSG = 1
 +};
 +
  struct todo_item {
        enum todo_command command;
        struct commit *commit;
 +      unsigned int flags;
        const char *arg;
        int arg_len;
        size_t offset_in_buf;
@@@ -1938,8 -1762,6 +1938,8 @@@ static int parse_insn_line(struct todo_
        char *end_of_object_name;
        int i, saved, status, padding;
  
 +      item->flags = 0;
 +
        /* left-trim */
        bol += strspn(bol, " \t");
  
                return error(_("missing arguments for %s"),
                             command_to_string(item->command));
  
 -      if (item->command == TODO_EXEC) {
 +      if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
 +          item->command == TODO_RESET) {
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
        }
  
 +      if (item->command == TODO_MERGE) {
 +              if (skip_prefix(bol, "-C", &bol))
 +                      bol += strspn(bol, " \t");
 +              else if (skip_prefix(bol, "-c", &bol)) {
 +                      bol += strspn(bol, " \t");
 +                      item->flags |= TODO_EDIT_MERGE_MSG;
 +              } else {
 +                      item->flags |= TODO_EDIT_MERGE_MSG;
 +                      item->commit = NULL;
 +                      item->arg = bol;
 +                      item->arg_len = (int)(eol - bol);
 +                      return 0;
 +              }
 +      }
 +
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
        if (status < 0)
                return -1;
  
 -      item->commit = lookup_commit_reference(&commit_oid);
 +      item->commit = lookup_commit_reference(the_repository, &commit_oid);
        return !item->commit;
  }
  
@@@ -2065,23 -1871,6 +2065,23 @@@ static int count_commands(struct todo_l
        return count;
  }
  
 +static int get_item_line_offset(struct todo_list *todo_list, int index)
 +{
 +      return index < todo_list->nr ?
 +              todo_list->items[index].offset_in_buf : todo_list->buf.len;
 +}
 +
 +static const char *get_item_line(struct todo_list *todo_list, int index)
 +{
 +      return todo_list->buf.buf + get_item_line_offset(todo_list, index);
 +}
 +
 +static int get_item_line_length(struct todo_list *todo_list, int index)
 +{
 +      return get_item_line_offset(todo_list, index + 1)
 +              -  get_item_line_offset(todo_list, index);
 +}
 +
  static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
  {
        int fd;
@@@ -2214,7 -2003,6 +2214,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];
  
@@@ -2262,11 -2046,6 +2262,11 @@@ static int read_populate_opts(struct re
                if (file_exists(rebase_path_verbose()))
                        opts->verbose = 1;
  
 +              if (file_exists(rebase_path_signoff())) {
 +                      opts->allow_ff = 0;
 +                      opts->signoff = 1;
 +              }
 +
                read_strategy_opts(opts, &buf);
                strbuf_release(&buf);
  
                        }
                }
  
 +              if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
 +                      if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
 +                              return error(_("unusable squash-onto"));
 +                      opts->have_squash_onto = 1;
 +              }
 +
                return 0;
        }
  
@@@ -2330,10 -2103,6 +2330,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;
  }
  
@@@ -2363,9 -2132,9 +2363,9 @@@ static int save_head(const char *head
        written = write_in_full(fd, buf.buf, buf.len);
        strbuf_release(&buf);
        if (written < 0) {
 +              error_errno(_("could not write to '%s'"), git_path_head_file());
                rollback_lock_file(&head_lock);
 -              return error_errno(_("could not write to '%s'"),
 -                                 git_path_head_file());
 +              return -1;
        }
        if (commit_lock_file(&head_lock) < 0)
                return error(_("failed to finalize '%s'"), git_path_head_file());
@@@ -2411,8 -2180,8 +2411,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"));
@@@ -2486,27 -2255,29 +2486,27 @@@ static int save_todo(struct todo_list *
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
                return error_errno(_("could not lock '%s'"), todo_path);
 -      offset = next < todo_list->nr ?
 -              todo_list->items[next].offset_in_buf : todo_list->buf.len;
 +      offset = get_item_line_offset(todo_list, next);
        if (write_in_full(fd, todo_list->buf.buf + offset,
                        todo_list->buf.len - offset) < 0)
                return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
                return error(_("failed to finalize '%s'"), todo_path);
  
 -      if (is_rebase_i(opts)) {
 -              const char *done_path = rebase_path_done();
 -              int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
 -              int prev_offset = !next ? 0 :
 -                      todo_list->items[next - 1].offset_in_buf;
 +      if (is_rebase_i(opts) && next > 0) {
 +              const char *done = rebase_path_done();
 +              int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
 +              int ret = 0;
  
 -              if (fd >= 0 && offset > prev_offset &&
 -                  write_in_full(fd, todo_list->buf.buf + prev_offset,
 -                                offset - prev_offset) < 0) {
 -                      close(fd);
 -                      return error_errno(_("could not write to '%s'"),
 -                                         done_path);
 -              }
 -              if (fd >= 0)
 -                      close(fd);
 +              if (fd < 0)
 +                      return 0;
 +              if (write_in_full(fd, get_item_line(todo_list, next - 1),
 +                                get_item_line_length(todo_list, next - 1))
 +                  < 0)
 +                      ret = error_errno(_("could not write to '%s'"), done);
 +              if (close(fd) < 0)
 +                      ret = error_errno(_("failed to finalize '%s'"), done);
 +              return ret;
        }
        return 0;
  }
@@@ -2617,17 -2388,15 +2617,17 @@@ static int error_with_patch(struct comm
                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));
 +              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",
 +              fprintf_ln(stderr, _("Could not apply %s... %.*s"),
                        short_commit_name(commit), subject_len, subject);
  
        return exit_code;
@@@ -2639,11 -2408,10 +2639,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);
  }
  
@@@ -2656,8 -2424,6 +2656,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);
  
        return status;
  }
  
 +static int safe_append(const char *filename, const char *fmt, ...)
 +{
 +      va_list ap;
 +      struct lock_file lock = LOCK_INIT;
 +      int fd = hold_lock_file_for_update(&lock, filename,
 +                                         LOCK_REPORT_ON_ERROR);
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      if (fd < 0)
 +              return -1;
 +
 +      if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {
 +              error_errno(_("could not read '%s'"), filename);
 +              rollback_lock_file(&lock);
 +              return -1;
 +      }
 +      strbuf_complete(&buf, '\n');
 +      va_start(ap, fmt);
 +      strbuf_vaddf(&buf, fmt, ap);
 +      va_end(ap);
 +
 +      if (write_in_full(fd, buf.buf, buf.len) < 0) {
 +              error_errno(_("could not write to '%s'"), filename);
 +              strbuf_release(&buf);
 +              rollback_lock_file(&lock);
 +              return -1;
 +      }
 +      if (commit_lock_file(&lock) < 0) {
 +              strbuf_release(&buf);
 +              rollback_lock_file(&lock);
 +              return error(_("failed to finalize '%s'"), filename);
 +      }
 +
 +      strbuf_release(&buf);
 +      return 0;
 +}
 +
 +static int do_label(const char *name, int len)
 +{
 +      struct ref_store *refs = get_main_ref_store(the_repository);
 +      struct ref_transaction *transaction;
 +      struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
 +      struct strbuf msg = STRBUF_INIT;
 +      int ret = 0;
 +      struct object_id head_oid;
 +
 +      if (len == 1 && *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);
 +
 +      transaction = ref_store_transaction_begin(refs, &err);
 +      if (!transaction) {
 +              error("%s", err.buf);
 +              ret = -1;
 +      } else if (get_oid("HEAD", &head_oid)) {
 +              error(_("could not read HEAD"));
 +              ret = -1;
 +      } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
 +                                        NULL, 0, msg.buf, &err) < 0 ||
 +                 ref_transaction_commit(transaction, &err)) {
 +              error("%s", err.buf);
 +              ret = -1;
 +      }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      strbuf_release(&msg);
 +
 +      if (!ret)
 +              ret = safe_append(rebase_path_refs_to_delete(),
 +                                "%s\n", ref_name.buf);
 +      strbuf_release(&ref_name);
 +
 +      return ret;
 +}
 +
 +static const char *reflog_message(struct replay_opts *opts,
 +      const char *sub_action, const char *fmt, ...);
 +
 +static int do_reset(const char *name, int len, struct replay_opts *opts)
 +{
 +      struct strbuf ref_name = STRBUF_INIT;
 +      struct object_id oid;
 +      struct lock_file lock = LOCK_INIT;
 +      struct tree_desc desc;
 +      struct tree *tree;
 +      struct unpack_trees_options unpack_tree_opts;
 +      int ret = 0, i;
 +
 +      if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
 +              return -1;
 +
 +      if (len == 10 && !strncmp("[new root]", name, len)) {
 +              if (!opts->have_squash_onto) {
 +                      const char *hex;
 +                      if (commit_tree("", 0, the_hash_algo->empty_tree,
 +                                      NULL, &opts->squash_onto,
 +                                      NULL, NULL))
 +                              return error(_("writing fake root commit"));
 +                      opts->have_squash_onto = 1;
 +                      hex = oid_to_hex(&opts->squash_onto);
 +                      if (write_message(hex, strlen(hex),
 +                                        rebase_path_squash_onto(), 0))
 +                              return error(_("writing squash-onto"));
 +              }
 +              oidcpy(&oid, &opts->squash_onto);
 +      } else {
 +              /* Determine the length of the label */
 +              for (i = 0; i < len; i++)
 +                      if (isspace(name[i]))
 +                              len = i;
 +
 +              strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
 +              if (get_oid(ref_name.buf, &oid) &&
 +                  get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
 +                      error(_("could not read '%s'"), ref_name.buf);
 +                      rollback_lock_file(&lock);
 +                      strbuf_release(&ref_name);
 +                      return -1;
 +              }
 +      }
 +
 +      memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
 +      setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
 +      unpack_tree_opts.head_idx = 1;
 +      unpack_tree_opts.src_index = &the_index;
 +      unpack_tree_opts.dst_index = &the_index;
 +      unpack_tree_opts.fn = oneway_merge;
 +      unpack_tree_opts.merge = 1;
 +      unpack_tree_opts.update = 1;
 +
 +      if (read_cache_unmerged()) {
 +              rollback_lock_file(&lock);
 +              strbuf_release(&ref_name);
 +              return error_resolve_conflict(_(action_name(opts)));
 +      }
 +
 +      if (!fill_tree_descriptor(&desc, &oid)) {
 +              error(_("failed to find tree of %s"), oid_to_hex(&oid));
 +              rollback_lock_file(&lock);
 +              free((void *)desc.buffer);
 +              strbuf_release(&ref_name);
 +              return -1;
 +      }
 +
 +      if (unpack_trees(1, &desc, &unpack_tree_opts)) {
 +              rollback_lock_file(&lock);
 +              free((void *)desc.buffer);
 +              strbuf_release(&ref_name);
 +              return -1;
 +      }
 +
 +      tree = parse_tree_indirect(&oid);
 +      prime_cache_tree(&the_index, tree);
 +
 +      if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
 +              ret = error(_("could not write index"));
 +      free((void *)desc.buffer);
 +
 +      if (!ret)
 +              ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
 +                                              len, name), "HEAD", &oid,
 +                               NULL, 0, UPDATE_REFS_MSG_ON_ERR);
 +
 +      strbuf_release(&ref_name);
 +      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)
 +{
 +      int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
 +              EDIT_MSG | VERIFY_MSG : 0;
 +      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, k;
 +      static struct lock_file lock;
 +      const char *p;
 +
 +      if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
 +              ret = -1;
 +              goto leave_merge;
 +      }
 +
 +      head_commit = lookup_commit_reference_by_name("HEAD");
 +      if (!head_commit) {
 +              ret = error(_("cannot merge without a current revision"));
 +              goto leave_merge;
 +      }
 +
 +      /*
 +       * 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 (!to_merge) {
 +              ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
 +              goto leave_merge;
 +      }
 +
 +      if (opts->have_squash_onto &&
 +          !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
 +              /*
 +               * When the user tells us to "merge" something into a
 +               * "[new root]", let's simply fast-forward to the merge head.
 +               */
 +              rollback_lock_file(&lock);
 +              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;
 +      }
 +
 +      if (commit) {
 +              const char *message = get_commit_buffer(commit, NULL);
 +              const char *body;
 +              int len;
 +
 +              if (!message) {
 +                      ret = error(_("could not get commit message of '%s'"),
 +                                  oid_to_hex(&commit->object.oid));
 +                      goto leave_merge;
 +              }
 +              write_author_script(message);
 +              find_commit_subject(message, &body);
 +              len = strlen(body);
 +              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(the_repository));
 +                      goto leave_merge;
 +              }
 +      } else {
 +              struct strbuf buf = STRBUF_INIT;
 +              int len;
 +
 +              strbuf_addf(&buf, "author %s", git_author_info(0));
 +              write_author_script(buf.buf);
 +              strbuf_reset(&buf);
 +
 +              if (oneline_offset < arg_len) {
 +                      p = arg + oneline_offset;
 +                      len = arg_len - oneline_offset;
 +              } else {
 +                      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(the_repository), 0);
 +              strbuf_release(&buf);
 +              if (ret) {
 +                      error_errno(_("could not write '%s'"),
 +                                  git_path_merge_msg(the_repository));
 +                      goto leave_merge;
 +              }
 +      }
 +
 +      /*
 +       * If HEAD is not identical to the first parent of the original merge
 +       * commit, we cannot fast-forward.
 +       */
 +      can_fast_forward = opts->allow_ff && commit && commit->parents &&
 +              !oidcmp(&commit->parents->item->object.oid,
 +                      &head_commit->object.oid);
 +
 +      /*
 +       * If any merge head is different from the original one, we cannot
 +       * fast-forward.
 +       */
 +      if (can_fast_forward) {
 +              struct commit_list *p = commit->parents->next;
 +
 +              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) {
 +              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(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,
 +                           &bases->item->object.oid)) {
 +              ret = 0;
 +              /* skip merging an ancestor of HEAD */
 +              goto leave_merge;
 +      }
 +
 +      for (j = bases; j; j = j->next)
 +              commit_list_insert(j->item, &reversed);
 +      free_commit_list(bases);
 +
 +      read_cache();
 +      init_merge_options(&o);
 +      o.branch1 = "HEAD";
 +      o.branch2 = ref_name.buf;
 +      o.buffer_output = 2;
 +
 +      ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
 +      if (ret <= 0)
 +              fputs(o.obuf.buf, stdout);
 +      strbuf_release(&o.obuf);
 +      if (ret < 0) {
 +              error(_("could not even attempt to merge '%.*s'"),
 +                    merge_arg_len, arg);
 +              goto leave_merge;
 +      }
 +      /*
 +       * The return value of merge_recursive() is 1 on clean, and 0 on
 +       * unclean merge.
 +       *
 +       * Let's reverse that, so that do_merge() returns 0 upon success and
 +       * 1 upon failed merge (keeping the return value -1 for the cases where
 +       * we will want to reschedule the `merge` command).
 +       */
 +      ret = !ret;
 +
 +      if (active_cache_changed &&
 +          write_locked_index(&the_index, &lock, COMMIT_LOCK)) {
 +              ret = error(_("merge: Unable to write new index file"));
 +              goto leave_merge;
 +      }
 +
 +      rollback_lock_file(&lock);
 +      if (ret)
 +              rerere(opts->allow_rerere_auto);
 +      else
 +              /*
 +               * In case of problems, we now want to return a positive
 +               * value (a negative one would indicate that the `merge`
 +               * command needs to be rescheduled).
 +               */
 +              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;
 +}
 +
  static int is_final_fixup(struct todo_list *todo_list)
  {
        int i = todo_list->current;
@@@ -3239,20 -2550,9 +3239,20 @@@ static const char *reflog_message(struc
        return buf.buf;
  }
  
 +static const char rescheduled_advice[] =
 +N_("Could not execute the todo command\n"
 +"\n"
 +"    %.*s"
 +"\n"
 +"It has been rescheduled; To edit the command before continuing, please\n"
 +"edit the todo list first:\n"
 +"\n"
 +"    git rebase --edit-todo\n"
 +"    git rebase --continue\n");
 +
  static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
  {
 -      int res = 0;
 +      int res = 0, reschedule = 0;
  
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
                                        opts, is_final_fixup(todo_list));
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
 +                              advise(_(rescheduled_advice),
 +                                     get_item_line_length(todo_list,
 +                                                          todo_list->current),
 +                                     get_item_line(todo_list,
 +                                                   todo_list->current));
                                todo_list->current--;
                                if (save_todo(todo_list, opts))
                                        return -1;
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
 -                      } else if (res && is_rebase_i(opts))
 +                      } 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;
                                /* `current` will be incremented below */
                                todo_list->current = -1;
                        }
 +              } else if (item->command == TODO_LABEL) {
 +                      if ((res = do_label(item->arg, item->arg_len)))
 +                              reschedule = 1;
 +              } else if (item->command == TODO_RESET) {
 +                      if ((res = do_reset(item->arg, item->arg_len, opts)))
 +                              reschedule = 1;
 +              } else if (item->command == TODO_MERGE) {
 +                      if ((res = do_merge(item->commit,
 +                                          item->arg, item->arg_len,
 +                                          item->flags, opts)) < 0)
 +                              reschedule = 1;
 +                      else if (item->commit)
 +                              record_in_rewritten(&item->commit->object.oid,
 +                                                  peek_command(todo_list, 1));
 +                      if (res > 0)
 +                              /* failed with merge conflicts */
 +                              return error_with_patch(item->commit,
 +                                                      item->arg,
 +                                                      item->arg_len, opts,
 +                                                      res, 0);
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
  
 +              if (reschedule) {
 +                      advise(_(rescheduled_advice),
 +                             get_item_line_length(todo_list,
 +                                                  todo_list->current),
 +                             get_item_line(todo_list, todo_list->current));
 +                      todo_list->current--;
 +                      if (save_todo(todo_list, opts))
 +                              return -1;
 +                      if (item->commit)
 +                              return error_with_patch(item->commit,
 +                                                      item->arg,
 +                                                      item->arg_len, opts,
 +                                                      res, 0);
 +              }
 +
                todo_list->current++;
                if (res)
                        return res;
@@@ -3524,8 -2767,8 +3524,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);
  }
@@@ -3628,7 -2871,7 +3628,7 @@@ static int commit_staged_changes(struc
        }
  
        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"));
@@@ -3678,8 -2921,8 +3678,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;
@@@ -3731,10 -2974,8 +3731,10 @@@ int sequencer_pick_revisions(struct rep
                        continue;
  
                if (!get_oid(name, &oid)) {
 -                      if (!lookup_commit_reference_gently(&oid, 1)) {
 -                              enum object_type type = sha1_object_info(oid.hash, NULL);
 +                      if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
 +                              enum object_type type = oid_object_info(the_repository,
 +                                                                      &oid,
 +                                                                      NULL);
                                return error(_("%s: can't cherry-pick a %s"),
                                        name, type_name(type));
                        }
                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);
        }
  
@@@ -3846,346 -3085,6 +3846,346 @@@ void append_signoff(struct strbuf *msgb
        strbuf_release(&sob);
  }
  
 +struct labels_entry {
 +      struct hashmap_entry entry;
 +      char label[FLEX_ARRAY];
 +};
 +
 +static int labels_cmp(const void *fndata, const struct labels_entry *a,
 +                    const struct labels_entry *b, const void *key)
 +{
 +      return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
 +}
 +
 +struct string_entry {
 +      struct oidmap_entry entry;
 +      char string[FLEX_ARRAY];
 +};
 +
 +struct label_state {
 +      struct oidmap commit2label;
 +      struct hashmap labels;
 +      struct strbuf buf;
 +};
 +
 +static const char *label_oid(struct object_id *oid, const char *label,
 +                           struct label_state *state)
 +{
 +      struct labels_entry *labels_entry;
 +      struct string_entry *string_entry;
 +      struct object_id dummy;
 +      size_t len;
 +      int i;
 +
 +      string_entry = oidmap_get(&state->commit2label, oid);
 +      if (string_entry)
 +              return string_entry->string;
 +
 +      /*
 +       * For "uninteresting" commits, i.e. commits that are not to be
 +       * rebased, and which can therefore not be labeled, we use a unique
 +       * abbreviation of the commit name. This is slightly more complicated
 +       * than calling find_unique_abbrev() because we also need to make
 +       * sure that the abbreviation does not conflict with any other
 +       * label.
 +       *
 +       * We disallow "interesting" commits to be labeled by a string that
 +       * is a valid full-length hash, to ensure that we always can find an
 +       * abbreviation for any uninteresting commit's names that does not
 +       * clash with any other label.
 +       */
 +      if (!label) {
 +              char *p;
 +
 +              strbuf_reset(&state->buf);
 +              strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
 +              label = p = state->buf.buf;
 +
 +              find_unique_abbrev_r(p, oid, default_abbrev);
 +
 +              /*
 +               * We may need to extend the abbreviated hash so that there is
 +               * no conflicting label.
 +               */
 +              if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {
 +                      size_t i = strlen(p) + 1;
 +
 +                      oid_to_hex_r(p, oid);
 +                      for (; i < GIT_SHA1_HEXSZ; i++) {
 +                              char save = p[i];
 +                              p[i] = '\0';
 +                              if (!hashmap_get_from_hash(&state->labels,
 +                                                         strihash(p), p))
 +                                      break;
 +                              p[i] = save;
 +                      }
 +              }
 +      } else if (((len = strlen(label)) == the_hash_algo->hexsz &&
 +                  !get_oid_hex(label, &dummy)) ||
 +                 (len == 1 && *label == '#') ||
 +                 hashmap_get_from_hash(&state->labels,
 +                                       strihash(label), label)) {
 +              /*
 +               * If the label already exists, or if the label is a valid full
 +               * OID, or the label is a '#' (which we use as a separator
 +               * between merge heads and oneline), we append a dash and a
 +               * number to make it unique.
 +               */
 +              struct strbuf *buf = &state->buf;
 +
 +              strbuf_reset(buf);
 +              strbuf_add(buf, label, len);
 +
 +              for (i = 2; ; i++) {
 +                      strbuf_setlen(buf, len);
 +                      strbuf_addf(buf, "-%d", i);
 +                      if (!hashmap_get_from_hash(&state->labels,
 +                                                 strihash(buf->buf),
 +                                                 buf->buf))
 +                              break;
 +              }
 +
 +              label = buf->buf;
 +      }
 +
 +      FLEX_ALLOC_STR(labels_entry, label, label);
 +      hashmap_entry_init(labels_entry, strihash(label));
 +      hashmap_add(&state->labels, labels_entry);
 +
 +      FLEX_ALLOC_STR(string_entry, string, label);
 +      oidcpy(&string_entry->entry.oid, oid);
 +      oidmap_put(&state->commit2label, string_entry);
 +
 +      return string_entry->string;
 +}
 +
 +static int make_script_with_merges(struct pretty_print_context *pp,
 +                                 struct rev_info *revs, FILE *out,
 +                                 unsigned flags)
 +{
 +      int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 +      int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 +      struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
 +      struct strbuf label = STRBUF_INIT;
 +      struct commit_list *commits = NULL, **tail = &commits, *iter;
 +      struct commit_list *tips = NULL, **tips_tail = &tips;
 +      struct commit *commit;
 +      struct oidmap commit2todo = OIDMAP_INIT;
 +      struct string_entry *entry;
 +      struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
 +              shown = OIDSET_INIT;
 +      struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
 +
 +      int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
 +      const char *cmd_pick = abbr ? "p" : "pick",
 +              *cmd_label = abbr ? "l" : "label",
 +              *cmd_reset = abbr ? "t" : "reset",
 +              *cmd_merge = abbr ? "m" : "merge";
 +
 +      oidmap_init(&commit2todo, 0);
 +      oidmap_init(&state.commit2label, 0);
 +      hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
 +      strbuf_init(&state.buf, 32);
 +
 +      if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
 +              struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 +              FLEX_ALLOC_STR(entry, string, "onto");
 +              oidcpy(&entry->entry.oid, oid);
 +              oidmap_put(&state.commit2label, entry);
 +      }
 +
 +      /*
 +       * First phase:
 +       * - get onelines for all commits
 +       * - gather all branch tips (i.e. 2nd or later parents of merges)
 +       * - label all branch tips
 +       */
 +      while ((commit = get_revision(revs))) {
 +              struct commit_list *to_merge;
 +              const char *p1, *p2;
 +              struct object_id *oid;
 +              int is_empty;
 +
 +              tail = &commit_list_insert(commit, tail)->next;
 +              oidset_insert(&interesting, &commit->object.oid);
 +
 +              is_empty = is_original_commit_empty(commit);
 +              if (!is_empty && (commit->object.flags & PATCHSAME))
 +                      continue;
 +
 +              strbuf_reset(&oneline);
 +              pretty_print_commit(pp, commit, &oneline);
 +
 +              to_merge = commit->parents ? commit->parents->next : NULL;
 +              if (!to_merge) {
 +                      /* non-merge commit: easy case */
 +                      strbuf_reset(&buf);
 +                      if (!keep_empty && is_empty)
 +                              strbuf_addf(&buf, "%c ", comment_line_char);
 +                      strbuf_addf(&buf, "%s %s %s", cmd_pick,
 +                                  oid_to_hex(&commit->object.oid),
 +                                  oneline.buf);
 +
 +                      FLEX_ALLOC_STR(entry, string, buf.buf);
 +                      oidcpy(&entry->entry.oid, &commit->object.oid);
 +                      oidmap_put(&commit2todo, entry);
 +
 +                      continue;
 +              }
 +
 +              /* Create a label */
 +              strbuf_reset(&label);
 +              if (skip_prefix(oneline.buf, "Merge ", &p1) &&
 +                  (p1 = strchr(p1, '\'')) &&
 +                  (p2 = strchr(++p1, '\'')))
 +                      strbuf_add(&label, p1, p2 - p1);
 +              else if (skip_prefix(oneline.buf, "Merge pull request ",
 +                                   &p1) &&
 +                       (p1 = strstr(p1, " from ")))
 +                      strbuf_addstr(&label, p1 + strlen(" from "));
 +              else
 +                      strbuf_addbuf(&label, &oneline);
 +
 +              for (p1 = label.buf; *p1; p1++)
 +                      if (isspace(*p1))
 +                              *(char *)p1 = '-';
 +
 +              strbuf_reset(&buf);
 +              strbuf_addf(&buf, "%s -C %s",
 +                          cmd_merge, oid_to_hex(&commit->object.oid));
 +
 +              /* 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;
 +                      }
 +
 +                      tips_tail = &commit_list_insert(to_merge->item,
 +                                                      tips_tail)->next;
 +
 +                      strbuf_addstr(&buf, label_oid(oid, label.buf, &state));
 +              }
 +              strbuf_addf(&buf, " # %s", oneline.buf);
 +
 +              FLEX_ALLOC_STR(entry, string, buf.buf);
 +              oidcpy(&entry->entry.oid, &commit->object.oid);
 +              oidmap_put(&commit2todo, entry);
 +      }
 +
 +      /*
 +       * Second phase:
 +       * - label branch points
 +       * - add HEAD to the branch tips
 +       */
 +      for (iter = commits; iter; iter = iter->next) {
 +              struct commit_list *parent = iter->item->parents;
 +              for (; parent; parent = parent->next) {
 +                      struct object_id *oid = &parent->item->object.oid;
 +                      if (!oidset_contains(&interesting, oid))
 +                              continue;
 +                      if (!oidset_contains(&child_seen, oid))
 +                              oidset_insert(&child_seen, oid);
 +                      else
 +                              label_oid(oid, "branch-point", &state);
 +              }
 +
 +              /* Add HEAD as implict "tip of branch" */
 +              if (!iter->next)
 +                      tips_tail = &commit_list_insert(iter->item,
 +                                                      tips_tail)->next;
 +      }
 +
 +      /*
 +       * Third phase: output the todo list. This is a bit tricky, as we
 +       * want to avoid jumping back and forth between revisions. To
 +       * accomplish that goal, we walk backwards from the branch tips,
 +       * gathering commits not yet shown, reversing the list on the fly,
 +       * then outputting that list (labeling revisions as needed).
 +       */
 +      fprintf(out, "%s onto\n", cmd_label);
 +      for (iter = tips; iter; iter = iter->next) {
 +              struct commit_list *list = NULL, *iter2;
 +
 +              commit = iter->item;
 +              if (oidset_contains(&shown, &commit->object.oid))
 +                      continue;
 +              entry = oidmap_get(&state.commit2label, &commit->object.oid);
 +
 +              if (entry)
 +                      fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
 +              else
 +                      fprintf(out, "\n");
 +
 +              while (oidset_contains(&interesting, &commit->object.oid) &&
 +                     !oidset_contains(&shown, &commit->object.oid)) {
 +                      commit_list_insert(commit, &list);
 +                      if (!commit->parents) {
 +                              commit = NULL;
 +                              break;
 +                      }
 +                      commit = commit->parents->item;
 +              }
 +
 +              if (!commit)
 +                      fprintf(out, "%s %s\n", cmd_reset,
 +                              rebase_cousins ? "onto" : "[new root]");
 +              else {
 +                      const char *to = NULL;
 +
 +                      entry = oidmap_get(&state.commit2label,
 +                                         &commit->object.oid);
 +                      if (entry)
 +                              to = entry->string;
 +                      else if (!rebase_cousins)
 +                              to = label_oid(&commit->object.oid, NULL,
 +                                             &state);
 +
 +                      if (!to || !strcmp(to, "onto"))
 +                              fprintf(out, "%s onto\n", cmd_reset);
 +                      else {
 +                              strbuf_reset(&oneline);
 +                              pretty_print_commit(pp, commit, &oneline);
 +                              fprintf(out, "%s %s # %s\n",
 +                                      cmd_reset, to, oneline.buf);
 +                      }
 +              }
 +
 +              for (iter2 = list; iter2; iter2 = iter2->next) {
 +                      struct object_id *oid = &iter2->item->object.oid;
 +                      entry = oidmap_get(&commit2todo, oid);
 +                      /* only show if not already upstream */
 +                      if (entry)
 +                              fprintf(out, "%s\n", entry->string);
 +                      entry = oidmap_get(&state.commit2label, oid);
 +                      if (entry)
 +                              fprintf(out, "%s %s\n",
 +                                      cmd_label, entry->string);
 +                      oidset_insert(&shown, oid);
 +              }
 +
 +              free_commit_list(list);
 +      }
 +
 +      free_commit_list(commits);
 +      free_commit_list(tips);
 +
 +      strbuf_release(&label);
 +      strbuf_release(&oneline);
 +      strbuf_release(&buf);
 +
 +      oidmap_free(&commit2todo, 1);
 +      oidmap_free(&state.commit2label, 1);
 +      hashmap_free(&state.labels, 1);
 +      strbuf_release(&state.buf);
 +
 +      return 0;
 +}
 +
  int sequencer_make_script(FILE *out, int argc, const char **argv,
                          unsigned flags)
  {
        struct commit *commit;
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 +      int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
  
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
 -      revs.max_parents = 1;
 -      revs.cherry_pick = 1;
 +      if (!rebase_merges)
 +              revs.max_parents = 1;
 +      revs.cherry_mark = 1;
        revs.limited = 1;
        revs.reverse = 1;
        revs.right_only = 1;
        if (prepare_revision_walk(&revs) < 0)
                return error(_("make_script: error preparing revisions"));
  
 +      if (rebase_merges)
 +              return make_script_with_merges(&pp, &revs, out, flags);
 +
        while ((commit = get_revision(&revs))) {
 +              int is_empty  = is_original_commit_empty(commit);
 +
 +              if (!is_empty && (commit->object.flags & PATCHSAME))
 +                      continue;
                strbuf_reset(&buf);
 -              if (!keep_empty && is_original_commit_empty(commit))
 +              if (!keep_empty && is_empty)
                        strbuf_addf(&buf, "%c ", comment_line_char);
                strbuf_addf(&buf, "%s %s ", insn,
                            oid_to_hex(&commit->object.oid));
@@@ -4255,9 -3145,10 +4255,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);
@@@ -4342,16 -3212,8 +4342,16 @@@ int transform_todos(unsigned flags
                                          short_commit_name(item->commit) :
                                          oid_to_hex(&item->commit->object.oid);
  
 +                      if (item->command == TODO_MERGE) {
 +                              if (item->flags & TODO_EDIT_MERGE_MSG)
 +                                      strbuf_addstr(&buf, " -c");
 +                              else
 +                                      strbuf_addstr(&buf, " -C");
 +                      }
 +
                        strbuf_addf(&buf, " %s", oid);
                }
 +
                /* add all the rest */
                if (!item->arg_len)
                        strbuf_addch(&buf, '\n');
@@@ -4384,7 -3246,6 +4384,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.
@@@ -4398,9 -3259,6 +4398,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);
  
@@@ -4536,7 -3393,8 +4536,7 @@@ int skip_unnecessary_picks(void
                oid = &item->commit->object.oid;
        }
        if (i > 0) {
 -              int offset = i < todo_list.nr ?
 -                      todo_list.items[i].offset_in_buf : todo_list.buf.len;
 +              int offset = get_item_line_offset(&todo_list, i);
                const char *done_path = rebase_path_done();
  
                fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
@@@ -4584,8 -3442,6 +4584,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
@@@ -4602,7 -3458,6 +4602,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.
         *
                struct subject2item_entry *entry;
  
                next[i] = tail[i] = -1;
 -              if (item->command >= TODO_EXEC) {
 +              if (!item->commit || item->command == TODO_DROP) {
                        subjects[i] = NULL;
                        continue;
                }
  
                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 */
                                continue;
  
                        while (cur >= 0) {
 -                              int offset = todo_list.items[cur].offset_in_buf;
 -                              int end_offset = cur + 1 < todo_list.nr ?
 -                                      todo_list.items[cur + 1].offset_in_buf :
 -                                      todo_list.buf.len;
 -                              char *bol = todo_list.buf.buf + offset;
 -                              char *eol = todo_list.buf.buf + end_offset;
 +                              const char *bol =
 +                                      get_item_line(&todo_list, cur);
 +                              const char *eol =
 +                                      get_item_line(&todo_list, cur + 1);
  
                                /* replace 'pick', by 'fixup' or 'squash' */
                                command = todo_list.items[cur].command;
        hashmap_free(&subject2item, 1);
        todo_list_release(&todo_list);
  
 +      clear_commit_todo_item(&commit_todo);
        return res;
  }
index 65ed7db1592d97ae7120e028bc6023446e973a4a,ee871dd1bba095e8a0d0e8b8873a8f4851d3ab90..25099d715cee0dff321c75b944f25f58169bf645
@@@ -24,7 -24,7 +24,7 @@@ test_expect_success 'interactive rebas
        git checkout master &&
  
        FAKE_LINES="edit 1" git rebase -i HEAD^ &&
 -      test-chmtime =-60 F1 &&
 +      test-tool chmtime =-60 F1 &&
        git rebase --continue
  '
  
@@@ -36,7 -36,7 +36,7 @@@ test_expect_success 'non-interactive re
        test_must_fail git rebase --onto master master topic &&
        echo "Resolved" >F2 &&
        git add F2 &&
 -      test-chmtime =-60 F1 &&
 +      test-tool chmtime =-60 F1 &&
        git rebase --continue
  '
  
@@@ -60,7 -60,7 +60,7 @@@ test_expect_success 'rebase --continue 
        EOF
        chmod +x test-bin/git-merge-funny &&
        (
 -              PATH=./test-bin:$PATH
 +              PATH=./test-bin:$PATH &&
                test_must_fail git rebase -s funny -Xopt master topic
        ) &&
        test -f funny.was.run &&
        echo "Resolved" >F2 &&
        git add F2 &&
        (
 -              PATH=./test-bin:$PATH
 +              PATH=./test-bin:$PATH &&
 +              git rebase --continue
 +      ) &&
 +      test -f funny.was.run
 +'
 +
 +test_expect_success 'rebase -i --continue handles merge strategy and options' '
 +      rm -fr .git/rebase-* &&
 +      git reset --hard commit-new-file-F2-on-topic-branch &&
 +      test_commit "commit-new-file-F3-on-topic-branch-for-dash-i" F3 32 &&
 +      test_when_finished "rm -fr test-bin funny.was.run funny.args" &&
 +      mkdir test-bin &&
 +      cat >test-bin/git-merge-funny <<-EOF &&
 +      #!$SHELL_PATH
 +      echo "\$@" >>funny.args
 +      case "\$1" in --opt) ;; *) exit 2 ;; esac
 +      case "\$2" in --foo) ;; *) exit 2 ;; esac
 +      case "\$4" in --) ;; *) exit 2 ;; esac
 +      shift 2 &&
 +      >funny.was.run &&
 +      exec git merge-recursive "\$@"
 +      EOF
 +      chmod +x test-bin/git-merge-funny &&
 +      (
 +              PATH=./test-bin:$PATH &&
 +              test_must_fail git rebase -i -s funny -Xopt -Xfoo master topic
 +      ) &&
 +      test -f funny.was.run &&
 +      rm funny.was.run &&
 +      echo "Resolved" >F2 &&
 +      git add F2 &&
 +      (
 +              PATH=./test-bin:$PATH &&
                git rebase --continue
        ) &&
        test -f funny.was.run
@@@ -160,13 -128,15 +160,15 @@@ test_expect_success '--skip after faile
        : The first squash was skipped, therefore: &&
        git show HEAD >out &&
        test_i18ngrep "# This is a combination of 2 commits" out &&
+       test_i18ngrep "# This is the commit message #2:" out &&
  
        (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) &&
        git show HEAD >out &&
        test_i18ngrep ! "# This is a combination" out &&
  
        : Final squash failed, but there was still a squash &&
-       test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt
+       test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt &&
+       test_i18ngrep "# This is the commit message #2:" .git/copy.txt
  '
  
  test_expect_success 'setup rerere database' '