Merge branch 'jn/status-translatable'
authorJunio C Hamano <gitster@pobox.com>
Sun, 20 Mar 2011 06:24:19 +0000 (23:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 20 Mar 2011 06:24:19 +0000 (23:24 -0700)
* jn/status-translatable:
commit, status: use status_printf{,_ln,_more} helpers
commit: refer to commit template as s->fp
wt-status: add helpers for printing wt-status lines

Conflicts:
builtin/commit.c

1  2 
builtin/commit.c
wt-status.c
wt-status.h
diff --combined builtin/commit.c
index de0e11137823fc514a6868c5679c56b3f91b0833,ae62a25f52a2a500f4f27f9b03b2279c2197ef31..3979b823ef4227aac641be48c47581c56423b9d7
@@@ -54,17 -54,9 +54,17 @@@ static const char empty_amend_advice[] 
  "it empty. You can repeat your command with --allow-empty, or you can\n"
  "remove the commit entirely with \"git reset HEAD^\".\n";
  
 +static const char empty_cherry_pick_advice[] =
 +"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
 +"If you wish to commit it anyway, use:\n"
 +"\n"
 +"    git commit --allow-empty\n"
 +"\n"
 +"Otherwise, please use 'git reset'\n";
 +
  static unsigned char head_sha1[20];
  
 -static char *use_message_buffer;
 +static const char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -76,11 -68,6 +76,11 @@@ static enum 
  
  static const char *logfile, *force_author;
  static const char *template_file;
 +/*
 + * The _message variables are commit names from which to take
 + * the commit message and/or authorship.
 + */
 +static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
  static int all, edit_flag, also, interactive, only, amend, signoff;
@@@ -101,8 -88,7 +101,8 @@@ static enum 
  } cleanup_mode;
  static char *cleanup_arg;
  
 -static int use_editor = 1, initial_commit, in_merge, include_status = 1;
 +static enum commit_whence whence;
 +static int use_editor = 1, initial_commit, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
  static struct strbuf message;
@@@ -133,13 -119,13 +133,13 @@@ static struct option builtin_commit_opt
  
        OPT_GROUP("Commit message options"),
        OPT_FILENAME('F', "file", &logfile, "read message from file"),
 -      OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
 -      OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
 -      OPT_CALLBACK('m', "message", &message, "MESSAGE", "commit message", opt_parse_m),
 -      OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
 -      OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
 -      OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
 -      OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
 +      OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
 +      OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
 +      OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
 +      OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
 +      OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
 +      OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
 +      OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
        OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_END()
  };
  
 +static void determine_whence(struct wt_status *s)
 +{
 +      if (file_exists(git_path("MERGE_HEAD")))
 +              whence = FROM_MERGE;
 +      else if (file_exists(git_path("CHERRY_PICK_HEAD")))
 +              whence = FROM_CHERRY_PICK;
 +      else
 +              whence = FROM_COMMIT;
 +      if (s)
 +              s->whence = whence;
 +}
 +
 +static const char *whence_s(void)
 +{
 +      char *s = "";
 +
 +      switch (whence) {
 +      case FROM_COMMIT:
 +              break;
 +      case FROM_MERGE:
 +              s = "merge";
 +              break;
 +      case FROM_CHERRY_PICK:
 +              s = "cherry-pick";
 +              break;
 +      }
 +
 +      return s;
 +}
 +
  static void rollback_index_files(void)
  {
        switch (commit_style) {
@@@ -422,8 -378,8 +422,8 @@@ static char *prepare_index(int argc, co
         */
        commit_style = COMMIT_PARTIAL;
  
 -      if (in_merge)
 -              die("cannot do a partial commit during a merge.");
 +      if (whence != FROM_COMMIT)
 +              die("cannot do a partial commit during a %s.", whence_s());
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@@ -513,18 -469,18 +513,18 @@@ static void determine_author_info(struc
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
  
 -      if (use_message && !renew_authorship) {
 +      if (author_message) {
                const char *a, *lb, *rb, *eol;
  
 -              a = strstr(use_message_buffer, "\nauthor ");
 +              a = strstr(author_message_buffer, "\nauthor ");
                if (!a)
 -                      die("invalid commit: %s", use_message);
 +                      die("invalid commit: %s", author_message);
  
                lb = strchrnul(a + strlen("\nauthor "), '<');
                rb = strchrnul(lb, '>');
                eol = strchrnul(rb, '\n');
                if (!*lb || !*rb || !*eol)
 -                      die("invalid commit: %s", use_message);
 +                      die("invalid commit: %s", author_message);
  
                if (lb == a + strlen("\nauthor "))
                        /* \nauthor <foo@example.com> */
@@@ -612,7 -568,6 +612,6 @@@ static int prepare_to_commit(const cha
        int commitable, saved_color_setting;
        struct strbuf sb = STRBUF_INIT;
        char *buffer;
-       FILE *fp;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
        int ident_shown = 0;
                if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
                        die_errno("could not read SQUASH_MSG");
                hook_arg1 = "squash";
 -      } else if (template_file && !stat(template_file, &statbuf)) {
 +      } else if (template_file) {
                if (strbuf_read_file(&sb, template_file, 0) < 0)
                        die_errno("could not read '%s'", template_file);
                hook_arg1 = "template";
        }
  
        /*
 -       * This final case does not modify the template message,
 -       * it just sets the argument to the prepare-commit-msg hook.
 +       * The remaining cases don't modify the template message, but
 +       * just set the argument(s) to the prepare-commit-msg hook.
         */
 -      else if (in_merge)
 +      else if (whence == FROM_MERGE)
                hook_arg1 = "merge";
 +      else if (whence == FROM_CHERRY_PICK) {
 +              hook_arg1 = "commit";
 +              hook_arg2 = "CHERRY_PICK_HEAD";
 +      }
  
        if (squash_message) {
                /*
                hook_arg2 = "";
        }
  
-       fp = fopen(git_path(commit_editmsg), "w");
-       if (fp == NULL)
+       s->fp = fopen(git_path(commit_editmsg), "w");
+       if (s->fp == NULL)
                die_errno("could not open '%s'", git_path(commit_editmsg));
  
        if (cleanup_mode != CLEANUP_NONE)
                strbuf_release(&sob);
        }
  
-       if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+       if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
                die_errno("could not write commit template");
  
        strbuf_release(&sb);
        strbuf_addstr(&committer_ident, git_committer_info(0));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
 -              if (in_merge)
 +              if (whence != FROM_COMMIT)
-                       fprintf(fp,
-                               "#\n"
-                               "# It looks like you may be committing a %s.\n"
-                               "# If this is not correct, please remove the file\n"
-                               "#      %s\n"
-                               "# and try again.\n"
-                               "#\n",
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
+                               "\n"
 -                              "It looks like you may be committing a MERGE.\n"
++                              "It looks like you may be committing a %s.\n"
+                               "If this is not correct, please remove the file\n"
+                               "       %s\n"
+                               "and try again.\n"
+                               "",
 -                              git_path("MERGE_HEAD"));
 +                              whence_s(),
 +                              git_path(whence == FROM_MERGE
 +                                       ? "MERGE_HEAD"
 +                                       : "CHERRY_PICK_HEAD"));
-               fprintf(fp,
-                       "\n"
-                       "# Please enter the commit message for your changes.");
+               fprintf(s->fp, "\n");
+               status_printf(s, GIT_COLOR_NORMAL,
+                       "Please enter the commit message for your changes.");
                if (cleanup_mode == CLEANUP_ALL)
-                       fprintf(fp,
+                       status_printf_more(s, GIT_COLOR_NORMAL,
                                " Lines starting\n"
-                               "with '#' will be ignored, and an empty"
+                               "with '#' will be ignored, and an empty"
                                " message aborts the commit.\n");
                else /* CLEANUP_SPACE, that is. */
-                       fprintf(fp,
+                       status_printf_more(s, GIT_COLOR_NORMAL,
                                " Lines starting\n"
-                               "with '#' will be kept; you may remove them"
+                               "with '#' will be kept; you may remove them"
                                " yourself if you want to.\n"
-                               "An empty message aborts the commit.\n");
+                               "An empty message aborts the commit.\n");
                if (only_include_assumed)
-                       fprintf(fp, "# %s\n", only_include_assumed);
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
+                                       "%s", only_include_assumed);
  
                ai_tmp = cut_ident_timestamp_part(author_ident->buf);
                ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
                if (strcmp(author_ident->buf, committer_ident.buf))
-                       fprintf(fp,
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
                                "%s"
-                               "# Author:    %s\n",
-                               ident_shown++ ? "" : "#\n",
+                               "Author:    %s",
+                               ident_shown++ ? "" : "\n",
                                author_ident->buf);
  
                if (!user_ident_sufficiently_given())
-                       fprintf(fp,
+                       status_printf_ln(s, GIT_COLOR_NORMAL,
                                "%s"
-                               "# Committer: %s\n",
-                               ident_shown++ ? "" : "#\n",
+                               "Committer: %s",
+                               ident_shown++ ? "" : "\n",
                                committer_ident.buf);
  
                if (ident_shown)
-                       fprintf(fp, "#\n");
+                       status_printf_ln(s, GIT_COLOR_NORMAL, "");
  
                saved_color_setting = s->use_color;
                s->use_color = 0;
-               commitable = run_status(fp, index_file, prefix, 1, s);
+               commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
  
                *ai_tmp = ' ';
        }
        strbuf_release(&committer_ident);
  
-       fclose(fp);
+       fclose(s->fp);
  
 -      if (!commitable && !in_merge && !allow_empty &&
 +      /*
 +       * Reject an attempt to record a non-merge empty commit without
 +       * explicit --allow-empty. In the cherry-pick case, it may be
 +       * empty due to conflict resolution, which the user should okay.
 +       */
 +      if (!commitable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(head_sha1))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(empty_amend_advice, stderr);
 +              else if (whence == FROM_CHERRY_PICK)
 +                      fputs(empty_cherry_pick_advice, stderr);
                return 0;
        }
  
@@@ -955,28 -898,6 +956,28 @@@ static void handle_untracked_files_arg(
                die("Invalid untracked files mode '%s'", untracked_files_arg);
  }
  
 +static const char *read_commit_message(const char *name)
 +{
 +      const char *out_enc, *out;
 +      struct commit *commit;
 +
 +      commit = lookup_commit_reference_by_name(name);
 +      if (!commit)
 +              die("could not lookup commit %s", name);
 +      out_enc = get_commit_output_encoding();
 +      out = logmsg_reencode(commit, out_enc);
 +
 +      /*
 +       * If we failed to reencode the buffer, just copy it
 +       * byte for byte so the user can try to fix it up.
 +       * This also handles the case where input and output
 +       * encodings are identical.
 +       */
 +      if (out == NULL)
 +              out = xstrdup(commit->buffer);
 +      return out;
 +}
 +
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
 -      if (amend && in_merge)
 -              die("You are in the middle of a merge -- cannot amend.");
 +      if (amend && whence != FROM_COMMIT)
 +              die("You are in the middle of a %s -- cannot amend.", whence_s());
        if (fixup_message && squash_message)
                die("Options --squash and --fixup cannot be used together");
        if (use_message)
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
 -      if (!use_message && renew_authorship)
 +      if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
                die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
 -              const char *out_enc;
 -              struct commit *commit;
 -
 -              commit = lookup_commit_reference_by_name(use_message);
 -              if (!commit)
 -                      die("could not lookup commit %s", use_message);
 -              out_enc = get_commit_output_encoding();
 -              use_message_buffer = logmsg_reencode(commit, out_enc);
 -
 -              /*
 -               * If we failed to reencode the buffer, just copy it
 -               * byte for byte so the user can try to fix it up.
 -               * This also handles the case where input and output
 -               * encodings are identical.
 -               */
 -              if (use_message_buffer == NULL)
 -                      use_message_buffer = xstrdup(commit->buffer);
 +              use_message_buffer = read_commit_message(use_message);
 +              if (!renew_authorship) {
 +                      author_message = use_message;
 +                      author_message_buffer = use_message_buffer;
 +              }
 +      }
 +      if (whence == FROM_CHERRY_PICK && !renew_authorship) {
 +              author_message = "CHERRY_PICK_HEAD";
 +              author_message_buffer = read_commit_message(author_message);
        }
  
        if (!!also + !!only + !!all + !!interactive > 1)
@@@ -1188,7 -1117,7 +1189,7 @@@ int cmd_status(int argc, const char **a
        wt_status_prepare(&s);
        gitmodules_config();
        git_config(git_status_config, &s);
 -      in_merge = file_exists(git_path("MERGE_HEAD"));
 +      determine_whence(&s);
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
        }
  
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
 -      s.in_merge = in_merge;
        s.ignore_submodule_arg = ignore_submodule_arg;
        wt_status_collect(&s);
  
@@@ -1285,6 -1215,7 +1286,6 @@@ static void print_summary(const char *p
        get_commit_format(format.buf, &rev);
        rev.always_show_header = 0;
        rev.diffopt.detect_rename = 1;
 -      rev.diffopt.rename_limit = 100;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
@@@ -1371,7 -1302,8 +1372,7 @@@ int cmd_commit(int argc, const char **a
  
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
 -      in_merge = file_exists(git_path("MERGE_HEAD"));
 -      s.in_merge = in_merge;
 +      determine_whence(&s);
  
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
  
                for (c = commit->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
 -      } else if (in_merge) {
 +      } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
                FILE *fp;
  
                        parents = reduce_heads(parents);
        } else {
                if (!reflog_msg)
 -                      reflog_msg = "commit";
 +                      reflog_msg = (whence == FROM_CHERRY_PICK)
 +                                      ? "commit (cherry-pick)"
 +                                      : "commit";
                pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
        }
  
                die("cannot update HEAD ref");
        }
  
 +      unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined wt-status.c
index 4daa8bb5242fc689973839a13a3a28477efbb6bf,c14cbe48d159090a8da471da9edafc4e9cc71df4..53558d7e5f517479af478e375076718189c2d2d5
@@@ -32,6 -32,80 +32,80 @@@ static const char *color(int slot, stru
        return c;
  }
  
+ static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
+               const char *fmt, va_list ap, const char *trail)
+ {
+       struct strbuf sb = STRBUF_INIT;
+       struct strbuf linebuf = STRBUF_INIT;
+       const char *line, *eol;
+       strbuf_vaddf(&sb, fmt, ap);
+       if (!sb.len) {
+               strbuf_addch(&sb, '#');
+               if (!trail)
+                       strbuf_addch(&sb, ' ');
+               color_print_strbuf(s->fp, color, &sb);
+               if (trail)
+                       fprintf(s->fp, "%s", trail);
+               strbuf_release(&sb);
+               return;
+       }
+       for (line = sb.buf; *line; line = eol + 1) {
+               eol = strchr(line, '\n');
+               strbuf_reset(&linebuf);
+               if (at_bol) {
+                       strbuf_addch(&linebuf, '#');
+                       if (*line != '\n' && *line != '\t')
+                               strbuf_addch(&linebuf, ' ');
+               }
+               if (eol)
+                       strbuf_add(&linebuf, line, eol - line);
+               else
+                       strbuf_addstr(&linebuf, line);
+               color_print_strbuf(s->fp, color, &linebuf);
+               if (eol)
+                       fprintf(s->fp, "\n");
+               else
+                       break;
+               at_bol = 1;
+       }
+       if (trail)
+               fprintf(s->fp, "%s", trail);
+       strbuf_release(&linebuf);
+       strbuf_release(&sb);
+ }
+ void status_printf_ln(struct wt_status *s, const char *color,
+                       const char *fmt, ...)
+ {
+       va_list ap;
+       va_start(ap, fmt);
+       status_vprintf(s, 1, color, fmt, ap, "\n");
+       va_end(ap);
+ }
+ void status_printf(struct wt_status *s, const char *color,
+                       const char *fmt, ...)
+ {
+       va_list ap;
+       va_start(ap, fmt);
+       status_vprintf(s, 1, color, fmt, ap, NULL);
+       va_end(ap);
+ }
+ void status_printf_more(struct wt_status *s, const char *color,
+                       const char *fmt, ...)
+ {
+       va_list ap;
+       va_start(ap, fmt);
+       status_vprintf(s, 0, color, fmt, ap, NULL);
+       va_end(ap);
+ }
  void wt_status_prepare(struct wt_status *s)
  {
        unsigned char sha1[20];
@@@ -57,33 -131,33 +131,33 @@@ static void wt_status_print_unmerged_he
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
-       color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+       status_printf_ln(s, c, "Unmerged paths:");
        if (!advice_status_hints)
                return;
 -      if (s->in_merge)
 +      if (s->whence != FROM_COMMIT)
                ;
        else if (!s->is_initial)
-               color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+               status_printf_ln(s, c, "  (use \"git reset %s <file>...\" to unstage)", s->reference);
        else
-               color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
-       color_fprintf_ln(s->fp, c, "#");
+               status_printf_ln(s, c, "  (use \"git rm --cached <file>...\" to unstage)");
+       status_printf_ln(s, c, "  (use \"git add/rm <file>...\" as appropriate to mark resolution)");
+       status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_cached_header(struct wt_status *s)
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
-       color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+       status_printf_ln(s, c, "Changes to be committed:");
        if (!advice_status_hints)
                return;
 -      if (s->in_merge)
 +      if (s->whence != FROM_COMMIT)
                ; /* NEEDSWORK: use "git reset --unresolve"??? */
        else if (!s->is_initial)
-               color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+               status_printf_ln(s, c, "  (use \"git reset %s <file>...\" to unstage)", s->reference);
        else
-               color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       color_fprintf_ln(s->fp, c, "#");
+               status_printf_ln(s, c, "  (use \"git rm --cached <file>...\" to unstage)");
+       status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_dirty_header(struct wt_status *s,
  {
        const char *c = color(WT_STATUS_HEADER, s);
  
-       color_fprintf_ln(s->fp, c, "# Changes not staged for commit:");
+       status_printf_ln(s, c, "Changes not staged for commit:");
        if (!advice_status_hints)
                return;
        if (!has_deleted)
-               color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
+               status_printf_ln(s, c, "  (use \"git add <file>...\" to update what will be committed)");
        else
-               color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
-       color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+               status_printf_ln(s, c, "  (use \"git add/rm <file>...\" to update what will be committed)");
+       status_printf_ln(s, c, "  (use \"git checkout -- <file>...\" to discard changes in working directory)");
        if (has_dirty_submodules)
-               color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
-       color_fprintf_ln(s->fp, c, "#");
+               status_printf_ln(s, c, "  (commit or discard the untracked or modified content in submodules)");
+       status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_other_header(struct wt_status *s,
                                         const char *how)
  {
        const char *c = color(WT_STATUS_HEADER, s);
-       color_fprintf_ln(s->fp, c, "# %s files:", what);
+       status_printf_ln(s, c, "%s files:", what);
        if (!advice_status_hints)
                return;
-       color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
-       color_fprintf_ln(s->fp, c, "#");
+       status_printf_ln(s, c, "  (use \"git %s <file>...\" to include in what will be committed)", how);
+       status_printf_ln(s, c, "");
  }
  
  static void wt_status_print_trailer(struct wt_status *s)
  {
-       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+       status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
  }
  
  #define quote_path quote_path_relative
@@@ -133,7 -207,7 +207,7 @@@ static void wt_status_print_unmerged_da
        const char *one, *how = "bug";
  
        one = quote_path(it->string, -1, &onebuf, s->prefix);
-       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+       status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (d->stagemask) {
        case 1: how = "both deleted:"; break;
        case 2: how = "added by us:"; break;
        case 6: how = "both added:"; break;
        case 7: how = "both modified:"; break;
        }
-       color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+       status_printf_more(s, c, "%-20s%s\n", how, one);
        strbuf_release(&onebuf);
  }
  
@@@ -186,40 -260,40 +260,40 @@@ static void wt_status_print_change_data
        one = quote_path(one_name, -1, &onebuf, s->prefix);
        two = quote_path(two_name, -1, &twobuf, s->prefix);
  
-       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+       status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (status) {
        case DIFF_STATUS_ADDED:
-               color_fprintf(s->fp, c, "new file:   %s", one);
+               status_printf_more(s, c, "new file:   %s", one);
                break;
        case DIFF_STATUS_COPIED:
-               color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
+               status_printf_more(s, c, "copied:     %s -> %s", one, two);
                break;
        case DIFF_STATUS_DELETED:
-               color_fprintf(s->fp, c, "deleted:    %s", one);
+               status_printf_more(s, c, "deleted:    %s", one);
                break;
        case DIFF_STATUS_MODIFIED:
-               color_fprintf(s->fp, c, "modified:   %s", one);
+               status_printf_more(s, c, "modified:   %s", one);
                break;
        case DIFF_STATUS_RENAMED:
-               color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
+               status_printf_more(s, c, "renamed:    %s -> %s", one, two);
                break;
        case DIFF_STATUS_TYPE_CHANGED:
-               color_fprintf(s->fp, c, "typechange: %s", one);
+               status_printf_more(s, c, "typechange: %s", one);
                break;
        case DIFF_STATUS_UNKNOWN:
-               color_fprintf(s->fp, c, "unknown:    %s", one);
+               status_printf_more(s, c, "unknown:    %s", one);
                break;
        case DIFF_STATUS_UNMERGED:
-               color_fprintf(s->fp, c, "unmerged:   %s", one);
+               status_printf_more(s, c, "unmerged:   %s", one);
                break;
        default:
                die("bug: unhandled diff status %c", status);
        }
        if (extra.len) {
-               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
+               status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
                strbuf_release(&extra);
        }
-       fprintf(s->fp, "\n");
+       status_printf_more(s, GIT_COLOR_NORMAL, "\n");
        strbuf_release(&onebuf);
        strbuf_release(&twobuf);
  }
@@@ -323,7 -397,7 +397,7 @@@ static void wt_status_collect_changes_w
      }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_files(&rev, 0);
  }
  
@@@ -348,22 -422,20 +422,22 @@@ static void wt_status_collect_changes_i
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
 -      rev.prune_data = s->pathspec;
 +      init_pathspec(&rev.prune_data, s->pathspec);
        run_diff_index(&rev, 1);
  }
  
  static void wt_status_collect_changes_initial(struct wt_status *s)
  {
 +      struct pathspec pathspec;
        int i;
  
 +      init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
  
 -              if (!ce_path_match(ce, s->pathspec))
 +              if (!ce_path_match(ce, &pathspec))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
 +      free_pathspec(&pathspec);
  }
  
  static void wt_status_collect_untracked(struct wt_status *s)
@@@ -576,9 -647,9 +650,9 @@@ static void wt_status_print_other(struc
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
                it = &(l->items[i]);
-               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
-               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
-                                quote_path(it->string, strlen(it->string),
+               status_printf(s, color(WT_STATUS_HEADER, s), "\t");
+               status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
+                       "%s\n", quote_path(it->string, strlen(it->string),
                                            &buf, s->prefix));
        }
        strbuf_release(&buf);
@@@ -645,17 -716,17 +719,17 @@@ void wt_status_print(struct wt_status *
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
                        on_what = "Not currently on any branch.";
                }
-               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
-               color_fprintf(s->fp, branch_status_color, "%s", on_what);
-               color_fprintf_ln(s->fp, branch_color, "%s", branch_name);
+               status_printf(s, color(WT_STATUS_HEADER, s), "");
+               status_printf_more(s, branch_status_color, "%s", on_what);
+               status_printf_more(s, branch_color, "%s\n", branch_name);
                if (!s->is_initial)
                        wt_status_print_tracking(s);
        }
  
        if (s->is_initial) {
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+               status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
+               status_printf_ln(s, color(WT_STATUS_HEADER, s), "Initial commit");
+               status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
        }
  
        wt_status_print_updated(s);
                if (s->show_ignored_files)
                        wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
        } else if (s->commitable)
-               fprintf(s->fp, "# Untracked files not listed%s\n",
+               status_printf_ln(s, GIT_COLOR_NORMAL, "Untracked files not listed%s",
                        advice_status_hints
                        ? " (use -u option to show untracked files)" : "");
  
                wt_status_print_verbose(s);
        if (!s->commitable) {
                if (s->amend)
-                       fprintf(s->fp, "# No changes\n");
+                       status_printf_ln(s, GIT_COLOR_NORMAL, "No changes");
                else if (s->nowarn)
                        ; /* nothing */
                else if (s->workdir_dirty)
diff --combined wt-status.h
index cec482a56e8ec5b780a938037bdea7432bb93475,595c5fa137a2cf245af1c584c9326be458239c8a..682b4c8f7da2c58f741a958f6488a48fd7b483b4
@@@ -24,13 -24,6 +24,13 @@@ enum untracked_status_type 
        SHOW_ALL_UNTRACKED_FILES
  };
  
 +/* from where does this commit originate */
 +enum commit_whence {
 +      FROM_COMMIT,     /* normal */
 +      FROM_MERGE,      /* commit came from merge */
 +      FROM_CHERRY_PICK /* commit came from cherry-pick */
 +};
 +
  struct wt_status_change_data {
        int worktree_status;
        int index_status;
@@@ -47,7 -40,7 +47,7 @@@ struct wt_status 
        const char **pathspec;
        int verbose;
        int amend;
 -      int in_merge;
 +      enum commit_whence whence;
        int nowarn;
        int use_color;
        int relative_paths;
@@@ -75,4 -68,11 +75,11 @@@ void wt_status_collect(struct wt_statu
  void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch);
  void wt_porcelain_print(struct wt_status *s, int null_termination);
  
+ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...)
+       ;
+ void status_printf(struct wt_status *s, const char *color, const char *fmt, ...)
+       ;
+ void status_printf_more(struct wt_status *s, const char *color, const char *fmt, ...)
+       __attribute__((format(printf, 3, 4)));
  #endif /* STATUS_H */