diff options
Diffstat (limited to 'pretty.c')
-rw-r--r-- | pretty.c | 500 |
1 files changed, 325 insertions, 175 deletions
@@ -410,10 +410,7 @@ void pp_user_info(const struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding) { - struct strbuf name; - struct strbuf mail; struct ident_split ident; - int linelen; char *line_end; const char *mailbuf, *namebuf; size_t namelen, maillen; @@ -422,18 +419,10 @@ void pp_user_info(const struct pretty_print_context *pp, if (pp->fmt == CMIT_FMT_ONELINE) return; - line_end = strchr(line, '\n'); - if (!line_end) { - line_end = strchr(line, '\0'); - if (!line_end) - return; - } - - linelen = ++line_end - line; - if (split_ident_line(&ident, line, linelen)) + line_end = strchrnul(line, '\n'); + if (split_ident_line(&ident, line, line_end - line)) return; - mailbuf = ident.mail_begin; maillen = ident.mail_end - ident.mail_begin; namebuf = ident.name_begin; @@ -442,43 +431,33 @@ void pp_user_info(const struct pretty_print_context *pp, if (pp->mailmap) map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); - strbuf_init(&mail, 0); - strbuf_init(&name, 0); - - strbuf_add(&mail, mailbuf, maillen); - strbuf_add(&name, namebuf, namelen); - - namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */ - if (pp->fmt == CMIT_FMT_EMAIL) { strbuf_addstr(sb, "From: "); - if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) { - add_rfc2047(sb, name.buf, name.len, + if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) { + add_rfc2047(sb, namebuf, namelen, encoding, RFC2047_ADDRESS); max_length = 76; /* per rfc2047 */ - } else if (needs_rfc822_quoting(name.buf, name.len)) { + } else if (needs_rfc822_quoting(namebuf, namelen)) { struct strbuf quoted = STRBUF_INIT; - add_rfc822_quoted("ed, name.buf, name.len); + add_rfc822_quoted("ed, namebuf, namelen); strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len, -6, 1, max_length); strbuf_release("ed); } else { - strbuf_add_wrapped_bytes(sb, name.buf, name.len, + strbuf_add_wrapped_bytes(sb, namebuf, namelen, -6, 1, max_length); } - if (namelen - name.len + last_line_length(sb) > max_length) - strbuf_addch(sb, '\n'); - strbuf_addf(sb, " <%s>\n", mail.buf); + if (max_length < + last_line_length(sb) + strlen(" <") + maillen + strlen(">")) + strbuf_addch(sb, '\n'); + strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf); } else { - strbuf_addf(sb, "%s: %.*s%s <%s>\n", what, - (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, - " ", name.buf, mail.buf); + strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what, + (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, " ", + (int)namelen, namebuf, (int)maillen, mailbuf); } - strbuf_release(&mail); - strbuf_release(&name); - switch (pp->fmt) { case CMIT_FMT_MEDIUM: strbuf_addf(sb, "Date: %s\n", @@ -606,6 +585,7 @@ static char *replace_encoding_header(char *buf, const char *encoding) } char *logmsg_reencode(const struct commit *commit, + char **commit_encoding, const char *output_encoding) { static const char *utf8 = "UTF-8"; @@ -627,9 +607,15 @@ char *logmsg_reencode(const struct commit *commit, sha1_to_hex(commit->object.sha1), typename(type)); } - if (!output_encoding || !*output_encoding) + if (!output_encoding || !*output_encoding) { + if (commit_encoding) + *commit_encoding = + get_header(commit, msg, "encoding"); return msg; + } encoding = get_header(commit, msg, "encoding"); + if (commit_encoding) + *commit_encoding = encoding; use_encoding = encoding ? encoding : utf8; if (same_encoding(use_encoding, output_encoding)) { /* @@ -670,7 +656,8 @@ char *logmsg_reencode(const struct commit *commit, if (out) out = replace_encoding_header(out, output_encoding); - free(encoding); + if (!commit_encoding) + free(encoding); /* * If the re-encoding failed, out might be NULL here; in that * case we just return the commit message verbatim. @@ -764,26 +751,38 @@ struct chunk { size_t len; }; +enum flush_type { + no_flush, + flush_right, + flush_left, + flush_left_and_steal, + flush_both +}; + +enum trunc_type { + trunc_none, + trunc_left, + trunc_middle, + trunc_right +}; + struct format_commit_context { const struct commit *commit; 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 *gpg_status; - char good_bad; - char *signer; - char *key; - } signature; + struct signature_check signature_check; + enum flush_type flush_type; + enum trunc_type truncate; char *message; + char *commit_encoding; size_t width, indent1, indent2; + int auto_color; + int padding; /* These offsets are relative to the start of the commit message. */ struct chunk author; struct chunk committer; - struct chunk encoding; size_t message_off; size_t subject_off; size_t body_off; @@ -830,9 +829,6 @@ static void parse_commit_header(struct format_commit_context *context) } else if (!prefixcmp(msg + i, "committer ")) { context->committer.off = i + 10; context->committer.len = eol - i - 10; - } else if (!prefixcmp(msg + i, "encoding ")) { - context->encoding.off = i + 9; - context->encoding.len = eol - i - 9; } i = eol; } @@ -913,23 +909,6 @@ static void parse_commit_message(struct format_commit_context *c) c->commit_message_parsed = 1; } -static void format_decoration(struct strbuf *sb, const struct commit *commit) -{ - struct name_decoration *d; - const char *prefix = " ("; - - load_ref_decorations(DECORATE_SHORT_REFS); - d = lookup_decoration(&name_decoration, &commit->object); - while (d) { - strbuf_addstr(sb, prefix); - prefix = ", "; - strbuf_addstr(sb, d->name); - d = d->next; - } - if (prefix[0] == ',') - strbuf_addch(sb, ')'); -} - static void strbuf_wrap(struct strbuf *sb, size_t pos, size_t width, size_t indent1, size_t indent2) { @@ -959,64 +938,6 @@ static void rewrap_message_tail(struct strbuf *sb, c->indent2 = new_indent2; } -static struct { - char result; - const char *check; -} signature_check[] = { - { 'G', "\n[GNUPG:] GOODSIG " }, - { 'B', "\n[GNUPG:] BADSIG " }, -}; - -static void parse_signature_lines(struct format_commit_context *ctx) -{ - const char *buf = ctx->signature.gpg_status; - 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); - ctx->signature.key = xmemdupz(found, 16); - found += 17; - 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; - struct strbuf gpg_status = 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, &gpg_status); - if (status && !gpg_output.len) - goto out; - ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL); - ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL); - parse_signature_lines(ctx); - - out: - strbuf_release(&gpg_status); - 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, @@ -1034,7 +955,112 @@ static int format_reflog_person(struct strbuf *sb, return format_person_part(sb, part, ident, strlen(ident), dmode); } -static size_t format_commit_one(struct strbuf *sb, const char *placeholder, +static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ + const char *placeholder, + struct format_commit_context *c) +{ + if (placeholder[1] == '(') { + const char *begin = placeholder + 2; + const char *end = strchr(begin, ')'); + char color[COLOR_MAXLEN]; + + if (!end) + return 0; + if (!prefixcmp(begin, "auto,")) { + if (!want_color(c->pretty_ctx->color)) + return end - placeholder + 1; + begin += 5; + } + color_parse_mem(begin, + end - begin, + "--pretty format", color); + strbuf_addstr(sb, color); + return end - placeholder + 1; + } + if (!prefixcmp(placeholder + 1, "red")) { + strbuf_addstr(sb, GIT_COLOR_RED); + return 4; + } else if (!prefixcmp(placeholder + 1, "green")) { + strbuf_addstr(sb, GIT_COLOR_GREEN); + return 6; + } else if (!prefixcmp(placeholder + 1, "blue")) { + strbuf_addstr(sb, GIT_COLOR_BLUE); + return 5; + } else if (!prefixcmp(placeholder + 1, "reset")) { + strbuf_addstr(sb, GIT_COLOR_RESET); + return 6; + } else + return 0; +} + +static size_t parse_padding_placeholder(struct strbuf *sb, + const char *placeholder, + struct format_commit_context *c) +{ + const char *ch = placeholder; + enum flush_type flush_type; + int to_column = 0; + + switch (*ch++) { + case '<': + flush_type = flush_right; + break; + case '>': + if (*ch == '<') { + flush_type = flush_both; + ch++; + } else if (*ch == '>') { + flush_type = flush_left_and_steal; + ch++; + } else + flush_type = flush_left; + break; + default: + return 0; + } + + /* the next value means "wide enough to that column" */ + if (*ch == '|') { + to_column = 1; + ch++; + } + + if (*ch == '(') { + const char *start = ch + 1; + const char *end = start + strcspn(start, ",)"); + char *next; + int width; + if (!end || end == start) + return 0; + width = strtoul(start, &next, 10); + if (next == start || width == 0) + return 0; + c->padding = to_column ? -width : width; + c->flush_type = flush_type; + + if (*end == ',') { + start = end + 1; + end = strchr(start, ')'); + if (!end || end == start) + return 0; + if (!prefixcmp(start, "trunc)")) + c->truncate = trunc_right; + else if (!prefixcmp(start, "ltrunc)")) + c->truncate = trunc_left; + else if (!prefixcmp(start, "mtrunc)")) + c->truncate = trunc_middle; + else + return 0; + } else + c->truncate = trunc_none; + + return end - placeholder + 1; + } + return 0; +} + +static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ + const char *placeholder, void *context) { struct format_commit_context *c = context; @@ -1046,38 +1072,20 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, /* these are independent of the commit */ switch (placeholder[0]) { case 'C': - if (placeholder[1] == '(') { - const char *begin = placeholder + 2; - const char *end = strchr(begin, ')'); - char color[COLOR_MAXLEN]; - - if (!end) - return 0; - if (!prefixcmp(begin, "auto,")) { - if (!want_color(c->pretty_ctx->color)) - return end - placeholder + 1; - begin += 5; - } - color_parse_mem(begin, - end - begin, - "--pretty format", color); - strbuf_addstr(sb, color); - return end - placeholder + 1; + if (!prefixcmp(placeholder + 1, "(auto)")) { + c->auto_color = 1; + return 7; /* consumed 7 bytes, "C(auto)" */ + } else { + int ret = parse_color(sb, placeholder, c); + if (ret) + c->auto_color = 0; + /* + * Otherwise, we decided to treat %C<unknown> + * as a literal string, and the previous + * %C(auto) is still valid. + */ + return ret; } - if (!prefixcmp(placeholder + 1, "red")) { - strbuf_addstr(sb, GIT_COLOR_RED); - return 4; - } else if (!prefixcmp(placeholder + 1, "green")) { - strbuf_addstr(sb, GIT_COLOR_GREEN); - return 6; - } else if (!prefixcmp(placeholder + 1, "blue")) { - strbuf_addstr(sb, GIT_COLOR_BLUE); - return 5; - } else if (!prefixcmp(placeholder + 1, "reset")) { - strbuf_addstr(sb, GIT_COLOR_RESET); - return 6; - } else - return 0; case 'n': /* newline */ strbuf_addch(sb, '\n'); return 1; @@ -1115,6 +1123,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, return end - placeholder + 1; } else return 0; + + case '<': + case '>': + return parse_padding_placeholder(sb, placeholder, c); } /* these depend on the commit */ @@ -1123,13 +1135,19 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, switch (placeholder[0]) { case 'H': /* commit hash */ + strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); strbuf_addstr(sb, sha1_to_hex(commit->object.sha1)); + strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; case 'h': /* abbreviated commit hash */ - if (add_again(sb, &c->abbrev_commit_hash)) + strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); + if (add_again(sb, &c->abbrev_commit_hash)) { + strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; + } strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, c->pretty_ctx->abbrev)); + strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ @@ -1166,7 +1184,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': - format_decoration(sb, commit); + load_ref_decorations(DECORATE_SHORT_REFS); + format_decorations(sb, commit, c->auto_color); return 1; case 'g': /* reflog info */ switch(placeholder[1]) { @@ -1202,27 +1221,29 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, } if (placeholder[0] == 'G') { - if (!c->commit_signature_parsed) - parse_commit_signature(c); + if (!c->signature_check.result) + check_commit_signature(c->commit, &(c->signature_check)); switch (placeholder[1]) { case 'G': - if (c->signature.gpg_output) - strbuf_addstr(sb, c->signature.gpg_output); + if (c->signature_check.gpg_output) + strbuf_addstr(sb, c->signature_check.gpg_output); break; case '?': - switch (c->signature.good_bad) { + switch (c->signature_check.result) { case 'G': case 'B': - strbuf_addch(sb, c->signature.good_bad); + case 'U': + case 'N': + strbuf_addch(sb, c->signature_check.result); } break; case 'S': - if (c->signature.signer) - strbuf_addstr(sb, c->signature.signer); + if (c->signature_check.signer) + strbuf_addstr(sb, c->signature_check.signer); break; case 'K': - if (c->signature.key) - strbuf_addstr(sb, c->signature.key); + if (c->signature_check.key) + strbuf_addstr(sb, c->signature_check.key); break; } return 2; @@ -1243,7 +1264,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, msg + c->committer.off, c->committer.len, c->pretty_ctx->date_mode); case 'e': /* encoding */ - strbuf_add(sb, msg + c->encoding.off, c->encoding.len); + if (c->commit_encoding) + strbuf_addstr(sb, c->commit_encoding); return 1; case 'B': /* raw body */ /* message_off is always left at the initial newline */ @@ -1269,7 +1291,111 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, return 0; /* unknown placeholder */ } -static size_t format_commit_item(struct strbuf *sb, const char *placeholder, +static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */ + const char *placeholder, + struct format_commit_context *c) +{ + struct strbuf local_sb = STRBUF_INIT; + int total_consumed = 0, len, padding = c->padding; + if (padding < 0) { + const char *start = strrchr(sb->buf, '\n'); + int occupied; + if (!start) + start = sb->buf; + occupied = utf8_strnwidth(start, -1, 1); + padding = (-padding) - occupied; + } + while (1) { + int modifier = *placeholder == 'C'; + int consumed = format_commit_one(&local_sb, placeholder, c); + total_consumed += consumed; + + if (!modifier) + break; + + placeholder += consumed; + if (*placeholder != '%') + break; + placeholder++; + total_consumed++; + } + len = utf8_strnwidth(local_sb.buf, -1, 1); + + if (c->flush_type == flush_left_and_steal) { + const char *ch = sb->buf + sb->len - 1; + while (len > padding && ch > sb->buf) { + const char *p; + if (*ch == ' ') { + ch--; + padding++; + continue; + } + /* check for trailing ansi sequences */ + if (*ch != 'm') + break; + p = ch - 1; + while (ch - p < 10 && *p != '\033') + p--; + if (*p != '\033' || + ch + 1 - p != display_mode_esc_sequence_len(p)) + break; + /* + * got a good ansi sequence, put it back to + * local_sb as we're cutting sb + */ + strbuf_insert(&local_sb, 0, p, ch + 1 - p); + ch = p - 1; + } + strbuf_setlen(sb, ch + 1 - sb->buf); + c->flush_type = flush_left; + } + + if (len > padding) { + switch (c->truncate) { + case trunc_left: + strbuf_utf8_replace(&local_sb, + 0, len - (padding - 2), + ".."); + break; + case trunc_middle: + strbuf_utf8_replace(&local_sb, + padding / 2 - 1, + len - (padding - 2), + ".."); + break; + case trunc_right: + strbuf_utf8_replace(&local_sb, + padding - 2, len - (padding - 2), + ".."); + break; + case trunc_none: + break; + } + strbuf_addstr(sb, local_sb.buf); + } else { + int sb_len = sb->len, offset = 0; + if (c->flush_type == flush_left) + offset = padding - len; + else if (c->flush_type == flush_both) + offset = (padding - len) / 2; + /* + * we calculate padding in columns, now + * convert it back to chars + */ + padding = padding - len + local_sb.len; + strbuf_grow(sb, padding); + strbuf_setlen(sb, sb_len + padding); + memset(sb->buf + sb_len, ' ', sb->len - sb_len); + memcpy(sb->buf + sb_len + offset, local_sb.buf, + local_sb.len); + } + strbuf_release(&local_sb); + c->flush_type = no_flush; + return total_consumed; +} + +static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */ + const char *placeholder, void *context) { int consumed; @@ -1298,7 +1424,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, placeholder++; orig_len = sb->len; - consumed = format_commit_one(sb, placeholder, context); + if (((struct format_commit_context *)context)->flush_type != no_flush) + consumed = format_and_pad_commit(sb, placeholder, context); + else + consumed = format_commit_one(sb, placeholder, context); if (magic == NO_MAGIC) return consumed; @@ -1349,19 +1478,40 @@ void format_commit_message(const struct commit *commit, { struct format_commit_context context; const char *output_enc = pretty_ctx->output_encoding; + const char *utf8 = "UTF-8"; memset(&context, 0, sizeof(context)); context.commit = commit; context.pretty_ctx = pretty_ctx; context.wrap_start = sb->len; - context.message = logmsg_reencode(commit, output_enc); + context.message = logmsg_reencode(commit, + &context.commit_encoding, + output_enc); strbuf_expand(sb, format, format_commit_item, &context); rewrap_message_tail(sb, &context, 0, 0, 0); + if (output_enc) { + if (same_encoding(utf8, output_enc)) + output_enc = NULL; + } else { + if (context.commit_encoding && + !same_encoding(context.commit_encoding, utf8)) + output_enc = context.commit_encoding; + } + + if (output_enc) { + int outsz; + char *out = reencode_string_len(sb->buf, sb->len, + output_enc, utf8, &outsz); + if (out) + strbuf_attach(sb, out, outsz, outsz + 1); + } + + free(context.commit_encoding); logmsg_free(context.message, commit); - free(context.signature.gpg_output); - free(context.signature.signer); + free(context.signature_check.gpg_output); + free(context.signature_check.signer); } static void pp_header(const struct pretty_print_context *pp, @@ -1517,7 +1667,7 @@ void pretty_print_commit(const struct pretty_print_context *pp, } encoding = get_log_output_encoding(); - msg = reencoded = logmsg_reencode(commit, encoding); + msg = reencoded = logmsg_reencode(commit, NULL, encoding); if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL) indent = 0; |