Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Sat, 15 Mar 2008 07:09:20 +0000 (00:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 15 Mar 2008 07:09:33 +0000 (00:09 -0700)
* maint:
  format-patch: generate MIME header as needed even when there is format.header

1  2 
builtin-log.c
commit.h
log-tree.c
log-tree.h
pretty.c

diff --combined builtin-log.c
index d983cbc7bc98f1b28332c18946e5111a6dcba9ab,99d69f079132ed67e2d5bc83bf95e6e60ba22f6f..5c00725f030460ba34c702b9ad31e577ca70d361
@@@ -5,7 -5,6 +5,7 @@@
   *             2006 Junio Hamano
   */
  #include "cache.h"
 +#include "color.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
  #include "reflog-walk.h"
  #include "patch-ids.h"
  #include "refs.h"
 +#include "run-command.h"
 +#include "shortlog.h"
  
  static int default_show_root = 1;
  static const char *fmt_patch_subject_prefix = "PATCH";
 +static const char *fmt_pretty;
  
  static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
  {
@@@ -55,8 -51,6 +55,8 @@@ static void cmd_log_init(int argc, cons
  
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
 +      if (fmt_pretty)
 +              rev->commit_format = get_commit_format(fmt_pretty);
        rev->verbose_header = 1;
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->show_root_diff = default_show_root;
@@@ -203,8 -197,7 +203,8 @@@ static int cmd_log_walk(struct rev_inf
        if (rev->early_output)
                setup_early_output(rev);
  
 -      prepare_revision_walk(rev);
 +      if (prepare_revision_walk(rev))
 +              die("revision walk setup failed");
  
        if (rev->early_output)
                finish_early_output(rev);
  
  static int git_log_config(const char *var, const char *value)
  {
 +      if (!strcmp(var, "format.pretty"))
 +              return git_config_string(&fmt_pretty, var, value);
        if (!strcmp(var, "format.subjectprefix")) {
                if (!value)
                        config_error_nonbool(var);
@@@ -244,10 -235,6 +244,10 @@@ int cmd_whatchanged(int argc, const cha
        struct rev_info rev;
  
        git_config(git_log_config);
 +
 +      if (diff_use_color_default == -1)
 +              diff_use_color_default = git_use_color_default;
 +
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.simplify_history = 0;
@@@ -320,10 -307,6 +320,10 @@@ int cmd_show(int argc, const char **arg
        int i, count, ret = 0;
  
        git_config(git_log_config);
 +
 +      if (diff_use_color_default == -1)
 +              diff_use_color_default = git_use_color_default;
 +
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.combine_merges = 1;
@@@ -384,10 -367,6 +384,10 @@@ int cmd_log_reflog(int argc, const cha
        struct rev_info rev;
  
        git_config(git_log_config);
 +
 +      if (diff_use_color_default == -1)
 +              diff_use_color_default = git_use_color_default;
 +
        init_revisions(&rev, prefix);
        init_reflog_walk(&rev.reflog_info);
        rev.abbrev_commit = 1;
@@@ -416,10 -395,6 +416,10 @@@ int cmd_log(int argc, const char **argv
        struct rev_info rev;
  
        git_config(git_log_config);
 +
 +      if (diff_use_color_default == -1)
 +              diff_use_color_default = git_use_color_default;
 +
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        cmd_log_init(argc, argv, prefix, &rev);
@@@ -435,47 -410,24 +435,47 @@@ static int istitlechar(char c
                (c >= '0' && c <= '9') || c == '.' || c == '_';
  }
  
 -static char *extra_headers = NULL;
 -static int extra_headers_size = 0;
  static const char *fmt_patch_suffix = ".patch";
  static int numbered = 0;
  static int auto_number = 0;
  
 +static char **extra_hdr;
 +static int extra_hdr_nr;
 +static int extra_hdr_alloc;
 +
 +static char **extra_to;
 +static int extra_to_nr;
 +static int extra_to_alloc;
 +
 +static char **extra_cc;
 +static int extra_cc_nr;
 +static int extra_cc_alloc;
 +
 +static void add_header(const char *value)
 +{
 +      int len = strlen(value);
 +      while (value[len - 1] == '\n')
 +              len--;
 +      if (!strncasecmp(value, "to: ", 4)) {
 +              ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
 +              extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
 +              return;
 +      }
 +      if (!strncasecmp(value, "cc: ", 4)) {
 +              ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
 +              extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
 +              return;
 +      }
 +      ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
 +      extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
 +}
 +
  static int git_format_config(const char *var, const char *value)
  {
        if (!strcmp(var, "format.headers")) {
 -              int len;
 -
                if (!value)
                        die("format.headers without value");
 -              len = strlen(value);
 -              extra_headers_size += len + 1;
 -              extra_headers = xrealloc(extra_headers, extra_headers_size);
 -              extra_headers[extra_headers_size - len - 1] = 0;
 -              strcat(extra_headers, value);
 +              add_header(value);
                return 0;
        }
        if (!strcmp(var, "format.suffix")) {
  }
  
  
 +static const char *get_oneline_for_filename(struct commit *commit,
 +                                          int keep_subject)
 +{
 +      static char filename[PATH_MAX];
 +      char *sol;
 +      int len = 0;
 +      int suffix_len = strlen(fmt_patch_suffix) + 1;
 +
 +      sol = strstr(commit->buffer, "\n\n");
 +      if (!sol)
 +              filename[0] = '\0';
 +      else {
 +              int j, space = 0;
 +
 +              sol += 2;
 +              /* strip [PATCH] or [PATCH blabla] */
 +              if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
 +                      char *eos = strchr(sol + 6, ']');
 +                      if (eos) {
 +                              while (isspace(*eos))
 +                                      eos++;
 +                              sol = eos;
 +                      }
 +              }
 +
 +              for (j = 0;
 +                   j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
 +                           len < sizeof(filename) - suffix_len &&
 +                           sol[j] && sol[j] != '\n';
 +                   j++) {
 +                      if (istitlechar(sol[j])) {
 +                              if (space) {
 +                                      filename[len++] = '-';
 +                                      space = 0;
 +                              }
 +                              filename[len++] = sol[j];
 +                              if (sol[j] == '.')
 +                                      while (sol[j + 1] == '.')
 +                                              j++;
 +                      } else
 +                              space = 1;
 +              }
 +              while (filename[len - 1] == '.'
 +                     || filename[len - 1] == '-')
 +                      len--;
 +              filename[len] = '\0';
 +      }
 +      return filename;
 +}
 +
  static FILE *realstdout = NULL;
  static const char *output_directory = NULL;
  
 -static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
 -                       int numbered_files)
 +static int reopen_stdout(const char *oneline, int nr, int total)
  {
        char filename[PATH_MAX];
 -      char *sol;
        int len = 0;
        int suffix_len = strlen(fmt_patch_suffix) + 1;
  
        if (output_directory) {
 -              if (strlen(output_directory) >=
 +              len = snprintf(filename, sizeof(filename), "%s",
 +                              output_directory);
 +              if (len >=
                    sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error("name of output directory is too long");
 -              strlcpy(filename, output_directory, sizeof(filename) - suffix_len);
 -              len = strlen(filename);
                if (filename[len - 1] != '/')
                        filename[len++] = '/';
        }
  
 -      if (numbered_files) {
 -              sprintf(filename + len, "%d", nr);
 -              len = strlen(filename);
 -
 -      } else {
 -              sprintf(filename + len, "%04d", nr);
 -              len = strlen(filename);
 -
 -              sol = strstr(commit->buffer, "\n\n");
 -              if (sol) {
 -                      int j, space = 1;
 -
 -                      sol += 2;
 -                      /* strip [PATCH] or [PATCH blabla] */
 -                      if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
 -                              char *eos = strchr(sol + 6, ']');
 -                              if (eos) {
 -                                      while (isspace(*eos))
 -                                              eos++;
 -                                      sol = eos;
 -                              }
 -                      }
 -
 -                      for (j = 0;
 -                           j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
 -                                   len < sizeof(filename) - suffix_len &&
 -                                   sol[j] && sol[j] != '\n';
 -                           j++) {
 -                              if (istitlechar(sol[j])) {
 -                                      if (space) {
 -                                              filename[len++] = '-';
 -                                              space = 0;
 -                                      }
 -                                      filename[len++] = sol[j];
 -                                      if (sol[j] == '.')
 -                                              while (sol[j + 1] == '.')
 -                                                      j++;
 -                              } else
 -                                      space = 1;
 -                      }
 -                      while (filename[len - 1] == '.'
 -                             || filename[len - 1] == '-')
 -                              len--;
 -                      filename[len] = 0;
 -              }
 -              if (len + suffix_len >= sizeof(filename))
 -                      return error("Patch pathname too long");
 +      if (!oneline)
 +              len += sprintf(filename + len, "%d", nr);
 +      else {
 +              len += sprintf(filename + len, "%04d-", nr);
 +              len += snprintf(filename + len, sizeof(filename) - len - 1
 +                              - suffix_len, "%s", oneline);
                strcpy(filename + len, fmt_patch_suffix);
        }
  
@@@ -611,8 -556,7 +611,8 @@@ static void get_patch_ids(struct rev_in
        o2->flags ^= UNINTERESTING;
        add_pending_object(&check_rev, o1, "o1");
        add_pending_object(&check_rev, o2, "o2");
 -      prepare_revision_walk(&check_rev);
 +      if (prepare_revision_walk(&check_rev))
 +              die("revision walk setup failed");
  
        while ((commit = get_revision(&check_rev)) != NULL) {
                /* ignore merges */
        o2->flags = flags2;
  }
  
 -static void gen_message_id(char *dest, unsigned int length, char *base)
 +static void gen_message_id(struct rev_info *info, char *base)
  {
        const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
        const char *email_start = strrchr(committer, '<');
        const char *email_end = strrchr(committer, '>');
 -      if(!email_start || !email_end || email_start > email_end - 1)
 +      struct strbuf buf;
 +      if (!email_start || !email_end || email_start > email_end - 1)
                die("Could not extract email from committer identity.");
 -      snprintf(dest, length, "%s.%lu.git.%.*s", base,
 -               (unsigned long) time(NULL),
 -               (int)(email_end - email_start - 1), email_start + 1);
 +      strbuf_init(&buf, 0);
 +      strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
 +                  (unsigned long) time(NULL),
 +                  (int)(email_end - email_start - 1), email_start + 1);
 +      info->message_id = strbuf_detach(&buf, NULL);
 +}
 +
 +static void make_cover_letter(struct rev_info *rev, int use_stdout,
 +                            int numbered, int numbered_files,
 +                            struct commit *origin,
 +                            int nr, struct commit **list, struct commit *head)
 +{
 +      const char *committer;
 +      char *head_sha1;
 +      const char *subject_start = NULL;
 +      const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
 +      const char *msg;
 +      const char *extra_headers = rev->extra_headers;
 +      struct shortlog log;
 +      struct strbuf sb;
 +      int i;
 +      const char *encoding = "utf-8";
 +      struct diff_options opts;
++      int need_8bit_cte = 0;
 +
 +      if (rev->commit_format != CMIT_FMT_EMAIL)
 +              die("Cover letter needs email format");
 +
 +      if (!use_stdout && reopen_stdout(numbered_files ?
 +                              NULL : "cover-letter", 0, rev->total))
 +              return;
 +
 +      head_sha1 = sha1_to_hex(head->object.sha1);
 +
-       log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers);
++      log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers,
++                              &need_8bit_cte);
 +
 +      committer = git_committer_info(0);
 +
 +      msg = body;
 +      strbuf_init(&sb, 0);
 +      pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
 +                   encoding);
 +      pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
-                     encoding, 0);
++                    encoding, need_8bit_cte);
 +      pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
 +      printf("%s\n", sb.buf);
 +
 +      strbuf_release(&sb);
 +
 +      shortlog_init(&log);
 +      log.wrap_lines = 1;
 +      log.wrap = 72;
 +      log.in1 = 2;
 +      log.in2 = 4;
 +      for (i = 0; i < nr; i++)
 +              shortlog_add_commit(&log, list[i]);
 +
 +      shortlog_output(&log);
 +
 +      /*
 +       * We can only do diffstat with a unique reference point
 +       */
 +      if (!origin)
 +              return;
 +
 +      memcpy(&opts, &rev->diffopt, sizeof(opts));
 +      opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
 +
 +      diff_setup_done(&opts);
 +
 +      diff_tree_sha1(origin->tree->object.sha1,
 +                     head->tree->object.sha1,
 +                     "", &opts);
 +      diffcore_std(&opts);
 +      diff_flush(&opts);
 +
 +      printf("\n");
  }
  
  static const char *clean_message_id(const char *msg_id)
@@@ -752,13 -622,11 +754,13 @@@ int cmd_format_patch(int argc, const ch
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
        int thread = 0;
 +      int cover_letter = 0;
 +      int boundary_count = 0;
 +      struct commit *origin = NULL, *head = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
        char *add_signoff = NULL;
 -      char message_id[1024];
 -      char ref_message_id[1024];
 +      struct strbuf buf;
  
        git_config(git_format_config);
        init_revisions(&rev, prefix);
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
  
        rev.subject_prefix = fmt_patch_subject_prefix;
 -      rev.extra_headers = extra_headers;
  
        /*
         * Parse the arguments before setup_revisions(), or something
                                die("Need a number for --start-number");
                        start_number = strtol(argv[i], NULL, 10);
                }
 +              else if (!prefixcmp(argv[i], "--cc=")) {
 +                      ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
 +                      extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
 +              }
                else if (!strcmp(argv[i], "-k") ||
                                !strcmp(argv[i], "--keep-subject")) {
                        keep_subject = 1;
                        rev.subject_prefix = argv[i] + 17;
                } else if (!prefixcmp(argv[i], "--suffix="))
                        fmt_patch_suffix = argv[i] + 9;
 +              else if (!strcmp(argv[i], "--cover-letter"))
 +                      cover_letter = 1;
                else
                        argv[j++] = argv[i];
        }
        argc = j;
  
 +      strbuf_init(&buf, 0);
 +
 +      for (i = 0; i < extra_hdr_nr; i++) {
 +              strbuf_addstr(&buf, extra_hdr[i]);
 +              strbuf_addch(&buf, '\n');
 +      }
 +
 +      if (extra_to_nr)
 +              strbuf_addstr(&buf, "To: ");
 +      for (i = 0; i < extra_to_nr; i++) {
 +              if (i)
 +                      strbuf_addstr(&buf, "    ");
 +              strbuf_addstr(&buf, extra_to[i]);
 +              if (i + 1 < extra_to_nr)
 +                      strbuf_addch(&buf, ',');
 +              strbuf_addch(&buf, '\n');
 +      }
 +
 +      if (extra_cc_nr)
 +              strbuf_addstr(&buf, "Cc: ");
 +      for (i = 0; i < extra_cc_nr; i++) {
 +              if (i)
 +                      strbuf_addstr(&buf, "    ");
 +              strbuf_addstr(&buf, extra_cc[i]);
 +              if (i + 1 < extra_cc_nr)
 +                      strbuf_addch(&buf, ',');
 +              strbuf_addch(&buf, '\n');
 +      }
 +
 +      rev.extra_headers = strbuf_detach(&buf, 0);
 +
        if (start_number < 0)
                start_number = 1;
        if (numbered && keep_subject)
                 * get_revision() to do the usual traversal.
                 */
        }
 +      if (cover_letter) {
 +              /* remember the range */
 +              int i;
 +              for (i = 0; i < rev.pending.nr; i++) {
 +                      struct object *o = rev.pending.objects[i].item;
 +                      if (!(o->flags & UNINTERESTING))
 +                              head = (struct commit *)o;
 +              }
 +              /* We can't generate a cover letter without any patches */
 +              if (!head)
 +                      return 0;
 +      }
  
        if (ignore_if_in_upstream)
                get_patch_ids(&rev, &ids, prefix);
        if (!use_stdout)
                realstdout = xfdopen(xdup(1), "w");
  
 -      prepare_revision_walk(&rev);
 +      if (prepare_revision_walk(&rev))
 +              die("revision walk setup failed");
 +      rev.boundary = 1;
        while ((commit = get_revision(&rev)) != NULL) {
 +              if (commit->object.flags & BOUNDARY) {
 +                      boundary_count++;
 +                      origin = (boundary_count == 1) ? commit : NULL;
 +                      continue;
 +              }
 +
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
 -      rev.add_signoff = add_signoff;
        if (in_reply_to)
                rev.ref_message_id = clean_message_id(in_reply_to);
 +      if (cover_letter) {
 +              if (thread)
 +                      gen_message_id(&rev, "cover");
 +              make_cover_letter(&rev, use_stdout, numbered, numbered_files,
 +                                origin, nr, list, head);
 +              total++;
 +              start_number--;
 +      }
 +      rev.add_signoff = add_signoff;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
                rev.nr = total - nr + (start_number - 1);
                /* Make the second and subsequent mails replies to the first */
                if (thread) {
 -                      if (nr == (total - 2)) {
 -                              strncpy(ref_message_id, message_id,
 -                                      sizeof(ref_message_id));
 -                              ref_message_id[sizeof(ref_message_id)-1]='\0';
 -                              rev.ref_message_id = ref_message_id;
 +                      /* Have we already had a message ID? */
 +                      if (rev.message_id) {
 +                              /*
 +                               * If we've got the ID to be a reply
 +                               * to, discard the current ID;
 +                               * otherwise, make everything a reply
 +                               * to that.
 +                               */
 +                              if (rev.ref_message_id)
 +                                      free(rev.message_id);
 +                              else
 +                                      rev.ref_message_id = rev.message_id;
                        }
 -                      gen_message_id(message_id, sizeof(message_id),
 -                                     sha1_to_hex(commit->object.sha1));
 -                      rev.message_id = message_id;
 +                      gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
                }
 -              if (!use_stdout)
 -                      if (reopen_stdout(commit, rev.nr, keep_subject,
 -                                        numbered_files))
 -                              die("Failed to create output files");
 +              if (!use_stdout && reopen_stdout(numbered_files ? NULL :
 +                              get_oneline_for_filename(commit, keep_subject),
 +                              rev.nr, rev.total))
 +                      die("Failed to create output files");
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
                commit->buffer = NULL;
@@@ -1124,8 -923,7 +1126,8 @@@ int cmd_cherry(int argc, const char **a
                die("Unknown commit %s", limit);
  
        /* reverse the list of commits */
 -      prepare_revision_walk(&revs);
 +      if (prepare_revision_walk(&revs))
 +              die("revision walk setup failed");
        while ((commit = get_revision(&revs)) != NULL) {
                /* ignore merges */
                if (commit->parents && commit->parents->next)
diff --combined commit.h
index a1e95914263355e2021f54be4837ed25f1210757,000528a67b0f087793da2001d69993260bdaeb4a..2f63bc8b2fac0d748e13d4435d550d61002092b3
+++ b/commit.h
@@@ -70,22 -70,7 +70,22 @@@ extern void pretty_print_commit(enum cm
                                  struct strbuf *,
                                  int abbrev, const char *subject,
                                  const char *after_subject, enum date_mode,
-                               int non_ascii_present);
+                               int need_8bit_cte);
 +void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
 +                 const char *line, enum date_mode dmode,
 +                 const char *encoding);
 +void pp_title_line(enum cmit_fmt fmt,
 +                 const char **msg_p,
 +                 struct strbuf *sb,
 +                 const char *subject,
 +                 const char *after_subject,
 +                 const char *encoding,
-                  int plain_non_ascii);
++                 int need_8bit_cte);
 +void pp_remainder(enum cmit_fmt fmt,
 +                const char **msg_p,
 +                struct strbuf *sb,
 +                int indent);
 +
  
  /** Removes the first commit from a list sorted by date, and adds all
   * of its parents.
@@@ -116,7 -101,6 +116,7 @@@ struct commit_graft 
  struct commit_graft *read_graft_line(char *buf, int len);
  int register_commit_graft(struct commit_graft *, int);
  int read_graft_file(const char *graft_file);
 +struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
  
  extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
  
diff --combined log-tree.c
index 608f697cf3860245510bff907a643d9b6143e724,dd94f393a06a0c8a7fc6420af0b9487df47ec4a4..5b2963998cc497177d913b6224799531d45dbb4a
@@@ -137,72 -137,6 +137,77 @@@ static int has_non_ascii(const char *s
        return 0;
  }
  
-                            const char **subject_p, const char **extra_headers_p)
 +void log_write_email_headers(struct rev_info *opt, const char *name,
++                           const char **subject_p,
++                           const char **extra_headers_p,
++                           int *need_8bit_cte_p)
 +{
 +      const char *subject = NULL;
 +      const char *extra_headers = opt->extra_headers;
++
++      *need_8bit_cte_p = 0; /* unknown */
 +      if (opt->total > 0) {
 +              static char buffer[64];
 +              snprintf(buffer, sizeof(buffer),
 +                       "Subject: [%s %0*d/%d] ",
 +                       opt->subject_prefix,
 +                       digits_in_number(opt->total),
 +                       opt->nr, opt->total);
 +              subject = buffer;
 +      } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
 +              static char buffer[256];
 +              snprintf(buffer, sizeof(buffer),
 +                       "Subject: [%s] ",
 +                       opt->subject_prefix);
 +              subject = buffer;
 +      } else {
 +              subject = "Subject: ";
 +      }
 +
 +      printf("From %s Mon Sep 17 00:00:00 2001\n", name);
 +      if (opt->message_id)
 +              printf("Message-Id: <%s>\n", opt->message_id);
 +      if (opt->ref_message_id)
 +              printf("In-Reply-To: <%s>\nReferences: <%s>\n",
 +                     opt->ref_message_id, opt->ref_message_id);
 +      if (opt->mime_boundary) {
 +              static char subject_buffer[1024];
 +              static char buffer[1024];
++              *need_8bit_cte_p = -1; /* NEVER */
 +              snprintf(subject_buffer, sizeof(subject_buffer) - 1,
 +                       "%s"
 +                       "MIME-Version: 1.0\n"
 +                       "Content-Type: multipart/mixed;"
 +                       " boundary=\"%s%s\"\n"
 +                       "\n"
 +                       "This is a multi-part message in MIME "
 +                       "format.\n"
 +                       "--%s%s\n"
 +                       "Content-Type: text/plain; "
 +                       "charset=UTF-8; format=fixed\n"
 +                       "Content-Transfer-Encoding: 8bit\n\n",
 +                       extra_headers ? extra_headers : "",
 +                       mime_boundary_leader, opt->mime_boundary,
 +                       mime_boundary_leader, opt->mime_boundary);
 +              extra_headers = subject_buffer;
 +
 +              snprintf(buffer, sizeof(buffer) - 1,
 +                       "--%s%s\n"
 +                       "Content-Type: text/x-patch;"
 +                       " name=\"%s.diff\"\n"
 +                       "Content-Transfer-Encoding: 8bit\n"
 +                       "Content-Disposition: %s;"
 +                       " filename=\"%s.diff\"\n\n",
 +                       mime_boundary_leader, opt->mime_boundary,
 +                       name,
 +                       opt->no_inline ? "attachment" : "inline",
 +                       name);
 +              opt->diffopt.stat_sep = buffer;
 +      }
 +      *subject_p = subject;
 +      *extra_headers_p = extra_headers;
 +}
 +
  void show_log(struct rev_info *opt, const char *sep)
  {
        struct strbuf msgbuf;
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
        const char *extra;
        const char *subject = NULL, *extra_headers = opt->extra_headers;
+       int need_8bit_cte = 0;
  
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
 -              if (opt->left_right) {
 -                      if (commit->object.flags & BOUNDARY)
 -                              putchar('-');
 -                      else if (commit->object.flags & SYMMETRIC_LEFT)
 +              if (commit->object.flags & BOUNDARY)
 +                      putchar('-');
 +              else if (commit->object.flags & UNINTERESTING)
 +                      putchar('^');
 +              else if (opt->left_right) {
 +                      if (commit->object.flags & SYMMETRIC_LEFT)
                                putchar('<');
                        else
                                putchar('>');
         */
  
        if (opt->commit_format == CMIT_FMT_EMAIL) {
 -              char *sha1 = sha1_to_hex(commit->object.sha1);
 -              if (opt->total > 0) {
 -                      static char buffer[64];
 -                      snprintf(buffer, sizeof(buffer),
 -                                      "Subject: [%s %0*d/%d] ",
 -                                      opt->subject_prefix,
 -                                      digits_in_number(opt->total),
 -                                      opt->nr, opt->total);
 -                      subject = buffer;
 -              } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
 -                      static char buffer[256];
 -                      snprintf(buffer, sizeof(buffer),
 -                                      "Subject: [%s] ",
 -                                      opt->subject_prefix);
 -                      subject = buffer;
 -              } else {
 -                      subject = "Subject: ";
 -              }
 -
 -              printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
 -              if (opt->message_id)
 -                      printf("Message-Id: <%s>\n", opt->message_id);
 -              if (opt->ref_message_id)
 -                      printf("In-Reply-To: <%s>\nReferences: <%s>\n",
 -                             opt->ref_message_id, opt->ref_message_id);
 -              if (opt->mime_boundary) {
 -                      static char subject_buffer[1024];
 -                      static char buffer[1024];
 -
 -                      need_8bit_cte = -1; /* never */
 -                      snprintf(subject_buffer, sizeof(subject_buffer) - 1,
 -                               "%s"
 -                               "MIME-Version: 1.0\n"
 -                               "Content-Type: multipart/mixed;"
 -                               " boundary=\"%s%s\"\n"
 -                               "\n"
 -                               "This is a multi-part message in MIME "
 -                               "format.\n"
 -                               "--%s%s\n"
 -                               "Content-Type: text/plain; "
 -                               "charset=UTF-8; format=fixed\n"
 -                               "Content-Transfer-Encoding: 8bit\n\n",
 -                               extra_headers ? extra_headers : "",
 -                               mime_boundary_leader, opt->mime_boundary,
 -                               mime_boundary_leader, opt->mime_boundary);
 -                      extra_headers = subject_buffer;
 -
 -                      snprintf(buffer, sizeof(buffer) - 1,
 -                               "--%s%s\n"
 -                               "Content-Type: text/x-patch;"
 -                               " name=\"%s.diff\"\n"
 -                               "Content-Transfer-Encoding: 8bit\n"
 -                               "Content-Disposition: %s;"
 -                               " filename=\"%s.diff\"\n\n",
 -                               mime_boundary_leader, opt->mime_boundary,
 -                               sha1,
 -                               opt->no_inline ? "attachment" : "inline",
 -                               sha1);
 -                      opt->diffopt.stat_sep = buffer;
 -              }
 +              log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
-                                       &subject, &extra_headers);
++                                      &subject, &extra_headers,
++                                      &need_8bit_cte);
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
                if (opt->commit_format != CMIT_FMT_ONELINE)
                        fputs("commit ", stdout);
                if (commit->object.flags & BOUNDARY)
                        putchar('-');
 +              else if (commit->object.flags & UNINTERESTING)
 +                      putchar('^');
                else if (opt->left_right) {
                        if (commit->object.flags & SYMMETRIC_LEFT)
                                putchar('<');
                }
        }
  
 +      if (!commit->buffer)
 +              return;
 +
        /*
         * And then the pretty-printed message itself
         */
        strbuf_init(&msgbuf, 0);
+       if (need_8bit_cte >= 0)
+               need_8bit_cte = has_non_ascii(opt->add_signoff);
        pretty_print_commit(opt->commit_format, commit, &msgbuf,
                            abbrev, subject, extra_headers, opt->date_mode,
-                           has_non_ascii(opt->add_signoff));
+                           need_8bit_cte);
  
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
diff --combined log-tree.h
index 0cc9344eabdd0046edf360958376b2037ef643dc,b33f7cd7ac2ef6a2587109c4ee618d63ccedae96..8946ff377ca455dd1c4efaa609ce9af382005836
@@@ -13,7 -13,5 +13,9 @@@ int log_tree_commit(struct rev_info *, 
  int log_tree_opt_parse(struct rev_info *, const char **, int);
  void show_log(struct rev_info *opt, const char *sep);
  void show_decorations(struct commit *commit);
-                            const char **subject_p, const char **extra_headers_p);
 +void log_write_email_headers(struct rev_info *opt, const char *name,
++                           const char **subject_p,
++                           const char **extra_headers_p,
++                           int *need_8bit_cte_p);
  
  #endif
diff --combined pretty.c
index 703f52176bf0b5cbf52cf1f77ba55ba6d87fac94,0963bb08c125c8fc0c4d8ee5e8ff293145678158..16bfb86cd3ce6d6b471cdc313114563ca78837dc
+++ b/pretty.c
@@@ -30,7 -30,8 +30,7 @@@ enum cmit_fmt get_commit_format(const c
        if (*arg == '=')
                arg++;
        if (!prefixcmp(arg, "format:")) {
 -              if (user_format)
 -                      free(user_format);
 +              free(user_format);
                user_format = xstrdup(arg + 7);
                return CMIT_FMT_USERFORMAT;
        }
@@@ -109,9 -110,9 +109,9 @@@ needquote
        strbuf_addstr(sb, "?=");
  }
  
 -static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
 -                       const char *line, enum date_mode dmode,
 -                       const char *encoding)
 +void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
 +                const char *line, enum date_mode dmode,
 +                const char *encoding)
  {
        char *date;
        int namelen;
@@@ -281,59 -282,59 +281,59 @@@ static char *logmsg_reencode(const stru
        return out;
  }
  
 -static void format_person_part(struct strbuf *sb, char part,
 +static size_t format_person_part(struct strbuf *sb, char part,
                                 const char *msg, int len)
  {
 +      /* currently all placeholders have same length */
 +      const int placeholder_len = 2;
        int start, end, tz = 0;
 -      unsigned long date;
 +      unsigned long date = 0;
        char *ep;
  
 -      /* parse name */
 +      /* advance 'end' to point to email start delimiter */
        for (end = 0; end < len && msg[end] != '<'; end++)
                ; /* do nothing */
 +
        /*
 -       * If it does not even have a '<' and '>', that is
 -       * quite a bogus commit author and we discard it;
 -       * this is in line with add_user_info() that is used
 -       * in the normal codepath.  When end points at the '<'
 -       * that we found, it should have matching '>' later,
 -       * which means start (beginning of email address) must
 -       * be strictly below len.
 +       * When end points at the '<' that we found, it should have
 +       * matching '>' later, which means 'end' must be strictly
 +       * below len - 1.
         */
 -      start = end + 1;
 -      if (start >= len - 1)
 -              return;
 -      while (end > 0 && isspace(msg[end - 1]))
 -              end--;
 +      if (end >= len - 2)
 +              goto skip;
 +
        if (part == 'n') {      /* name */
 +              while (end > 0 && isspace(msg[end - 1]))
 +                      end--;
                strbuf_add(sb, msg, end);
 -              return;
 +              return placeholder_len;
        }
 +      start = ++end; /* save email start position */
  
 -      /* parse email */
 -      for (end = start; end < len && msg[end] != '>'; end++)
 +      /* advance 'end' to point to email end delimiter */
 +      for ( ; end < len && msg[end] != '>'; end++)
                ; /* do nothing */
  
        if (end >= len)
 -              return;
 +              goto skip;
  
        if (part == 'e') {      /* email */
                strbuf_add(sb, msg + start, end - start);
 -              return;
 +              return placeholder_len;
        }
  
 -      /* parse date */
 +      /* advance 'start' to point to date start delimiter */
        for (start = end + 1; start < len && isspace(msg[start]); start++)
                ; /* do nothing */
        if (start >= len)
 -              return;
 +              goto skip;
        date = strtoul(msg + start, &ep, 10);
        if (msg + start == ep)
 -              return;
 +              goto skip;
  
        if (part == 't') {      /* date, UNIX timestamp */
                strbuf_add(sb, msg + start, ep - (msg + start));
 -              return;
 +              return placeholder_len;
        }
  
        /* parse tz */
        switch (part) {
        case 'd':       /* date */
                strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
 -              return;
 +              return placeholder_len;
        case 'D':       /* date, RFC2822 style */
                strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
 -              return;
 +              return placeholder_len;
        case 'r':       /* date, relative */
                strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
 -              return;
 +              return placeholder_len;
        case 'i':       /* date, ISO 8601 */
                strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
 -              return;
 +              return placeholder_len;
        }
 +
 +skip:
 +      /*
 +       * bogus commit, 'sb' cannot be updated, but we still need to
 +       * compute a valid return value.
 +       */
 +      if (part == 'n' || part == 'e' || part == 't' || part == 'd'
 +          || part == 'D' || part == 'r' || part == 'i')
 +              return placeholder_len;
 +
 +      return 0; /* unknown placeholder */
  }
  
  struct chunk {
@@@ -450,7 -440,7 +450,7 @@@ static void parse_commit_header(struct 
        context->commit_header_parsed = 1;
  }
  
 -static void format_commit_item(struct strbuf *sb, const char *placeholder,
 +static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                                 void *context)
  {
        struct format_commit_context *c = context;
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
 -              switch (placeholder[3]) {
 -              case 'd':       /* red */
 +              if (!prefixcmp(placeholder + 1, "red")) {
                        strbuf_addstr(sb, "\033[31m");
 -                      return;
 -              case 'e':       /* green */
 +                      return 4;
 +              } else if (!prefixcmp(placeholder + 1, "green")) {
                        strbuf_addstr(sb, "\033[32m");
 -                      return;
 -              case 'u':       /* blue */
 +                      return 6;
 +              } else if (!prefixcmp(placeholder + 1, "blue")) {
                        strbuf_addstr(sb, "\033[34m");
 -                      return;
 -              case 's':       /* reset color */
 +                      return 5;
 +              } else if (!prefixcmp(placeholder + 1, "reset")) {
                        strbuf_addstr(sb, "\033[m");
 -                      return;
 -              }
 +                      return 6;
 +              } else
 +                      return 0;
        case 'n':               /* newline */
                strbuf_addch(sb, '\n');
 -              return;
 +              return 1;
        }
  
        /* these depend on the commit */
        switch (placeholder[0]) {
        case 'H':               /* commit hash */
                strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
 -              return;
 +              return 1;
        case 'h':               /* abbreviated commit hash */
                if (add_again(sb, &c->abbrev_commit_hash))
 -                      return;
 +                      return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
                                                     DEFAULT_ABBREV));
                c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
 -              return;
 +              return 1;
        case 'T':               /* tree hash */
                strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
 -              return;
 +              return 1;
        case 't':               /* abbreviated tree hash */
                if (add_again(sb, &c->abbrev_tree_hash))
 -                      return;
 +                      return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
                                                     DEFAULT_ABBREV));
                c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
 -              return;
 +              return 1;
        case 'P':               /* parent hashes */
                for (p = commit->parents; p; p = p->next) {
                        if (p != commit->parents)
                                strbuf_addch(sb, ' ');
                        strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
                }
 -              return;
 +              return 1;
        case 'p':               /* abbreviated parent hashes */
                if (add_again(sb, &c->abbrev_parent_hashes))
 -                      return;
 +                      return 1;
                for (p = commit->parents; p; p = p->next) {
                        if (p != commit->parents)
                                strbuf_addch(sb, ' ');
                }
                c->abbrev_parent_hashes.len = sb->len -
                                              c->abbrev_parent_hashes.off;
 -              return;
 +              return 1;
        case 'm':               /* left/right/bottom */
                strbuf_addch(sb, (commit->object.flags & BOUNDARY)
                                 ? '-'
                                 : (commit->object.flags & SYMMETRIC_LEFT)
                                 ? '<'
                                 : '>');
 -              return;
 +              return 1;
        }
  
        /* For the rest we have to parse the commit header. */
                parse_commit_header(c);
  
        switch (placeholder[0]) {
 -      case 's':
 +      case 's':       /* subject */
                strbuf_add(sb, msg + c->subject.off, c->subject.len);
 -              return;
 -      case 'a':
 -              format_person_part(sb, placeholder[1],
 +              return 1;
 +      case 'a':       /* author ... */
 +              return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len);
 -              return;
 -      case 'c':
 -              format_person_part(sb, placeholder[1],
 +      case 'c':       /* committer ... */
 +              return format_person_part(sb, placeholder[1],
                                   msg + c->committer.off, c->committer.len);
 -              return;
 -      case 'e':
 +      case 'e':       /* encoding */
                strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
 -              return;
 -      case 'b':
 +              return 1;
 +      case 'b':       /* body */
                strbuf_addstr(sb, msg + c->body_off);
 -              return;
 +              return 1;
        }
 +      return 0;       /* unknown placeholder */
  }
  
  void format_commit_message(const struct commit *commit,
                             const void *format, struct strbuf *sb)
  {
 -      const char *placeholders[] = {
 -              "H",            /* commit hash */
 -              "h",            /* abbreviated commit hash */
 -              "T",            /* tree hash */
 -              "t",            /* abbreviated tree hash */
 -              "P",            /* parent hashes */
 -              "p",            /* abbreviated parent hashes */
 -              "an",           /* author name */
 -              "ae",           /* author email */
 -              "ad",           /* author date */
 -              "aD",           /* author date, RFC2822 style */
 -              "ar",           /* author date, relative */
 -              "at",           /* author date, UNIX timestamp */
 -              "ai",           /* author date, ISO 8601 */
 -              "cn",           /* committer name */
 -              "ce",           /* committer email */
 -              "cd",           /* committer date */
 -              "cD",           /* committer date, RFC2822 style */
 -              "cr",           /* committer date, relative */
 -              "ct",           /* committer date, UNIX timestamp */
 -              "ci",           /* committer date, ISO 8601 */
 -              "e",            /* encoding */
 -              "s",            /* subject */
 -              "b",            /* body */
 -              "Cred",         /* red */
 -              "Cgreen",       /* green */
 -              "Cblue",        /* blue */
 -              "Creset",       /* reset color */
 -              "n",            /* newline */
 -              "m",            /* left/right/bottom */
 -              NULL
 -      };
        struct format_commit_context context;
  
        memset(&context, 0, sizeof(context));
        context.commit = commit;
 -      strbuf_expand(sb, format, placeholders, format_commit_item, &context);
 +      strbuf_expand(sb, format, format_commit_item, &context);
  }
  
  static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
 -                      add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
 +                      pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
                    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
 -                      add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
 +                      pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
                }
        }
  }
  
 -static void pp_title_line(enum cmit_fmt fmt,
 -                        const char **msg_p,
 -                        struct strbuf *sb,
 -                        const char *subject,
 -                        const char *after_subject,
 -                        const char *encoding,
 -                        int need_8bit_cte)
 +void pp_title_line(enum cmit_fmt fmt,
 +                 const char **msg_p,
 +                 struct strbuf *sb,
 +                 const char *subject,
 +                 const char *after_subject,
 +                 const char *encoding,
-                  int plain_non_ascii)
++                 int need_8bit_cte)
  {
        struct strbuf title;
  
        }
        strbuf_addch(sb, '\n');
  
-       if (plain_non_ascii) {
+       if (need_8bit_cte > 0) {
                const char *header_fmt =
                        "MIME-Version: 1.0\n"
                        "Content-Type: text/plain; charset=%s\n"
        strbuf_release(&title);
  }
  
 -static void pp_remainder(enum cmit_fmt fmt,
 -                       const char **msg_p,
 -                       struct strbuf *sb,
 -                       int indent)
 +void pp_remainder(enum cmit_fmt fmt,
 +                const char **msg_p,
 +                struct strbuf *sb,
 +                int indent)
  {
        int first = 1;
        for (;;) {
  }
  
  void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                                 struct strbuf *sb, int abbrev,
-                                 const char *subject, const char *after_subject,
-                                 enum date_mode dmode, int plain_non_ascii)
+                        struct strbuf *sb, int abbrev,
+                        const char *subject, const char *after_subject,
+                        enum date_mode dmode, int need_8bit_cte)
  {
        unsigned long beginning_of_body;
        int indent = 4;
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
                indent = 0;
  
-       /* After-subject is used to pass in Content-Type: multipart
-        * MIME header; in that case we do not have to do the
-        * plaintext content type even if the commit message has
-        * non 7-bit ASCII character.  Otherwise, check if we need
-        * to say this is not a 7-bit ASCII.
+       /*
+        * We need to check and emit Content-type: to mark it
+        * as 8-bit if we haven't done so.
         */
-       if (fmt == CMIT_FMT_EMAIL && !after_subject) {
+       if (fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
                int i, ch, in_body;
  
                for (in_body = i = 0; (ch = msg[i]); i++) {
                                        in_body = 1;
                        }
                        else if (non_ascii(ch)) {
-                               plain_non_ascii = 1;
+                               need_8bit_cte = 1;
                                break;
                        }
                }
        /* These formats treat the title line specially. */
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
                pp_title_line(fmt, &msg, sb, subject,
-                             after_subject, encoding, plain_non_ascii);
+                             after_subject, encoding, need_8bit_cte);
  
        beginning_of_body = sb->len;
        if (fmt != CMIT_FMT_ONELINE)