git
/
pretty.c
2363 строки · 57.7 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "config.h"5#include "commit.h"6#include "environment.h"7#include "gettext.h"8#include "hash.h"9#include "hex.h"10#include "utf8.h"11#include "diff.h"12#include "pager.h"13#include "revision.h"14#include "string-list.h"15#include "mailmap.h"16#include "log-tree.h"17#include "notes.h"18#include "color.h"19#include "reflog-walk.h"20#include "gpg-interface.h"21#include "trailer.h"22#include "run-command.h"23#include "object-name.h"24
25/*
26* The limit for formatting directives, which enable the caller to append
27* arbitrarily many bytes to the formatted buffer. This includes padding
28* and wrapping formatters.
29*/
30#define FORMATTING_LIMIT (16 * 1024)31
32static char *user_format;33static struct cmt_fmt_map {34const char *name;35enum cmit_fmt format;36int is_tformat;37int expand_tabs_in_log;38int is_alias;39enum date_mode_type default_date_mode_type;40const char *user_format;41} *commit_formats;42static size_t builtin_formats_len;43static size_t commit_formats_len;44static size_t commit_formats_alloc;45static struct cmt_fmt_map *find_commit_format(const char *sought);46
47int commit_format_is_empty(enum cmit_fmt fmt)48{
49return fmt == CMIT_FMT_USERFORMAT && !*user_format;50}
51
52static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)53{
54free(user_format);55user_format = xstrdup(cp);56if (is_tformat)57rev->use_terminator = 1;58rev->commit_format = CMIT_FMT_USERFORMAT;59}
60
61static int git_pretty_formats_config(const char *var, const char *value,62const struct config_context *ctx UNUSED,63void *cb UNUSED)64{
65struct cmt_fmt_map *commit_format = NULL;66const char *name;67char *fmt;68int i;69
70if (!skip_prefix(var, "pretty.", &name))71return 0;72
73for (i = 0; i < builtin_formats_len; i++) {74if (!strcmp(commit_formats[i].name, name))75return 0;76}77
78for (i = builtin_formats_len; i < commit_formats_len; i++) {79if (!strcmp(commit_formats[i].name, name)) {80commit_format = &commit_formats[i];81break;82}83}84
85if (!commit_format) {86ALLOC_GROW(commit_formats, commit_formats_len+1,87commit_formats_alloc);88commit_format = &commit_formats[commit_formats_len];89memset(commit_format, 0, sizeof(*commit_format));90commit_formats_len++;91}92
93commit_format->name = xstrdup(name);94commit_format->format = CMIT_FMT_USERFORMAT;95if (git_config_string(&fmt, var, value))96return -1;97
98if (skip_prefix(fmt, "format:", &commit_format->user_format)) {99commit_format->is_tformat = 0;100} else if (skip_prefix(fmt, "tformat:", &commit_format->user_format)) {101commit_format->is_tformat = 1;102} else if (strchr(fmt, '%')) {103commit_format->is_tformat = 1;104commit_format->user_format = fmt;105} else {106commit_format->is_alias = 1;107commit_format->user_format = fmt;108}109
110return 0;111}
112
113static void setup_commit_formats(void)114{
115struct cmt_fmt_map builtin_formats[] = {116{ "raw", CMIT_FMT_RAW, 0, 0 },117{ "medium", CMIT_FMT_MEDIUM, 0, 8 },118{ "short", CMIT_FMT_SHORT, 0, 0 },119{ "email", CMIT_FMT_EMAIL, 0, 0 },120{ "mboxrd", CMIT_FMT_MBOXRD, 0, 0 },121{ "fuller", CMIT_FMT_FULLER, 0, 8 },122{ "full", CMIT_FMT_FULL, 0, 8 },123{ "oneline", CMIT_FMT_ONELINE, 1, 0 },124{ "reference", CMIT_FMT_USERFORMAT, 1, 0,1250, DATE_SHORT, "%C(auto)%h (%s, %ad)" },126/*127* Please update $__git_log_pretty_formats in
128* git-completion.bash when you add new formats.
129*/
130};131commit_formats_len = ARRAY_SIZE(builtin_formats);132builtin_formats_len = commit_formats_len;133ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);134COPY_ARRAY(commit_formats, builtin_formats,135ARRAY_SIZE(builtin_formats));136
137git_config(git_pretty_formats_config, NULL);138}
139
140static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,141const char *original,142int num_redirections)143{
144struct cmt_fmt_map *found = NULL;145size_t found_match_len = 0;146int i;147
148if (num_redirections >= commit_formats_len)149die("invalid --pretty format: "150"'%s' references an alias which points to itself",151original);152
153for (i = 0; i < commit_formats_len; i++) {154size_t match_len;155
156if (!istarts_with(commit_formats[i].name, sought))157continue;158
159match_len = strlen(commit_formats[i].name);160if (found == NULL || found_match_len > match_len) {161found = &commit_formats[i];162found_match_len = match_len;163}164}165
166if (found && found->is_alias) {167found = find_commit_format_recursive(found->user_format,168original,169num_redirections+1);170}171
172return found;173}
174
175static struct cmt_fmt_map *find_commit_format(const char *sought)176{
177if (!commit_formats)178setup_commit_formats();179
180return find_commit_format_recursive(sought, sought, 0);181}
182
183void get_commit_format(const char *arg, struct rev_info *rev)184{
185struct cmt_fmt_map *commit_format;186
187rev->use_terminator = 0;188if (!arg) {189rev->commit_format = CMIT_FMT_DEFAULT;190return;191}192if (skip_prefix(arg, "format:", &arg)) {193save_user_format(rev, arg, 0);194return;195}196
197if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) {198save_user_format(rev, arg, 1);199return;200}201
202commit_format = find_commit_format(arg);203if (!commit_format)204die("invalid --pretty format: %s", arg);205
206rev->commit_format = commit_format->format;207rev->use_terminator = commit_format->is_tformat;208rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log;209if (!rev->date_mode_explicit && commit_format->default_date_mode_type)210rev->date_mode.type = commit_format->default_date_mode_type;211if (commit_format->format == CMIT_FMT_USERFORMAT) {212save_user_format(rev, commit_format->user_format,213commit_format->is_tformat);214}215}
216
217/*
218* Generic support for pretty-printing the header
219*/
220static int get_one_line(const char *msg)221{
222int ret = 0;223
224for (;;) {225char c = *msg++;226if (!c)227break;228ret++;229if (c == '\n')230break;231}232return ret;233}
234
235/* High bit set, or ISO-2022-INT */
236static int non_ascii(int ch)237{
238return !isascii(ch) || ch == '\033';239}
240
241int has_non_ascii(const char *s)242{
243int ch;244if (!s)245return 0;246while ((ch = *s++) != '\0') {247if (non_ascii(ch))248return 1;249}250return 0;251}
252
253static int is_rfc822_special(char ch)254{
255switch (ch) {256case '(':257case ')':258case '<':259case '>':260case '[':261case ']':262case ':':263case ';':264case '@':265case ',':266case '.':267case '"':268case '\\':269return 1;270default:271return 0;272}273}
274
275static int needs_rfc822_quoting(const char *s, int len)276{
277int i;278for (i = 0; i < len; i++)279if (is_rfc822_special(s[i]))280return 1;281return 0;282}
283
284static int last_line_length(struct strbuf *sb)285{
286int i;287
288/* How many bytes are already used on the last line? */289for (i = sb->len - 1; i >= 0; i--)290if (sb->buf[i] == '\n')291break;292return sb->len - (i + 1);293}
294
295static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)296{
297int i;298
299/* just a guess, we may have to also backslash-quote */300strbuf_grow(out, len + 2);301
302strbuf_addch(out, '"');303for (i = 0; i < len; i++) {304switch (s[i]) {305case '"':306case '\\':307strbuf_addch(out, '\\');308/* fall through */309default:310strbuf_addch(out, s[i]);311}312}313strbuf_addch(out, '"');314}
315
316enum rfc2047_type {317RFC2047_SUBJECT,318RFC2047_ADDRESS
319};320
321static int is_rfc2047_special(char ch, enum rfc2047_type type)322{
323/*324* rfc2047, section 4.2:
325*
326* 8-bit values which correspond to printable ASCII characters other
327* than "=", "?", and "_" (underscore), MAY be represented as those
328* characters. (But see section 5 for restrictions.) In
329* particular, SPACE and TAB MUST NOT be represented as themselves
330* within encoded words.
331*/
332
333/*334* rule out non-ASCII characters and non-printable characters (the
335* non-ASCII check should be redundant as isprint() is not localized
336* and only knows about ASCII, but be defensive about that)
337*/
338if (non_ascii(ch) || !isprint(ch))339return 1;340
341/*342* rule out special printable characters (' ' should be the only
343* whitespace character considered printable, but be defensive and use
344* isspace())
345*/
346if (isspace(ch) || ch == '=' || ch == '?' || ch == '_')347return 1;348
349/*350* rfc2047, section 5.3:
351*
352* As a replacement for a 'word' entity within a 'phrase', for example,
353* one that precedes an address in a From, To, or Cc header. The ABNF
354* definition for 'phrase' from RFC 822 thus becomes:
355*
356* phrase = 1*( encoded-word / word )
357*
358* In this case the set of characters that may be used in a "Q"-encoded
359* 'encoded-word' is restricted to: <upper and lower case ASCII
360* letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
361* (underscore, ASCII 95.)>. An 'encoded-word' that appears within a
362* 'phrase' MUST be separated from any adjacent 'word', 'text' or
363* 'special' by 'linear-white-space'.
364*/
365
366if (type != RFC2047_ADDRESS)367return 0;368
369/* '=' and '_' are special cases and have been checked above */370return !(isalnum(ch) || ch == '!' || ch == '*' || ch == '+' || ch == '-' || ch == '/');371}
372
373static int needs_rfc2047_encoding(const char *line, int len)374{
375int i;376
377for (i = 0; i < len; i++) {378int ch = line[i];379if (non_ascii(ch) || ch == '\n')380return 1;381if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))382return 1;383}384
385return 0;386}
387
388static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,389const char *encoding, enum rfc2047_type type)390{
391static const int max_encoded_length = 76; /* per rfc2047 */392int i;393int line_len = last_line_length(sb);394
395strbuf_grow(sb, len * 3 + strlen(encoding) + 100);396strbuf_addf(sb, "=?%s?q?", encoding);397line_len += strlen(encoding) + 5; /* 5 for =??q? */398
399while (len) {400/*401* RFC 2047, section 5 (3):
402*
403* Each 'encoded-word' MUST represent an integral number of
404* characters. A multi-octet character may not be split across
405* adjacent 'encoded- word's.
406*/
407const unsigned char *p = (const unsigned char *)line;408int chrlen = mbs_chrlen(&line, &len, encoding);409int is_special = (chrlen > 1) || is_rfc2047_special(*p, type);410
411/* "=%02X" * chrlen, or the byte itself */412const char *encoded_fmt = is_special ? "=%02X" : "%c";413int encoded_len = is_special ? 3 * chrlen : 1;414
415/*416* According to RFC 2047, we could encode the special character
417* ' ' (space) with '_' (underscore) for readability. But many
418* programs do not understand this and just leave the
419* underscore in place. Thus, we do nothing special here, which
420* causes ' ' to be encoded as '=20', avoiding this problem.
421*/
422
423if (line_len + encoded_len + 2 > max_encoded_length) {424/* It won't fit with trailing "?=" --- break the line */425strbuf_addf(sb, "?=\n =?%s?q?", encoding);426line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */427}428
429for (i = 0; i < chrlen; i++)430strbuf_addf(sb, encoded_fmt, p[i]);431line_len += encoded_len;432}433strbuf_addstr(sb, "?=");434}
435
436const char *show_ident_date(const struct ident_split *ident,437struct date_mode mode)438{
439timestamp_t date = 0;440long tz = 0;441
442if (ident->date_begin && ident->date_end)443date = parse_timestamp(ident->date_begin, NULL, 10);444if (date_overflows(date))445date = 0;446else {447if (ident->tz_begin && ident->tz_end)448tz = strtol(ident->tz_begin, NULL, 10);449if (tz >= INT_MAX || tz <= INT_MIN)450tz = 0;451}452return show_date(date, tz, mode);453}
454
455static inline void strbuf_add_with_color(struct strbuf *sb, const char *color,456const char *buf, size_t buflen)457{
458strbuf_addstr(sb, color);459strbuf_add(sb, buf, buflen);460if (*color)461strbuf_addstr(sb, GIT_COLOR_RESET);462}
463
464static void append_line_with_color(struct strbuf *sb, struct grep_opt *opt,465const char *line, size_t linelen,466int color, enum grep_context ctx,467enum grep_header_field field)468{
469const char *buf, *eol, *line_color, *match_color;470regmatch_t match;471int eflags = 0;472
473buf = line;474eol = buf + linelen;475
476if (!opt || !want_color(color) || opt->invert)477goto end;478
479line_color = opt->colors[GREP_COLOR_SELECTED];480match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];481
482while (grep_next_match(opt, buf, eol, ctx, &match, field, eflags)) {483if (match.rm_so == match.rm_eo)484break;485
486strbuf_add_with_color(sb, line_color, buf, match.rm_so);487strbuf_add_with_color(sb, match_color, buf + match.rm_so,488match.rm_eo - match.rm_so);489buf += match.rm_eo;490eflags = REG_NOTBOL;491}492
493if (eflags)494strbuf_add_with_color(sb, line_color, buf, eol - buf);495else {496end:497strbuf_add(sb, buf, eol - buf);498}499}
500
501static int use_in_body_from(const struct pretty_print_context *pp,502const struct ident_split *ident)503{
504if (pp->rev && pp->rev->force_in_body_from)505return 1;506if (ident_cmp(pp->from_ident, ident))507return 1;508return 0;509}
510
511void pp_user_info(struct pretty_print_context *pp,512const char *what, struct strbuf *sb,513const char *line, const char *encoding)514{
515struct ident_split ident;516char *line_end;517const char *mailbuf, *namebuf;518size_t namelen, maillen;519int max_length = 78; /* per rfc2822 */520
521if (pp->fmt == CMIT_FMT_ONELINE)522return;523
524line_end = strchrnul(line, '\n');525if (split_ident_line(&ident, line, line_end - line))526return;527
528mailbuf = ident.mail_begin;529maillen = ident.mail_end - ident.mail_begin;530namebuf = ident.name_begin;531namelen = ident.name_end - ident.name_begin;532
533if (pp->mailmap)534map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);535
536if (cmit_fmt_is_mail(pp->fmt)) {537if (pp->from_ident && use_in_body_from(pp, &ident)) {538struct strbuf buf = STRBUF_INIT;539
540strbuf_addstr(&buf, "From: ");541strbuf_add(&buf, namebuf, namelen);542strbuf_addstr(&buf, " <");543strbuf_add(&buf, mailbuf, maillen);544strbuf_addstr(&buf, ">\n");545string_list_append(&pp->in_body_headers,546strbuf_detach(&buf, NULL));547
548mailbuf = pp->from_ident->mail_begin;549maillen = pp->from_ident->mail_end - mailbuf;550namebuf = pp->from_ident->name_begin;551namelen = pp->from_ident->name_end - namebuf;552}553
554strbuf_addstr(sb, "From: ");555if (pp->encode_email_headers &&556needs_rfc2047_encoding(namebuf, namelen)) {557add_rfc2047(sb, namebuf, namelen,558encoding, RFC2047_ADDRESS);559max_length = 76; /* per rfc2047 */560} else if (needs_rfc822_quoting(namebuf, namelen)) {561struct strbuf quoted = STRBUF_INIT;562add_rfc822_quoted("ed, namebuf, namelen);563strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,564-6, 1, max_length);565strbuf_release("ed);566} else {567strbuf_add_wrapped_bytes(sb, namebuf, namelen,568-6, 1, max_length);569}570
571if (max_length <572last_line_length(sb) + strlen(" <") + maillen + strlen(">"))573strbuf_addch(sb, '\n');574strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);575} else {576struct strbuf id = STRBUF_INIT;577enum grep_header_field field = GREP_HEADER_FIELD_MAX;578struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;579
580if (!strcmp(what, "Author"))581field = GREP_HEADER_AUTHOR;582else if (!strcmp(what, "Commit"))583field = GREP_HEADER_COMMITTER;584
585strbuf_addf(sb, "%s: ", what);586if (pp->fmt == CMIT_FMT_FULLER)587strbuf_addchars(sb, ' ', 4);588
589strbuf_addf(&id, "%.*s <%.*s>", (int)namelen, namebuf,590(int)maillen, mailbuf);591
592append_line_with_color(sb, opt, id.buf, id.len, pp->color,593GREP_CONTEXT_HEAD, field);594strbuf_addch(sb, '\n');595strbuf_release(&id);596}597
598switch (pp->fmt) {599case CMIT_FMT_MEDIUM:600strbuf_addf(sb, "Date: %s\n",601show_ident_date(&ident, pp->date_mode));602break;603case CMIT_FMT_EMAIL:604case CMIT_FMT_MBOXRD:605strbuf_addf(sb, "Date: %s\n",606show_ident_date(&ident, DATE_MODE(RFC2822)));607break;608case CMIT_FMT_FULLER:609strbuf_addf(sb, "%sDate: %s\n", what,610show_ident_date(&ident, pp->date_mode));611break;612default:613/* notin' */614break;615}616}
617
618static int is_blank_line(const char *line, int *len_p)619{
620int len = *len_p;621while (len && isspace(line[len - 1]))622len--;623*len_p = len;624return !len;625}
626
627const char *skip_blank_lines(const char *msg)628{
629for (;;) {630int linelen = get_one_line(msg);631int ll = linelen;632if (!linelen)633break;634if (!is_blank_line(msg, &ll))635break;636msg += linelen;637}638return msg;639}
640
641static void add_merge_info(const struct pretty_print_context *pp,642struct strbuf *sb, const struct commit *commit)643{
644struct commit_list *parent = commit->parents;645
646if ((pp->fmt == CMIT_FMT_ONELINE) || (cmit_fmt_is_mail(pp->fmt)) ||647!parent || !parent->next)648return;649
650strbuf_addstr(sb, "Merge:");651
652while (parent) {653struct object_id *oidp = &parent->item->object.oid;654strbuf_addch(sb, ' ');655if (pp->abbrev)656strbuf_add_unique_abbrev(sb, oidp, pp->abbrev);657else658strbuf_addstr(sb, oid_to_hex(oidp));659parent = parent->next;660}661strbuf_addch(sb, '\n');662}
663
664static char *get_header(const char *msg, const char *key)665{
666size_t len;667const char *v = find_commit_header(msg, key, &len);668return v ? xmemdupz(v, len) : NULL;669}
670
671static char *replace_encoding_header(char *buf, const char *encoding)672{
673struct strbuf tmp = STRBUF_INIT;674size_t start, len;675char *cp = buf;676
677/* guess if there is an encoding header before a \n\n */678while (!starts_with(cp, "encoding ")) {679cp = strchr(cp, '\n');680if (!cp || *++cp == '\n')681return buf;682}683start = cp - buf;684cp = strchr(cp, '\n');685if (!cp)686return buf; /* should not happen but be defensive */687len = cp + 1 - (buf + start);688
689strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);690if (is_encoding_utf8(encoding)) {691/* we have re-coded to UTF-8; drop the header */692strbuf_remove(&tmp, start, len);693} else {694/* just replaces XXXX in 'encoding XXXX\n' */695strbuf_splice(&tmp, start + strlen("encoding "),696len - strlen("encoding \n"),697encoding, strlen(encoding));698}699return strbuf_detach(&tmp, NULL);700}
701
702const char *repo_logmsg_reencode(struct repository *r,703const struct commit *commit,704char **commit_encoding,705const char *output_encoding)706{
707static const char *utf8 = "UTF-8";708const char *use_encoding;709char *encoding;710const char *msg = repo_get_commit_buffer(r, commit, NULL);711char *out;712
713if (!output_encoding || !*output_encoding) {714if (commit_encoding)715*commit_encoding = get_header(msg, "encoding");716return msg;717}718encoding = get_header(msg, "encoding");719if (commit_encoding)720*commit_encoding = encoding;721use_encoding = encoding ? encoding : utf8;722if (same_encoding(use_encoding, output_encoding)) {723/*724* No encoding work to be done. If we have no encoding header
725* at all, then there's nothing to do, and we can return the
726* message verbatim (whether newly allocated or not).
727*/
728if (!encoding)729return msg;730
731/*732* Otherwise, we still want to munge the encoding header in the
733* result, which will be done by modifying the buffer. If we
734* are using a fresh copy, we can reuse it. But if we are using
735* the cached copy from repo_get_commit_buffer, we need to duplicate it
736* to avoid munging the cached copy.
737*/
738if (msg == get_cached_commit_buffer(r, commit, NULL))739out = xstrdup(msg);740else741out = (char *)msg;742}743else {744/*745* There's actual encoding work to do. Do the reencoding, which
746* still leaves the header to be replaced in the next step. At
747* this point, we are done with msg. If we allocated a fresh
748* copy, we can free it.
749*/
750out = reencode_string(msg, output_encoding, use_encoding);751if (out)752repo_unuse_commit_buffer(r, commit, msg);753}754
755/*756* This replacement actually consumes the buffer we hand it, so we do
757* not have to worry about freeing the old "out" here.
758*/
759if (out)760out = replace_encoding_header(out, output_encoding);761
762if (!commit_encoding)763free(encoding);764/*765* If the re-encoding failed, out might be NULL here; in that
766* case we just return the commit message verbatim.
767*/
768return out ? out : msg;769}
770
771static int mailmap_name(const char **email, size_t *email_len,772const char **name, size_t *name_len)773{
774static struct string_list *mail_map;775if (!mail_map) {776CALLOC_ARRAY(mail_map, 1);777read_mailmap(mail_map);778}779return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);780}
781
782static size_t format_person_part(struct strbuf *sb, char part,783const char *msg, int len,784struct date_mode dmode)785{
786/* currently all placeholders have same length */787const int placeholder_len = 2;788struct ident_split s;789const char *name, *mail;790size_t maillen, namelen;791
792if (split_ident_line(&s, msg, len) < 0)793goto skip;794
795name = s.name_begin;796namelen = s.name_end - s.name_begin;797mail = s.mail_begin;798maillen = s.mail_end - s.mail_begin;799
800if (part == 'N' || part == 'E' || part == 'L') /* mailmap lookup */801mailmap_name(&mail, &maillen, &name, &namelen);802if (part == 'n' || part == 'N') { /* name */803strbuf_add(sb, name, namelen);804return placeholder_len;805}806if (part == 'e' || part == 'E') { /* email */807strbuf_add(sb, mail, maillen);808return placeholder_len;809}810if (part == 'l' || part == 'L') { /* local-part */811const char *at = memchr(mail, '@', maillen);812if (at)813maillen = at - mail;814strbuf_add(sb, mail, maillen);815return placeholder_len;816}817
818if (!s.date_begin)819goto skip;820
821if (part == 't') { /* date, UNIX timestamp */822strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);823return placeholder_len;824}825
826switch (part) {827case 'd': /* date */828strbuf_addstr(sb, show_ident_date(&s, dmode));829return placeholder_len;830case 'D': /* date, RFC2822 style */831strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RFC2822)));832return placeholder_len;833case 'r': /* date, relative */834strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RELATIVE)));835return placeholder_len;836case 'i': /* date, ISO 8601-like */837strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601)));838return placeholder_len;839case 'I': /* date, ISO 8601 strict */840strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT)));841return placeholder_len;842case 'h': /* date, human */843strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(HUMAN)));844return placeholder_len;845case 's':846strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(SHORT)));847return placeholder_len;848}849
850skip:851/*852* reading from either a bogus commit, or a reflog entry with
853* %gn, %ge, etc.; 'sb' cannot be updated, but we still need
854* to compute a valid return value.
855*/
856if (part == 'n' || part == 'e' || part == 't' || part == 'd'857|| part == 'D' || part == 'r' || part == 'i')858return placeholder_len;859
860return 0; /* unknown placeholder */861}
862
863struct chunk {864size_t off;865size_t len;866};867
868enum flush_type {869no_flush,870flush_right,871flush_left,872flush_left_and_steal,873flush_both
874};875
876enum trunc_type {877trunc_none,878trunc_left,879trunc_middle,880trunc_right
881};882
883struct format_commit_context {884struct repository *repository;885const struct commit *commit;886const struct pretty_print_context *pretty_ctx;887unsigned commit_header_parsed:1;888unsigned commit_message_parsed:1;889struct signature_check signature_check;890enum flush_type flush_type;891enum trunc_type truncate;892const char *message;893char *commit_encoding;894size_t width, indent1, indent2;895int auto_color;896int padding;897
898/* These offsets are relative to the start of the commit message. */899struct chunk author;900struct chunk committer;901size_t message_off;902size_t subject_off;903size_t body_off;904
905/* The following ones are relative to the result struct strbuf. */906size_t wrap_start;907};908
909static void parse_commit_header(struct format_commit_context *context)910{
911const char *msg = context->message;912int i;913
914for (i = 0; msg[i]; i++) {915const char *name;916int eol;917for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)918; /* do nothing */919
920if (i == eol) {921break;922} else if (skip_prefix(msg + i, "author ", &name)) {923context->author.off = name - msg;924context->author.len = msg + eol - name;925} else if (skip_prefix(msg + i, "committer ", &name)) {926context->committer.off = name - msg;927context->committer.len = msg + eol - name;928}929i = eol;930}931context->message_off = i;932context->commit_header_parsed = 1;933}
934
935static int istitlechar(char c)936{
937return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||938(c >= '0' && c <= '9') || c == '.' || c == '_';939}
940
941void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len)942{
943size_t trimlen;944size_t start_len = sb->len;945int space = 2;946int i;947
948for (i = 0; i < len; i++) {949if (istitlechar(msg[i])) {950if (space == 1)951strbuf_addch(sb, '-');952space = 0;953strbuf_addch(sb, msg[i]);954if (msg[i] == '.')955while (msg[i+1] == '.')956i++;957} else958space |= 1;959}960
961/* trim any trailing '.' or '-' characters */962trimlen = 0;963while (sb->len - trimlen > start_len &&964(sb->buf[sb->len - 1 - trimlen] == '.'965|| sb->buf[sb->len - 1 - trimlen] == '-'))966trimlen++;967strbuf_remove(sb, sb->len - trimlen, trimlen);968}
969
970const char *format_subject(struct strbuf *sb, const char *msg,971const char *line_separator)972{
973int first = 1;974
975for (;;) {976const char *line = msg;977int linelen = get_one_line(line);978
979msg += linelen;980if (!linelen || is_blank_line(line, &linelen))981break;982
983if (!sb)984continue;985strbuf_grow(sb, linelen + 2);986if (!first)987strbuf_addstr(sb, line_separator);988strbuf_add(sb, line, linelen);989first = 0;990}991return msg;992}
993
994static void parse_commit_message(struct format_commit_context *c)995{
996const char *msg = c->message + c->message_off;997const char *start = c->message;998
999msg = skip_blank_lines(msg);1000c->subject_off = msg - start;1001
1002msg = format_subject(NULL, msg, NULL);1003msg = skip_blank_lines(msg);1004c->body_off = msg - start;1005
1006c->commit_message_parsed = 1;1007}
1008
1009static void strbuf_wrap(struct strbuf *sb, size_t pos,1010size_t width, size_t indent1, size_t indent2)1011{
1012struct strbuf tmp = STRBUF_INIT;1013
1014if (pos)1015strbuf_add(&tmp, sb->buf, pos);1016strbuf_add_wrapped_text(&tmp, sb->buf + pos,1017cast_size_t_to_int(indent1),1018cast_size_t_to_int(indent2),1019cast_size_t_to_int(width));1020strbuf_swap(&tmp, sb);1021strbuf_release(&tmp);1022}
1023
1024static void rewrap_message_tail(struct strbuf *sb,1025struct format_commit_context *c,1026size_t new_width, size_t new_indent1,1027size_t new_indent2)1028{
1029if (c->width == new_width && c->indent1 == new_indent1 &&1030c->indent2 == new_indent2)1031return;1032if (c->wrap_start < sb->len)1033strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);1034c->wrap_start = sb->len;1035c->width = new_width;1036c->indent1 = new_indent1;1037c->indent2 = new_indent2;1038}
1039
1040static int format_reflog_person(struct strbuf *sb,1041char part,1042struct reflog_walk_info *log,1043struct date_mode dmode)1044{
1045const char *ident;1046
1047if (!log)1048return 2;1049
1050ident = get_reflog_ident(log);1051if (!ident)1052return 2;1053
1054return format_person_part(sb, part, ident, strlen(ident), dmode);1055}
1056
1057static size_t parse_color(struct strbuf *sb, /* in UTF-8 */1058const char *placeholder,1059struct format_commit_context *c)1060{
1061const char *rest = placeholder;1062const char *basic_color = NULL;1063
1064if (placeholder[1] == '(') {1065const char *begin = placeholder + 2;1066const char *end = strchr(begin, ')');1067char color[COLOR_MAXLEN];1068
1069if (!end)1070return 0;1071
1072if (skip_prefix(begin, "auto,", &begin)) {1073if (!want_color(c->pretty_ctx->color))1074return end - placeholder + 1;1075} else if (skip_prefix(begin, "always,", &begin)) {1076/* nothing to do; we do not respect want_color at all */1077} else {1078/* the default is the same as "auto" */1079if (!want_color(c->pretty_ctx->color))1080return end - placeholder + 1;1081}1082
1083if (color_parse_mem(begin, end - begin, color) < 0)1084die(_("unable to parse --pretty format"));1085strbuf_addstr(sb, color);1086return end - placeholder + 1;1087}1088
1089/*1090* We handle things like "%C(red)" above; for historical reasons, there
1091* are a few colors that can be specified without parentheses (and
1092* they cannot support things like "auto" or "always" at all).
1093*/
1094if (skip_prefix(placeholder + 1, "red", &rest))1095basic_color = GIT_COLOR_RED;1096else if (skip_prefix(placeholder + 1, "green", &rest))1097basic_color = GIT_COLOR_GREEN;1098else if (skip_prefix(placeholder + 1, "blue", &rest))1099basic_color = GIT_COLOR_BLUE;1100else if (skip_prefix(placeholder + 1, "reset", &rest))1101basic_color = GIT_COLOR_RESET;1102
1103if (basic_color && want_color(c->pretty_ctx->color))1104strbuf_addstr(sb, basic_color);1105
1106return rest - placeholder;1107}
1108
1109static size_t parse_padding_placeholder(const char *placeholder,1110struct format_commit_context *c)1111{
1112const char *ch = placeholder;1113enum flush_type flush_type;1114int to_column = 0;1115
1116switch (*ch++) {1117case '<':1118flush_type = flush_right;1119break;1120case '>':1121if (*ch == '<') {1122flush_type = flush_both;1123ch++;1124} else if (*ch == '>') {1125flush_type = flush_left_and_steal;1126ch++;1127} else1128flush_type = flush_left;1129break;1130default:1131return 0;1132}1133
1134/* the next value means "wide enough to that column" */1135if (*ch == '|') {1136to_column = 1;1137ch++;1138}1139
1140if (*ch == '(') {1141const char *start = ch + 1;1142const char *end = start + strcspn(start, ",)");1143char *next;1144int width;1145if (!*end || end == start)1146return 0;1147width = strtol(start, &next, 10);1148
1149/*1150* We need to limit the amount of padding, or otherwise this
1151* would allow the user to pad the buffer by arbitrarily many
1152* bytes and thus cause resource exhaustion.
1153*/
1154if (width < -FORMATTING_LIMIT || width > FORMATTING_LIMIT)1155return 0;1156
1157if (next == start || width == 0)1158return 0;1159if (width < 0) {1160if (to_column)1161width += term_columns();1162if (width < 0)1163return 0;1164}1165c->padding = to_column ? -width : width;1166c->flush_type = flush_type;1167
1168if (*end == ',') {1169start = end + 1;1170end = strchr(start, ')');1171if (!end || end == start)1172return 0;1173if (starts_with(start, "trunc)"))1174c->truncate = trunc_right;1175else if (starts_with(start, "ltrunc)"))1176c->truncate = trunc_left;1177else if (starts_with(start, "mtrunc)"))1178c->truncate = trunc_middle;1179else1180return 0;1181} else1182c->truncate = trunc_none;1183
1184return end - placeholder + 1;1185}1186return 0;1187}
1188
1189static int match_placeholder_arg_value(const char *to_parse, const char *candidate,1190const char **end, const char **valuestart,1191size_t *valuelen)1192{
1193const char *p;1194
1195if (!(skip_prefix(to_parse, candidate, &p)))1196return 0;1197if (valuestart) {1198if (*p == '=') {1199*valuestart = p + 1;1200*valuelen = strcspn(*valuestart, ",)");1201p = *valuestart + *valuelen;1202} else {1203if (*p != ',' && *p != ')')1204return 0;1205*valuestart = NULL;1206*valuelen = 0;1207}1208}1209if (*p == ',') {1210*end = p + 1;1211return 1;1212}1213if (*p == ')') {1214*end = p;1215return 1;1216}1217return 0;1218}
1219
1220static int match_placeholder_bool_arg(const char *to_parse, const char *candidate,1221const char **end, int *val)1222{
1223const char *argval;1224char *strval;1225size_t arglen;1226int v;1227
1228if (!match_placeholder_arg_value(to_parse, candidate, end, &argval, &arglen))1229return 0;1230
1231if (!argval) {1232*val = 1;1233return 1;1234}1235
1236strval = xstrndup(argval, arglen);1237v = git_parse_maybe_bool(strval);1238free(strval);1239
1240if (v == -1)1241return 0;1242
1243*val = v;1244
1245return 1;1246}
1247
1248static int format_trailer_match_cb(const struct strbuf *key, void *ud)1249{
1250const struct string_list *list = ud;1251const struct string_list_item *item;1252
1253for_each_string_list_item (item, list) {1254if (key->len == (uintptr_t)item->util &&1255!strncasecmp(item->string, key->buf, key->len))1256return 1;1257}1258return 0;1259}
1260
1261static struct strbuf *expand_string_arg(struct strbuf *sb,1262const char *argval, size_t arglen)1263{
1264char *fmt = xstrndup(argval, arglen);1265const char *format = fmt;1266
1267strbuf_reset(sb);1268while (strbuf_expand_step(sb, &format)) {1269size_t len;1270
1271if (skip_prefix(format, "%", &format))1272strbuf_addch(sb, '%');1273else if ((len = strbuf_expand_literal(sb, format)))1274format += len;1275else1276strbuf_addch(sb, '%');1277}1278free(fmt);1279return sb;1280}
1281
1282int format_set_trailers_options(struct process_trailer_options *opts,1283struct string_list *filter_list,1284struct strbuf *sepbuf,1285struct strbuf *kvsepbuf,1286const char **arg,1287char **invalid_arg)1288{
1289for (;;) {1290const char *argval;1291size_t arglen;1292
1293if (**arg == ')')1294break;1295
1296if (match_placeholder_arg_value(*arg, "key", arg, &argval, &arglen)) {1297uintptr_t len = arglen;1298
1299if (!argval)1300return -1;1301
1302if (len && argval[len - 1] == ':')1303len--;1304string_list_append(filter_list, argval)->util = (char *)len;1305
1306opts->filter = format_trailer_match_cb;1307opts->filter_data = filter_list;1308opts->only_trailers = 1;1309} else if (match_placeholder_arg_value(*arg, "separator", arg, &argval, &arglen)) {1310opts->separator = expand_string_arg(sepbuf, argval, arglen);1311} else if (match_placeholder_arg_value(*arg, "key_value_separator", arg, &argval, &arglen)) {1312opts->key_value_separator = expand_string_arg(kvsepbuf, argval, arglen);1313} else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) &&1314!match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) &&1315!match_placeholder_bool_arg(*arg, "keyonly", arg, &opts->key_only) &&1316!match_placeholder_bool_arg(*arg, "valueonly", arg, &opts->value_only)) {1317if (invalid_arg) {1318size_t len = strcspn(*arg, ",)");1319*invalid_arg = xstrndup(*arg, len);1320}1321return -1;1322}1323}1324return 0;1325}
1326
1327static size_t parse_describe_args(const char *start, struct strvec *args)1328{
1329struct {1330const char *name;1331enum {1332DESCRIBE_ARG_BOOL,1333DESCRIBE_ARG_INTEGER,1334DESCRIBE_ARG_STRING,1335} type;1336} option[] = {1337{ "tags", DESCRIBE_ARG_BOOL},1338{ "abbrev", DESCRIBE_ARG_INTEGER },1339{ "exclude", DESCRIBE_ARG_STRING },1340{ "match", DESCRIBE_ARG_STRING },1341};1342const char *arg = start;1343
1344for (;;) {1345int found = 0;1346const char *argval;1347size_t arglen = 0;1348int optval = 0;1349int i;1350
1351for (i = 0; !found && i < ARRAY_SIZE(option); i++) {1352switch (option[i].type) {1353case DESCRIBE_ARG_BOOL:1354if (match_placeholder_bool_arg(arg, option[i].name, &arg, &optval)) {1355if (optval)1356strvec_pushf(args, "--%s", option[i].name);1357else1358strvec_pushf(args, "--no-%s", option[i].name);1359found = 1;1360}1361break;1362case DESCRIBE_ARG_INTEGER:1363if (match_placeholder_arg_value(arg, option[i].name, &arg,1364&argval, &arglen)) {1365char *endptr;1366if (!arglen)1367return 0;1368strtol(argval, &endptr, 10);1369if (endptr - argval != arglen)1370return 0;1371strvec_pushf(args, "--%s=%.*s", option[i].name, (int)arglen, argval);1372found = 1;1373}1374break;1375case DESCRIBE_ARG_STRING:1376if (match_placeholder_arg_value(arg, option[i].name, &arg,1377&argval, &arglen)) {1378if (!arglen)1379return 0;1380strvec_pushf(args, "--%s=%.*s", option[i].name, (int)arglen, argval);1381found = 1;1382}1383break;1384}1385}1386if (!found)1387break;1388
1389}1390return arg - start;1391}
1392
1393
1394static int parse_decoration_option(const char **arg,1395const char *name,1396char **opt)1397{
1398const char *argval;1399size_t arglen;1400
1401if (match_placeholder_arg_value(*arg, name, arg, &argval, &arglen)) {1402struct strbuf sb = STRBUF_INIT;1403
1404expand_string_arg(&sb, argval, arglen);1405*opt = strbuf_detach(&sb, NULL);1406return 1;1407}1408return 0;1409}
1410
1411static void parse_decoration_options(const char **arg,1412struct decoration_options *opts)1413{
1414while (parse_decoration_option(arg, "prefix", &opts->prefix) ||1415parse_decoration_option(arg, "suffix", &opts->suffix) ||1416parse_decoration_option(arg, "separator", &opts->separator) ||1417parse_decoration_option(arg, "pointer", &opts->pointer) ||1418parse_decoration_option(arg, "tag", &opts->tag))1419;1420}
1421
1422static void free_decoration_options(const struct decoration_options *opts)1423{
1424free(opts->prefix);1425free(opts->suffix);1426free(opts->separator);1427free(opts->pointer);1428free(opts->tag);1429}
1430
1431static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */1432const char *placeholder,1433void *context)1434{
1435struct format_commit_context *c = context;1436const struct commit *commit = c->commit;1437const char *msg = c->message;1438struct commit_list *p;1439const char *arg, *eol;1440size_t res;1441char **slot;1442
1443/* these are independent of the commit */1444res = strbuf_expand_literal(sb, placeholder);1445if (res)1446return res;1447
1448switch (placeholder[0]) {1449case 'C':1450if (starts_with(placeholder + 1, "(auto)")) {1451c->auto_color = want_color(c->pretty_ctx->color);1452if (c->auto_color && sb->len)1453strbuf_addstr(sb, GIT_COLOR_RESET);1454return 7; /* consumed 7 bytes, "C(auto)" */1455} else {1456int ret = parse_color(sb, placeholder, c);1457if (ret)1458c->auto_color = 0;1459/*1460* Otherwise, we decided to treat %C<unknown>
1461* as a literal string, and the previous
1462* %C(auto) is still valid.
1463*/
1464return ret;1465}1466case 'w':1467if (placeholder[1] == '(') {1468unsigned long width = 0, indent1 = 0, indent2 = 0;1469char *next;1470const char *start = placeholder + 2;1471const char *end = strchr(start, ')');1472if (!end)1473return 0;1474if (end > start) {1475width = strtoul(start, &next, 10);1476if (*next == ',') {1477indent1 = strtoul(next + 1, &next, 10);1478if (*next == ',') {1479indent2 = strtoul(next + 1,1480&next, 10);1481}1482}1483if (*next != ')')1484return 0;1485}1486
1487/*1488* We need to limit the format here as it allows the
1489* user to prepend arbitrarily many bytes to the buffer
1490* when rewrapping.
1491*/
1492if (width > FORMATTING_LIMIT ||1493indent1 > FORMATTING_LIMIT ||1494indent2 > FORMATTING_LIMIT)1495return 0;1496rewrap_message_tail(sb, c, width, indent1, indent2);1497return end - placeholder + 1;1498} else1499return 0;1500
1501case '<':1502case '>':1503return parse_padding_placeholder(placeholder, c);1504}1505
1506if (skip_prefix(placeholder, "(describe", &arg)) {1507struct child_process cmd = CHILD_PROCESS_INIT;1508struct strbuf out = STRBUF_INIT;1509struct strbuf err = STRBUF_INIT;1510struct pretty_print_describe_status *describe_status;1511
1512describe_status = c->pretty_ctx->describe_status;1513if (describe_status) {1514if (!describe_status->max_invocations)1515return 0;1516describe_status->max_invocations--;1517}1518
1519cmd.git_cmd = 1;1520strvec_push(&cmd.args, "describe");1521
1522if (*arg == ':') {1523arg++;1524arg += parse_describe_args(arg, &cmd.args);1525}1526
1527if (*arg != ')') {1528child_process_clear(&cmd);1529return 0;1530}1531
1532strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));1533pipe_command(&cmd, NULL, 0, &out, 0, &err, 0);1534strbuf_rtrim(&out);1535strbuf_addbuf(sb, &out);1536strbuf_release(&out);1537strbuf_release(&err);1538return arg - placeholder + 1;1539}1540
1541/* these depend on the commit */1542if (!commit->object.parsed)1543parse_object(the_repository, &commit->object.oid);1544
1545switch (placeholder[0]) {1546case 'H': /* commit hash */1547strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));1548strbuf_addstr(sb, oid_to_hex(&commit->object.oid));1549strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));1550return 1;1551case 'h': /* abbreviated commit hash */1552strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));1553strbuf_add_unique_abbrev(sb, &commit->object.oid,1554c->pretty_ctx->abbrev);1555strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));1556return 1;1557case 'T': /* tree hash */1558strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit)));1559return 1;1560case 't': /* abbreviated tree hash */1561strbuf_add_unique_abbrev(sb,1562get_commit_tree_oid(commit),1563c->pretty_ctx->abbrev);1564return 1;1565case 'P': /* parent hashes */1566for (p = commit->parents; p; p = p->next) {1567if (p != commit->parents)1568strbuf_addch(sb, ' ');1569strbuf_addstr(sb, oid_to_hex(&p->item->object.oid));1570}1571return 1;1572case 'p': /* abbreviated parent hashes */1573for (p = commit->parents; p; p = p->next) {1574if (p != commit->parents)1575strbuf_addch(sb, ' ');1576strbuf_add_unique_abbrev(sb, &p->item->object.oid,1577c->pretty_ctx->abbrev);1578}1579return 1;1580case 'm': /* left/right/bottom */1581strbuf_addstr(sb, get_revision_mark(NULL, commit));1582return 1;1583case 'd':1584format_decorations(sb, commit, c->auto_color, NULL);1585return 1;1586case 'D':1587{1588const struct decoration_options opts = {1589.prefix = (char *) "",1590.suffix = (char *) "",1591};1592
1593format_decorations(sb, commit, c->auto_color, &opts);1594return 1;1595}1596case 'S': /* tag/branch like --source */1597if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))1598return 0;1599slot = revision_sources_at(c->pretty_ctx->rev->sources, commit);1600if (!(slot && *slot))1601return 0;1602strbuf_addstr(sb, *slot);1603return 1;1604case 'g': /* reflog info */1605switch(placeholder[1]) {1606case 'd': /* reflog selector */1607case 'D':1608if (c->pretty_ctx->reflog_info)1609get_reflog_selector(sb,1610c->pretty_ctx->reflog_info,1611c->pretty_ctx->date_mode,1612c->pretty_ctx->date_mode_explicit,1613(placeholder[1] == 'd'));1614return 2;1615case 's': /* reflog message */1616if (c->pretty_ctx->reflog_info)1617get_reflog_message(sb, c->pretty_ctx->reflog_info);1618return 2;1619case 'n':1620case 'N':1621case 'e':1622case 'E':1623return format_reflog_person(sb,1624placeholder[1],1625c->pretty_ctx->reflog_info,1626c->pretty_ctx->date_mode);1627}1628return 0; /* unknown %g placeholder */1629case 'N':1630if (c->pretty_ctx->notes_message) {1631strbuf_addstr(sb, c->pretty_ctx->notes_message);1632return 1;1633}1634return 0;1635}1636
1637if (placeholder[0] == 'G') {1638if (!c->signature_check.result)1639check_commit_signature(c->commit, &(c->signature_check));1640switch (placeholder[1]) {1641case 'G':1642if (c->signature_check.output)1643strbuf_addstr(sb, c->signature_check.output);1644break;1645case '?':1646switch (c->signature_check.result) {1647case 'G':1648switch (c->signature_check.trust_level) {1649case TRUST_UNDEFINED:1650case TRUST_NEVER:1651strbuf_addch(sb, 'U');1652break;1653default:1654strbuf_addch(sb, 'G');1655break;1656}1657break;1658case 'B':1659case 'E':1660case 'N':1661case 'X':1662case 'Y':1663case 'R':1664strbuf_addch(sb, c->signature_check.result);1665}1666break;1667case 'S':1668if (c->signature_check.signer)1669strbuf_addstr(sb, c->signature_check.signer);1670break;1671case 'K':1672if (c->signature_check.key)1673strbuf_addstr(sb, c->signature_check.key);1674break;1675case 'F':1676if (c->signature_check.fingerprint)1677strbuf_addstr(sb, c->signature_check.fingerprint);1678break;1679case 'P':1680if (c->signature_check.primary_key_fingerprint)1681strbuf_addstr(sb, c->signature_check.primary_key_fingerprint);1682break;1683case 'T':1684strbuf_addstr(sb, gpg_trust_level_to_str(c->signature_check.trust_level));1685break;1686default:1687return 0;1688}1689return 2;1690}1691
1692if (skip_prefix(placeholder, "(decorate", &arg)) {1693struct decoration_options opts = { NULL };1694size_t ret = 0;1695
1696if (*arg == ':') {1697arg++;1698parse_decoration_options(&arg, &opts);1699}1700if (*arg == ')') {1701format_decorations(sb, commit, c->auto_color, &opts);1702ret = arg - placeholder + 1;1703}1704
1705free_decoration_options(&opts);1706return ret;1707}1708
1709/* For the rest we have to parse the commit header. */1710if (!c->commit_header_parsed) {1711msg = c->message =1712repo_logmsg_reencode(c->repository, commit,1713&c->commit_encoding, "UTF-8");1714parse_commit_header(c);1715}1716
1717switch (placeholder[0]) {1718case 'a': /* author ... */1719return format_person_part(sb, placeholder[1],1720msg + c->author.off, c->author.len,1721c->pretty_ctx->date_mode);1722case 'c': /* committer ... */1723return format_person_part(sb, placeholder[1],1724msg + c->committer.off, c->committer.len,1725c->pretty_ctx->date_mode);1726case 'e': /* encoding */1727if (c->commit_encoding)1728strbuf_addstr(sb, c->commit_encoding);1729return 1;1730case 'B': /* raw body */1731/* message_off is always left at the initial newline */1732strbuf_addstr(sb, msg + c->message_off + 1);1733return 1;1734}1735
1736/* Now we need to parse the commit message. */1737if (!c->commit_message_parsed)1738parse_commit_message(c);1739
1740switch (placeholder[0]) {1741case 's': /* subject */1742format_subject(sb, msg + c->subject_off, " ");1743return 1;1744case 'f': /* sanitized subject */1745eol = strchrnul(msg + c->subject_off, '\n');1746format_sanitized_subject(sb, msg + c->subject_off, eol - (msg + c->subject_off));1747return 1;1748case 'b': /* body */1749strbuf_addstr(sb, msg + c->body_off);1750return 1;1751}1752
1753if (skip_prefix(placeholder, "(trailers", &arg)) {1754struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;1755struct string_list filter_list = STRING_LIST_INIT_NODUP;1756struct strbuf sepbuf = STRBUF_INIT;1757struct strbuf kvsepbuf = STRBUF_INIT;1758size_t ret = 0;1759
1760opts.no_divider = 1;1761
1762if (*arg == ':') {1763arg++;1764if (format_set_trailers_options(&opts, &filter_list, &sepbuf, &kvsepbuf, &arg, NULL))1765goto trailer_out;1766}1767if (*arg == ')') {1768format_trailers_from_commit(&opts, msg + c->subject_off, sb);1769ret = arg - placeholder + 1;1770}1771trailer_out:1772string_list_clear(&filter_list, 0);1773strbuf_release(&sepbuf);1774return ret;1775}1776
1777return 0; /* unknown placeholder */1778}
1779
1780static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */1781const char *placeholder,1782struct format_commit_context *c)1783{
1784struct strbuf local_sb = STRBUF_INIT;1785size_t total_consumed = 0;1786int len, padding = c->padding;1787
1788if (padding < 0) {1789const char *start = strrchr(sb->buf, '\n');1790int occupied;1791if (!start)1792start = sb->buf;1793occupied = utf8_strnwidth(start, strlen(start), 1);1794occupied += c->pretty_ctx->graph_width;1795padding = (-padding) - occupied;1796}1797while (1) {1798int modifier = *placeholder == 'C';1799size_t consumed = format_commit_one(&local_sb, placeholder, c);1800total_consumed += consumed;1801
1802if (!modifier)1803break;1804
1805placeholder += consumed;1806if (*placeholder != '%')1807break;1808placeholder++;1809total_consumed++;1810}1811len = utf8_strnwidth(local_sb.buf, local_sb.len, 1);1812
1813if (c->flush_type == flush_left_and_steal) {1814const char *ch = sb->buf + sb->len - 1;1815while (len > padding && ch > sb->buf) {1816const char *p;1817if (*ch == ' ') {1818ch--;1819padding++;1820continue;1821}1822/* check for trailing ansi sequences */1823if (*ch != 'm')1824break;1825p = ch - 1;1826while (p > sb->buf && ch - p < 10 && *p != '\033')1827p--;1828if (*p != '\033' ||1829ch + 1 - p != display_mode_esc_sequence_len(p))1830break;1831/*1832* got a good ansi sequence, put it back to
1833* local_sb as we're cutting sb
1834*/
1835strbuf_insert(&local_sb, 0, p, ch + 1 - p);1836ch = p - 1;1837}1838strbuf_setlen(sb, ch + 1 - sb->buf);1839c->flush_type = flush_left;1840}1841
1842if (len > padding) {1843switch (c->truncate) {1844case trunc_left:1845strbuf_utf8_replace(&local_sb,18460, len - (padding - 2),1847"..");1848break;1849case trunc_middle:1850strbuf_utf8_replace(&local_sb,1851padding / 2 - 1,1852len - (padding - 2),1853"..");1854break;1855case trunc_right:1856strbuf_utf8_replace(&local_sb,1857padding - 2, len - (padding - 2),1858"..");1859break;1860case trunc_none:1861break;1862}1863strbuf_addbuf(sb, &local_sb);1864} else {1865size_t sb_len = sb->len, offset = 0;1866if (c->flush_type == flush_left)1867offset = padding - len;1868else if (c->flush_type == flush_both)1869offset = (padding - len) / 2;1870/*1871* we calculate padding in columns, now
1872* convert it back to chars
1873*/
1874padding = padding - len + local_sb.len;1875strbuf_addchars(sb, ' ', padding);1876memcpy(sb->buf + sb_len + offset, local_sb.buf,1877local_sb.len);1878}1879strbuf_release(&local_sb);1880c->flush_type = no_flush;1881return total_consumed;1882}
1883
1884static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */1885const char *placeholder,1886struct format_commit_context *context)1887{
1888size_t consumed, orig_len;1889enum {1890NO_MAGIC,1891ADD_LF_BEFORE_NON_EMPTY,1892DEL_LF_BEFORE_EMPTY,1893ADD_SP_BEFORE_NON_EMPTY
1894} magic = NO_MAGIC;1895
1896switch (placeholder[0]) {1897case '-':1898magic = DEL_LF_BEFORE_EMPTY;1899break;1900case '+':1901magic = ADD_LF_BEFORE_NON_EMPTY;1902break;1903case ' ':1904magic = ADD_SP_BEFORE_NON_EMPTY;1905break;1906default:1907break;1908}1909if (magic != NO_MAGIC) {1910placeholder++;1911
1912switch (placeholder[0]) {1913case 'w':1914/*1915* `%+w()` cannot ever expand to a non-empty string,
1916* and it potentially changes the layout of preceding
1917* contents. We're thus not able to handle the magic in
1918* this combination and refuse the pattern.
1919*/
1920return 0;1921};1922}1923
1924orig_len = sb->len;1925if (context->flush_type == no_flush)1926consumed = format_commit_one(sb, placeholder, context);1927else1928consumed = format_and_pad_commit(sb, placeholder, context);1929if (magic == NO_MAGIC)1930return consumed;1931
1932if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {1933while (sb->len && sb->buf[sb->len - 1] == '\n')1934strbuf_setlen(sb, sb->len - 1);1935} else if (orig_len != sb->len) {1936if (magic == ADD_LF_BEFORE_NON_EMPTY)1937strbuf_insertstr(sb, orig_len, "\n");1938else if (magic == ADD_SP_BEFORE_NON_EMPTY)1939strbuf_insertstr(sb, orig_len, " ");1940}1941return consumed + 1;1942}
1943
1944void userformat_find_requirements(const char *fmt, struct userformat_want *w)1945{
1946if (!fmt) {1947if (!user_format)1948return;1949fmt = user_format;1950}1951while ((fmt = strchr(fmt, '%'))) {1952fmt++;1953if (skip_prefix(fmt, "%", &fmt))1954continue;1955
1956if (*fmt == '+' || *fmt == '-' || *fmt == ' ')1957fmt++;1958
1959switch (*fmt) {1960case 'N':1961w->notes = 1;1962break;1963case 'S':1964w->source = 1;1965break;1966case 'd':1967case 'D':1968w->decorate = 1;1969break;1970case '(':1971if (starts_with(fmt + 1, "decorate"))1972w->decorate = 1;1973break;1974}1975}1976}
1977
1978void repo_format_commit_message(struct repository *r,1979const struct commit *commit,1980const char *format, struct strbuf *sb,1981const struct pretty_print_context *pretty_ctx)1982{
1983struct format_commit_context context = {1984.repository = r,1985.commit = commit,1986.pretty_ctx = pretty_ctx,1987.wrap_start = sb->len1988};1989const char *output_enc = pretty_ctx->output_encoding;1990const char *utf8 = "UTF-8";1991
1992while (strbuf_expand_step(sb, &format)) {1993size_t len;1994
1995if (skip_prefix(format, "%", &format))1996strbuf_addch(sb, '%');1997else if ((len = format_commit_item(sb, format, &context)))1998format += len;1999else2000strbuf_addch(sb, '%');2001}2002rewrap_message_tail(sb, &context, 0, 0, 0);2003
2004/*2005* Convert output to an actual output encoding; note that
2006* format_commit_item() will always use UTF-8, so we don't
2007* have to bother if that's what the output wants.
2008*/
2009if (output_enc) {2010if (same_encoding(utf8, output_enc))2011output_enc = NULL;2012} else {2013if (context.commit_encoding &&2014!same_encoding(context.commit_encoding, utf8))2015output_enc = context.commit_encoding;2016}2017
2018if (output_enc) {2019size_t outsz;2020char *out = reencode_string_len(sb->buf, sb->len,2021output_enc, utf8, &outsz);2022if (out)2023strbuf_attach(sb, out, outsz, outsz + 1);2024}2025
2026free(context.commit_encoding);2027repo_unuse_commit_buffer(r, commit, context.message);2028}
2029
2030static void pp_header(struct pretty_print_context *pp,2031const char *encoding,2032const struct commit *commit,2033const char **msg_p,2034struct strbuf *sb)2035{
2036int parents_shown = 0;2037
2038for (;;) {2039const char *name, *line = *msg_p;2040int linelen = get_one_line(*msg_p);2041
2042if (!linelen)2043return;2044*msg_p += linelen;2045
2046if (linelen == 1)2047/* End of header */2048return;2049
2050if (pp->fmt == CMIT_FMT_RAW) {2051strbuf_add(sb, line, linelen);2052continue;2053}2054
2055if (starts_with(line, "parent ")) {2056if (linelen != the_hash_algo->hexsz + 8)2057die("bad parent line in commit");2058continue;2059}2060
2061if (!parents_shown) {2062unsigned num = commit_list_count(commit->parents);2063/* with enough slop */2064strbuf_grow(sb, num * (GIT_MAX_HEXSZ + 10) + 20);2065add_merge_info(pp, sb, commit);2066parents_shown = 1;2067}2068
2069/*2070* MEDIUM == DEFAULT shows only author with dates.
2071* FULL shows both authors but not dates.
2072* FULLER shows both authors and dates.
2073*/
2074if (skip_prefix(line, "author ", &name)) {2075strbuf_grow(sb, linelen + 80);2076pp_user_info(pp, "Author", sb, name, encoding);2077}2078if (skip_prefix(line, "committer ", &name) &&2079(pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {2080strbuf_grow(sb, linelen + 80);2081pp_user_info(pp, "Commit", sb, name, encoding);2082}2083}2084}
2085
2086void pp_email_subject(struct pretty_print_context *pp,2087const char **msg_p,2088struct strbuf *sb,2089const char *encoding,2090int need_8bit_cte)2091{
2092static const int max_length = 78; /* per rfc2047 */2093struct strbuf title;2094
2095strbuf_init(&title, 80);2096*msg_p = format_subject(&title, *msg_p,2097pp->preserve_subject ? "\n" : " ");2098
2099strbuf_grow(sb, title.len + 1024);2100fmt_output_email_subject(sb, pp->rev);2101if (pp->encode_email_headers &&2102needs_rfc2047_encoding(title.buf, title.len))2103add_rfc2047(sb, title.buf, title.len,2104encoding, RFC2047_SUBJECT);2105else2106strbuf_add_wrapped_bytes(sb, title.buf, title.len,2107-last_line_length(sb), 1, max_length);2108strbuf_addch(sb, '\n');2109
2110if (need_8bit_cte == 0) {2111int i;2112for (i = 0; i < pp->in_body_headers.nr; i++) {2113if (has_non_ascii(pp->in_body_headers.items[i].string)) {2114need_8bit_cte = 1;2115break;2116}2117}2118}2119
2120if (need_8bit_cte > 0) {2121const char *header_fmt =2122"MIME-Version: 1.0\n"2123"Content-Type: text/plain; charset=%s\n"2124"Content-Transfer-Encoding: 8bit\n";2125strbuf_addf(sb, header_fmt, encoding);2126}2127if (pp->after_subject) {2128strbuf_addstr(sb, pp->after_subject);2129}2130
2131strbuf_addch(sb, '\n');2132
2133if (pp->in_body_headers.nr) {2134int i;2135for (i = 0; i < pp->in_body_headers.nr; i++) {2136strbuf_addstr(sb, pp->in_body_headers.items[i].string);2137free(pp->in_body_headers.items[i].string);2138}2139string_list_clear(&pp->in_body_headers, 0);2140strbuf_addch(sb, '\n');2141}2142
2143strbuf_release(&title);2144}
2145
2146static int pp_utf8_width(const char *start, const char *end)2147{
2148int width = 0;2149size_t remain = end - start;2150
2151while (remain) {2152int n = utf8_width(&start, &remain);2153if (n < 0 || !start)2154return -1;2155width += n;2156}2157return width;2158}
2159
2160static void strbuf_add_tabexpand(struct strbuf *sb, struct grep_opt *opt,2161int color, int tabwidth, const char *line,2162int linelen)2163{
2164const char *tab;2165
2166while ((tab = memchr(line, '\t', linelen)) != NULL) {2167int width = pp_utf8_width(line, tab);2168
2169/*2170* If it wasn't well-formed utf8, or it
2171* had characters with badly defined
2172* width (control characters etc), just
2173* give up on trying to align things.
2174*/
2175if (width < 0)2176break;2177
2178/* Output the data .. */2179append_line_with_color(sb, opt, line, tab - line, color,2180GREP_CONTEXT_BODY,2181GREP_HEADER_FIELD_MAX);2182
2183/* .. and the de-tabified tab */2184strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth));2185
2186/* Skip over the printed part .. */2187linelen -= tab + 1 - line;2188line = tab + 1;2189}2190
2191/*2192* Print out everything after the last tab without
2193* worrying about width - there's nothing more to
2194* align.
2195*/
2196append_line_with_color(sb, opt, line, linelen, color, GREP_CONTEXT_BODY,2197GREP_HEADER_FIELD_MAX);2198}
2199
2200/*
2201* pp_handle_indent() prints out the intendation, and
2202* the whole line (without the final newline), after
2203* de-tabifying.
2204*/
2205static void pp_handle_indent(struct pretty_print_context *pp,2206struct strbuf *sb, int indent,2207const char *line, int linelen)2208{
2209struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;2210
2211strbuf_addchars(sb, ' ', indent);2212if (pp->expand_tabs_in_log)2213strbuf_add_tabexpand(sb, opt, pp->color, pp->expand_tabs_in_log,2214line, linelen);2215else2216append_line_with_color(sb, opt, line, linelen, pp->color,2217GREP_CONTEXT_BODY,2218GREP_HEADER_FIELD_MAX);2219}
2220
2221static int is_mboxrd_from(const char *line, int len)2222{
2223/*2224* a line matching /^From $/ here would only have len == 4
2225* at this point because is_empty_line would've trimmed all
2226* trailing space
2227*/
2228return len > 4 && starts_with(line + strspn(line, ">"), "From ");2229}
2230
2231void pp_remainder(struct pretty_print_context *pp,2232const char **msg_p,2233struct strbuf *sb,2234int indent)2235{
2236struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;2237int first = 1;2238
2239for (;;) {2240const char *line = *msg_p;2241int linelen = get_one_line(line);2242*msg_p += linelen;2243
2244if (!linelen)2245break;2246
2247if (is_blank_line(line, &linelen)) {2248if (first)2249continue;2250if (pp->fmt == CMIT_FMT_SHORT)2251break;2252}2253first = 0;2254
2255strbuf_grow(sb, linelen + indent + 20);2256if (indent)2257pp_handle_indent(pp, sb, indent, line, linelen);2258else if (pp->expand_tabs_in_log)2259strbuf_add_tabexpand(sb, opt, pp->color,2260pp->expand_tabs_in_log, line,2261linelen);2262else {2263if (pp->fmt == CMIT_FMT_MBOXRD &&2264is_mboxrd_from(line, linelen))2265strbuf_addch(sb, '>');2266
2267append_line_with_color(sb, opt, line, linelen,2268pp->color, GREP_CONTEXT_BODY,2269GREP_HEADER_FIELD_MAX);2270}2271strbuf_addch(sb, '\n');2272}2273}
2274
2275void pretty_print_commit(struct pretty_print_context *pp,2276const struct commit *commit,2277struct strbuf *sb)2278{
2279unsigned long beginning_of_body;2280int indent = 4;2281const char *msg;2282const char *reencoded;2283const char *encoding;2284int need_8bit_cte = pp->need_8bit_cte;2285
2286if (pp->fmt == CMIT_FMT_USERFORMAT) {2287repo_format_commit_message(the_repository, commit,2288user_format, sb, pp);2289return;2290}2291
2292encoding = get_log_output_encoding();2293msg = reencoded = repo_logmsg_reencode(the_repository, commit, NULL,2294encoding);2295
2296if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt))2297indent = 0;2298
2299/*2300* We need to check and emit Content-type: to mark it
2301* as 8-bit if we haven't done so.
2302*/
2303if (cmit_fmt_is_mail(pp->fmt) && need_8bit_cte == 0) {2304int i, ch, in_body;2305
2306for (in_body = i = 0; (ch = msg[i]); i++) {2307if (!in_body) {2308/* author could be non 7-bit ASCII but2309* the log may be so; skip over the
2310* header part first.
2311*/
2312if (ch == '\n' && msg[i+1] == '\n')2313in_body = 1;2314}2315else if (non_ascii(ch)) {2316need_8bit_cte = 1;2317break;2318}2319}2320}2321
2322pp_header(pp, encoding, commit, &msg, sb);2323if (pp->fmt != CMIT_FMT_ONELINE && !cmit_fmt_is_mail(pp->fmt)) {2324strbuf_addch(sb, '\n');2325}2326
2327/* Skip excess blank lines at the beginning of body, if any... */2328msg = skip_blank_lines(msg);2329
2330/* These formats treat the title line specially. */2331if (pp->fmt == CMIT_FMT_ONELINE) {2332msg = format_subject(sb, msg, " ");2333strbuf_addch(sb, '\n');2334} else if (cmit_fmt_is_mail(pp->fmt))2335pp_email_subject(pp, &msg, sb, encoding, need_8bit_cte);2336
2337beginning_of_body = sb->len;2338if (pp->fmt != CMIT_FMT_ONELINE)2339pp_remainder(pp, &msg, sb, indent);2340strbuf_rtrim(sb);2341
2342/* Make sure there is an EOLN for the non-oneline case */2343if (pp->fmt != CMIT_FMT_ONELINE)2344strbuf_addch(sb, '\n');2345
2346/*2347* The caller may append additional body text in e-mail
2348* format. Make sure we did not strip the blank line
2349* between the header and the body.
2350*/
2351if (cmit_fmt_is_mail(pp->fmt) && sb->len <= beginning_of_body)2352strbuf_addch(sb, '\n');2353
2354repo_unuse_commit_buffer(the_repository, commit, reencoded);2355}
2356
2357void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,2358struct strbuf *sb)2359{
2360struct pretty_print_context pp = {0};2361pp.fmt = fmt;2362pretty_print_commit(&pp, commit, sb);2363}
2364