mirror of
https://github.com/git/git
synced 2024-08-24 18:26:02 +00:00
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:
commit
42977bf5c7
|
@ -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
113
pretty.c
|
@ -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 */
|
||||||
|
|
21
strbuf.c
21
strbuf.c
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
8
strbuf.h
8
strbuf.h
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
23
trailer.c
23
trailer.c
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in a new issue