Merge branch 'rs/strbuf-read-file-or-whine'
authorJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2018 22:54:07 +0000 (14:54 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2018 22:54:07 +0000 (14:54 -0800)
Code clean-up.

* rs/strbuf-read-file-or-whine:
sequencer: factor out strbuf_read_file_or_whine()

1  2 
sequencer.c
diff --combined sequencer.c
index c24ecdb21fdaac2dd70a127b761e536f1eef196f,c31d48b7b8c7b820f3caafef33e24785f287e2d7..a35a0e60b8035ad0a02962f0fc0f0791806a28c6
@@@ -1,10 -1,10 +1,10 @@@
  #include "cache.h"
  #include "config.h"
  #include "lockfile.h"
 -#include "sequencer.h"
  #include "dir.h"
  #include "object.h"
  #include "commit.h"
 +#include "sequencer.h"
  #include "tag.h"
  #include "run-command.h"
  #include "exec_cmd.h"
  #include "log-tree.h"
  #include "wt-status.h"
  #include "hashmap.h"
 +#include "notes-utils.h"
 +#include "sigchain.h"
  
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
  const char sign_off_header[] = "Signed-off-by: ";
  static const char cherry_picked_prefix[] = "(cherry picked from commit ";
  
 +GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
 +
  GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
  
  static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
@@@ -134,51 -130,6 +134,51 @@@ static GIT_PATH_FUNC(rebase_path_strate
  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 int git_sequencer_config(const char *k, const char *v, void *cb)
 +{
 +      struct replay_opts *opts = cb;
 +      int status;
 +
 +      if (!strcmp(k, "commit.cleanup")) {
 +              const char *s;
 +
 +              status = git_config_string(&s, k, v);
 +              if (status)
 +                      return status;
 +
 +              if (!strcmp(s, "verbatim"))
 +                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
 +              else if (!strcmp(s, "whitespace"))
 +                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
 +              else if (!strcmp(s, "strip"))
 +                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
 +              else if (!strcmp(s, "scissors"))
 +                      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
 +              else
 +                      warning(_("invalid commit message cleanup mode '%s'"),
 +                                s);
 +
 +              return status;
 +      }
 +
 +      if (!strcmp(k, "commit.gpgsign")) {
 +              opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
 +              return 0;
 +      }
 +
 +      status = git_gpg_config(k, v, NULL);
 +      if (status)
 +              return status;
 +
 +      return git_diff_basic_config(k, v, NULL);
 +}
 +
 +void sequencer_init_config(struct replay_opts *opts)
 +{
 +      opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
 +      git_config(git_sequencer_config, opts);
 +}
 +
  static inline int is_rebase_i(const struct replay_opts *opts)
  {
        return opts->action == REPLAY_INTERACTIVE_REBASE;
@@@ -396,7 -347,7 +396,7 @@@ static int read_oneliner(struct strbuf 
  
  static struct tree *empty_tree(void)
  {
 -      return lookup_tree(&empty_tree_oid);
 +      return lookup_tree(the_hash_algo->empty_tree);
  }
  
  static int error_dirty_index(struct replay_opts *opts)
@@@ -442,7 -393,7 +442,7 @@@ static int fast_forward_to(const struc
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD",
 -                                 to->hash, unborn ? null_sha1 : from->hash,
 +                                 to, unborn ? &null_oid : from,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                ref_transaction_free(transaction);
@@@ -487,8 -438,7 +487,8 @@@ static int do_recursive_merge(struct co
        char **xopt;
        static struct lock_file index_lock;
  
 -      hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
 +      if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
 +              return -1;
  
        read_cache();
  
        o.branch2 = next ? next_label : "(empty tree)";
        if (is_rebase_i(opts))
                o.buffer_output = 2;
 +      o.show_rename_progress = 1;
  
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
        if (is_rebase_i(opts) && clean <= 0)
                fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
 +      diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
        if (clean < 0)
                return clean;
  
                        _(action_name(opts)));
        rollback_lock_file(&index_lock);
  
 -      if (opts->signoff)
 -              append_signoff(msgbuf, 0, 0);
 -
        if (!clean)
                append_conflicts_hint(msgbuf);
  
@@@ -538,8 -489,8 +538,8 @@@ static int is_index_unchanged(void
        struct object_id head_oid;
        struct commit *head_commit;
  
 -      if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
 -              return error(_("could not resolve HEAD commit\n"));
 +      if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
 +              return error(_("could not resolve HEAD commit"));
  
        head_commit = lookup_commit(&head_oid);
  
  
        if (!cache_tree_fully_valid(active_cache_tree))
                if (cache_tree_update(&the_index, 0))
 -                      return error(_("unable to update cache tree\n"));
 +                      return error(_("unable to update cache tree"));
  
        return !oidcmp(&active_cache_tree->oid,
                       &head_commit->tree->object.oid);
@@@ -642,18 -593,6 +642,18 @@@ static int read_env_script(struct argv_
        return 0;
  }
  
 +static char *get_author(const char *message)
 +{
 +      size_t len;
 +      const char *a;
 +
 +      a = find_commit_header(message, "author", &len);
 +      if (a)
 +              return xmemdupz(a, len);
 +
 +      return NULL;
 +}
 +
  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"
@@@ -716,6 -655,8 +716,6 @@@ static int run_git_commit(const char *d
                argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
                argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
 -      if (opts->signoff)
 -              argv_array_push(&cmd.args, "-s");
        if (defmsg)
                argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        if ((flags & CLEANUP_MSG))
        return run_command(&cmd);
  }
  
 +static int rest_is_empty(const struct strbuf *sb, int start)
 +{
 +      int i, eol;
 +      const char *nl;
 +
 +      /* Check if the rest is just whitespace and Signed-off-by's. */
 +      for (i = start; i < sb->len; i++) {
 +              nl = memchr(sb->buf + i, '\n', sb->len - i);
 +              if (nl)
 +                      eol = nl - sb->buf;
 +              else
 +                      eol = sb->len;
 +
 +              if (strlen(sign_off_header) <= eol - i &&
 +                  starts_with(sb->buf + i, sign_off_header)) {
 +                      i = eol;
 +                      continue;
 +              }
 +              while (i < eol)
 +                      if (!isspace(sb->buf[i++]))
 +                              return 0;
 +      }
 +
 +      return 1;
 +}
 +
 +/*
 + * Find out if the message in the strbuf contains only whitespace and
 + * Signed-off-by lines.
 + */
 +int message_is_empty(const struct strbuf *sb,
 +                   enum commit_msg_cleanup_mode cleanup_mode)
 +{
 +      if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
 +              return 0;
 +      return rest_is_empty(sb, 0);
 +}
 +
 +/*
 + * See if the user edited the message in the editor or left what
 + * was in the template intact
 + */
 +int template_untouched(const struct strbuf *sb, const char *template_file,
 +                     enum commit_msg_cleanup_mode cleanup_mode)
 +{
 +      struct strbuf tmpl = STRBUF_INIT;
 +      const char *start;
 +
 +      if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
 +              return 0;
 +
 +      if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
 +              return 0;
 +
 +      strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
 +      if (!skip_prefix(sb->buf, tmpl.buf, &start))
 +              start = sb->buf;
 +      strbuf_release(&tmpl);
 +      return rest_is_empty(sb, start - sb->buf);
 +}
 +
 +int update_head_with_reflog(const struct commit *old_head,
 +                          const struct object_id *new_head,
 +                          const char *action, const struct strbuf *msg,
 +                          struct strbuf *err)
 +{
 +      struct ref_transaction *transaction;
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *nl;
 +      int ret = 0;
 +
 +      if (action) {
 +              strbuf_addstr(&sb, action);
 +              strbuf_addstr(&sb, ": ");
 +      }
 +
 +      nl = strchr(msg->buf, '\n');
 +      if (nl) {
 +              strbuf_add(&sb, msg->buf, nl + 1 - msg->buf);
 +      } else {
 +              strbuf_addbuf(&sb, msg);
 +              strbuf_addch(&sb, '\n');
 +      }
 +
 +      transaction = ref_transaction_begin(err);
 +      if (!transaction ||
 +          ref_transaction_update(transaction, "HEAD", new_head,
 +                                 old_head ? &old_head->object.oid : &null_oid,
 +                                 0, sb.buf, err) ||
 +          ref_transaction_commit(transaction, err)) {
 +              ret = -1;
 +      }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&sb);
 +
 +      return ret;
 +}
 +
 +static int run_rewrite_hook(const struct object_id *oldoid,
 +                          const struct object_id *newoid)
 +{
 +      struct child_process proc = CHILD_PROCESS_INIT;
 +      const char *argv[3];
 +      int code;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      argv[0] = find_hook("post-rewrite");
 +      if (!argv[0])
 +              return 0;
 +
 +      argv[1] = "amend";
 +      argv[2] = NULL;
 +
 +      proc.argv = argv;
 +      proc.in = -1;
 +      proc.stdout_to_stderr = 1;
 +
 +      code = start_command(&proc);
 +      if (code)
 +              return code;
 +      strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
 +      sigchain_push(SIGPIPE, SIG_IGN);
 +      write_in_full(proc.in, sb.buf, sb.len);
 +      close(proc.in);
 +      strbuf_release(&sb);
 +      sigchain_pop(SIGPIPE);
 +      return finish_command(&proc);
 +}
 +
 +void commit_post_rewrite(const struct commit *old_head,
 +                       const struct object_id *new_head)
 +{
 +      struct notes_rewrite_cfg *cfg;
 +
 +      cfg = init_copy_notes_for_rewrite("amend");
 +      if (cfg) {
 +              /* we are amending, so old_head is not NULL */
 +              copy_note_for_rewrite(cfg, &old_head->object.oid, new_head);
 +              finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
 +      }
 +      run_rewrite_hook(&old_head->object.oid, new_head);
 +}
 +
 +static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit)
 +{
 +      struct argv_array hook_env = ARGV_ARRAY_INIT;
 +      int ret;
 +      const char *name;
 +
 +      name = git_path_commit_editmsg();
 +      if (write_message(msg->buf, msg->len, name, 0))
 +              return -1;
 +
 +      argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file());
 +      argv_array_push(&hook_env, "GIT_EDITOR=:");
 +      if (commit)
 +              ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
 +                                "commit", commit, NULL);
 +      else
 +              ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
 +                                "message", NULL);
 +      if (ret)
 +              ret = error(_("'prepare-commit-msg' hook failed"));
 +      argv_array_clear(&hook_env);
 +
 +      return ret;
 +}
 +
 +static const char implicit_ident_advice_noconfig[] =
 +N_("Your name and email address were configured automatically based\n"
 +"on your username and hostname. Please check that they are accurate.\n"
 +"You can suppress this message by setting them explicitly. Run the\n"
 +"following command and follow the instructions in your editor to edit\n"
 +"your configuration file:\n"
 +"\n"
 +"    git config --global --edit\n"
 +"\n"
 +"After doing this, you may fix the identity used for this commit with:\n"
 +"\n"
 +"    git commit --amend --reset-author\n");
 +
 +static const char implicit_ident_advice_config[] =
 +N_("Your name and email address were configured automatically based\n"
 +"on your username and hostname. Please check that they are accurate.\n"
 +"You can suppress this message by setting them explicitly:\n"
 +"\n"
 +"    git config --global user.name \"Your Name\"\n"
 +"    git config --global user.email you@example.com\n"
 +"\n"
 +"After doing this, you may fix the identity used for this commit with:\n"
 +"\n"
 +"    git commit --amend --reset-author\n");
 +
 +static const char *implicit_ident_advice(void)
 +{
 +      char *user_config = expand_user_path("~/.gitconfig", 0);
 +      char *xdg_config = xdg_config_home("config");
 +      int config_exists = file_exists(user_config) || file_exists(xdg_config);
 +
 +      free(user_config);
 +      free(xdg_config);
 +
 +      if (config_exists)
 +              return _(implicit_ident_advice_config);
 +      else
 +              return _(implicit_ident_advice_noconfig);
 +
 +}
 +
 +void print_commit_summary(const char *prefix, const struct object_id *oid,
 +                        unsigned int flags)
 +{
 +      struct rev_info rev;
 +      struct commit *commit;
 +      struct strbuf format = STRBUF_INIT;
 +      const char *head;
 +      struct pretty_print_context pctx = {0};
 +      struct strbuf author_ident = STRBUF_INIT;
 +      struct strbuf committer_ident = STRBUF_INIT;
 +
 +      commit = lookup_commit(oid);
 +      if (!commit)
 +              die(_("couldn't look up newly created commit"));
 +      if (parse_commit(commit))
 +              die(_("could not parse newly created commit"));
 +
 +      strbuf_addstr(&format, "format:%h] %s");
 +
 +      format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
 +      format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
 +      if (strbuf_cmp(&author_ident, &committer_ident)) {
 +              strbuf_addstr(&format, "\n Author: ");
 +              strbuf_addbuf_percentquote(&format, &author_ident);
 +      }
 +      if (flags & SUMMARY_SHOW_AUTHOR_DATE) {
 +              struct strbuf date = STRBUF_INIT;
 +
 +              format_commit_message(commit, "%ad", &date, &pctx);
 +              strbuf_addstr(&format, "\n Date: ");
 +              strbuf_addbuf_percentquote(&format, &date);
 +              strbuf_release(&date);
 +      }
 +      if (!committer_ident_sufficiently_given()) {
 +              strbuf_addstr(&format, "\n Committer: ");
 +              strbuf_addbuf_percentquote(&format, &committer_ident);
 +              if (advice_implicit_identity) {
 +                      strbuf_addch(&format, '\n');
 +                      strbuf_addstr(&format, implicit_ident_advice());
 +              }
 +      }
 +      strbuf_release(&author_ident);
 +      strbuf_release(&committer_ident);
 +
 +      init_revisions(&rev, prefix);
 +      setup_revisions(0, NULL, &rev, NULL);
 +
 +      rev.diff = 1;
 +      rev.diffopt.output_format =
 +              DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
 +
 +      rev.verbose_header = 1;
 +      rev.show_root_diff = 1;
 +      get_commit_format(format.buf, &rev);
 +      rev.always_show_header = 0;
 +      rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
 +      rev.diffopt.break_opt = 0;
 +      diff_setup_done(&rev.diffopt);
 +
 +      head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 +      if (!head)
 +              die_errno(_("unable to resolve HEAD after creating commit"));
 +      if (!strcmp(head, "HEAD"))
 +              head = _("detached HEAD");
 +      else
 +              skip_prefix(head, "refs/heads/", &head);
 +      printf("[%s%s ", head, (flags & SUMMARY_INITIAL_COMMIT) ?
 +                                              _(" (root-commit)") : "");
 +
 +      if (!log_tree_commit(&rev, commit)) {
 +              rev.always_show_header = 1;
 +              rev.use_terminator = 1;
 +              log_tree_commit(&rev, commit);
 +      }
 +
 +      strbuf_release(&format);
 +}
 +
 +static int parse_head(struct commit **head)
 +{
 +      struct commit *current_head;
 +      struct object_id oid;
 +
 +      if (get_oid("HEAD", &oid)) {
 +              current_head = NULL;
 +      } else {
 +              current_head = lookup_commit_reference(&oid);
 +              if (!current_head)
 +                      return error(_("could not parse HEAD"));
 +              if (oidcmp(&oid, &current_head->object.oid)) {
 +                      warning(_("HEAD %s is not a commit!"),
 +                              oid_to_hex(&oid));
 +              }
 +              if (parse_commit(current_head))
 +                      return error(_("could not parse HEAD commit"));
 +      }
 +      *head = current_head;
 +
 +      return 0;
 +}
 +
 +/*
 + * Try to commit without forking 'git commit'. In some cases we need
 + * to run 'git commit' to display an error message
 + *
 + * Returns:
 + *  -1 - error unable to commit
 + *   0 - success
 + *   1 - run 'git commit'
 + */
 +static int try_to_commit(struct strbuf *msg, const char *author,
 +                       struct replay_opts *opts, unsigned int flags,
 +                       struct object_id *oid)
 +{
 +      struct object_id tree;
 +      struct commit *current_head;
 +      struct commit_list *parents = NULL;
 +      struct commit_extra_header *extra = NULL;
 +      struct strbuf err = STRBUF_INIT;
 +      struct strbuf commit_msg = STRBUF_INIT;
 +      char *amend_author = NULL;
 +      const char *hook_commit = NULL;
 +      enum commit_msg_cleanup_mode cleanup;
 +      int res = 0;
 +
 +      if (parse_head(&current_head))
 +              return -1;
 +
 +      if (flags & AMEND_MSG) {
 +              const char *exclude_gpgsig[] = { "gpgsig", NULL };
 +              const char *out_enc = get_commit_output_encoding();
 +              const char *message = logmsg_reencode(current_head, NULL,
 +                                                    out_enc);
 +
 +              if (!msg) {
 +                      const char *orig_message = NULL;
 +
 +                      find_commit_subject(message, &orig_message);
 +                      msg = &commit_msg;
 +                      strbuf_addstr(msg, orig_message);
 +                      hook_commit = "HEAD";
 +              }
 +              author = amend_author = get_author(message);
 +              unuse_commit_buffer(current_head, message);
 +              if (!author) {
 +                      res = error(_("unable to parse commit author"));
 +                      goto out;
 +              }
 +              parents = copy_commit_list(current_head->parents);
 +              extra = read_commit_extra_headers(current_head, exclude_gpgsig);
 +      } else if (current_head) {
 +              commit_list_insert(current_head, &parents);
 +      }
 +
 +      if (write_cache_as_tree(tree.hash, 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)) {
 +              res = 1; /* run 'git commit' to display error message */
 +              goto out;
 +      }
 +
 +      if (find_hook("prepare-commit-msg")) {
 +              res = run_prepare_commit_msg_hook(msg, hook_commit);
 +              if (res)
 +                      goto out;
 +              if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(),
 +                                   2048) < 0) {
 +                      res = error_errno(_("unable to read commit message "
 +                                            "from '%s'"),
 +                                          git_path_commit_editmsg());
 +                      goto out;
 +              }
 +              msg = &commit_msg;
 +      }
 +
 +      cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
 +                                        opts->default_msg_cleanup;
 +
 +      if (cleanup != COMMIT_MSG_CLEANUP_NONE)
 +              strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
 +      if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
 +              res = 1; /* run 'git commit' to display error message */
 +              goto out;
 +      }
 +
 +      if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
 +                               oid, author, opts->gpg_sign, extra)) {
 +              res = error(_("failed to write commit object"));
 +              goto out;
 +      }
 +
 +      if (update_head_with_reflog(current_head, oid,
 +                                  getenv("GIT_REFLOG_ACTION"), msg, &err)) {
 +              res = error("%s", err.buf);
 +              goto out;
 +      }
 +
 +      if (flags & AMEND_MSG)
 +              commit_post_rewrite(current_head, oid);
 +
 +out:
 +      free_commit_extra_headers(extra);
 +      strbuf_release(&err);
 +      strbuf_release(&commit_msg);
 +      free(amend_author);
 +
 +      return res;
 +}
 +
 +static int do_commit(const char *msg_file, const char *author,
 +                   struct replay_opts *opts, unsigned int flags)
 +{
 +      int res = 1;
 +
 +      if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
 +              struct object_id oid;
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0)
 +                      return error_errno(_("unable to read commit message "
 +                                           "from '%s'"),
 +                                         msg_file);
 +
 +              res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
 +                                  &oid);
 +              strbuf_release(&sb);
 +              if (!res) {
 +                      unlink(git_path_cherry_pick_head());
 +                      unlink(git_path_merge_msg());
 +                      if (!is_rebase_i(opts))
 +                              print_commit_summary(NULL, &oid,
 +                                              SUMMARY_SHOW_AUTHOR_DATE);
 +                      return res;
 +              }
 +      }
 +      if (res == 1)
 +              return run_git_commit(msg_file, opts, flags);
 +
 +      return res;
 +}
 +
  static int is_original_commit_empty(struct commit *commit)
  {
        const struct object_id *ptree_oid;
  
        if (parse_commit(commit))
 -              return error(_("could not parse commit %s\n"),
 +              return error(_("could not parse commit %s"),
                             oid_to_hex(&commit->object.oid));
        if (commit->parents) {
                struct commit *parent = commit->parents->item;
                if (parse_commit(parent))
 -                      return error(_("could not parse parent commit %s\n"),
 +                      return error(_("could not parse parent commit %s"),
                                oid_to_hex(&parent->object.oid));
                ptree_oid = &parent->tree->object.oid;
        } else {
 -              ptree_oid = &empty_tree_oid; /* commit is root */
 +              ptree_oid = the_hash_algo->empty_tree; /* commit is root */
        }
  
        return !oidcmp(ptree_oid, &commit->tree->object.oid);
@@@ -1308,13 -794,6 +1308,13 @@@ static const char *command_to_string(co
        die("Unknown command: %d", command);
  }
  
 +static char command_to_char(const enum todo_command command)
 +{
 +      if (command < TODO_COMMENT && todo_command_info[command].c)
 +              return todo_command_info[command].c;
 +      return comment_line_char;
 +}
 +
  static int is_noop(const enum todo_command command)
  {
        return TODO_NOOP <= command;
@@@ -1463,7 -942,6 +1463,7 @@@ static int do_pick_commit(enum todo_com
        struct object_id head;
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
 +      char *author = NULL;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
        int res, unborn = 0, allow;
        } else {
                unborn = get_oid("HEAD", &head);
                if (unborn)
 -                      oidcpy(&head, &empty_tree_oid);
 -              if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
 +                      oidcpy(&head, the_hash_algo->empty_tree);
 +              if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
 +                                     NULL, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
                        opts);
                if (res || command != TODO_REWORD)
                        goto leave;
 -              flags |= EDIT_MSG | AMEND_MSG;
 -              if (command == TODO_REWORD)
 -                      flags |= VERIFY_MSG;
 +              flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG;
                msg_file = NULL;
                goto fast_forward_edit;
        }
                        strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
                        strbuf_addstr(&msgbuf, ")\n");
                }
 +              if (!is_fixup(command))
 +                      author = get_author(msg.message);
        }
  
        if (command == TODO_REWORD)
                }
        }
  
 +      if (opts->signoff)
 +              append_signoff(&msgbuf, 0, 0);
 +
        if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
                res = -1;
        else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
         * write it at all.
         */
        if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
 -          update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL,
 -                     REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
 +          update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
 +                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
        if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
 -          update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL,
 -                     REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
 +          update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
 +                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
  
        if (res) {
                goto leave;
        } else if (allow)
                flags |= ALLOW_EMPTY;
 -      if (!opts->no_commit)
 +      if (!opts->no_commit) {
  fast_forward_edit:
 -              res = run_git_commit(msg_file, opts, flags);
 +              if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 +                      res = do_commit(msg_file, author, opts, flags);
 +              else
 +                      res = error(_("unable to parse commit author"));
 +      }
  
        if (!res && final_fixup) {
                unlink(rebase_path_fixup_msg());
  
  leave:
        free_message(commit, &msg);
 +      free(author);
        update_abort_safety_file();
  
        return res;
@@@ -1715,6 -1184,7 +1715,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)));
                }
@@@ -1783,26 -1253,20 +1783,26 @@@ static int parse_insn_line(struct todo_
        if (i >= TODO_COMMENT)
                return -1;
  
 +      /* Eat up extra spaces/ tabs before object name */
 +      padding = strspn(bol, " \t");
 +      bol += padding;
 +
        if (item->command == TODO_NOOP) {
 +              if (bol != eol)
 +                      return error(_("%s does not accept arguments: '%s'"),
 +                                   command_to_string(item->command), bol);
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = eol - bol;
                return 0;
        }
  
 -      /* Eat up extra spaces/ tabs before object name */
 -      padding = strspn(bol, " \t");
        if (!padding)
 -              return -1;
 -      bol += padding;
 +              return error(_("missing arguments for %s"),
 +                           command_to_string(item->command));
  
        if (item->command == TODO_EXEC) {
 +              item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
@@@ -1869,22 -1333,31 +1869,31 @@@ static int count_commands(struct todo_l
        return count;
  }
  
+ static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
+ {
+       int fd;
+       ssize_t len;
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return error_errno(_("could not open '%s'"), path);
+       len = strbuf_read(sb, fd, 0);
+       close(fd);
+       if (len < 0)
+               return error(_("could not read '%s'."), path);
+       return len;
+ }
  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;
+       int res;
  
        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);
+       if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
+               return -1;
  
        res = stat(todo_file, &st);
        if (res)
@@@ -2166,7 -1639,7 +2175,7 @@@ static int rollback_single_pick(void
        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_oid.hash, NULL))
 +      if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_oid(&head_oid))
                return error(_("cannot abort from a branch yet to be born"));
@@@ -2314,9 -1787,6 +2323,9 @@@ static int make_patch(struct commit *co
        p = short_commit_name(commit);
        if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
                return -1;
 +      if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
 +                     NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
 +              res |= error(_("could not update %s"), "REBASE_HEAD");
  
        strbuf_addf(&buf, "%s/patch", get_dir(opts));
        memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@@ -2401,15 -1871,12 +2410,15 @@@ static int error_failed_squash(struct c
  
  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;
  }
  
@@@ -2568,7 -2033,6 +2577,7 @@@ static int pick_commits(struct todo_lis
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
 +                      delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
@@@ -2669,8 -2133,8 +2678,8 @@@ cleanup_head_ref
                        }
                        msg = reflog_message(opts, "finish", "%s onto %s",
                                head_ref.buf, buf.buf);
 -                      if (update_ref(msg, head_ref.buf, head.hash, orig.hash,
 -                                      REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
 +                      if (update_ref(msg, head_ref.buf, &head, &orig,
 +                                     REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
@@@ -2824,7 -2288,7 +2833,7 @@@ int sequencer_continue(struct replay_op
                        if (res)
                                goto release_todo_list;
                }
 -              if (index_differs_from("HEAD", 0, 0)) {
 +              if (index_differs_from("HEAD", NULL, 0)) {
                        res = error_dirty_index(opts);
                        goto release_todo_list;
                }
@@@ -2982,16 -2446,14 +2991,16 @@@ void append_signoff(struct strbuf *msgb
        strbuf_release(&sob);
  }
  
 -int sequencer_make_script(int keep_empty, FILE *out,
 -              int argc, const char **argv)
 +int sequencer_make_script(FILE *out, int argc, const char **argv,
 +                        unsigned flags)
  {
        char *format = NULL;
        struct pretty_print_context pp = {0};
        struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct commit *commit;
 +      int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 +      const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
  
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
                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));
 +              strbuf_addf(&buf, "%s %s ", insn,
 +                          oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, &buf);
                strbuf_addch(&buf, '\n');
                fputs(buf.buf, out);
        return 0;
  }
  
 -
 -int transform_todo_ids(int shorten_ids)
 +/*
 + * Add commands after pick and (series of) squash/fixup commands
 + * in the todo list.
 + */
 +int sequencer_add_exec_commands(const char *commands)
  {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
 -      int fd, res, i;
 -      FILE *out;
 +      struct todo_item *item;
 +      struct strbuf *buf = &todo_list.buf;
 +      size_t offset = 0, commands_len = strlen(commands);
 +      int i, first;
  
 -      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);
 +      if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
 -      }
 -      close(fd);
  
 -      res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
 -      if (res) {
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
  
 -      out = fopen(todo_file, "w");
 -      if (!out) {
 +      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);
 +                      offset += commands_len;
 +              }
 +              first = 0;
 +      }
 +
 +      /* append final <commands> */
 +      strbuf_add(buf, commands, commands_len);
 +
 +      i = write_message(buf->buf, buf->len, todo_file, 0);
 +      todo_list_release(&todo_list);
 +      return i;
 +}
 +
 +int transform_todos(unsigned flags)
 +{
 +      const char *todo_file = rebase_path_todo();
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct todo_item *item;
 +      int i;
 +
 +      if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
 +              return error(_("could not read '%s'."), todo_file);
 +
 +      if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
 -              return error(_("unable to open '%s' for writing"), todo_file);
 +              return error(_("unusable todo list: '%s'"), 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);
 +
 +      for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
 +              /* if the item is not a command write it and continue */
 +              if (item->command >= TODO_COMMENT) {
 +                      strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg);
 +                      continue;
 +              }
 +
 +              /* add command to the buffer */
 +              if (flags & TODO_LIST_ABBREVIATE_CMDS)
 +                      strbuf_addch(&buf, command_to_char(item->command));
 +              else
 +                      strbuf_addstr(&buf, command_to_string(item->command));
 +
 +              /* add commit id */
 +              if (item->commit) {
 +                      const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
 +                                        short_commit_name(item->commit) :
 +                                        oid_to_hex(&item->commit->object.oid);
 +
 +                      strbuf_addf(&buf, " %s", oid);
                }
 +              /* add all the rest */
 +              if (!item->arg_len)
 +                      strbuf_addch(&buf, '\n');
 +              else
 +                      strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
        }
 -      fclose(out);
 +
 +      i = write_message(buf.buf, buf.len, todo_file, 0);
        todo_list_release(&todo_list);
 -      return 0;
 +      return i;
  }
  
  enum check_level {
@@@ -3155,20 -2584,13 +3164,13 @@@ int check_todo_list(void
        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;
+       int advise_to_edit_todo = 0, res = 0, 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);
+       if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
+               res = -1;
                goto leave_check;
        }
-       close(fd);
        advise_to_edit_todo = res =
                parse_insn_buffer(todo_list.buf.buf, &todo_list);
  
  
        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);
+       if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
+               res = -1;
                goto leave_check;
        }
-       close(fd);
        strbuf_release(&todo_file);
        res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
  
@@@ -3245,19 -2660,6 +3240,19 @@@ leave_check
        return res;
  }
  
 +static int rewrite_file(const char *path, const char *buf, size_t len)
 +{
 +      int rc = 0;
 +      int fd = open(path, O_WRONLY | O_TRUNC);
 +      if (fd < 0)
 +              return error_errno(_("could not open '%s' for writing"), path);
 +      if (write_in_full(fd, buf, len) < 0)
 +              rc = error_errno(_("could not write to '%s'"), path);
 +      if (close(fd) && !rc)
 +              rc = error_errno(_("could not close '%s'"), path);
 +      return rc;
 +}
 +
  /* skip picking commits whose parents are unchanged */
  int skip_unnecessary_picks(void)
  {
        }
        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 (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
+               return -1;
        if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
                todo_list_release(&todo_list);
                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());
 +              if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset,
 +                               todo_list.buf.len - offset) < 0) {
                        todo_list_release(&todo_list);
 -                      close(fd);
                        return -1;
                }
 -              close(fd);
  
                todo_list.current = i;
                if (is_fixup(peek_command(&todo_list, 0)))
@@@ -3374,17 -2787,11 +3362,11 @@@ 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;
+       int res = 0, rearranged = 0, *next, *tail, 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 (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
+               return -1;
        if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
                todo_list_release(&todo_list);
                return -1;
                        }
                }
  
 -              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);
 +              res = rewrite_file(todo_file, buf.buf, buf.len);
                strbuf_release(&buf);
        }