Merge branch 'db/cover-letter'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 20:06:41 +0000 (12:06 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 20:06:41 +0000 (12:06 -0800)
* db/cover-letter:
Improve collection of information for format-patch --cover-letter
Add API access to shortlog
t4014: Replace sed's non-standard 'Q' by standard 'q'
Support a --cc=<email> option in format-patch
Combine To: and Cc: headers
Fix format.headers not ending with a newline
Add tests for extra headers in format-patch
Add a --cover-letter option to format-patch
Export some email and pretty-printing functions
Improve message-id generation flow control for format-patch
Add more tests for format-patch

Conflicts:

builtin-log.c
builtin-shortlog.c
pretty.c

1  2 
builtin-log.c
log-tree.c
pretty.c
revision.h
diff --combined builtin-log.c
index c67d63cb1cf588877fa10a7546757f2cd249c5c6,3112d96db0dd60b50cd4b5c02e51821f678fb8ff..3209ea5c6d61d18dfe885f07b20d11119799b803
@@@ -5,7 -5,6 +5,7 @@@
   *             2006 Junio Hamano
   */
  #include "cache.h"
 +#include "color.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
@@@ -15,6 -14,8 +15,8 @@@
  #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";
@@@ -198,8 -199,7 +200,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);
@@@ -237,10 -237,6 +239,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;
@@@ -313,10 -309,6 +315,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;
@@@ -377,10 -369,6 +379,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;
@@@ -409,10 -397,6 +411,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);
@@@ -428,24 -412,47 +430,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);
        }
  
@@@ -574,8 -588,7 +606,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;
+       const char *origin_sha1, *head_sha1;
+       const char *argv[7];
+       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";
+       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);
+       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);
+       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       printf("%s\n", sb.buf);
+       strbuf_release(&sb);
+       shortlog_init(&log);
+       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;
+       origin_sha1 = sha1_to_hex(origin->object.sha1);
+       argv[0] = "diff";
+       argv[1] = "--stat";
+       argv[2] = "--summary";
+       argv[3] = head_sha1;
+       argv[4] = "--not";
+       argv[5] = origin_sha1;
+       argv[6] = "--";
+       argv[7] = NULL;
+       fflush(stdout);
+       run_command_v_opt(argv, RUN_GIT_CMD);
+       fflush(stdout);
+       printf("\n");
  }
  
  static const char *clean_message_id(const char *msg_id)
@@@ -641,11 -727,13 +746,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 (prepare_revision_walk(&rev))
                die("revision walk setup failed");
+       rev.boundary = 1;
        while ((commit = get_revision(&rev)) != NULL) {
+               if (commit->object.flags & BOUNDARY) {
+                       fprintf(stderr, "Boundary %s\n", sha1_to_hex(commit->object.sha1));
+                       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;
@@@ -943,8 -1100,7 +1119,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 log-tree.c
index e9ba6df9d230e8adc12df32b502729c09c8e7d5a,1b084dc27b2cd1c74af7fea288569e5c2e12c76d..608f697cf3860245510bff907a643d9b6143e724
@@@ -137,6 -137,72 +137,72 @@@ static int has_non_ascii(const char *s
        return 0;
  }
  
+ void log_write_email_headers(struct rev_info *opt, const char *name,
+                            const char **subject_p, const char **extra_headers_p)
+ {
+       const char *subject = NULL;
+       const char *extra_headers = opt->extra_headers;
+       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];
+               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;
  
        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];
-                       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);
        } 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
         */
diff --combined pretty.c
index 997f5837d521ded5f158d12abc0c025f74e83341,d5db1bd872af150c458fe60d17a159d11134a2eb..a5b2d8c6048c78e360b3526c247b9b272bb3ffc3
+++ b/pretty.c
@@@ -110,9 -110,9 +110,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;
@@@ -282,59 -282,59 +282,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 pp_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 {
@@@ -451,7 -440,7 +451,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 plain_non_ascii)
+ 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)
  {
        struct strbuf title;
  
        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 (;;) {
diff --combined revision.h
index b5f01f8309b6630be7169eb72f613e5deb0ebcab,e3559d019d5b33ec1bcf5b2c2d820c252aecfc7c..c8b3b948ecc1cc8b45859a6e1ea11763addfb8b5
@@@ -33,7 -33,6 +33,7 @@@ struct rev_info 
                        prune:1,
                        no_merges:1,
                        no_walk:1,
 +                      show_all:1,
                        remove_empty_trees:1,
                        simplify_history:1,
                        lifo:1,
@@@ -75,7 -74,7 +75,7 @@@
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
-       const char      *message_id;
+       char            *message_id;
        const char      *ref_message_id;
        const char      *add_signoff;
        const char      *extra_headers;