Merge branch 'jk/rebase-i-exec-gitdir-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 6 Nov 2017 04:11:28 +0000 (13:11 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 6 Nov 2017 04:11:28 +0000 (13:11 +0900)
A recent regression in "git rebase -i" that broke execution of git
commands from subdirectories via "exec" insn has been fixed.

* jk/rebase-i-exec-gitdir-fix:
sequencer: pass absolute GIT_DIR to exec commands

1  2 
sequencer.c
t/t3404-rebase-interactive.sh
diff --combined sequencer.c
index 46c997e2df50f81662df877384fbfc5287fa6d10,689d9a311731411fa5a0bb3710f7a31c82bc7ba8..7c874be1c61359a929307abe9d240ba01bd588c3
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "config.h"
  #include "lockfile.h"
  #include "sequencer.h"
  #include "dir.h"
@@@ -20,7 -19,6 +20,7 @@@
  #include "trailer.h"
  #include "log-tree.h"
  #include "wt-status.h"
 +#include "hashmap.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
@@@ -128,7 -126,6 +128,7 @@@ static GIT_PATH_FUNC(rebase_path_onto, 
  static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
  static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
  static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 +static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
  
  static inline int is_rebase_i(const struct replay_opts *opts)
  {
@@@ -204,7 -201,7 +204,7 @@@ int sequencer_remove_state(struct repla
                free(opts->xopts[i]);
        free(opts->xopts);
  
 -      strbuf_addf(&dir, "%s", get_dir(opts));
 +      strbuf_addstr(&dir, get_dir(opts));
        remove_dir_recursively(&dir, 0);
        strbuf_release(&dir);
  
@@@ -347,7 -344,7 +347,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(EMPTY_TREE_SHA1_BIN);
 +      return lookup_tree(&empty_tree_oid);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -377,7 -374,7 +377,7 @@@ static void update_abort_safety_file(vo
                write_file(git_path_abort_safety_file(), "%s", "");
  }
  
 -static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 +static int fast_forward_to(const struct object_id *to, const struct object_id *from,
                        int unborn, struct replay_opts *opts)
  {
        struct ref_transaction *transaction;
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
 -                                 to, unborn ? null_sha1 : from,
 +                                 to->hash, unborn ? null_sha1 : from->hash,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@@ -429,7 -426,7 +429,7 @@@ void append_conflicts_hint(struct strbu
  
  static int do_recursive_merge(struct commit *base, struct commit *next,
                              const char *base_label, const char *next_label,
 -                            unsigned char *head, struct strbuf *msgbuf,
 +                            struct object_id *head, struct strbuf *msgbuf,
                              struct replay_opts *opts)
  {
        struct merge_options o;
  
        if (active_cache_changed &&
            write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
 -              /* TRANSLATORS: %s will be "revert", "cherry-pick" or
 +              /*
 +               * TRANSLATORS: %s will be "revert", "cherry-pick" or
                 * "rebase -i".
                 */
                return error(_("%s: Unable to write new index file"),
  
  static int is_index_unchanged(void)
  {
 -      unsigned char head_sha1[20];
 +      struct object_id head_oid;
        struct commit *head_commit;
  
 -      if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
 +      if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
                return error(_("could not resolve HEAD commit\n"));
  
 -      head_commit = lookup_commit(head_sha1);
 +      head_commit = lookup_commit(&head_oid);
  
        /*
         * If head_commit is NULL, check_commit, called from
                if (cache_tree_update(&the_index, 0))
                        return error(_("unable to update cache tree\n"));
  
 -      return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
 +      return !oidcmp(&active_cache_tree->oid,
 +                     &head_commit->tree->object.oid);
  }
  
  static int write_author_script(const char *message)
@@@ -607,12 -602,6 +607,12 @@@ N_("you have staged changes in your wor
  "\n"
  "  git rebase --continue\n");
  
 +#define ALLOW_EMPTY (1<<0)
 +#define EDIT_MSG    (1<<1)
 +#define AMEND_MSG   (1<<2)
 +#define CLEANUP_MSG (1<<3)
 +#define VERIFY_MSG  (1<<4)
 +
  /*
   * If we are cherry-pick, and if the merge did not result in
   * hand-editing, we will hit this commit and inherit the original
   * author metadata.
   */
  static int run_git_commit(const char *defmsg, struct replay_opts *opts,
 -                        int allow_empty, int edit, int amend,
 -                        int cleanup_commit_message)
 +                        unsigned int flags)
  {
        struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
        cmd.git_cmd = 1;
  
        if (is_rebase_i(opts)) {
 -              if (!edit) {
 +              if (!(flags & EDIT_MSG)) {
                        cmd.stdout_to_stderr = 1;
                        cmd.err = -1;
                }
        }
  
        argv_array_push(&cmd.args, "commit");
 -      argv_array_push(&cmd.args, "-n");
  
 -      if (amend)
 +      if (!(flags & VERIFY_MSG))
 +              argv_array_push(&cmd.args, "-n");
 +      if ((flags & AMEND_MSG))
                argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
                argv_array_push(&cmd.args, "-s");
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
 -      if (cleanup_commit_message)
 +      if ((flags & CLEANUP_MSG))
                argv_array_push(&cmd.args, "--cleanup=strip");
 -      if (edit)
 +      if ((flags & EDIT_MSG))
                argv_array_push(&cmd.args, "-e");
 -      else if (!cleanup_commit_message &&
 +      else if (!(flags & CLEANUP_MSG) &&
                 !opts->signoff && !opts->record_origin &&
                 git_config_get_value("commit.cleanup", &value))
                argv_array_push(&cmd.args, "--cleanup=verbatim");
  
 -      if (allow_empty)
 +      if ((flags & ALLOW_EMPTY))
                argv_array_push(&cmd.args, "--allow-empty");
  
        if (opts->allow_empty_message)
  
  static int is_original_commit_empty(struct commit *commit)
  {
 -      const unsigned char *ptree_sha1;
 +      const struct object_id *ptree_oid;
  
        if (parse_commit(commit))
                return error(_("could not parse commit %s\n"),
                if (parse_commit(parent))
                        return error(_("could not parse parent commit %s\n"),
                                oid_to_hex(&parent->object.oid));
 -              ptree_sha1 = parent->tree->object.oid.hash;
 +              ptree_oid = &parent->tree->object.oid;
        } else {
 -              ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
 +              ptree_oid = &empty_tree_oid; /* commit is root */
        }
  
 -      return !hashcmp(ptree_sha1, commit->tree->object.oid.hash);
 +      return !oidcmp(ptree_oid, &commit->tree->object.oid);
  }
  
  /*
@@@ -839,13 -828,13 +839,13 @@@ static int update_squash_messages(enum 
                strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
                strbuf_release(&header);
        } else {
 -              unsigned char head[20];
 +              struct object_id head;
                struct commit *head_commit;
                const char *head_message, *body;
  
 -              if (get_sha1("HEAD", head))
 +              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(&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"));
  
  static void flush_rewritten_pending(void) {
        struct strbuf buf = STRBUF_INIT;
 -      unsigned char newsha1[20];
 +      struct object_id newoid;
        FILE *out;
  
 -      if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
 -                      !get_sha1("HEAD", newsha1) &&
 -                      (out = fopen(rebase_path_rewritten_list(), "a"))) {
 +      if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), (GIT_MAX_HEXSZ + 1) * 2) > 0 &&
 +          !get_oid("HEAD", &newoid) &&
 +          (out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) {
                char *bol = buf.buf, *eol;
  
                while (*bol) {
                        eol = strchrnul(bol, '\n');
                        fprintf(out, "%.*s %s\n", (int)(eol - bol),
 -                                      bol, sha1_to_hex(newsha1));
 +                                      bol, oid_to_hex(&newoid));
                        if (!*eol)
                                break;
                        bol = eol + 1;
  
  static void record_in_rewritten(struct object_id *oid,
                enum todo_command next_command) {
 -      FILE *out = fopen(rebase_path_rewritten_pending(), "a");
 +      FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a");
  
        if (!out)
                return;
  static int do_pick_commit(enum todo_command command, struct commit *commit,
                struct replay_opts *opts, int final_fixup)
  {
 -      int edit = opts->edit, cleanup_commit_message = 0;
 -      const char *msg_file = edit ? NULL : git_path_merge_msg();
 -      unsigned char head[20];
 +      unsigned int flags = opts->edit ? EDIT_MSG : 0;
 +      const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
 +      struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
 -      int res, unborn = 0, amend = 0, allow = 0;
 +      int res, unborn = 0, allow;
  
        if (opts->no_commit) {
                /*
                 * that represents the "current" state for merge-recursive
                 * to work on.
                 */
 -              if (write_cache_as_tree(head, 0, NULL))
 +              if (write_cache_as_tree(head.hash, 0, NULL))
                        return error(_("your index file is unmerged."));
        } else {
 -              unborn = get_sha1("HEAD", head);
 +              unborn = get_oid("HEAD", &head);
                if (unborn)
 -                      hashcpy(head, EMPTY_TREE_SHA1_BIN);
 +                      oidcpy(&head, &empty_tree_oid);
                if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
                        return error_dirty_index(opts);
        }
                        oid_to_hex(&commit->object.oid));
  
        if (opts->allow_ff && !is_fixup(command) &&
 -          ((parent && !hashcmp(parent->object.oid.hash, head)) ||
 +          ((parent && !oidcmp(&parent->object.oid, &head)) ||
             (!parent && unborn))) {
                if (is_rebase_i(opts))
                        write_author_script(msg.message);
 -              res = fast_forward_to(commit->object.oid.hash, head, unborn,
 +              res = fast_forward_to(&commit->object.oid, &head, unborn,
                        opts);
                if (res || command != TODO_REWORD)
                        goto leave;
 -              edit = amend = 1;
 +              flags |= EDIT_MSG | AMEND_MSG;
 +              if (command == TODO_REWORD)
 +                      flags |= VERIFY_MSG;
                msg_file = NULL;
                goto fast_forward_edit;
        }
                        strbuf_addstr(&msgbuf, p);
  
                if (opts->record_origin) {
 +                      strbuf_complete_line(&msgbuf);
                        if (!has_conforming_footer(&msgbuf, NULL, 0))
                                strbuf_addch(&msgbuf, '\n');
                        strbuf_addstr(&msgbuf, cherry_picked_prefix);
        }
  
        if (command == TODO_REWORD)
 -              edit = 1;
 +              flags |= EDIT_MSG | VERIFY_MSG;
        else if (is_fixup(command)) {
                if (update_squash_messages(command, commit, opts))
                        return -1;
 -              amend = 1;
 +              flags |= AMEND_MSG;
                if (!final_fixup)
                        msg_file = rebase_path_squash_msg();
                else if (file_exists(rebase_path_fixup_msg())) {
 -                      cleanup_commit_message = 1;
 +                      flags |= CLEANUP_MSG;
                        msg_file = rebase_path_fixup_msg();
                } else {
 -                      const char *dest = git_path("SQUASH_MSG");
 +                      const char *dest = git_path_squash_msg();
                        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());
                        msg_file = dest;
 -                      edit = 1;
 +                      flags |= EDIT_MSG;
                }
        }
  
                res = -1;
        else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
 -                                       head, &msgbuf, opts);
 +                                       &head, &msgbuf, opts);
                if (res < 0)
                        return res;
                res |= write_message(msgbuf.buf, msgbuf.len,
                commit_list_insert(next, &remotes);
                res |= try_merge_command(opts->strategy,
                                         opts->xopts_nr, (const char **)opts->xopts,
 -                                      common, sha1_to_hex(head), remotes);
 +                                      common, oid_to_hex(&head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
        }
        if (allow < 0) {
                res = allow;
                goto leave;
 -      }
 +      } else if (allow)
 +              flags |= ALLOW_EMPTY;
        if (!opts->no_commit)
  fast_forward_edit:
 -              res = run_git_commit(msg_file, opts, allow, edit, amend,
 -                                   cleanup_commit_message);
 +              res = run_git_commit(msg_file, opts, flags);
  
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
@@@ -1184,6 -1170,7 +1184,6 @@@ static int read_and_refresh_cache(struc
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed && index_fd >= 0) {
                if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
 -                      rollback_lock_file(&index_lock);
                        return error(_("git %s: failed to refresh the index"),
                                _(action_name(opts)));
                }
@@@ -1205,7 -1192,6 +1205,7 @@@ struct todo_list 
        struct todo_item *items;
        int nr, alloc, current;
        int done_nr, total_nr;
 +      struct stat_data stat;
  };
  
  #define TODO_LIST_INIT { STRBUF_INIT }
  static void todo_list_release(struct todo_list *todo_list)
  {
        strbuf_release(&todo_list->buf);
 -      free(todo_list->items);
 -      todo_list->items = NULL;
 +      FREE_AND_NULL(todo_list->items);
        todo_list->nr = todo_list->alloc = 0;
  }
  
@@@ -1225,7 -1212,7 +1225,7 @@@ static struct todo_item *append_new_tod
  
  static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
  {
 -      unsigned char commit_sha1[20];
 +      struct object_id commit_oid;
        char *end_of_object_name;
        int i, saved, status, padding;
  
        end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
        saved = *end_of_object_name;
        *end_of_object_name = '\0';
 -      status = get_sha1(bol, commit_sha1);
 +      status = get_oid(bol, &commit_oid);
        *end_of_object_name = saved;
  
        item->arg = end_of_object_name + strspn(end_of_object_name, " \t");
        if (status < 0)
                return -1;
  
 -      item->commit = lookup_commit_reference(commit_sha1);
 +      item->commit = lookup_commit_reference(&commit_oid);
        return !item->commit;
  }
  
@@@ -1335,7 -1322,6 +1335,7 @@@ static int count_commands(struct todo_l
  static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
  {
 +      struct stat st;
        const char *todo_file = get_todo_path(opts);
        int fd, res;
  
        }
        close(fd);
  
 +      res = stat(todo_file, &st);
 +      if (res)
 +              return error(_("could not stat '%s'"), todo_file);
 +      fill_stat_data(&todo_list->stat, &st);
 +
        res = parse_insn_buffer(todo_list->buf.buf, todo_list);
        if (res) {
                if (is_rebase_i(opts))
  
        if (is_rebase_i(opts)) {
                struct todo_list done = TODO_LIST_INIT;
 -              FILE *f = fopen(rebase_path_msgtotal(), "w");
 +              FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w");
  
                if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
                                !parse_insn_buffer(done.buf.buf, &done))
@@@ -1439,11 -1420,7 +1439,11 @@@ static int populate_opts_cb(const char 
        else if (!strcmp(key, "options.strategy-option")) {
                ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
                opts->xopts[opts->xopts_nr++] = xstrdup(value);
 -      } else
 +      } else if (!strcmp(key, "options.allow-rerere-auto"))
 +              opts->allow_rerere_auto =
 +                      git_config_bool_or_int(key, value, &error_flag) ?
 +                              RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
 +      else
                return error(_("invalid key: %s"), key);
  
        if (!error_flag)
@@@ -1484,15 -1461,6 +1484,15 @@@ static int read_populate_opts(struct re
                                free(opts->gpg_sign);
                                opts->gpg_sign = xstrdup(buf.buf + 2);
                        }
 +                      strbuf_reset(&buf);
 +              }
 +
 +              if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
 +                      if (!strcmp(buf.buf, "--rerere-autoupdate"))
 +                              opts->allow_rerere_auto = RERERE_AUTOUPDATE;
 +                      else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
 +                              opts->allow_rerere_auto = RERERE_NOAUTOUPDATE;
 +                      strbuf_reset(&buf);
                }
  
                if (file_exists(rebase_path_verbose()))
@@@ -1565,7 -1533,6 +1565,7 @@@ static int save_head(const char *head
        static struct lock_file head_lock;
        struct strbuf buf = STRBUF_INIT;
        int fd;
 +      ssize_t written;
  
        fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
        if (fd < 0) {
                return error_errno(_("could not lock HEAD"));
        }
        strbuf_addf(&buf, "%s\n", head);
 -      if (write_in_full(fd, buf.buf, buf.len) < 0) {
 +      written = write_in_full(fd, buf.buf, buf.len);
 +      strbuf_release(&buf);
 +      if (written < 0) {
                rollback_lock_file(&head_lock);
                return error_errno(_("could not write to '%s'"),
                                   git_path_head_file());
@@@ -1611,37 -1576,36 +1611,37 @@@ static int rollback_is_safe(void
        return !oidcmp(&actual_head, &expected_head);
  }
  
 -static int reset_for_rollback(const unsigned char *sha1)
 +static int reset_for_rollback(const struct object_id *oid)
  {
        const char *argv[4];    /* reset --merge <arg> + NULL */
  
        argv[0] = "reset";
        argv[1] = "--merge";
 -      argv[2] = sha1_to_hex(sha1);
 +      argv[2] = oid_to_hex(oid);
        argv[3] = NULL;
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
  
  static int rollback_single_pick(void)
  {
 -      unsigned char head_sha1[20];
 +      struct object_id head_oid;
  
        if (!file_exists(git_path_cherry_pick_head()) &&
            !file_exists(git_path_revert_head()))
                return error(_("no cherry-pick or revert in progress"));
 -      if (read_ref_full("HEAD", 0, head_sha1, NULL))
 +      if (read_ref_full("HEAD", 0, head_oid.hash, NULL))
                return error(_("cannot resolve HEAD"));
 -      if (is_null_sha1(head_sha1))
 +      if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
 -      return reset_for_rollback(head_sha1);
 +      return reset_for_rollback(&head_oid);
  }
  
  int sequencer_rollback(struct replay_opts *opts)
  {
        FILE *f;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct strbuf buf = STRBUF_INIT;
 +      const char *p;
  
        f = fopen(git_path_head_file(), "r");
        if (!f && errno == ENOENT) {
                goto fail;
        }
        fclose(f);
 -      if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
 +      if (parse_oid_hex(buf.buf, &oid, &p) || *p != '\0') {
                error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
                        git_path_head_file());
                goto fail;
        }
 -      if (is_null_sha1(sha1)) {
 +      if (is_null_oid(&oid)) {
                error(_("cannot abort from a branch yet to be born"));
                goto fail;
        }
                warning(_("You seem to have moved HEAD. "
                          "Not rewinding, check your HEAD!"));
        } else
 -      if (reset_for_rollback(sha1))
 +      if (reset_for_rollback(&oid))
                goto fail;
        strbuf_release(&buf);
        return sequencer_remove_state(opts);
@@@ -1760,10 -1724,6 +1760,10 @@@ static int save_opts(struct replay_opt
                                                        "options.strategy-option",
                                                        opts->xopts[i], "^$", 0);
        }
 +      if (opts->allow_rerere_auto)
 +              res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto",
 +                                                   opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
 +                                                   "true" : "false");
        return res;
  }
  
@@@ -1810,13 -1770,13 +1810,13 @@@ static int make_patch(struct commit *co
  
  static int intend_to_amend(void)
  {
 -      unsigned char head[20];
 +      struct object_id head;
        char *p;
  
 -      if (get_sha1("HEAD", head))
 +      if (get_oid("HEAD", &head))
                return error(_("cannot read HEAD"));
  
 -      p = sha1_to_hex(head);
 +      p = oid_to_hex(&head);
        return write_message(p, strlen(p), rebase_path_amend(), 1);
  }
  
@@@ -1852,21 -1812,24 +1852,24 @@@ static int error_failed_squash(struct c
                return error(_("could not rename '%s' to '%s'"),
                        rebase_path_squash_msg(), rebase_path_message());
        unlink(rebase_path_fixup_msg());
 -      unlink(git_path("MERGE_MSG"));
 -      if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666))
 +      unlink(git_path_merge_msg());
 +      if (copy_file(git_path_merge_msg(), 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());
        return error_with_patch(commit, subject, subject_len, opts, 1, 0);
  }
  
  static int do_exec(const char *command_line)
  {
+       struct argv_array child_env = ARGV_ARRAY_INIT;
        const char *child_argv[] = { NULL, NULL };
        int dirty, status;
  
        fprintf(stderr, "Executing: %s\n", command_line);
        child_argv[0] = command_line;
-       status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+       argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+       status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
+                                         child_env.argv);
  
        /* force re-reading of the cache */
        if (discard_cache() < 0 || read_cache() < 0)
                status = 1;
        }
  
+       argv_array_clear(&child_env);
        return status;
  }
  
@@@ -1938,13 -1903,11 +1943,13 @@@ static int apply_autostash(struct repla
        strbuf_trim(&stash_sha1);
  
        child.git_cmd = 1;
 +      child.no_stdout = 1;
 +      child.no_stderr = 1;
        argv_array_push(&child.args, "stash");
        argv_array_push(&child.args, "apply");
        argv_array_push(&child.args, stash_sha1.buf);
        if (!run_command(&child))
 -              printf(_("Applied autostash."));
 +              fprintf(stderr, _("Applied autostash.\n"));
        else {
                struct child_process store = CHILD_PROCESS_INIT;
  
                if (run_command(&store))
                        ret = error(_("cannot store %s"), stash_sha1.buf);
                else
 -                      printf(_("Applying autostash resulted in conflicts.\n"
 -                              "Your changes are safe in the stash.\n"
 -                              "You can run \"git stash pop\" or"
 -                              " \"git stash drop\" at any time.\n"));
 +                      fprintf(stderr,
 +                              _("Applying autostash resulted in conflicts.\n"
 +                                "Your changes are safe in the stash.\n"
 +                                "You can run \"git stash pop\" or"
 +                                " \"git stash drop\" at any time.\n"));
        }
  
        strbuf_release(&stash_sha1);
@@@ -2040,8 -2002,7 +2045,8 @@@ static int pick_commits(struct todo_lis
                        if (item->command == TODO_EDIT) {
                                struct commit *commit = item->commit;
                                if (!res)
 -                                      warning(_("stopped at %s... %.*s"),
 +                                      fprintf(stderr,
 +                                              _("Stopped at %s...  %.*s\n"),
                                                short_commit_name(commit),
                                                item->arg_len, item->arg);
                                return error_with_patch(commit,
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(item->arg + item->arg_len);
                        int saved = *end_of_arg;
 +                      struct stat st;
  
                        *end_of_arg = '\0';
                        res = do_exec(item->arg);
                        *end_of_arg = saved;
 +
 +                      /* Reread the todo file if it has changed. */
 +                      if (res)
 +                              ; /* fall through */
 +                      else if (stat(get_todo_path(opts), &st))
 +                              res = error_errno(_("could not stat '%s'"),
 +                                                get_todo_path(opts));
 +                      else if (match_stat_data(&todo_list->stat, &st)) {
 +                              todo_list_release(todo_list);
 +                              if (read_populate_todo(todo_list, opts))
 +                                      res = -1; /* message was printed */
 +                              /* `current` will be incremented below */
 +                              todo_list->current = -1;
 +                      }
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
  
                if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
                                starts_with(head_ref.buf, "refs/")) {
                        const char *msg;
 -                      unsigned char head[20], orig[20];
 +                      struct object_id head, orig;
                        int res;
  
 -                      if (get_sha1("HEAD", head)) {
 +                      if (get_oid("HEAD", &head)) {
                                res = error(_("cannot read HEAD"));
  cleanup_head_ref:
                                strbuf_release(&head_ref);
                                return res;
                        }
                        if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
 -                                      get_sha1_hex(buf.buf, orig)) {
 +                                      get_oid_hex(buf.buf, &orig)) {
                                res = error(_("could not read orig-head"));
                                goto cleanup_head_ref;
                        }
 +                      strbuf_reset(&buf);
                        if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
                                res = error(_("could not read 'onto'"));
                                goto cleanup_head_ref;
                        }
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
 -                      if (update_ref(msg, head_ref.buf, head, orig,
 +                      if (update_ref(msg, head_ref.buf, head.hash, orig.hash,
                                        REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                        log_tree_opt.disable_stdin = 1;
  
                        if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
 -                          !get_sha1(buf.buf, orig.hash) &&
 -                          !get_sha1("HEAD", head.hash)) {
 -                              diff_tree_sha1(orig.hash, head.hash,
 -                                             "", &log_tree_opt.diffopt);
 +                          !get_oid(buf.buf, &orig) &&
 +                          !get_oid("HEAD", &head)) {
 +                              diff_tree_oid(&orig, &head, "",
 +                                            &log_tree_opt.diffopt);
                                log_tree_diff_flush(&log_tree_opt);
                        }
                }
@@@ -2213,12 -2158,12 +2218,12 @@@ static int continue_single_pick(void
  
  static int commit_staged_changes(struct replay_opts *opts)
  {
 -      int amend = 0;
 +      unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
  
        if (has_unstaged_changes(1))
                return error(_("cannot rebase: You have unstaged changes."));
        if (!has_uncommitted_changes(0)) {
 -              const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD");
 +              const char *cherry_pick_head = git_path_cherry_pick_head();
  
                if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
  
        if (file_exists(rebase_path_amend())) {
                struct strbuf rev = STRBUF_INIT;
 -              unsigned char head[20], to_amend[20];
 +              struct object_id head, to_amend;
  
 -              if (get_sha1("HEAD", head))
 +              if (get_oid("HEAD", &head))
                        return error(_("cannot amend non-existing commit"));
                if (!read_oneliner(&rev, rebase_path_amend(), 0))
                        return error(_("invalid file: '%s'"), rebase_path_amend());
 -              if (get_sha1_hex(rev.buf, to_amend))
 +              if (get_oid_hex(rev.buf, &to_amend))
                        return error(_("invalid contents: '%s'"),
                                rebase_path_amend());
 -              if (hashcmp(head, to_amend))
 +              if (oidcmp(&head, &to_amend))
                        return error(_("\nYou have uncommitted changes in your "
                                       "working tree. Please, commit them\n"
                                       "first and then run 'git rebase "
                                       "--continue' again."));
  
                strbuf_release(&rev);
 -              amend = 1;
 +              flags |= AMEND_MSG;
        }
  
 -      if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0))
 +      if (run_git_commit(rebase_path_message(), opts, flags))
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
        return 0;
@@@ -2288,7 -2233,7 +2293,7 @@@ int sequencer_continue(struct replay_op
                struct object_id oid;
  
                if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
 -                  !get_sha1_committish(buf.buf, oid.hash))
 +                  !get_oid_committish(buf.buf, &oid))
                        record_in_rewritten(&oid, peek_command(&todo_list, 0));
                strbuf_release(&buf);
        }
@@@ -2309,7 -2254,7 +2314,7 @@@ static int single_pick(struct commit *c
  int sequencer_pick_revisions(struct replay_opts *opts)
  {
        struct todo_list todo_list = TODO_LIST_INIT;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        int i, res;
  
        assert(opts->revs);
                return -1;
  
        for (i = 0; i < opts->revs->pending.nr; i++) {
 -              unsigned char sha1[20];
 +              struct object_id oid;
                const char *name = opts->revs->pending.objects[i].name;
  
                /* This happens when using --stdin. */
                if (!strlen(name))
                        continue;
  
 -              if (!get_sha1(name, sha1)) {
 -                      if (!lookup_commit_reference_gently(sha1, 1)) {
 -                              enum object_type type = sha1_object_info(sha1, NULL);
 +              if (!get_oid(name, &oid)) {
 +                      if (!lookup_commit_reference_gently(&oid, 1)) {
 +                              enum object_type type = sha1_object_info(oid.hash, NULL);
                                return error(_("%s: can't cherry-pick a %s"),
                                        name, typename(type));
                        }
        if (walk_revs_populate_todo(&todo_list, opts) ||
                        create_seq_dir() < 0)
                return -1;
 -      if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT))
 +      if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
                return error(_("can't revert as initial commit"));
 -      if (save_head(sha1_to_hex(sha1)))
 +      if (save_head(oid_to_hex(&oid)))
                return -1;
        if (save_opts(opts))
                return -1;
@@@ -2386,9 -2331,6 +2391,9 @@@ void append_signoff(struct strbuf *msgb
                                getenv("GIT_COMMITTER_EMAIL")));
        strbuf_addch(&sob, '\n');
  
 +      if (!ignore_footer)
 +              strbuf_complete_line(msgbuf);
 +
        /*
         * If the whole message buffer is equal to the sob, pretend that we
         * found a conforming footer with a matching sob
                         * the title and body to be filled in by the user.
                         */
                        append_newlines = "\n\n";
 -              } else if (msgbuf->buf[len - 1] != '\n') {
 -                      /*
 -                       * Incomplete line.  Complete the line and add a
 -                       * blank one so that there is an empty line between
 -                       * the message body and the sob.
 -                       */
 -                      append_newlines = "\n\n";
                } else if (len == 1) {
                        /*
                         * Buffer contains a single newline.  Add another
  
        strbuf_release(&sob);
  }
 +
 +int sequencer_make_script(int keep_empty, FILE *out,
 +              int argc, const char **argv)
 +{
 +      char *format = NULL;
 +      struct pretty_print_context pp = {0};
 +      struct strbuf buf = STRBUF_INIT;
 +      struct rev_info revs;
 +      struct commit *commit;
 +
 +      init_revisions(&revs, NULL);
 +      revs.verbose_header = 1;
 +      revs.max_parents = 1;
 +      revs.cherry_pick = 1;
 +      revs.limited = 1;
 +      revs.reverse = 1;
 +      revs.right_only = 1;
 +      revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
 +      revs.topo_order = 1;
 +
 +      revs.pretty_given = 1;
 +      git_config_get_string("rebase.instructionFormat", &format);
 +      if (!format || !*format) {
 +              free(format);
 +              format = xstrdup("%s");
 +      }
 +      get_commit_format(format, &revs);
 +      free(format);
 +      pp.fmt = revs.commit_format;
 +      pp.output_encoding = get_log_output_encoding();
 +
 +      if (setup_revisions(argc, argv, &revs, NULL) > 1)
 +              return error(_("make_script: unhandled options"));
 +
 +      if (prepare_revision_walk(&revs) < 0)
 +              return error(_("make_script: error preparing revisions"));
 +
 +      while ((commit = get_revision(&revs))) {
 +              strbuf_reset(&buf);
 +              if (!keep_empty && is_original_commit_empty(commit))
 +                      strbuf_addf(&buf, "%c ", comment_line_char);
 +              strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
 +              pretty_print_commit(&pp, commit, &buf);
 +              strbuf_addch(&buf, '\n');
 +              fputs(buf.buf, out);
 +      }
 +      strbuf_release(&buf);
 +      return 0;
 +}
 +
 +
 +int transform_todo_ids(int shorten_ids)
 +{
 +      const char *todo_file = rebase_path_todo();
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      int fd, res, i;
 +      FILE *out;
 +
 +      strbuf_reset(&todo_list.buf);
 +      fd = open(todo_file, O_RDONLY);
 +      if (fd < 0)
 +              return error_errno(_("could not open '%s'"), todo_file);
 +      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 +              close(fd);
 +              return error(_("could not read '%s'."), todo_file);
 +      }
 +      close(fd);
 +
 +      res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
 +      if (res) {
 +              todo_list_release(&todo_list);
 +              return error(_("unusable todo list: '%s'"), todo_file);
 +      }
 +
 +      out = fopen(todo_file, "w");
 +      if (!out) {
 +              todo_list_release(&todo_list);
 +              return error(_("unable to open '%s' for writing"), todo_file);
 +      }
 +      for (i = 0; i < todo_list.nr; i++) {
 +              struct todo_item *item = todo_list.items + i;
 +              int bol = item->offset_in_buf;
 +              const char *p = todo_list.buf.buf + bol;
 +              int eol = i + 1 < todo_list.nr ?
 +                      todo_list.items[i + 1].offset_in_buf :
 +                      todo_list.buf.len;
 +
 +              if (item->command >= TODO_EXEC && item->command != TODO_DROP)
 +                      fwrite(p, eol - bol, 1, out);
 +              else {
 +                      const char *id = shorten_ids ?
 +                              short_commit_name(item->commit) :
 +                              oid_to_hex(&item->commit->object.oid);
 +                      int len;
 +
 +                      p += strspn(p, " \t"); /* left-trim command */
 +                      len = strcspn(p, " \t"); /* length of command */
 +
 +                      fprintf(out, "%.*s %s %.*s\n",
 +                              len, p, id, item->arg_len, item->arg);
 +              }
 +      }
 +      fclose(out);
 +      todo_list_release(&todo_list);
 +      return 0;
 +}
 +
 +enum check_level {
 +      CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
 +};
 +
 +static enum check_level get_missing_commit_check_level(void)
 +{
 +      const char *value;
 +
 +      if (git_config_get_value("rebase.missingcommitscheck", &value) ||
 +                      !strcasecmp("ignore", value))
 +              return CHECK_IGNORE;
 +      if (!strcasecmp("warn", value))
 +              return CHECK_WARN;
 +      if (!strcasecmp("error", value))
 +              return CHECK_ERROR;
 +      warning(_("unrecognized setting %s for option "
 +                "rebase.missingCommitsCheck. Ignoring."), value);
 +      return CHECK_IGNORE;
 +}
 +
 +/*
 + * Check if the user dropped some commits by mistake
 + * Behaviour determined by rebase.missingCommitsCheck.
 + * Check if there is an unrecognized command or a
 + * bad SHA-1 in a command.
 + */
 +int check_todo_list(void)
 +{
 +      enum check_level check_level = get_missing_commit_check_level();
 +      struct strbuf todo_file = STRBUF_INIT;
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      struct strbuf missing = STRBUF_INIT;
 +      int advise_to_edit_todo = 0, res = 0, fd, i;
 +
 +      strbuf_addstr(&todo_file, rebase_path_todo());
 +      fd = open(todo_file.buf, O_RDONLY);
 +      if (fd < 0) {
 +              res = error_errno(_("could not open '%s'"), todo_file.buf);
 +              goto leave_check;
 +      }
 +      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 +              close(fd);
 +              res = error(_("could not read '%s'."), todo_file.buf);
 +              goto leave_check;
 +      }
 +      close(fd);
 +      advise_to_edit_todo = res =
 +              parse_insn_buffer(todo_list.buf.buf, &todo_list);
 +
 +      if (res || check_level == CHECK_IGNORE)
 +              goto leave_check;
 +
 +      /* Mark the commits in git-rebase-todo as seen */
 +      for (i = 0; i < todo_list.nr; i++) {
 +              struct commit *commit = todo_list.items[i].commit;
 +              if (commit)
 +                      commit->util = (void *)1;
 +      }
 +
 +      todo_list_release(&todo_list);
 +      strbuf_addstr(&todo_file, ".backup");
 +      fd = open(todo_file.buf, O_RDONLY);
 +      if (fd < 0) {
 +              res = error_errno(_("could not open '%s'"), todo_file.buf);
 +              goto leave_check;
 +      }
 +      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 +              close(fd);
 +              res = error(_("could not read '%s'."), todo_file.buf);
 +              goto leave_check;
 +      }
 +      close(fd);
 +      strbuf_release(&todo_file);
 +      res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
 +
 +      /* Find commits in git-rebase-todo.backup yet unseen */
 +      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) {
 +                      strbuf_addf(&missing, " - %s %.*s\n",
 +                                  short_commit_name(commit),
 +                                  item->arg_len, item->arg);
 +                      commit->util = (void *)1;
 +              }
 +      }
 +
 +      /* Warn about missing commits */
 +      if (!missing.len)
 +              goto leave_check;
 +
 +      if (check_level == CHECK_ERROR)
 +              advise_to_edit_todo = res = 1;
 +
 +      fprintf(stderr,
 +              _("Warning: some commits may have been dropped accidentally.\n"
 +              "Dropped commits (newer to older):\n"));
 +
 +      /* Make the list user-friendly and display */
 +      fputs(missing.buf, stderr);
 +      strbuf_release(&missing);
 +
 +      fprintf(stderr, _("To avoid this message, use \"drop\" to "
 +              "explicitly remove a commit.\n\n"
 +              "Use 'git config rebase.missingCommitsCheck' to change "
 +              "the level of warnings.\n"
 +              "The possible behaviours are: ignore, warn, error.\n\n"));
 +
 +leave_check:
 +      strbuf_release(&todo_file);
 +      todo_list_release(&todo_list);
 +
 +      if (advise_to_edit_todo)
 +              fprintf(stderr,
 +                      _("You can fix this with 'git rebase --edit-todo' "
 +                        "and then run 'git rebase --continue'.\n"
 +                        "Or you can abort the rebase with 'git rebase"
 +                        " --abort'.\n"));
 +
 +      return res;
 +}
 +
 +/* skip picking commits whose parents are unchanged */
 +int skip_unnecessary_picks(void)
 +{
 +      const char *todo_file = rebase_path_todo();
 +      struct strbuf buf = STRBUF_INIT;
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
 +      int fd, i;
 +
 +      if (!read_oneliner(&buf, rebase_path_onto(), 0))
 +              return error(_("could not read 'onto'"));
 +      if (get_oid(buf.buf, &onto_oid)) {
 +              strbuf_release(&buf);
 +              return error(_("need a HEAD to fixup"));
 +      }
 +      strbuf_release(&buf);
 +
 +      fd = open(todo_file, O_RDONLY);
 +      if (fd < 0) {
 +              return error_errno(_("could not open '%s'"), todo_file);
 +      }
 +      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 +              close(fd);
 +              return error(_("could not read '%s'."), todo_file);
 +      }
 +      close(fd);
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
 +              todo_list_release(&todo_list);
 +              return -1;
 +      }
 +
 +      for (i = 0; i < todo_list.nr; i++) {
 +              struct todo_item *item = todo_list.items + i;
 +
 +              if (item->command >= TODO_NOOP)
 +                      continue;
 +              if (item->command != TODO_PICK)
 +                      break;
 +              if (parse_commit(item->commit)) {
 +                      todo_list_release(&todo_list);
 +                      return error(_("could not parse commit '%s'"),
 +                              oid_to_hex(&item->commit->object.oid));
 +              }
 +              if (!item->commit->parents)
 +                      break; /* root commit */
 +              if (item->commit->parents->next)
 +                      break; /* merge commit */
 +              parent_oid = &item->commit->parents->item->object.oid;
 +              if (hashcmp(parent_oid->hash, oid->hash))
 +                      break;
 +              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;
 +              const char *done_path = rebase_path_done();
 +
 +              fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
 +              if (fd < 0) {
 +                      error_errno(_("could not open '%s' for writing"),
 +                                  done_path);
 +                      todo_list_release(&todo_list);
 +                      return -1;
 +              }
 +              if (write_in_full(fd, todo_list.buf.buf, offset) < 0) {
 +                      error_errno(_("could not write to '%s'"), done_path);
 +                      todo_list_release(&todo_list);
 +                      close(fd);
 +                      return -1;
 +              }
 +              close(fd);
 +
 +              fd = open(rebase_path_todo(), O_WRONLY, 0666);
 +              if (fd < 0) {
 +                      error_errno(_("could not open '%s' for writing"),
 +                                  rebase_path_todo());
 +                      todo_list_release(&todo_list);
 +                      return -1;
 +              }
 +              if (write_in_full(fd, todo_list.buf.buf + offset,
 +                              todo_list.buf.len - offset) < 0) {
 +                      error_errno(_("could not write to '%s'"),
 +                                  rebase_path_todo());
 +                      close(fd);
 +                      todo_list_release(&todo_list);
 +                      return -1;
 +              }
 +              if (ftruncate(fd, todo_list.buf.len - offset) < 0) {
 +                      error_errno(_("could not truncate '%s'"),
 +                                  rebase_path_todo());
 +                      todo_list_release(&todo_list);
 +                      close(fd);
 +                      return -1;
 +              }
 +              close(fd);
 +
 +              todo_list.current = i;
 +              if (is_fixup(peek_command(&todo_list, 0)))
 +                      record_in_rewritten(oid, peek_command(&todo_list, 0));
 +      }
 +
 +      todo_list_release(&todo_list);
 +      printf("%s\n", oid_to_hex(oid));
 +
 +      return 0;
 +}
 +
 +struct subject2item_entry {
 +      struct hashmap_entry entry;
 +      int i;
 +      char subject[FLEX_ARRAY];
 +};
 +
 +static int subject2item_cmp(const void *fndata,
 +                          const struct subject2item_entry *a,
 +                          const struct subject2item_entry *b, const void *key)
 +{
 +      return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
 +}
 +
 +/*
 + * 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
 + * after the former, and change "pick" to "fixup"/"squash".
 + *
 + * Note that if the config has specified a custom instruction format, each log
 + * message will have to be retrieved from the commit (as the oneline in the
 + * script cannot be trusted) in order to normalize the autosquash arrangement.
 + */
 +int rearrange_squash(void)
 +{
 +      const char *todo_file = rebase_path_todo();
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      struct hashmap subject2item;
 +      int res = 0, rearranged = 0, *next, *tail, fd, i;
 +      char **subjects;
 +
 +      fd = open(todo_file, O_RDONLY);
 +      if (fd < 0)
 +              return error_errno(_("could not open '%s'"), todo_file);
 +      if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
 +              close(fd);
 +              return error(_("could not read '%s'."), todo_file);
 +      }
 +      close(fd);
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
 +              todo_list_release(&todo_list);
 +              return -1;
 +      }
 +
 +      /*
 +       * The hashmap maps onelines to the respective todo list index.
 +       *
 +       * If any items need to be rearranged, the next[i] value will indicate
 +       * which item was moved directly after the i'th.
 +       *
 +       * In that case, last[i] will indicate the index of the latest item to
 +       * be moved to appear after the i'th.
 +       */
 +      hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp,
 +                   NULL, todo_list.nr);
 +      ALLOC_ARRAY(next, todo_list.nr);
 +      ALLOC_ARRAY(tail, todo_list.nr);
 +      ALLOC_ARRAY(subjects, todo_list.nr);
 +      for (i = 0; i < todo_list.nr; i++) {
 +              struct strbuf buf = STRBUF_INIT;
 +              struct todo_item *item = todo_list.items + i;
 +              const char *commit_buffer, *subject, *p;
 +              size_t subject_len;
 +              int i2 = -1;
 +              struct subject2item_entry *entry;
 +
 +              next[i] = tail[i] = -1;
 +              if (item->command >= TODO_EXEC) {
 +                      subjects[i] = NULL;
 +                      continue;
 +              }
 +
 +              if (is_fixup(item->command)) {
 +                      todo_list_release(&todo_list);
 +                      return error(_("the script was already rearranged."));
 +              }
 +
 +              item->commit->util = item;
 +
 +              parse_commit(item->commit);
 +              commit_buffer = get_commit_buffer(item->commit, NULL);
 +              find_commit_subject(commit_buffer, &subject);
 +              format_subject(&buf, subject, " ");
 +              subject = subjects[i] = strbuf_detach(&buf, &subject_len);
 +              unuse_commit_buffer(item->commit, commit_buffer);
 +              if ((skip_prefix(subject, "fixup! ", &p) ||
 +                   skip_prefix(subject, "squash! ", &p))) {
 +                      struct commit *commit2;
 +
 +                      for (;;) {
 +                              while (isspace(*p))
 +                                      p++;
 +                              if (!skip_prefix(p, "fixup! ", &p) &&
 +                                  !skip_prefix(p, "squash! ", &p))
 +                                      break;
 +                      }
 +
 +                      if ((entry = hashmap_get_from_hash(&subject2item,
 +                                                         strhash(p), p)))
 +                              /* found by title */
 +                              i2 = entry->i;
 +                      else if (!strchr(p, ' ') &&
 +                               (commit2 =
 +                                lookup_commit_reference_by_name(p)) &&
 +                               commit2->util)
 +                              /* found by commit name */
 +                              i2 = (struct todo_item *)commit2->util
 +                                      - todo_list.items;
 +                      else {
 +                              /* copy can be a prefix of the commit subject */
 +                              for (i2 = 0; i2 < i; i2++)
 +                                      if (subjects[i2] &&
 +                                          starts_with(subjects[i2], p))
 +                                              break;
 +                              if (i2 == i)
 +                                      i2 = -1;
 +                      }
 +              }
 +              if (i2 >= 0) {
 +                      rearranged = 1;
 +                      todo_list.items[i].command =
 +                              starts_with(subject, "fixup!") ?
 +                              TODO_FIXUP : TODO_SQUASH;
 +                      if (next[i2] < 0)
 +                              next[i2] = i;
 +                      else
 +                              next[tail[i2]] = i;
 +                      tail[i2] = i;
 +              } else if (!hashmap_get_from_hash(&subject2item,
 +                                              strhash(subject), subject)) {
 +                      FLEX_ALLOC_MEM(entry, subject, subject, subject_len);
 +                      entry->i = i;
 +                      hashmap_entry_init(entry, strhash(entry->subject));
 +                      hashmap_put(&subject2item, entry);
 +              }
 +      }
 +
 +      if (rearranged) {
 +              struct strbuf buf = STRBUF_INIT;
 +
 +              for (i = 0; i < todo_list.nr; i++) {
 +                      enum todo_command command = todo_list.items[i].command;
 +                      int cur = i;
 +
 +                      /*
 +                       * Initially, all commands are 'pick's. If it is a
 +                       * fixup or a squash now, we have rearranged it.
 +                       */
 +                      if (is_fixup(command))
 +                              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;
 +
 +                              /* replace 'pick', by 'fixup' or 'squash' */
 +                              command = todo_list.items[cur].command;
 +                              if (is_fixup(command)) {
 +                                      strbuf_addstr(&buf,
 +                                              todo_command_info[command].str);
 +                                      bol += strcspn(bol, " \t");
 +                              }
 +
 +                              strbuf_add(&buf, bol, eol - bol);
 +
 +                              cur = next[cur];
 +                      }
 +              }
 +
 +              fd = open(todo_file, O_WRONLY);
 +              if (fd < 0)
 +                      res = error_errno(_("could not open '%s'"), todo_file);
 +              else if (write(fd, buf.buf, buf.len) < 0)
 +                      res = error_errno(_("could not write to '%s'"), todo_file);
 +              else if (ftruncate(fd, buf.len) < 0)
 +                      res = error_errno(_("could not truncate '%s'"),
 +                                         todo_file);
 +              close(fd);
 +              strbuf_release(&buf);
 +      }
 +
 +      free(next);
 +      free(tail);
 +      for (i = 0; i < todo_list.nr; i++)
 +              free(subjects[i]);
 +      free(subjects);
 +      hashmap_free(&subject2item, 1);
 +      todo_list_release(&todo_list);
 +
 +      return res;
 +}
index 3704dbb2ecf6046e09adb03b451e4e531509b2f4,436615be87367b96b8d9ff59f359d1368aeddd20..6a82d1ed876dd5d1073dc63be8ba5720adbf12e3
@@@ -108,6 -108,17 +108,17 @@@ test_expect_success 'rebase -i with th
        rm -fr subdir
  '
  
+ test_expect_success 'rebase -i with exec allows git commands in subdirs' '
+       test_when_finished "rm -rf subdir" &&
+       test_when_finished "git rebase --abort ||:" &&
+       git checkout master &&
+       mkdir subdir && (cd subdir &&
+       set_fake_editor &&
+       FAKE_LINES="1 exec_cd_subdir_&&_git_rev-parse_--is-inside-work-tree" \
+               git rebase -i HEAD^
+       )
+ '
  test_expect_success 'rebase -i with the exec command checks tree cleanness' '
        git checkout master &&
        set_fake_editor &&
@@@ -169,13 -180,6 +180,13 @@@ test_expect_success 'reflog for the bra
        test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
  '
  
 +test_expect_success 'reflog for the branch shows correct finish message' '
 +      printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \
 +              "$(git rev-parse branch2)" >expected &&
 +      git log -g --pretty=%gs -1 refs/heads/branch1 >actual &&
 +      test_cmp expected actual
 +'
 +
  test_expect_success 'exchange two commits' '
        set_fake_editor &&
        FAKE_LINES="2 1" git rebase -i HEAD~2 &&
@@@ -373,7 -377,7 +384,7 @@@ test_expect_success 'verbose flag is he
        grep "^ file1 | 2 +-$" output
  '
  
 -test_expect_success 'multi-squash only fires up editor once' '
 +test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' '
        base=$(git rev-parse HEAD~4) &&
        set_fake_editor &&
        FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
        test 1 = $(git show | grep ONCE | wc -l)
  '
  
 -test_expect_success 'multi-fixup does not fire up editor' '
 +test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' '
        git checkout -b multi-fixup E &&
        base=$(git rev-parse HEAD~4) &&
        set_fake_editor &&
  ONCE
  EOF
  
 -test_expect_success 'squash and fixup generate correct log messages' '
 +test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messages' '
        git checkout -b squash-fixup E &&
        base=$(git rev-parse HEAD~4) &&
        set_fake_editor &&
        git branch -D squash-fixup
  '
  
 -test_expect_success 'squash ignores comments' '
 +test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' '
        git checkout -b skip-comments E &&
        base=$(git rev-parse HEAD~4) &&
        set_fake_editor &&
        git branch -D skip-comments
  '
  
 -test_expect_success 'squash ignores blank lines' '
 +test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' '
        git checkout -b skip-blank-lines E &&
        base=$(git rev-parse HEAD~4) &&
        set_fake_editor &&
@@@ -867,7 -871,7 +878,7 @@@ test_expect_success 'rebase -ix with se
        test_cmp expected actual
  '
  
 -test_expect_success 'rebase -ix with --autosquash' '
 +test_expect_success C_LOCALE_OUTPUT 'rebase -ix with --autosquash' '
        git reset --hard execute &&
        git checkout -b autosquash &&
        echo second >second.txt &&
@@@ -950,7 -954,7 +961,7 @@@ test_expect_success 'rebase -i --root f
        test 0 = $(git cat-file commit HEAD | grep -c ^parent\ )
  '
  
 -test_expect_success 'rebase --edit-todo does not works on non-interactive rebase' '
 +test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' '
        git reset --hard &&
        git checkout conflict-branch &&
        set_fake_editor &&
@@@ -1249,13 -1253,20 +1260,13 @@@ test_expect_success 'rebase -i respect
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
  '
  
 -cat >expect <<EOF
 -Warning: the command isn't recognized in the following line:
 - - badcmd $(git rev-list --oneline -1 master~1)
 -
 -You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
 -Or you can abort the rebase with 'git rebase --abort'.
 -EOF
 -
  test_expect_success 'static check of bad command' '
        rebase_setup_and_clean bad-cmd &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
                git rebase -i --root 2>actual &&
 -      test_i18ncmp expect actual &&
 +      test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual &&
 +      test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
        FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
        git rebase --continue &&
        test E = $(git cat-file commit HEAD | sed -ne \$p) &&
@@@ -1277,13 -1288,20 +1288,13 @@@ test_expect_success 'tabs and spaces ar
        test E = $(git cat-file commit HEAD | sed -ne \$p)
  '
  
 -cat >expect <<EOF
 -Warning: the SHA-1 is missing or isn't a commit in the following line:
 - - edit XXXXXXX False commit
 -
 -You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
 -Or you can abort the rebase with 'git rebase --abort'.
 -EOF
 -
  test_expect_success 'static check of bad SHA-1' '
        rebase_setup_and_clean bad-sha &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
                git rebase -i --root 2>actual &&
 -      test_i18ncmp expect actual &&
 +      test_i18ngrep "edit XXXXXXX False commit" actual &&
 +      test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
        FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
        git rebase --continue &&
        test E = $(git cat-file commit HEAD | sed -ne \$p)