Merge branch 'aw/pretty-trailers'

The %(trailers) formatter in "git log --format=..."  now allows to
optionally pick trailers selectively by keyword, show only values,
etc.

* aw/pretty-trailers:
  pretty: add support for separator option in %(trailers)
  strbuf: separate callback for strbuf_expand:ing literals
  pretty: add support for "valueonly" option in %(trailers)
  pretty: allow showing specific trailers
  pretty: single return path in %(trailers) handling
  pretty: allow %(trailers) options with explicit value
  doc: group pretty-format.txt placeholders descriptions
This commit is contained in:
Junio C Hamano 2019-03-07 09:59:52 +09:00
commit 42977bf5c7
7 changed files with 416 additions and 134 deletions

View file

@ -102,120 +102,160 @@ The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
+ +
The placeholders are: The placeholders are:
- '%H': commit hash - Placeholders that expand to a single literal character:
- '%h': abbreviated commit hash '%n':: newline
- '%T': tree hash '%%':: a raw '%'
- '%t': abbreviated tree hash '%x00':: print a byte from a hex code
- '%P': parent hashes
- '%p': abbreviated parent hashes - Placeholders that affect formatting of later placeholders:
- '%an': author name '%Cred':: switch color to red
- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] '%Cgreen':: switch color to green
or linkgit:git-blame[1]) '%Cblue':: switch color to blue
- '%ae': author email '%Creset':: reset color
- '%aE': author email (respecting .mailmap, see '%C(...)':: color specification, as described under Values in the
linkgit:git-shortlog[1] or linkgit:git-blame[1]) "CONFIGURATION FILE" section of linkgit:git-config[1]. By
- '%ad': author date (format respects --date= option) default, colors are shown only when enabled for log output
- '%aD': author date, RFC2822 style (by `color.diff`, `color.ui`, or `--color`, and respecting
- '%ar': author date, relative the `auto` settings of the former if we are going to a
- '%at': author date, UNIX timestamp terminal). `%C(auto,...)` is accepted as a historical
- '%ai': author date, ISO 8601-like format synonym for the default (e.g., `%C(auto,red)`). Specifying
- '%aI': author date, strict ISO 8601 format `%C(always,...)` will show the colors even when color is
- '%cn': committer name not otherwise enabled (though consider just using
- '%cN': committer name (respecting .mailmap, see `--color=always` to enable color for the whole output,
linkgit:git-shortlog[1] or linkgit:git-blame[1]) including this format and anything else git might color).
- '%ce': committer email `auto` alone (i.e. `%C(auto)`) will turn on auto coloring
- '%cE': committer email (respecting .mailmap, see on the next placeholders until the color is switched
linkgit:git-shortlog[1] or linkgit:git-blame[1]) again.
- '%cd': committer date (format respects --date= option) '%m':: left (`<`), right (`>`) or boundary (`-`) mark
- '%cD': committer date, RFC2822 style '%w([<w>[,<i1>[,<i2>]]])':: switch line wrapping, like the -w option of
- '%cr': committer date, relative linkgit:git-shortlog[1].
- '%ct': committer date, UNIX timestamp '%<(<N>[,trunc|ltrunc|mtrunc])':: make the next placeholder take at
- '%ci': committer date, ISO 8601-like format least N columns, padding spaces on
- '%cI': committer date, strict ISO 8601 format the right if necessary. Optionally
- '%d': ref names, like the --decorate option of linkgit:git-log[1] truncate at the beginning (ltrunc),
- '%D': ref names without the " (", ")" wrapping. the middle (mtrunc) or the end
- '%S': ref name given on the command line by which the commit was reached (trunc) if the output is longer than
(like `git log --source`), only works with `git log` N columns. Note that truncating
- '%e': encoding only works correctly with N >= 2.
- '%s': subject '%<|(<N>)':: make the next placeholder take at least until Nth
- '%f': sanitized subject line, suitable for a filename columns, padding spaces on the right if necessary
- '%b': body '%>(<N>)', '%>|(<N>)':: similar to '%<(<N>)', '%<|(<N>)' respectively,
- '%B': raw body (unwrapped subject and body) but padding spaces on the left
'%>>(<N>)', '%>>|(<N>)':: similar to '%>(<N>)', '%>|(<N>)'
respectively, except that if the next
placeholder takes more spaces than given and
there are spaces on its left, use those
spaces
'%><(<N>)', '%><|(<N>)':: similar to '%<(<N>)', '%<|(<N>)'
respectively, but padding both sides
(i.e. the text is centered)
- Placeholders that expand to information extracted from the commit:
'%H':: commit hash
'%h':: abbreviated commit hash
'%T':: tree hash
'%t':: abbreviated tree hash
'%P':: parent hashes
'%p':: abbreviated parent hashes
'%an':: author name
'%aN':: author name (respecting .mailmap, see linkgit:git-shortlog[1]
or linkgit:git-blame[1])
'%ae':: author email
'%aE':: author email (respecting .mailmap, see linkgit:git-shortlog[1]
or linkgit:git-blame[1])
'%ad':: author date (format respects --date= option)
'%aD':: author date, RFC2822 style
'%ar':: author date, relative
'%at':: author date, UNIX timestamp
'%ai':: author date, ISO 8601-like format
'%aI':: author date, strict ISO 8601 format
'%cn':: committer name
'%cN':: committer name (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1])
'%ce':: committer email
'%cE':: committer email (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1])
'%cd':: committer date (format respects --date= option)
'%cD':: committer date, RFC2822 style
'%cr':: committer date, relative
'%ct':: committer date, UNIX timestamp
'%ci':: committer date, ISO 8601-like format
'%cI':: committer date, strict ISO 8601 format
'%d':: ref names, like the --decorate option of linkgit:git-log[1]
'%D':: ref names without the " (", ")" wrapping.
'%S':: ref name given on the command line by which the commit was reached
(like `git log --source`), only works with `git log`
'%e':: encoding
'%s':: subject
'%f':: sanitized subject line, suitable for a filename
'%b':: body
'%B':: raw body (unwrapped subject and body)
ifndef::git-rev-list[] ifndef::git-rev-list[]
- '%N': commit notes '%N':: commit notes
endif::git-rev-list[] endif::git-rev-list[]
- '%GG': raw verification message from GPG for a signed commit '%GG':: raw verification message from GPG for a signed commit
- '%G?': show "G" for a good (valid) signature, '%G?':: show "G" for a good (valid) signature,
"B" for a bad signature, "B" for a bad signature,
"U" for a good signature with unknown validity, "U" for a good signature with unknown validity,
"X" for a good signature that has expired, "X" for a good signature that has expired,
"Y" for a good signature made by an expired key, "Y" for a good signature made by an expired key,
"R" for a good signature made by a revoked key, "R" for a good signature made by a revoked key,
"E" if the signature cannot be checked (e.g. missing key) "E" if the signature cannot be checked (e.g. missing key)
and "N" for no signature and "N" for no signature
- '%GS': show the name of the signer for a signed commit '%GS':: show the name of the signer for a signed commit
- '%GK': show the key used to sign a signed commit '%GK':: show the key used to sign a signed commit
- '%GF': show the fingerprint of the key used to sign a signed commit '%GF':: show the fingerprint of the key used to sign a signed commit
- '%GP': show the fingerprint of the primary key whose subkey was used '%GP':: show the fingerprint of the primary key whose subkey was used
to sign a signed commit to sign a signed commit
- '%gD': reflog selector, e.g., `refs/stash@{1}` or '%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2
`refs/stash@{2 minutes ago`}; the format follows the rules described minutes ago`}; the format follows the rules described for the
for the `-g` option. The portion before the `@` is the refname as `-g` option. The portion before the `@` is the refname as
given on the command line (so `git log -g refs/heads/master` would given on the command line (so `git log -g refs/heads/master`
yield `refs/heads/master@{0}`). would yield `refs/heads/master@{0}`).
- '%gd': shortened reflog selector; same as `%gD`, but the refname '%gd':: shortened reflog selector; same as `%gD`, but the refname
portion is shortened for human readability (so `refs/heads/master` portion is shortened for human readability (so
becomes just `master`). `refs/heads/master` becomes just `master`).
- '%gn': reflog identity name '%gn':: reflog identity name
- '%gN': reflog identity name (respecting .mailmap, see '%gN':: reflog identity name (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1]) linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%ge': reflog identity email '%ge':: reflog identity email
- '%gE': reflog identity email (respecting .mailmap, see '%gE':: reflog identity email (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1]) linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%gs': reflog subject '%gs':: reflog subject
- '%Cred': switch color to red '%(trailers[:options])':: display the trailers of the body as
- '%Cgreen': switch color to green interpreted by
- '%Cblue': switch color to blue linkgit:git-interpret-trailers[1]. The
- '%Creset': reset color `trailers` string may be followed by a colon
- '%C(...)': color specification, as described under Values in the and zero or more comma-separated options:
"CONFIGURATION FILE" section of linkgit:git-config[1]. ** 'key=<K>': only show trailers with specified key. Matching is done
By default, colors are shown only when enabled for log output (by case-insensitively and trailing colon is optional. If option is
`color.diff`, `color.ui`, or `--color`, and respecting the `auto` given multiple times trailer lines matching any of the keys are
settings of the former if we are going to a terminal). `%C(auto,...)` shown. This option automatically enables the `only` option so that
is accepted as a historical synonym for the default (e.g., non-trailer lines in the trailer block are hidden. If that is not
`%C(auto,red)`). Specifying `%C(always,...)` will show the colors desired it can be disabled with `only=false`. E.g.,
even when color is not otherwise enabled (though consider `%(trailers:key=Reviewed-by)` shows trailer lines with key
just using `--color=always` to enable color for the whole output, `Reviewed-by`.
including this format and anything else git might color). `auto` ** 'only[=val]': select whether non-trailer lines from the trailer
alone (i.e. `%C(auto)`) will turn on auto coloring on the next block should be included. The `only` keyword may optionally be
placeholders until the color is switched again. followed by an equal sign and one of `true`, `on`, `yes` to omit or
- '%m': left (`<`), right (`>`) or boundary (`-`) mark `false`, `off`, `no` to show the non-trailer lines. If option is
- '%n': newline given without value it is enabled. If given multiple times the last
- '%%': a raw '%' value is used.
- '%x00': print a byte from a hex code ** 'separator=<SEP>': specify a separator inserted between trailer
- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of lines. When this option is not given each trailer line is
linkgit:git-shortlog[1]. terminated with a line feed character. The string SEP may contain
- '%<(<N>[,trunc|ltrunc|mtrunc])': make the next placeholder take at the literal formatting codes described above. To use comma as
least N columns, padding spaces on the right if necessary. separator one must use `%x2C` as it would otherwise be parsed as
Optionally truncate at the beginning (ltrunc), the middle (mtrunc) next option. If separator option is given multiple times only the
or the end (trunc) if the output is longer than N columns. last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
Note that truncating only works correctly with N >= 2. shows all trailer lines whose key is "Ticket" separated by a comma
- '%<|(<N>)': make the next placeholder take at least until Nth and a space.
columns, padding spaces on the right if necessary ** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold`
- '%>(<N>)', '%>|(<N>)': similar to '%<(<N>)', '%<|(<N>)' option was given. In same way as to for `only` it can be followed
respectively, but padding spaces on the left by an equal sign and explicit value. E.g.,
- '%>>(<N>)', '%>>|(<N>)': similar to '%>(<N>)', '%>|(<N>)' `%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
respectively, except that if the next placeholder takes more spaces ** 'valueonly[=val]': skip over the key part of the trailer line and only
than given and there are spaces on its left, use those spaces show the value part. Also this optionally allows explicit value.
- '%><(<N>)', '%><|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
respectively, but padding both sides (i.e. the text is centered)
- %(trailers[:options]): display the trailers of the body as interpreted
by linkgit:git-interpret-trailers[1]. The `trailers` string may be
followed by a colon and zero or more comma-separated options. If the
`only` option is given, omit non-trailer lines from the trailer block.
If the `unfold` option is given, behave as if interpret-trailer's
`--unfold` option was given. E.g., `%(trailers:only,unfold)` to do
both.
NOTE: Some placeholders may depend on other options given to the NOTE: Some placeholders may depend on other options given to the
revision traversal engine. For example, the `%g*` reflog options will revision traversal engine. For example, the `%g*` reflog options will

113
pretty.c
View file

@ -1057,13 +1057,26 @@ static size_t parse_padding_placeholder(struct strbuf *sb,
return 0; return 0;
} }
static int match_placeholder_arg(const char *to_parse, const char *candidate, static int match_placeholder_arg_value(const char *to_parse, const char *candidate,
const char **end) const char **end, const char **valuestart,
size_t *valuelen)
{ {
const char *p; const char *p;
if (!(skip_prefix(to_parse, candidate, &p))) if (!(skip_prefix(to_parse, candidate, &p)))
return 0; return 0;
if (valuestart) {
if (*p == '=') {
*valuestart = p + 1;
*valuelen = strcspn(*valuestart, ",)");
p = *valuestart + *valuelen;
} else {
if (*p != ',' && *p != ')')
return 0;
*valuestart = NULL;
*valuelen = 0;
}
}
if (*p == ',') { if (*p == ',') {
*end = p + 1; *end = p + 1;
return 1; return 1;
@ -1075,6 +1088,47 @@ static int match_placeholder_arg(const char *to_parse, const char *candidate,
return 0; return 0;
} }
static int match_placeholder_bool_arg(const char *to_parse, const char *candidate,
const char **end, int *val)
{
const char *argval;
char *strval;
size_t arglen;
int v;
if (!match_placeholder_arg_value(to_parse, candidate, end, &argval, &arglen))
return 0;
if (!argval) {
*val = 1;
return 1;
}
strval = xstrndup(argval, arglen);
v = git_parse_maybe_bool(strval);
free(strval);
if (v == -1)
return 0;
*val = v;
return 1;
}
static int format_trailer_match_cb(const struct strbuf *key, void *ud)
{
const struct string_list *list = ud;
const struct string_list_item *item;
for_each_string_list_item (item, list) {
if (key->len == (uintptr_t)item->util &&
!strncasecmp(item->string, key->buf, key->len))
return 1;
}
return 0;
}
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *placeholder, const char *placeholder,
void *context) void *context)
@ -1084,10 +1138,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *msg = c->message; const char *msg = c->message;
struct commit_list *p; struct commit_list *p;
const char *arg; const char *arg;
int ch; size_t res;
char **slot; char **slot;
/* these are independent of the commit */ /* these are independent of the commit */
res = strbuf_expand_literal_cb(sb, placeholder, NULL);
if (res)
return res;
switch (placeholder[0]) { switch (placeholder[0]) {
case 'C': case 'C':
if (starts_with(placeholder + 1, "(auto)")) { if (starts_with(placeholder + 1, "(auto)")) {
@ -1106,16 +1164,6 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
*/ */
return ret; return ret;
} }
case 'n': /* newline */
strbuf_addch(sb, '\n');
return 1;
case 'x':
/* %x00 == NUL, %x0a == LF, etc. */
ch = hex2chr(placeholder + 1);
if (ch < 0)
return 0;
strbuf_addch(sb, ch);
return 3;
case 'w': case 'w':
if (placeholder[1] == '(') { if (placeholder[1] == '(') {
unsigned long width = 0, indent1 = 0, indent2 = 0; unsigned long width = 0, indent1 = 0, indent2 = 0;
@ -1322,24 +1370,53 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (skip_prefix(placeholder, "(trailers", &arg)) { if (skip_prefix(placeholder, "(trailers", &arg)) {
struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
struct string_list filter_list = STRING_LIST_INIT_NODUP;
struct strbuf sepbuf = STRBUF_INIT;
size_t ret = 0;
opts.no_divider = 1; opts.no_divider = 1;
if (*arg == ':') { if (*arg == ':') {
arg++; arg++;
for (;;) { for (;;) {
if (match_placeholder_arg(arg, "only", &arg)) const char *argval;
size_t arglen;
if (match_placeholder_arg_value(arg, "key", &arg, &argval, &arglen)) {
uintptr_t len = arglen;
if (!argval)
goto trailer_out;
if (len && argval[len - 1] == ':')
len--;
string_list_append(&filter_list, argval)->util = (char *)len;
opts.filter = format_trailer_match_cb;
opts.filter_data = &filter_list;
opts.only_trailers = 1; opts.only_trailers = 1;
else if (match_placeholder_arg(arg, "unfold", &arg)) } else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) {
opts.unfold = 1; char *fmt;
else
strbuf_reset(&sepbuf);
fmt = xstrndup(argval, arglen);
strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
free(fmt);
opts.separator = &sepbuf;
} else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
!match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) &&
!match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only))
break; break;
} }
} }
if (*arg == ')') { if (*arg == ')') {
format_trailers_from_commit(sb, msg + c->subject_off, &opts); format_trailers_from_commit(sb, msg + c->subject_off, &opts);
return arg - placeholder + 1; ret = arg - placeholder + 1;
} }
trailer_out:
string_list_clear(&filter_list, 0);
strbuf_release(&sepbuf);
return ret;
} }
return 0; /* unknown placeholder */ return 0; /* unknown placeholder */

View file

@ -380,6 +380,27 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
} }
} }
size_t strbuf_expand_literal_cb(struct strbuf *sb,
const char *placeholder,
void *context)
{
int ch;
switch (placeholder[0]) {
case 'n': /* newline */
strbuf_addch(sb, '\n');
return 1;
case 'x':
/* %x00 == NUL, %x0a == LF, etc. */
ch = hex2chr(placeholder + 1);
if (ch < 0)
return 0;
strbuf_addch(sb, ch);
return 3;
}
return 0;
}
size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
void *context) void *context)
{ {

View file

@ -320,6 +320,14 @@ void strbuf_expand(struct strbuf *sb,
expand_fn_t fn, expand_fn_t fn,
void *context); void *context);
/**
* Used as callback for `strbuf_expand` to only expand literals
* (i.e. %n and %xNN). The context argument is ignored.
*/
size_t strbuf_expand_literal_cb(struct strbuf *sb,
const char *placeholder,
void *context);
/** /**
* Used as callback for `strbuf_expand()`, expects an array of * Used as callback for `strbuf_expand()`, expects an array of
* struct strbuf_expand_dict_entry as context, i.e. pairs of * struct strbuf_expand_dict_entry as context, i.e. pairs of

View file

@ -578,6 +578,24 @@ test_expect_success '%(trailers:only) shows only "key: value" trailers' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success '%(trailers:only=yes) shows only "key: value" trailers' '
git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual &&
grep -v patch.description <trailers >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:only=no) shows all trailers' '
git log --no-walk --pretty=format:"%(trailers:only=no)" >actual &&
cat trailers >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:only=no,only=true) shows only "key: value" trailers' '
git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual &&
grep -v patch.description <trailers >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:unfold) unfolds trailers' ' test_expect_success '%(trailers:unfold) unfolds trailers' '
git log --no-walk --pretty="%(trailers:unfold)" >actual && git log --no-walk --pretty="%(trailers:unfold)" >actual &&
{ {
@ -598,6 +616,105 @@ test_expect_success ':only and :unfold work together' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'pretty format %(trailers:key=foo) shows that trailer' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by)" >actual &&
echo "Acked-by: A U Thor <author@example.com>" >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key=foo) is case insensitive' '
git log --no-walk --pretty="format:%(trailers:key=AcKed-bY)" >actual &&
echo "Acked-by: A U Thor <author@example.com>" >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key=foo:) trailing colon also works' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by:)" >actual &&
echo "Acked-by: A U Thor <author@example.com>" >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key=foo) multiple keys' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by:,key=Signed-off-By)" >actual &&
grep -v patch.description <trailers >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=nonexistant) becomes empty' '
git log --no-walk --pretty="x%(trailers:key=Nacked-by)x" >actual &&
echo "xx" >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=foo) handles multiple lines even if folded' '
git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by)" >actual &&
grep -v patch.description <trailers | grep -v Acked-by >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=foo,unfold) properly unfolds' '
git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by,unfold)" >actual &&
unfold <trailers | grep Signed-off-by >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key=foo,only=no) also includes nontrailer lines' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by,only=no)" >actual &&
{
echo "Acked-by: A U Thor <author@example.com>" &&
grep patch.description <trailers
} >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key) without value is error' '
git log --no-walk --pretty="tformat:%(trailers:key)" >actual &&
echo "%(trailers:key)" >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=foo,valueonly) shows only value' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual &&
echo "A U Thor <author@example.com>" >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:separator) changes separator' '
git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual &&
printf "XSigned-off-by: A U Thor <author@example.com>\0Acked-by: A U Thor <author@example.com>\0[ v2 updated patch description ]\0Signed-off-by: A U Thor <author@example.com>X" >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' '
git commit --allow-empty -F - <<-\EOF &&
Important fix
The fix is explained here
Closes: #1234
EOF
git commit --allow-empty -F - <<-\EOF &&
Another fix
The fix is explained here
Closes: #567
Closes: #890
EOF
git commit --allow-empty -F - <<-\EOF &&
Does not close any tickets
EOF
git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,valueonly)" HEAD~3.. >actual &&
test_write_lines \
"Does not close any tickets" \
"Another fix #567, #890" \
"Important fix #1234" >expect &&
test_cmp expect actual
'
test_expect_success 'trailer parsing not fooled by --- line' ' test_expect_success 'trailer parsing not fooled by --- line' '
git commit --allow-empty -F - <<-\EOF && git commit --allow-empty -F - <<-\EOF &&
this is the subject this is the subject

View file

@ -1129,10 +1129,11 @@ static void format_trailer_info(struct strbuf *out,
const struct trailer_info *info, const struct trailer_info *info,
const struct process_trailer_options *opts) const struct process_trailer_options *opts)
{ {
size_t origlen = out->len;
size_t i; size_t i;
/* If we want the whole block untouched, we can take the fast path. */ /* If we want the whole block untouched, we can take the fast path. */
if (!opts->only_trailers && !opts->unfold) { if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) {
strbuf_add(out, info->trailer_start, strbuf_add(out, info->trailer_start,
info->trailer_end - info->trailer_start); info->trailer_end - info->trailer_start);
return; return;
@ -1147,15 +1148,29 @@ static void format_trailer_info(struct strbuf *out,
struct strbuf val = STRBUF_INIT; struct strbuf val = STRBUF_INIT;
parse_trailer(&tok, &val, NULL, trailer, separator_pos); parse_trailer(&tok, &val, NULL, trailer, separator_pos);
if (opts->unfold) if (!opts->filter || opts->filter(&tok, opts->filter_data)) {
unfold_value(&val); if (opts->unfold)
unfold_value(&val);
strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); if (opts->separator && out->len != origlen)
strbuf_addbuf(out, opts->separator);
if (!opts->value_only)
strbuf_addf(out, "%s: ", tok.buf);
strbuf_addbuf(out, &val);
if (!opts->separator)
strbuf_addch(out, '\n');
}
strbuf_release(&tok); strbuf_release(&tok);
strbuf_release(&val); strbuf_release(&val);
} else if (!opts->only_trailers) { } else if (!opts->only_trailers) {
if (opts->separator && out->len != origlen) {
strbuf_addbuf(out, opts->separator);
}
strbuf_addstr(out, trailer); strbuf_addstr(out, trailer);
if (opts->separator) {
strbuf_rtrim(out);
}
} }
} }

View file

@ -72,6 +72,10 @@ struct process_trailer_options {
int only_input; int only_input;
int unfold; int unfold;
int no_divider; int no_divider;
int value_only;
const struct strbuf *separator;
int (*filter)(const struct strbuf *, void *);
void *filter_data;
}; };
#define PROCESS_TRAILER_OPTIONS_INIT {0} #define PROCESS_TRAILER_OPTIONS_INIT {0}