Merge branch 'nd/ita-empty-commit'
authorJunio C Hamano <gitster@pobox.com>
Thu, 27 Oct 2016 21:58:50 +0000 (14:58 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 27 Oct 2016 21:58:50 +0000 (14:58 -0700)
When new paths were added by "git add -N" to the index, it was
enough to circumvent the check by "git commit" to refrain from
making an empty commit without "--allow-empty". The same logic
prevented "git status" to show such a path as "new file" in the
"Changes not staged for commit" section.

* nd/ita-empty-commit:
commit: don't be fooled by ita entries when creating initial commit
commit: fix empty commit creation when there's no changes but ita entries
diff: add --ita-[in]visible-in-index
diff-lib: allow ita entries treated as "not yet exist in index"

1  2 
Documentation/diff-options.txt
builtin/commit.c
diff.c
diff.h
sequencer.c
wt-status.c
index 29630c238912ef9b70ee1a8e9f3906a4d1f63ddd,b249f6a2e7b97d8e249f79dfed1a023c8d29a78d..e6215c372c3c3c876125c8ac363a446fd840e112
@@@ -308,8 -308,6 +308,8 @@@ ifndef::git-format-patch[
        lines are highlighted.  E.g. `--ws-error-highlight=new,old`
        highlights whitespace errors on both deleted and added lines.
        `all` can be used as a short-hand for `old,new,context`.
 +      The `diff.wsErrorHighlight` configuration variable can be
 +      used to specify the default behaviour.
  
  endif::git-format-patch[]
  
@@@ -572,5 -570,13 +572,13 @@@ endif::git-format-patch[
  --line-prefix=<prefix>::
        Prepend an additional prefix to every line of output.
  
+ --ita-invisible-in-index::
+       By default entries added by "git add -N" appear as an existing
+       empty file in "git diff" and a new file in "git diff --cached".
+       This option makes the entry appear as a new file in "git diff"
+       and non-existent in "git diff --cached". This option could be
+       reverted with `--ita-visible-in-index`. Both options are
+       experimental and could be removed in future.
  For more detailed explanation on these common options, see also
  linkgit:gitdiffcore[7].
diff --combined builtin/commit.c
index 9fddb195c5588d651c8cd0c0b43ec11ab145f5ec,1e74bc19f674dcfc64e76d7766874e2bcad71c90..a2baa6ebd5b2dc1090b1bcc87c1e9cf95abd6398
@@@ -183,7 -183,7 +183,7 @@@ static void determine_whence(struct wt_
                whence = FROM_MERGE;
        else if (file_exists(git_path_cherry_pick_head())) {
                whence = FROM_CHERRY_PICK;
 -              if (file_exists(git_path(SEQ_DIR)))
 +              if (file_exists(git_path_seq_dir()))
                        sequencer_in_use = 1;
        }
        else
@@@ -894,9 -894,14 +894,14 @@@ static int prepare_to_commit(const cha
                if (amend)
                        parent = "HEAD^1";
  
-               if (get_sha1(parent, sha1))
-                       commitable = !!active_nr;
-               else {
+               if (get_sha1(parent, sha1)) {
+                       int i, ita_nr = 0;
+                       for (i = 0; i < active_nr; i++)
+                               if (ce_intent_to_add(active_cache[i]))
+                                       ita_nr++;
+                       commitable = active_nr - ita_nr > 0;
+               } else {
                        /*
                         * Unless the user did explicitly request a submodule
                         * ignore mode by passing a command line option we do
                        if (ignore_submodule_arg &&
                            !strcmp(ignore_submodule_arg, "all"))
                                diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
-                       commitable = index_differs_from(parent, diff_flags);
+                       commitable = index_differs_from(parent, diff_flags, 1);
                }
        }
        strbuf_release(&committer_ident);
diff --combined diff.c
index 4c09314cc7970788382a01c6ff839563d4563241,297e0340e93cdf3e14be14f03d41506756d48511..8981477c436dd11d97714f0ad7e1b9363e328798
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -43,7 -43,6 +43,7 @@@ static int diff_stat_graph_width
  static int diff_dirstat_permille_default = 30;
  static struct diff_options default_diff_options;
  static long diff_algorithm;
 +static unsigned ws_error_highlight_default = WSEH_NEW;
  
  static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -173,43 -172,6 +173,43 @@@ long parse_algorithm_value(const char *
        return -1;
  }
  
 +static int parse_one_token(const char **arg, const char *token)
 +{
 +      const char *rest;
 +      if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
 +              *arg = rest;
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +static int parse_ws_error_highlight(const char *arg)
 +{
 +      const char *orig_arg = arg;
 +      unsigned val = 0;
 +
 +      while (*arg) {
 +              if (parse_one_token(&arg, "none"))
 +                      val = 0;
 +              else if (parse_one_token(&arg, "default"))
 +                      val = WSEH_NEW;
 +              else if (parse_one_token(&arg, "all"))
 +                      val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
 +              else if (parse_one_token(&arg, "new"))
 +                      val |= WSEH_NEW;
 +              else if (parse_one_token(&arg, "old"))
 +                      val |= WSEH_OLD;
 +              else if (parse_one_token(&arg, "context"))
 +                      val |= WSEH_CONTEXT;
 +              else {
 +                      return -1 - (int)(arg - orig_arg);
 +              }
 +              if (*arg)
 +                      arg++;
 +      }
 +      return val;
 +}
 +
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -294,15 -256,6 +294,15 @@@ int git_diff_ui_config(const char *var
  
        if (git_diff_heuristic_config(var, value, cb) < 0)
                return -1;
 +
 +      if (!strcmp(var, "diff.wserrorhighlight")) {
 +              int val = parse_ws_error_highlight(value);
 +              if (val < 0)
 +                      return -1;
 +              ws_error_highlight_default = val;
 +              return 0;
 +      }
 +
        if (git_color_config(var, value, cb) < 0)
                return -1;
  
@@@ -2066,7 -2019,7 +2066,7 @@@ found_damage
                return;
  
        /* Show all directories with more than x% of the changes */
 -      qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
 +      QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
  }
  
@@@ -2110,7 -2063,7 +2110,7 @@@ static void show_dirstat_by_line(struc
                return;
  
        /* Show all directories with more than x% of the changes */
 -      qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
 +      QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
  }
  
@@@ -3096,21 -3049,6 +3096,21 @@@ static int similarity_index(struct diff
        return p->score * 100 / MAX_SCORE;
  }
  
 +static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev)
 +{
 +      if (startup_info->have_repository)
 +              return find_unique_abbrev(oid->hash, abbrev);
 +      else {
 +              char *hex = oid_to_hex(oid);
 +              if (abbrev < 0)
 +                      abbrev = FALLBACK_DEFAULT_ABBREV;
 +              if (abbrev > GIT_SHA1_HEXSZ)
 +                      die("BUG: oid abbreviation out of range: %d", abbrev);
 +              hex[abbrev] = '\0';
 +              return hex;
 +      }
 +}
 +
  static void fill_metainfo(struct strbuf *msg,
                          const char *name,
                          const char *other,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
 -              strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
 -                          find_unique_abbrev(one->oid.hash, abbrev));
 -              strbuf_addstr(msg, find_unique_abbrev(two->oid.hash, abbrev));
 +              strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
 +                          diff_abbrev_oid(&one->oid, abbrev),
 +                          diff_abbrev_oid(&two->oid, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@@ -3369,7 -3307,7 +3369,7 @@@ void diff_setup(struct diff_options *op
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
        options->context = diff_context_default;
 -      options->ws_error_highlight = WSEH_NEW;
 +      options->ws_error_highlight = ws_error_highlight_default;
        DIFF_OPT_SET(options, RENAME_EMPTY);
  
        /* pathchange left =NULL by default */
@@@ -3483,7 -3421,7 +3483,7 @@@ void diff_setup_done(struct diff_option
                         */
                        read_cache();
        }
 -      if (options->abbrev <= 0 || 40 < options->abbrev)
 +      if (40 < options->abbrev)
                options->abbrev = 40; /* full */
  
        /*
@@@ -3760,14 -3698,40 +3760,14 @@@ static void enable_patch_output(int *fm
        *fmt |= DIFF_FORMAT_PATCH;
  }
  
 -static int parse_one_token(const char **arg, const char *token)
 +static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
  {
 -      const char *rest;
 -      if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
 -              *arg = rest;
 -              return 1;
 -      }
 -      return 0;
 -}
 +      int val = parse_ws_error_highlight(arg);
  
 -static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
 -{
 -      const char *orig_arg = arg;
 -      unsigned val = 0;
 -      while (*arg) {
 -              if (parse_one_token(&arg, "none"))
 -                      val = 0;
 -              else if (parse_one_token(&arg, "default"))
 -                      val = WSEH_NEW;
 -              else if (parse_one_token(&arg, "all"))
 -                      val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
 -              else if (parse_one_token(&arg, "new"))
 -                      val |= WSEH_NEW;
 -              else if (parse_one_token(&arg, "old"))
 -                      val |= WSEH_OLD;
 -              else if (parse_one_token(&arg, "context"))
 -                      val |= WSEH_CONTEXT;
 -              else {
 -                      error("unknown value after ws-error-highlight=%.*s",
 -                            (int)(arg - orig_arg), orig_arg);
 -                      return 0;
 -              }
 -              if (*arg)
 -                      arg++;
 +      if (val < 0) {
 +              error("unknown value after ws-error-highlight=%.*s",
 +                    -1 - val, arg);
 +              return 0;
        }
        opt->ws_error_highlight = val;
        return 1;
@@@ -3986,7 -3950,11 +3986,11 @@@ int diff_opt_parse(struct diff_options 
        else if (skip_prefix(arg, "--submodule=", &arg))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
 -              return parse_ws_error_highlight(options, arg);
 +              return parse_ws_error_highlight_opt(options, arg);
+       else if (!strcmp(arg, "--ita-invisible-in-index"))
+               options->ita_invisible_in_index = 1;
+       else if (!strcmp(arg, "--ita-visible-in-index"))
+               options->ita_invisible_in_index = 0;
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
@@@ -4172,46 -4140,27 +4176,46 @@@ void diff_free_filepair(struct diff_fil
        free(p);
  }
  
 -/* This is different from find_unique_abbrev() in that
 - * it stuffs the result with dots for alignment.
 - */
 -const char *diff_unique_abbrev(const unsigned char *sha1, int len)
 +const char *diff_aligned_abbrev(const struct object_id *oid, int len)
  {
        int abblen;
        const char *abbrev;
 -      if (len == 40)
 -              return sha1_to_hex(sha1);
  
 -      abbrev = find_unique_abbrev(sha1, len);
 +      if (len == GIT_SHA1_HEXSZ)
 +              return oid_to_hex(oid);
 +
 +      abbrev = diff_abbrev_oid(oid, len);
        abblen = strlen(abbrev);
 -      if (abblen < 37) {
 -              static char hex[41];
 +
 +      /*
 +       * In well-behaved cases, where the abbbreviated result is the
 +       * same as the requested length, append three dots after the
 +       * abbreviation (hence the whole logic is limited to the case
 +       * where abblen < 37); when the actual abbreviated result is a
 +       * bit longer than the requested length, we reduce the number
 +       * of dots so that they match the well-behaved ones.  However,
 +       * if the actual abbreviation is longer than the requested
 +       * length by more than three, we give up on aligning, and add
 +       * three dots anyway, to indicate that the output is not the
 +       * full object name.  Yes, this may be suboptimal, but this
 +       * appears only in "diff --raw --abbrev" output and it is not
 +       * worth the effort to change it now.  Note that this would
 +       * likely to work fine when the automatic sizing of default
 +       * abbreviation length is used--we would be fed -1 in "len" in
 +       * that case, and will end up always appending three-dots, but
 +       * the automatic sizing is supposed to give abblen that ensures
 +       * uniqueness across all objects (statistically speaking).
 +       */
 +      if (abblen < GIT_SHA1_HEXSZ - 3) {
 +              static char hex[GIT_SHA1_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
                        xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
                        xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
 -      return sha1_to_hex(sha1);
 +
 +      return oid_to_hex(oid);
  }
  
  static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
        fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
 -                      diff_unique_abbrev(p->one->oid.hash, opt->abbrev));
 +                      diff_aligned_abbrev(&p->one->oid, opt->abbrev));
                fprintf(opt->file, "%s ",
 -                      diff_unique_abbrev(p->two->oid.hash, opt->abbrev));
 +                      diff_aligned_abbrev(&p->two->oid, opt->abbrev));
        }
        if (p->score) {
                fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p),
@@@ -4693,25 -4642,25 +4697,25 @@@ static int is_summary_empty(const struc
  }
  
  static const char rename_limit_warning[] =
 -"inexact rename detection was skipped due to too many files.";
 +N_("inexact rename detection was skipped due to too many files.");
  
  static const char degrade_cc_to_c_warning[] =
 -"only found copies from modified paths due to too many files.";
 +N_("only found copies from modified paths due to too many files.");
  
  static const char rename_limit_advice[] =
 -"you may want to set your %s variable to at least "
 -"%d and retry the command.";
 +N_("you may want to set your %s variable to at least "
 +   "%d and retry the command.");
  
  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
  {
        if (degraded_cc)
 -              warning(degrade_cc_to_c_warning);
 +              warning(_(degrade_cc_to_c_warning));
        else if (needed)
 -              warning(rename_limit_warning);
 +              warning(_(rename_limit_warning));
        else
                return;
        if (0 < needed && needed < 32767)
 -              warning(rename_limit_advice, varname, needed);
 +              warning(_(rename_limit_advice), varname, needed);
  }
  
  void diff_flush(struct diff_options *options)
@@@ -4978,7 -4927,7 +4982,7 @@@ static int diffnamecmp(const void *a_, 
  void diffcore_fix_diff_index(struct diff_options *options)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
 -      qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
 +      QSORT(q->queue, q->nr, diffnamecmp);
  }
  
  void diffcore_std(struct diff_options *options)
diff --combined diff.h
index 01afc70bd572448ed25cdc869b071994e948c546,94422e1e93a190b1959968b810de90bb4cb30a17..e9ccb38c26c7f1b5b0d5932bb98c2875276cf828
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -146,6 -146,7 +146,7 @@@ struct diff_options 
        int dirstat_permille;
        int setup;
        int abbrev;
+       int ita_invisible_in_index;
  /* white-space error highlighting */
  #define WSEH_NEW 1
  #define WSEH_CONTEXT 2
@@@ -340,11 -341,7 +341,11 @@@ extern void diff_warn_rename_limit(cons
  #define DIFF_STATUS_FILTER_AON                '*'
  #define DIFF_STATUS_FILTER_BROKEN     'B'
  
 -extern const char *diff_unique_abbrev(const unsigned char *, int);
 +/*
 + * This is different from find_unique_abbrev() in that
 + * it stuffs the result with dots for alignment.
 + */
 +extern const char *diff_aligned_abbrev(const struct object_id *sha1, int);
  
  /* do not report anything on removed paths */
  #define DIFF_SILENT_ON_REMOVED 01
@@@ -360,7 -357,7 +361,7 @@@ extern int diff_result_code(struct diff
  
  extern void diff_no_index(struct rev_info *, int, const char **);
  
- extern int index_differs_from(const char *def, int diff_flags);
+ extern int index_differs_from(const char *def, int diff_flags, int ita_invisible_in_index);
  
  /*
   * Fill the contents of the filespec "df", respecting any textconv defined by
diff --combined sequencer.c
index a61fe76f98f0a5805bb75e9b5c40c39b4b750498,b0826353e31ecbc47774addfe7a4358fa9c89bc7..5fd75f30dd4455bf71a12fd628b488fc2b17e192
  #include "merge-recursive.h"
  #include "refs.h"
  #include "argv-array.h"
 +#include "quote.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 ";
  
 -static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE)
 -static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE)
 -static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR)
 -static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE)
 +GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 +
 +static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 +static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 +static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
 +
 +/*
 + * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 + * GIT_AUTHOR_DATE that will be used for the commit that is currently
 + * being rebased.
 + */
 +static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
 +/*
 + * The following files are written by git-rebase just after parsing the
 + * command-line (and are only consumed, not modified, by the sequencer).
 + */
 +static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
 +
 +/* We will introduce the 'interactive rebase' mode later */
 +static inline int is_rebase_i(const struct replay_opts *opts)
 +{
 +      return 0;
 +}
 +
 +static const char *get_dir(const struct replay_opts *opts)
 +{
 +      return git_path_seq_dir();
 +}
 +
 +static const char *get_todo_path(const struct replay_opts *opts)
 +{
 +      return git_path_todo_file();
 +}
  
  static int is_rfc2822_line(const char *buf, int len)
  {
@@@ -138,37 -108,18 +138,37 @@@ static int has_conforming_footer(struc
        return 1;
  }
  
 -static void remove_sequencer_state(void)
 +static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
 +{
 +      static struct strbuf buf = STRBUF_INIT;
 +
 +      strbuf_reset(&buf);
 +      if (opts->gpg_sign)
 +              sq_quotef(&buf, "-S%s", opts->gpg_sign);
 +      return buf.buf;
 +}
 +
 +int sequencer_remove_state(struct replay_opts *opts)
  {
 -      struct strbuf seq_dir = STRBUF_INIT;
 +      struct strbuf dir = STRBUF_INIT;
 +      int i;
 +
 +      free(opts->gpg_sign);
 +      free(opts->strategy);
 +      for (i = 0; i < opts->xopts_nr; i++)
 +              free(opts->xopts[i]);
 +      free(opts->xopts);
 +
 +      strbuf_addf(&dir, "%s", get_dir(opts));
 +      remove_dir_recursively(&dir, 0);
 +      strbuf_release(&dir);
  
 -      strbuf_addstr(&seq_dir, git_path(SEQ_DIR));
 -      remove_dir_recursively(&seq_dir, 0);
 -      strbuf_release(&seq_dir);
 +      return 0;
  }
  
  static const char *action_name(const struct replay_opts *opts)
  {
 -      return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
 +      return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick");
  }
  
  struct commit_message {
        const char *message;
  };
  
 +static const char *short_commit_name(struct commit *commit)
 +{
 +      return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
 +}
 +
  static int get_message(struct commit *commit, struct commit_message *out)
  {
        const char *abbrev, *subject;
        int subject_len;
  
        out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding());
 -      abbrev = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
 +      abbrev = short_commit_name(commit);
  
        subject_len = find_commit_subject(out->message, &subject);
  
@@@ -234,64 -180,22 +234,64 @@@ static void print_advice(int show_hint
        }
  }
  
 -static int write_message(struct strbuf *msgbuf, const char *filename)
 +static int write_message(const void *buf, size_t len, const char *filename,
 +                       int append_eol)
  {
        static struct lock_file msg_file;
  
        int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
        if (msg_fd < 0)
 -              return error_errno(_("Could not lock '%s'"), filename);
 -      if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
 -              return error_errno(_("Could not write to %s"), filename);
 -      strbuf_release(msgbuf);
 -      if (commit_lock_file(&msg_file) < 0)
 -              return error(_("Error wrapping up %s."), filename);
 +              return error_errno(_("could not lock '%s'"), filename);
 +      if (write_in_full(msg_fd, buf, len) < 0) {
 +              rollback_lock_file(&msg_file);
 +              return error_errno(_("could not write to '%s'"), filename);
 +      }
 +      if (append_eol && write(msg_fd, "\n", 1) < 0) {
 +              rollback_lock_file(&msg_file);
 +              return error_errno(_("could not write eol to '%s"), filename);
 +      }
 +      if (commit_lock_file(&msg_file) < 0) {
 +              rollback_lock_file(&msg_file);
 +              return error(_("failed to finalize '%s'."), filename);
 +      }
  
        return 0;
  }
  
 +/*
 + * Reads a file that was presumably written by a shell script, i.e. with an
 + * end-of-line marker that needs to be stripped.
 + *
 + * Note that only the last end-of-line marker is stripped, consistent with the
 + * behavior of "$(cat path)" in a shell script.
 + *
 + * Returns 1 if the file was read, 0 if it could not be read or does not exist.
 + */
 +static int read_oneliner(struct strbuf *buf,
 +      const char *path, int skip_if_empty)
 +{
 +      int orig_len = buf->len;
 +
 +      if (!file_exists(path))
 +              return 0;
 +
 +      if (strbuf_read_file(buf, path, 0) < 0) {
 +              warning_errno(_("could not read '%s'"), path);
 +              return 0;
 +      }
 +
 +      if (buf->len > orig_len && buf->buf[buf->len - 1] == '\n') {
 +              if (--buf->len > orig_len && buf->buf[buf->len - 1] == '\r')
 +                      --buf->len;
 +              buf->buf[buf->len] = '\0';
 +      }
 +
 +      if (skip_if_empty && buf->len == orig_len)
 +              return 0;
 +
 +      return 1;
 +}
 +
  static struct tree *empty_tree(void)
  {
        return lookup_tree(EMPTY_TREE_SHA1_BIN);
  static int error_dirty_index(struct replay_opts *opts)
  {
        if (read_cache_unmerged())
 -              return error_resolve_conflict(action_name(opts));
 +              return error_resolve_conflict(_(action_name(opts)));
  
 -      /* Different translation strings for cherry-pick and revert */
 -      if (opts->action == REPLAY_PICK)
 -              error(_("Your local changes would be overwritten by cherry-pick."));
 -      else
 -              error(_("Your local changes would be overwritten by revert."));
 +      error(_("your local changes would be overwritten by %s."),
 +              _(action_name(opts)));
  
        if (advice_commit_before_merge)
 -              advise(_("Commit your changes or stash them to proceed."));
 +              advise(_("commit your changes or stash them to proceed."));
        return -1;
  }
  
@@@ -321,7 -228,7 +321,7 @@@ static int fast_forward_to(const unsign
        if (checkout_fast_forward(from, to, 1))
                return -1; /* the callee should have complained already */
  
 -      strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts));
 +      strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
  
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
@@@ -367,7 -274,7 +367,7 @@@ static int do_recursive_merge(struct co
        struct merge_options o;
        struct tree *result, *next_tree, *base_tree, *head_tree;
        int clean;
 -      const char **xopt;
 +      char **xopt;
        static struct lock_file index_lock;
  
        hold_locked_index(&index_lock, 1);
            write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
                /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
                return error(_("%s: Unable to write new index file"),
 -                      action_name(opts));
 +                      _(action_name(opts)));
        rollback_lock_file(&index_lock);
  
        if (opts->signoff)
@@@ -415,7 -322,7 +415,7 @@@ static int is_index_unchanged(void
        struct commit *head_commit;
  
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
 -              return error(_("Could not resolve HEAD commit\n"));
 +              return error(_("could not resolve HEAD commit\n"));
  
        head_commit = lookup_commit(head_sha1);
  
  
        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\n"));
  
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
  }
  
 +/*
 + * Read the author-script file into an environment block, ready for use in
 + * run_command(), that can be free()d afterwards.
 + */
 +static char **read_author_script(void)
 +{
 +      struct strbuf script = STRBUF_INIT;
 +      int i, count = 0;
 +      char *p, *p2, **env;
 +      size_t env_size;
 +
 +      if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
 +              return NULL;
 +
 +      for (p = script.buf; *p; p++)
 +              if (skip_prefix(p, "'\\\\''", (const char **)&p2))
 +                      strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
 +              else if (*p == '\'')
 +                      strbuf_splice(&script, p-- - script.buf, 1, "", 0);
 +              else if (*p == '\n') {
 +                      *p = '\0';
 +                      count++;
 +              }
 +
 +      env_size = (count + 1) * sizeof(*env);
 +      strbuf_grow(&script, env_size);
 +      memmove(script.buf + env_size, script.buf, script.len);
 +      p = script.buf + env_size;
 +      env = (char **)strbuf_detach(&script, NULL);
 +
 +      for (i = 0; i < count; i++) {
 +              env[i] = p;
 +              p += strlen(p) + 1;
 +      }
 +      env[count] = NULL;
 +
 +      return env;
 +}
 +
 +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"
 +"\n"
 +"  git commit --amend %s\n"
 +"\n"
 +"If they are meant to go into a new commit, run:\n"
 +"\n"
 +"  git commit %s\n"
 +"\n"
 +"In both cases, once you're done, continue with:\n"
 +"\n"
 +"  git rebase --continue\n");
 +
  /*
   * 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 date and name.
 + *
   * If we are revert, or if our cherry-pick results in a hand merge,
   * we had better say that the current user is responsible for that.
 + *
 + * An exception is when run_git_commit() is called during an
 + * interactive rebase: in that case, we will want to retain the
 + * author metadata.
   */
  static int run_git_commit(const char *defmsg, struct replay_opts *opts,
 -                        int allow_empty)
 +                        int allow_empty, int edit, int amend,
 +                        int cleanup_commit_message)
  {
 +      char **env = NULL;
        struct argv_array array;
        int rc;
        const char *value;
  
 +      if (is_rebase_i(opts)) {
 +              env = read_author_script();
 +              if (!env) {
 +                      const char *gpg_opt = gpg_sign_opt_quoted(opts);
 +
 +                      return error(_(staged_changes_advice),
 +                                   gpg_opt, gpg_opt);
 +              }
 +      }
 +
        argv_array_init(&array);
        argv_array_push(&array, "commit");
        argv_array_push(&array, "-n");
  
 +      if (amend)
 +              argv_array_push(&array, "--amend");
        if (opts->gpg_sign)
                argv_array_pushf(&array, "-S%s", opts->gpg_sign);
        if (opts->signoff)
                argv_array_push(&array, "-s");
 -      if (!opts->edit) {
 -              argv_array_push(&array, "-F");
 -              argv_array_push(&array, defmsg);
 -              if (!opts->signoff &&
 -                  !opts->record_origin &&
 -                  git_config_get_value("commit.cleanup", &value))
 -                      argv_array_push(&array, "--cleanup=verbatim");
 -      }
 +      if (defmsg)
 +              argv_array_pushl(&array, "-F", defmsg, NULL);
 +      if (cleanup_commit_message)
 +              argv_array_push(&array, "--cleanup=strip");
 +      if (edit)
 +              argv_array_push(&array, "-e");
 +      else if (!cleanup_commit_message &&
 +               !opts->signoff && !opts->record_origin &&
 +               git_config_get_value("commit.cleanup", &value))
 +              argv_array_push(&array, "--cleanup=verbatim");
  
        if (allow_empty)
                argv_array_push(&array, "--allow-empty");
        if (opts->allow_empty_message)
                argv_array_push(&array, "--allow-empty-message");
  
 -      rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
 +      rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL,
 +                      (const char *const *)env);
        argv_array_clear(&array);
 +      free(env);
 +
        return rc;
  }
  
@@@ -564,12 -394,12 +564,12 @@@ static int is_original_commit_empty(str
        const unsigned char *ptree_sha1;
  
        if (parse_commit(commit))
 -              return error(_("Could not parse commit %s\n"),
 +              return error(_("could not parse commit %s\n"),
                             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\n"),
                                oid_to_hex(&parent->object.oid));
                ptree_sha1 = parent->tree->object.oid.hash;
        } else {
@@@ -617,26 -447,7 +617,26 @@@ static int allow_empty(struct replay_op
                return 1;
  }
  
 -static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 +enum todo_command {
 +      TODO_PICK = 0,
 +      TODO_REVERT
 +};
 +
 +static const char *todo_command_strings[] = {
 +      "pick",
 +      "revert"
 +};
 +
 +static const char *command_to_string(const enum todo_command command)
 +{
 +      if (command < ARRAY_SIZE(todo_command_strings))
 +              return todo_command_strings[command];
 +      die("Unknown command: %d", command);
 +}
 +
 +
 +static int do_pick_commit(enum todo_command command, struct commit *commit,
 +              struct replay_opts *opts)
  {
        unsigned char head[20];
        struct commit *base, *next, *parent;
                 * to work on.
                 */
                if (write_cache_as_tree(head, 0, NULL))
 -                      return error(_("Your index file is unmerged."));
 +                      return error(_("your index file is unmerged."));
        } else {
                unborn = get_sha1("HEAD", head);
                if (unborn)
                        hashcpy(head, EMPTY_TREE_SHA1_BIN);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0))
+               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
                struct commit_list *p;
  
                if (!opts->mainline)
 -                      return error(_("Commit %s is a merge but no -m option was given."),
 +                      return error(_("commit %s is a merge but no -m option was given."),
                                oid_to_hex(&commit->object.oid));
  
                for (cnt = 1, p = commit->parents;
                     cnt++)
                        p = p->next;
                if (cnt != opts->mainline || !p)
 -                      return error(_("Commit %s does not have parent %d"),
 +                      return error(_("commit %s does not have parent %d"),
                                oid_to_hex(&commit->object.oid), opts->mainline);
                parent = p->item;
        } else if (0 < opts->mainline)
 -              return error(_("Mainline was specified but commit %s is not a merge."),
 +              return error(_("mainline was specified but commit %s is not a merge."),
                        oid_to_hex(&commit->object.oid));
        else
                parent = commit->parents->item;
                return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
  
        if (parent && parse_commit(parent) < 0)
 -              /* TRANSLATORS: The first %s will be "revert" or
 -                 "cherry-pick", the second %s a SHA1 */
 +              /* TRANSLATORS: The first %s will be a "todo" command like
 +                 "revert" or "pick", the second %s a SHA1. */
                return error(_("%s: cannot parse parent commit %s"),
 -                      action_name(opts), oid_to_hex(&parent->object.oid));
 +                      command_to_string(command),
 +                      oid_to_hex(&parent->object.oid));
  
        if (get_message(commit, &msg) != 0)
 -              return error(_("Cannot get commit message for %s"),
 +              return error(_("cannot get commit message for %s"),
                        oid_to_hex(&commit->object.oid));
  
        /*
         * reverse of it if we are revert.
         */
  
 -      if (opts->action == REPLAY_REVERT) {
 +      if (command == TODO_REVERT) {
                base = commit;
                base_label = msg.label;
                next = parent;
                }
        }
  
 -      if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
 +      if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
                res = do_recursive_merge(base, next, base_label, next_label,
                                         head, &msgbuf, opts);
                if (res < 0)
                        return res;
 -              res |= write_message(&msgbuf, git_path_merge_msg());
 +              res |= write_message(msgbuf.buf, msgbuf.len,
 +                                   git_path_merge_msg(), 0);
        } else {
                struct commit_list *common = NULL;
                struct commit_list *remotes = NULL;
  
 -              res = write_message(&msgbuf, git_path_merge_msg());
 +              res = write_message(msgbuf.buf, msgbuf.len,
 +                                  git_path_merge_msg(), 0);
  
                commit_list_insert(base, &common);
                commit_list_insert(next, &remotes);
 -              res |= try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
 +              res |= try_merge_command(opts->strategy,
 +                                       opts->xopts_nr, (const char **)opts->xopts,
                                        common, sha1_to_hex(head), remotes);
                free_commit_list(common);
                free_commit_list(remotes);
        }
 +      strbuf_release(&msgbuf);
  
        /*
         * If the merge was clean or if it failed due to conflict, we write
         * However, if the merge did not even start, then we don't want to
         * write it at all.
         */
 -      if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) &&
 +      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))
                res = -1;
 -      if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || 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))
                res = -1;
  
        if (res) {
 -              error(opts->action == REPLAY_REVERT
 +              error(command == TODO_REVERT
                      ? _("could not revert %s... %s")
                      : _("could not apply %s... %s"),
 -                    find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV),
 -                    msg.subject);
 +                    short_commit_name(commit), msg.subject);
                print_advice(res == 1, opts);
                rerere(opts->allow_rerere_auto);
                goto leave;
                goto leave;
        }
        if (!opts->no_commit)
 -              res = run_git_commit(git_path_merge_msg(), opts, allow);
 +              res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(),
 +                                   opts, allow, opts->edit, 0, 0);
  
  leave:
        free_message(commit, &msg);
@@@ -841,160 -647,133 +841,160 @@@ static int read_and_refresh_cache(struc
        if (read_index_preload(&the_index, NULL) < 0) {
                rollback_lock_file(&index_lock);
                return error(_("git %s: failed to read the index"),
 -                      action_name(opts));
 +                      _(action_name(opts)));
        }
        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));
 +                              _(action_name(opts)));
                }
        }
        rollback_lock_file(&index_lock);
        return 0;
  }
  
 -static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
 -              struct replay_opts *opts)
 +struct todo_item {
 +      enum todo_command command;
 +      struct commit *commit;
 +      const char *arg;
 +      int arg_len;
 +      size_t offset_in_buf;
 +};
 +
 +struct todo_list {
 +      struct strbuf buf;
 +      struct todo_item *items;
 +      int nr, alloc, current;
 +};
 +
 +#define TODO_LIST_INIT { STRBUF_INIT }
 +
 +static void todo_list_release(struct todo_list *todo_list)
  {
 -      struct commit_list *cur = NULL;
 -      const char *sha1_abbrev = NULL;
 -      const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
 -      const char *subject;
 -      int subject_len;
 +      strbuf_release(&todo_list->buf);
 +      free(todo_list->items);
 +      todo_list->items = NULL;
 +      todo_list->nr = todo_list->alloc = 0;
 +}
  
 -      for (cur = todo_list; cur; cur = cur->next) {
 -              const char *commit_buffer = get_commit_buffer(cur->item, NULL);
 -              sha1_abbrev = find_unique_abbrev(cur->item->object.oid.hash, DEFAULT_ABBREV);
 -              subject_len = find_commit_subject(commit_buffer, &subject);
 -              strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
 -                      subject_len, subject);
 -              unuse_commit_buffer(cur->item, commit_buffer);
 -      }
 -      return 0;
 +static struct todo_item *append_new_todo(struct todo_list *todo_list)
 +{
 +      ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);
 +      return todo_list->items + todo_list->nr++;
  }
  
 -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
 +static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
  {
        unsigned char commit_sha1[20];
 -      enum replay_action action;
        char *end_of_object_name;
 -      int saved, status, padding;
 -
 -      if (starts_with(bol, "pick")) {
 -              action = REPLAY_PICK;
 -              bol += strlen("pick");
 -      } else if (starts_with(bol, "revert")) {
 -              action = REPLAY_REVERT;
 -              bol += strlen("revert");
 -      } else
 -              return NULL;
 +      int i, saved, status, padding;
 +
 +      /* left-trim */
 +      bol += strspn(bol, " \t");
 +
 +      for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++)
 +              if (skip_prefix(bol, todo_command_strings[i], &bol)) {
 +                      item->command = i;
 +                      break;
 +              }
 +      if (i >= ARRAY_SIZE(todo_command_strings))
 +              return -1;
  
        /* Eat up extra spaces/ tabs before object name */
        padding = strspn(bol, " \t");
        if (!padding)
 -              return NULL;
 +              return -1;
        bol += padding;
  
 -      end_of_object_name = bol + strcspn(bol, " \t\n");
 +      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);
        *end_of_object_name = saved;
  
 -      /*
 -       * Verify that the action matches up with the one in
 -       * opts; we don't support arbitrary instructions
 -       */
 -      if (action != opts->action) {
 -              if (action == REPLAY_REVERT)
 -                    error((opts->action == REPLAY_REVERT)
 -                          ? _("Cannot revert during another revert.")
 -                          : _("Cannot revert during a cherry-pick."));
 -              else
 -                    error((opts->action == REPLAY_REVERT)
 -                          ? _("Cannot cherry-pick during a revert.")
 -                          : _("Cannot cherry-pick during another cherry-pick."));
 -              return NULL;
 -      }
 +      item->arg = end_of_object_name + strspn(end_of_object_name, " \t");
 +      item->arg_len = (int)(eol - item->arg);
  
        if (status < 0)
 -              return NULL;
 +              return -1;
  
 -      return lookup_commit_reference(commit_sha1);
 +      item->commit = lookup_commit_reference(commit_sha1);
 +      return !item->commit;
  }
  
 -static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
 -                      struct replay_opts *opts)
 +static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
  {
 -      struct commit_list **next = todo_list;
 -      struct commit *commit;
 -      char *p = buf;
 -      int i;
 +      struct todo_item *item;
 +      char *p = buf, *next_p;
 +      int i, res = 0;
  
 -      for (i = 1; *p; i++) {
 +      for (i = 1; *p; i++, p = next_p) {
                char *eol = strchrnul(p, '\n');
 -              commit = parse_insn_line(p, eol, opts);
 -              if (!commit)
 -                      return error(_("Could not parse line %d."), i);
 -              next = commit_list_append(commit, next);
 -              p = *eol ? eol + 1 : eol;
 +
 +              next_p = *eol ? eol + 1 /* skip LF */ : eol;
 +
 +              if (p != eol && eol[-1] == '\r')
 +                      eol--; /* strip Carriage Return */
 +
 +              item = append_new_todo(todo_list);
 +              item->offset_in_buf = p - todo_list->buf.buf;
 +              if (parse_insn_line(item, p, eol)) {
 +                      res = error(_("invalid line %d: %.*s"),
 +                              i, (int)(eol - p), p);
 +                      item->command = -1;
 +              }
        }
 -      if (!*todo_list)
 -              return error(_("No commits parsed."));
 -      return 0;
 +      if (!todo_list->nr)
 +              return error(_("no commits parsed."));
 +      return res;
  }
  
 -static int read_populate_todo(struct commit_list **todo_list,
 +static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
  {
 -      struct strbuf buf = STRBUF_INIT;
 +      const char *todo_file = get_todo_path(opts);
        int fd, res;
  
 -      fd = open(git_path_todo_file(), O_RDONLY);
 +      strbuf_reset(&todo_list->buf);
 +      fd = open(todo_file, O_RDONLY);
        if (fd < 0)
 -              return error_errno(_("Could not open %s"),
 -                                 git_path_todo_file());
 -      if (strbuf_read(&buf, fd, 0) < 0) {
 +              return error_errno(_("could not open '%s'"), todo_file);
 +      if (strbuf_read(&todo_list->buf, fd, 0) < 0) {
                close(fd);
 -              strbuf_release(&buf);
 -              return error(_("Could not read %s."), git_path_todo_file());
 +              return error(_("could not read '%s'."), todo_file);
        }
        close(fd);
  
 -      res = parse_insn_buffer(buf.buf, todo_list, opts);
 -      strbuf_release(&buf);
 +      res = parse_insn_buffer(todo_list->buf.buf, todo_list);
        if (res)
 -              return error(_("Unusable instruction sheet: %s"),
 -                      git_path_todo_file());
 +              return error(_("unusable instruction sheet: '%s'"), todo_file);
 +
 +      if (!is_rebase_i(opts)) {
 +              enum todo_command valid =
 +                      opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT;
 +              int i;
 +
 +              for (i = 0; i < todo_list->nr; i++)
 +                      if (valid == todo_list->items[i].command)
 +                              continue;
 +                      else if (valid == TODO_PICK)
 +                              return error(_("cannot cherry-pick during a revert."));
 +                      else
 +                              return error(_("cannot revert during a cherry-pick."));
 +      }
 +
 +      return 0;
 +}
 +
 +static int git_config_string_dup(char **dest,
 +                               const char *var, const char *value)
 +{
 +      if (!value)
 +              return config_error_nonbool(var);
 +      free(*dest);
 +      *dest = xstrdup(value);
        return 0;
  }
  
@@@ -1018,39 -797,23 +1018,39 @@@ static int populate_opts_cb(const char 
        else if (!strcmp(key, "options.mainline"))
                opts->mainline = git_config_int(key, value);
        else if (!strcmp(key, "options.strategy"))
 -              git_config_string(&opts->strategy, key, value);
 +              git_config_string_dup(&opts->strategy, key, value);
        else if (!strcmp(key, "options.gpg-sign"))
 -              git_config_string(&opts->gpg_sign, key, value);
 +              git_config_string_dup(&opts->gpg_sign, key, value);
        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
 -              return error(_("Invalid key: %s"), key);
 +              return error(_("invalid key: %s"), key);
  
        if (!error_flag)
 -              return error(_("Invalid value for %s: %s"), key, value);
 +              return error(_("invalid value for %s: %s"), key, value);
  
        return 0;
  }
  
 -static int read_populate_opts(struct replay_opts **opts)
 +static int read_populate_opts(struct replay_opts *opts)
  {
 +      if (is_rebase_i(opts)) {
 +              struct strbuf buf = STRBUF_INIT;
 +
 +              if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
 +                      if (!starts_with(buf.buf, "-S"))
 +                              strbuf_reset(&buf);
 +                      else {
 +                              free(opts->gpg_sign);
 +                              opts->gpg_sign = xstrdup(buf.buf + 2);
 +                      }
 +              }
 +              strbuf_release(&buf);
 +
 +              return 0;
 +      }
 +
        if (!file_exists(git_path_opts_file()))
                return 0;
        /*
         * about this case, though, because we wrote that file ourselves, so we
         * are pretty certain that it is syntactically correct.
         */
 -      if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0)
 -              return error(_("Malformed options sheet: %s"),
 +      if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0)
 +              return error(_("malformed options sheet: '%s'"),
                        git_path_opts_file());
        return 0;
  }
  
 -static int walk_revs_populate_todo(struct commit_list **todo_list,
 +static int walk_revs_populate_todo(struct todo_list *todo_list,
                                struct replay_opts *opts)
  {
 +      enum todo_command command = opts->action == REPLAY_PICK ?
 +              TODO_PICK : TODO_REVERT;
 +      const char *command_string = todo_command_strings[command];
        struct commit *commit;
 -      struct commit_list **next;
  
        if (prepare_revs(opts))
                return -1;
  
 -      next = todo_list;
 -      while ((commit = get_revision(opts->revs)))
 -              next = commit_list_append(commit, next);
 +      while ((commit = get_revision(opts->revs))) {
 +              struct todo_item *item = append_new_todo(todo_list);
 +              const char *commit_buffer = get_commit_buffer(commit, NULL);
 +              const char *subject;
 +              int subject_len;
 +
 +              item->command = command;
 +              item->commit = commit;
 +              item->arg = NULL;
 +              item->arg_len = 0;
 +              item->offset_in_buf = todo_list->buf.len;
 +              subject_len = find_commit_subject(commit_buffer, &subject);
 +              strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string,
 +                      short_commit_name(commit), subject_len, subject);
 +              unuse_commit_buffer(commit, commit_buffer);
 +      }
        return 0;
  }
  
@@@ -1103,7 -851,7 +1103,7 @@@ static int create_seq_dir(void
                return -1;
        }
        else if (mkdir(git_path_seq_dir(), 0777) < 0)
 -              return error_errno(_("Could not create sequencer directory %s"),
 +              return error_errno(_("could not create sequencer directory '%s'"),
                                   git_path_seq_dir());
        return 0;
  }
@@@ -1117,17 -865,17 +1117,17 @@@ static int save_head(const char *head
        fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
        if (fd < 0) {
                rollback_lock_file(&head_lock);
 -              return error_errno(_("Could not lock HEAD"));
 +              return error_errno(_("could not lock HEAD"));
        }
        strbuf_addf(&buf, "%s\n", head);
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
                rollback_lock_file(&head_lock);
 -              return error_errno(_("Could not write to %s"),
 +              return error_errno(_("could not write to '%s'"),
                                   git_path_head_file());
        }
        if (commit_lock_file(&head_lock) < 0) {
                rollback_lock_file(&head_lock);
 -              return error(_("Error wrapping up %s."), git_path_head_file());
 +              return error(_("failed to finalize '%s'."), git_path_head_file());
        }
        return 0;
  }
@@@ -1156,7 -904,7 +1156,7 @@@ static int rollback_single_pick(void
        return reset_for_rollback(head_sha1);
  }
  
 -static int sequencer_rollback(struct replay_opts *opts)
 +int sequencer_rollback(struct replay_opts *opts)
  {
        FILE *f;
        unsigned char sha1[20];
                return rollback_single_pick();
        }
        if (!f)
 -              return error_errno(_("cannot open %s"), git_path_head_file());
 +              return error_errno(_("cannot open '%s'"), git_path_head_file());
        if (strbuf_getline_lf(&buf, f)) {
 -              error(_("cannot read %s: %s"), git_path_head_file(),
 +              error(_("cannot read '%s': %s"), git_path_head_file(),
                      ferror(f) ?  strerror(errno) : _("unexpected end of file"));
                fclose(f);
                goto fail;
        }
        if (reset_for_rollback(sha1))
                goto fail;
 -      remove_sequencer_state();
        strbuf_release(&buf);
 -      return 0;
 +      return sequencer_remove_state(opts);
  fail:
        strbuf_release(&buf);
        return -1;
  }
  
 -static int save_todo(struct commit_list *todo_list, struct replay_opts *opts)
 +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
  {
        static struct lock_file todo_lock;
 -      struct strbuf buf = STRBUF_INIT;
 -      int fd;
 +      const char *todo_path = get_todo_path(opts);
 +      int next = todo_list->current, offset, fd;
  
 -      fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0);
 +      fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
 -              return error_errno(_("Could not lock '%s'"),
 -                                 git_path_todo_file());
 -      if (format_todo(&buf, todo_list, opts) < 0) {
 -              strbuf_release(&buf);
 -              return error(_("Could not format %s."), git_path_todo_file());
 -      }
 -      if (write_in_full(fd, buf.buf, buf.len) < 0) {
 -              strbuf_release(&buf);
 -              return error_errno(_("Could not write to %s"),
 -                                 git_path_todo_file());
 -      }
 -      if (commit_lock_file(&todo_lock) < 0) {
 -              strbuf_release(&buf);
 -              return error(_("Error wrapping up %s."), git_path_todo_file());
 -      }
 -      strbuf_release(&buf);
 +              return error_errno(_("could not lock '%s'"), todo_path);
 +      offset = next < todo_list->nr ?
 +              todo_list->items[next].offset_in_buf : todo_list->buf.len;
 +      if (write_in_full(fd, todo_list->buf.buf + offset,
 +                      todo_list->buf.len - offset) < 0)
 +              return error_errno(_("could not write to '%s'"), todo_path);
 +      if (commit_lock_file(&todo_lock) < 0)
 +              return error(_("failed to finalize '%s'."), todo_path);
        return 0;
  }
  
@@@ -1252,8 -1009,9 +1252,8 @@@ static int save_opts(struct replay_opt
        return res;
  }
  
 -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 +static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
  {
 -      struct commit_list *cur;
        int res;
  
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (read_and_refresh_cache(opts))
                return -1;
  
 -      for (cur = todo_list; cur; cur = cur->next) {
 -              if (save_todo(cur, opts))
 +      while (todo_list->current < todo_list->nr) {
 +              struct todo_item *item = todo_list->items + todo_list->current;
 +              if (save_todo(todo_list, opts))
                        return -1;
 -              res = do_pick_commit(cur->item, opts);
 +              res = do_pick_commit(item->command, item->commit, opts);
 +              todo_list->current++;
                if (res)
                        return res;
        }
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory
         */
 -      remove_sequencer_state();
 -      return 0;
 +      return sequencer_remove_state(opts);
  }
  
  static int continue_single_pick(void)
        return run_command_v_opt(argv, RUN_GIT_CMD);
  }
  
 -static int sequencer_continue(struct replay_opts *opts)
 +int sequencer_continue(struct replay_opts *opts)
  {
 -      struct commit_list *todo_list = NULL;
 +      struct todo_list todo_list = TODO_LIST_INIT;
 +      int res;
  
 -      if (!file_exists(git_path_todo_file()))
 +      if (read_and_refresh_cache(opts))
 +              return -1;
 +
 +      if (!file_exists(get_todo_path(opts)))
                return continue_single_pick();
 -      if (read_populate_opts(&opts) ||
 -                      read_populate_todo(&todo_list, opts))
 +      if (read_populate_opts(opts))
                return -1;
 +      if ((res = read_populate_todo(&todo_list, opts)))
 +              goto release_todo_list;
  
        /* Verify that the conflict has been resolved */
        if (file_exists(git_path_cherry_pick_head()) ||
            file_exists(git_path_revert_head())) {
 -              int ret = continue_single_pick();
 -              if (ret)
 -                      return ret;
 +              res = continue_single_pick();
 +              if (res)
 +                      goto release_todo_list;
        }
-       if (index_differs_from("HEAD", 0)) {
 -      if (index_differs_from("HEAD", 0, 0))
 -              return error_dirty_index(opts);
 -      todo_list = todo_list->next;
 -      return pick_commits(todo_list, opts);
++      if (index_differs_from("HEAD", 0, 0)) {
 +              res = error_dirty_index(opts);
 +              goto release_todo_list;
 +      }
 +      todo_list.current++;
 +      res = pick_commits(&todo_list, opts);
 +release_todo_list:
 +      todo_list_release(&todo_list);
 +      return res;
  }
  
  static int single_pick(struct commit *cmit, struct replay_opts *opts)
  {
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
 -      return do_pick_commit(cmit, opts);
 +      return do_pick_commit(opts->action == REPLAY_PICK ?
 +              TODO_PICK : TODO_REVERT, cmit, opts);
  }
  
  int sequencer_pick_revisions(struct replay_opts *opts)
  {
 -      struct commit_list *todo_list = NULL;
 +      struct todo_list todo_list = TODO_LIST_INIT;
        unsigned char sha1[20];
 -      int i;
 -
 -      if (opts->subcommand == REPLAY_NONE)
 -              assert(opts->revs);
 +      int i, res;
  
 +      assert(opts->revs);
        if (read_and_refresh_cache(opts))
                return -1;
  
 -      /*
 -       * Decide what to do depending on the arguments; a fresh
 -       * cherry-pick should be handled differently from an existing
 -       * one that is being continued
 -       */
 -      if (opts->subcommand == REPLAY_REMOVE_STATE) {
 -              remove_sequencer_state();
 -              return 0;
 -      }
 -      if (opts->subcommand == REPLAY_ROLLBACK)
 -              return sequencer_rollback(opts);
 -      if (opts->subcommand == REPLAY_CONTINUE)
 -              return sequencer_continue(opts);
 -
        for (i = 0; i < opts->revs->pending.nr; i++) {
                unsigned char sha1[20];
                const char *name = opts->revs->pending.objects[i].name;
                        create_seq_dir() < 0)
                return -1;
        if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT))
 -              return error(_("Can't revert as initial commit"));
 +              return error(_("can't revert as initial commit"));
        if (save_head(sha1_to_hex(sha1)))
                return -1;
        if (save_opts(opts))
                return -1;
 -      return pick_commits(todo_list, opts);
 +      res = pick_commits(&todo_list, opts);
 +      todo_list_release(&todo_list);
 +      return res;
  }
  
  void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
diff --combined wt-status.c
index 0bd2781225a2a9fa3e7a553aaf49112745743244,5b0965ef27556bbb99052044dab8840350fd3274..a2e9d332d8332bb1dbeea26b683c5e3f032a822e
@@@ -16,7 -16,6 +16,7 @@@
  #include "strbuf.h"
  #include "utf8.h"
  #include "worktree.h"
 +#include "lockfile.h"
  
  static const char cut_line[] =
  "------------------------ >8 ------------------------\n";
@@@ -438,7 -437,7 +438,7 @@@ static void wt_status_collect_changed_c
  
                switch (p->status) {
                case DIFF_STATUS_ADDED:
-                       die("BUG: worktree status add???");
+                       d->mode_worktree = p->two->mode;
                        break;
  
                case DIFF_STATUS_DELETED:
@@@ -548,6 -547,7 +548,7 @@@ static void wt_status_collect_changes_w
        setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
+       rev.diffopt.ita_invisible_in_index = 1;
        if (!s->show_untracked_files)
                DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
        if (s->ignore_submodule_arg) {
@@@ -571,6 -571,7 +572,7 @@@ static void wt_status_collect_changes_i
        setup_revisions(0, NULL, &rev, &opt);
  
        DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+       rev.diffopt.ita_invisible_in_index = 1;
        if (s->ignore_submodule_arg) {
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
        } else {
@@@ -606,6 -607,8 +608,8 @@@ static void wt_status_collect_changes_i
  
                if (!ce_path_match(ce, &s->pathspec, NULL))
                        continue;
+               if (ce_intent_to_add(ce))
+                       continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                if (!d) {
@@@ -912,6 -915,7 +916,7 @@@ static void wt_longstatus_print_verbose
  
        init_revisions(&rev, NULL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+       rev.diffopt.ita_invisible_in_index = 1;
  
        memset(&opt, 0, sizeof(opt));
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
@@@ -1111,6 -1115,7 +1116,6 @@@ static void abbrev_sha1_in_line(struct 
        split = strbuf_split_max(line, ' ', 3);
        if (split[0] && split[1]) {
                unsigned char sha1[20];
 -              const char *abbrev;
  
                /*
                 * strbuf_split_max left a space. Trim it and re-add
                 */
                strbuf_trim(split[1]);
                if (!get_sha1(split[1]->buf, sha1)) {
 -                      abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
                        strbuf_reset(split[1]);
 -                      strbuf_addf(split[1], "%s ", abbrev);
 +                      strbuf_add_unique_abbrev(split[1], sha1,
 +                                               DEFAULT_ABBREV);
 +                      strbuf_addch(split[1], ' ');
                        strbuf_reset(line);
                        for (i = 0; split[i]; i++)
                                strbuf_addbuf(line, split[i]);
@@@ -1344,8 -1348,10 +1349,8 @@@ static char *get_branch(const struct wo
        else if (starts_with(sb.buf, "refs/"))
                ;
        else if (!get_sha1_hex(sb.buf, sha1)) {
 -              const char *abbrev;
 -              abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
                strbuf_reset(&sb);
 -              strbuf_addstr(&sb, abbrev);
 +              strbuf_add_unique_abbrev(&sb, sha1, DEFAULT_ABBREV);
        } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
                goto got_nothing;
        else                    /* bisect */
@@@ -1382,7 -1388,8 +1387,7 @@@ static int grab_1st_switch(unsigned cha
        if (!strcmp(cb->buf.buf, "HEAD")) {
                /* HEAD is relative. Resolve it to the right reflog entry. */
                strbuf_reset(&cb->buf);
 -              strbuf_addstr(&cb->buf,
 -                            find_unique_abbrev(nsha1, DEFAULT_ABBREV));
 +              strbuf_add_unique_abbrev(&cb->buf, nsha1, DEFAULT_ABBREV);
        }
        return 1;
  }
@@@ -2207,80 -2214,3 +2212,80 @@@ void wt_status_print(struct wt_status *
                break;
        }
  }
 +
 +/**
 + * Returns 1 if there are unstaged changes, 0 otherwise.
 + */
 +int has_unstaged_changes(int ignore_submodules)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      init_revisions(&rev_info, NULL);
 +      if (ignore_submodules)
 +              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_files(&rev_info, 0);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * Returns 1 if there are uncommitted changes, 0 otherwise.
 + */
 +int has_uncommitted_changes(int ignore_submodules)
 +{
 +      struct rev_info rev_info;
 +      int result;
 +
 +      if (is_cache_unborn())
 +              return 0;
 +
 +      init_revisions(&rev_info, NULL);
 +      if (ignore_submodules)
 +              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 +      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +      add_head_to_pending(&rev_info);
 +      diff_setup_done(&rev_info.diffopt);
 +      result = run_diff_index(&rev_info, 1);
 +      return diff_result_code(&rev_info.diffopt, result);
 +}
 +
 +/**
 + * If the work tree has unstaged or uncommitted changes, dies with the
 + * appropriate message.
 + */
 +int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
 +{
 +      struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
 +      int err = 0;
 +
 +      hold_locked_index(lock_file, 0);
 +      refresh_cache(REFRESH_QUIET);
 +      update_index_if_able(&the_index, lock_file);
 +      rollback_lock_file(lock_file);
 +
 +      if (has_unstaged_changes(ignore_submodules)) {
 +              /* TRANSLATORS: the action is e.g. "pull with rebase" */
 +              error(_("cannot %s: You have unstaged changes."), _(action));
 +              err = 1;
 +      }
 +
 +      if (has_uncommitted_changes(ignore_submodules)) {
 +              if (err)
 +                      error(_("additionally, your index contains uncommitted changes."));
 +              else
 +                      error(_("cannot %s: Your index contains uncommitted changes."),
 +                            _(action));
 +              err = 1;
 +      }
 +
 +      if (err) {
 +              if (hint)
 +                      error("%s", hint);
 +              if (!gently)
 +                      exit(128);
 +      }
 +
 +      return err;
 +}