Code

Merge branch 'jc/pull-signed-tag-doc'
[git.git] / pretty.c
index 65d20a7a2e7cafd79ff95c02cab3303ebb72351c..8688b8f2d45a493aa8b51b29f7d46b2abff7f30e 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -9,6 +9,7 @@
 #include "notes.h"
 #include "color.h"
 #include "reflog-walk.h"
+#include "gpg-interface.h"
 
 static char *user_format;
 static struct cmt_fmt_map {
@@ -208,6 +209,58 @@ int has_non_ascii(const char *s)
        return 0;
 }
 
+static int is_rfc822_special(char ch)
+{
+       switch (ch) {
+       case '(':
+       case ')':
+       case '<':
+       case '>':
+       case '[':
+       case ']':
+       case ':':
+       case ';':
+       case '@':
+       case ',':
+       case '.':
+       case '"':
+       case '\\':
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static int has_rfc822_specials(const char *s, int len)
+{
+       int i;
+       for (i = 0; i < len; i++)
+               if (is_rfc822_special(s[i]))
+                       return 1;
+       return 0;
+}
+
+static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
+{
+       int i;
+
+       /* just a guess, we may have to also backslash-quote */
+       strbuf_grow(out, len + 2);
+
+       strbuf_addch(out, '"');
+       for (i = 0; i < len; i++) {
+               switch (s[i]) {
+               case '"':
+               case '\\':
+                       strbuf_addch(out, '\\');
+                       /* fall through */
+               default:
+                       strbuf_addch(out, s[i]);
+               }
+       }
+       strbuf_addch(out, '"');
+}
+
 static int is_rfc2047_special(char ch)
 {
        return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
@@ -266,16 +319,16 @@ needquote:
        strbuf_addstr(sb, "?=");
 }
 
-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_user_info(const struct pretty_print_context *pp,
+                 const char *what, struct strbuf *sb,
+                 const char *line, const char *encoding)
 {
        char *date;
        int namelen;
        unsigned long time;
        int tz;
 
-       if (fmt == CMIT_FMT_ONELINE)
+       if (pp->fmt == CMIT_FMT_ONELINE)
                return;
        date = strchr(line, '>');
        if (!date)
@@ -284,32 +337,48 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
 
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                char *name_tail = strchr(line, '<');
                int display_name_length;
+               int final_line;
                if (!name_tail)
                        return;
                while (line < name_tail && isspace(name_tail[-1]))
                        name_tail--;
                display_name_length = name_tail - line;
                strbuf_addstr(sb, "From: ");
-               add_rfc2047(sb, line, display_name_length, encoding);
+               if (!has_rfc822_specials(line, display_name_length)) {
+                       add_rfc2047(sb, line, display_name_length, encoding);
+               } else {
+                       struct strbuf quoted = STRBUF_INIT;
+                       add_rfc822_quoted(&quoted, line, display_name_length);
+                       add_rfc2047(sb, quoted.buf, quoted.len, encoding);
+                       strbuf_release(&quoted);
+               }
+               for (final_line = 0; final_line < sb->len; final_line++)
+                       if (sb->buf[sb->len - final_line - 1] == '\n')
+                               break;
+               if (namelen - display_name_length + final_line > 78) {
+                       strbuf_addch(sb, '\n');
+                       if (!isspace(name_tail[0]))
+                               strbuf_addch(sb, ' ');
+               }
                strbuf_add(sb, name_tail, namelen - display_name_length);
                strbuf_addch(sb, '\n');
        } else {
                strbuf_addf(sb, "%s: %.*s%.*s\n", what,
-                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+                             (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
                              "    ", namelen, line);
        }
-       switch (fmt) {
+       switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
-               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, dmode));
+               strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, pp->date_mode));
                break;
        case CMIT_FMT_EMAIL:
                strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
-               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
+               strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode));
                break;
        default:
                /* notin' */
@@ -340,12 +409,12 @@ static const char *skip_empty_lines(const char *msg)
        return msg;
 }
 
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
-                       const struct commit *commit, int abbrev)
+static void add_merge_info(const struct pretty_print_context *pp,
+                          struct strbuf *sb, const struct commit *commit)
 {
        struct commit_list *parent = commit->parents;
 
-       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+       if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
            !parent || !parent->next)
                return;
 
@@ -354,8 +423,8 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
        while (parent) {
                struct commit *p = parent->item;
                const char *hex = NULL;
-               if (abbrev)
-                       hex = find_unique_abbrev(p->object.sha1, abbrev);
+               if (pp->abbrev)
+                       hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
                if (!hex)
                        hex = sha1_to_hex(p->object.sha1);
                parent = parent->next;
@@ -572,6 +641,12 @@ struct format_commit_context {
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       unsigned commit_signature_parsed:1;
+       struct {
+               char *gpg_output;
+               char good_bad;
+               char *signer;
+       } signature;
        char *message;
        size_t width, indent1, indent2;
 
@@ -754,6 +829,76 @@ static void rewrap_message_tail(struct strbuf *sb,
        c->indent2 = new_indent2;
 }
 
+static struct {
+       char result;
+       const char *check;
+} signature_check[] = {
+       { 'G', ": Good signature from " },
+       { 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct format_commit_context *ctx)
+{
+       const char *buf = ctx->signature.gpg_output;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+               const char *found = strstr(buf, signature_check[i].check);
+               const char *next;
+               if (!found)
+                       continue;
+               ctx->signature.good_bad = signature_check[i].result;
+               found += strlen(signature_check[i].check);
+               next = strchrnul(found, '\n');
+               ctx->signature.signer = xmemdupz(found, next - found);
+               break;
+       }
+}
+
+static void parse_commit_signature(struct format_commit_context *ctx)
+{
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+
+       ctx->commit_signature_parsed = 1;
+
+       if (parse_signed_commit(ctx->commit->object.sha1,
+                               &payload, &signature) <= 0)
+               goto out;
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               goto out;
+       ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+       parse_signature_lines(ctx);
+
+ out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+}
+
+
+static int format_reflog_person(struct strbuf *sb,
+                               char part,
+                               struct reflog_walk_info *log,
+                               enum date_mode dmode)
+{
+       const char *ident;
+
+       if (!log)
+               return 2;
+
+       ident = get_reflog_ident(log);
+       if (!ident)
+               return 2;
+
+       return format_person_part(sb, part, ident, strlen(ident), dmode);
+}
+
 static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                                void *context)
 {
@@ -876,11 +1021,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                                              c->abbrev_parent_hashes.off;
                return 1;
        case 'm':               /* left/right/bottom */
-               strbuf_addch(sb, (commit->object.flags & BOUNDARY)
-                                ? '-'
-                                : (commit->object.flags & SYMMETRIC_LEFT)
-                                ? '<'
-                                : '>');
+               strbuf_addstr(sb, get_revision_mark(NULL, commit));
                return 1;
        case 'd':
                format_decoration(sb, commit);
@@ -899,6 +1040,14 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                        if (c->pretty_ctx->reflog_info)
                                get_reflog_message(sb, c->pretty_ctx->reflog_info);
                        return 2;
+               case 'n':
+               case 'N':
+               case 'e':
+               case 'E':
+                       return format_reflog_person(sb,
+                                                   placeholder[1],
+                                                   c->pretty_ctx->reflog_info,
+                                                   c->pretty_ctx->date_mode);
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
@@ -910,6 +1059,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                return 0;
        }
 
+       if (placeholder[0] == 'G') {
+               if (!c->commit_signature_parsed)
+                       parse_commit_signature(c);
+               switch (placeholder[1]) {
+               case 'G':
+                       if (c->signature.gpg_output)
+                               strbuf_addstr(sb, c->signature.gpg_output);
+                       break;
+               case '?':
+                       switch (c->signature.good_bad) {
+                       case 'G':
+                       case 'B':
+                               strbuf_addch(sb, c->signature.good_bad);
+                       }
+                       break;
+               case 'S':
+                       if (c->signature.signer)
+                               strbuf_addstr(sb, c->signature.signer);
+                       break;
+               }
+               return 2;
+       }
+
+
        /* For the rest we have to parse the commit header. */
        if (!c->commit_header_parsed)
                parse_commit_header(c);
@@ -1020,7 +1193,7 @@ void userformat_find_requirements(const char *fmt, struct userformat_want *w)
                        return;
                fmt = user_format;
        }
-       strbuf_expand(&dummy, user_format, userformat_want_item, w);
+       strbuf_expand(&dummy, fmt, userformat_want_item, w);
        strbuf_release(&dummy);
 }
 
@@ -1030,7 +1203,6 @@ void format_commit_message(const struct commit *commit,
 {
        struct format_commit_context context;
        static const char utf8[] = "UTF-8";
-       const char *enc;
        const char *output_enc = pretty_ctx->output_encoding;
 
        memset(&context, 0, sizeof(context));
@@ -1039,10 +1211,13 @@ void format_commit_message(const struct commit *commit,
        context.wrap_start = sb->len;
        context.message = commit->buffer;
        if (output_enc) {
-               enc = get_header(commit, "encoding");
-               enc = enc ? enc : utf8;
-               if (strcmp(enc, output_enc))
+               char *enc = get_header(commit, "encoding");
+               if (strcmp(enc ? enc : utf8, output_enc)) {
                        context.message = logmsg_reencode(commit, output_enc);
+                       if (!context.message)
+                               context.message = commit->buffer;
+               }
+               free(enc);
        }
 
        strbuf_expand(sb, format, format_commit_item, &context);
@@ -1050,11 +1225,11 @@ void format_commit_message(const struct commit *commit,
 
        if (context.message != commit->buffer)
                free(context.message);
+       free(context.signature.gpg_output);
+       free(context.signature.signer);
 }
 
-static void pp_header(enum cmit_fmt fmt,
-                     int abbrev,
-                     enum date_mode dmode,
+static void pp_header(const struct pretty_print_context *pp,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
@@ -1074,7 +1249,7 @@ static void pp_header(enum cmit_fmt fmt,
                        /* End of header */
                        return;
 
-               if (fmt == CMIT_FMT_RAW) {
+               if (pp->fmt == CMIT_FMT_RAW) {
                        strbuf_add(sb, line, linelen);
                        continue;
                }
@@ -1094,7 +1269,7 @@ static void pp_header(enum cmit_fmt fmt,
                                ;
                        /* with enough slop */
                        strbuf_grow(sb, num * 50 + 20);
-                       add_merge_info(fmt, sb, commit, abbrev);
+                       add_merge_info(pp, sb, commit);
                        parents_shown = 1;
                }
 
@@ -1105,32 +1280,31 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info(pp, "Author", sb, line + 7, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
-                   (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+                   (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info(pp, "Commit", sb, line + 10, encoding);
                }
        }
 }
 
-void pp_title_line(enum cmit_fmt fmt,
+void pp_title_line(const struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
-                  const char *subject,
-                  const char *after_subject,
                   const char *encoding,
                   int need_8bit_cte)
 {
        struct strbuf title;
 
        strbuf_init(&title, 80);
-       *msg_p = format_subject(&title, *msg_p, " ");
+       *msg_p = format_subject(&title, *msg_p,
+                               pp->preserve_subject ? "\n" : " ");
 
        strbuf_grow(sb, title.len + 1024);
-       if (subject) {
-               strbuf_addstr(sb, subject);
+       if (pp->subject) {
+               strbuf_addstr(sb, pp->subject);
                add_rfc2047(sb, title.buf, title.len, encoding);
        } else {
                strbuf_addbuf(sb, &title);
@@ -1144,16 +1318,16 @@ void pp_title_line(enum cmit_fmt fmt,
                        "Content-Transfer-Encoding: 8bit\n";
                strbuf_addf(sb, header_fmt, encoding);
        }
-       if (after_subject) {
-               strbuf_addstr(sb, after_subject);
+       if (pp->after_subject) {
+               strbuf_addstr(sb, pp->after_subject);
        }
-       if (fmt == CMIT_FMT_EMAIL) {
+       if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addch(sb, '\n');
        }
        strbuf_release(&title);
 }
 
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent)
@@ -1170,7 +1344,7 @@ void pp_remainder(enum cmit_fmt fmt,
                if (is_empty_line(line, &linelen)) {
                        if (first)
                                continue;
-                       if (fmt == CMIT_FMT_SHORT)
+                       if (pp->fmt == CMIT_FMT_SHORT)
                                break;
                }
                first = 0;
@@ -1195,19 +1369,19 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
        return logmsg_reencode(commit, encoding);
 }
 
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                        struct strbuf *sb,
-                        const struct pretty_print_context *context)
+void pretty_print_commit(const struct pretty_print_context *pp,
+                        const struct commit *commit,
+                        struct strbuf *sb)
 {
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        char *reencoded;
        const char *encoding;
-       int need_8bit_cte = context->need_8bit_cte;
+       int need_8bit_cte = pp->need_8bit_cte;
 
-       if (fmt == CMIT_FMT_USERFORMAT) {
-               format_commit_message(commit, user_format, sb, context);
+       if (pp->fmt == CMIT_FMT_USERFORMAT) {
+               format_commit_message(commit, user_format, sb, pp);
                return;
        }
 
@@ -1216,14 +1390,14 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                msg = reencoded;
        }
 
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
                indent = 0;
 
        /*
         * 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 && need_8bit_cte == 0) {
+       if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
                int i, ch, in_body;
 
                for (in_body = i = 0; (ch = msg[i]); i++) {
@@ -1242,9 +1416,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                }
        }
 
-       pp_header(fmt, context->abbrev, context->date_mode, encoding,
-                 commit, &msg, sb);
-       if (fmt != CMIT_FMT_ONELINE && !context->subject) {
+       pp_header(pp, encoding, commit, &msg, sb);
+       if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
                strbuf_addch(sb, '\n');
        }
 
@@ -1252,17 +1425,16 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
        msg = skip_empty_lines(msg);
 
        /* These formats treat the title line specially. */
-       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
-               pp_title_line(fmt, &msg, sb, context->subject,
-                             context->after_subject, encoding, need_8bit_cte);
+       if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+               pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
 
        beginning_of_body = sb->len;
-       if (fmt != CMIT_FMT_ONELINE)
-               pp_remainder(fmt, &msg, sb, indent);
+       if (pp->fmt != CMIT_FMT_ONELINE)
+               pp_remainder(pp, &msg, sb, indent);
        strbuf_rtrim(sb);
 
        /* Make sure there is an EOLN for the non-oneline case */
-       if (fmt != CMIT_FMT_ONELINE)
+       if (pp->fmt != CMIT_FMT_ONELINE)
                strbuf_addch(sb, '\n');
 
        /*
@@ -1270,12 +1442,20 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
         * format.  Make sure we did not strip the blank line
         * between the header and the body.
         */
-       if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
+       if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
 
-       if (context->show_notes)
+       if (pp->show_notes)
                format_display_notes(commit->object.sha1, sb, encoding,
                                     NOTES_SHOW_HEADER | NOTES_INDENT);
 
        free(reencoded);
 }
+
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                   struct strbuf *sb)
+{
+       struct pretty_print_context pp = {0};
+       pp.fmt = fmt;
+       pretty_print_commit(&pp, commit, sb);
+}