зеркало из https://github.com/microsoft/git.git
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:
Коммит
42977bf5c7
|
@ -102,120 +102,160 @@ The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
|
|||
+
|
||||
The placeholders are:
|
||||
|
||||
- '%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)
|
||||
- Placeholders that expand to a single literal character:
|
||||
'%n':: newline
|
||||
'%%':: a raw '%'
|
||||
'%x00':: print a byte from a hex code
|
||||
|
||||
- Placeholders that affect formatting of later placeholders:
|
||||
'%Cred':: switch color to red
|
||||
'%Cgreen':: switch color to green
|
||||
'%Cblue':: switch color to blue
|
||||
'%Creset':: reset color
|
||||
'%C(...)':: color specification, as described under Values in the
|
||||
"CONFIGURATION FILE" section of linkgit:git-config[1]. By
|
||||
default, colors are shown only when enabled for log output
|
||||
(by `color.diff`, `color.ui`, or `--color`, and respecting
|
||||
the `auto` settings of the former if we are going to a
|
||||
terminal). `%C(auto,...)` is accepted as a historical
|
||||
synonym for the default (e.g., `%C(auto,red)`). Specifying
|
||||
`%C(always,...)` will show the colors even when color is
|
||||
not otherwise enabled (though consider just using
|
||||
`--color=always` to enable color for the whole output,
|
||||
including this format and anything else git might color).
|
||||
`auto` alone (i.e. `%C(auto)`) will turn on auto coloring
|
||||
on the next placeholders until the color is switched
|
||||
again.
|
||||
'%m':: left (`<`), right (`>`) or boundary (`-`) mark
|
||||
'%w([<w>[,<i1>[,<i2>]]])':: switch line wrapping, like the -w option of
|
||||
linkgit:git-shortlog[1].
|
||||
'%<(<N>[,trunc|ltrunc|mtrunc])':: make the next placeholder take at
|
||||
least N columns, padding spaces on
|
||||
the right if necessary. Optionally
|
||||
truncate at the beginning (ltrunc),
|
||||
the middle (mtrunc) or the end
|
||||
(trunc) if the output is longer than
|
||||
N columns. Note that truncating
|
||||
only works correctly with N >= 2.
|
||||
'%<|(<N>)':: make the next placeholder take at least until Nth
|
||||
columns, padding spaces on the right if necessary
|
||||
'%>(<N>)', '%>|(<N>)':: similar to '%<(<N>)', '%<|(<N>)' respectively,
|
||||
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[]
|
||||
- '%N': commit notes
|
||||
'%N':: commit notes
|
||||
endif::git-rev-list[]
|
||||
- '%GG': raw verification message from GPG for a signed commit
|
||||
- '%G?': show "G" for a good (valid) signature,
|
||||
"B" for a bad signature,
|
||||
"U" for a good signature with unknown validity,
|
||||
"X" for a good signature that has expired,
|
||||
"Y" for a good signature made by an expired key,
|
||||
"R" for a good signature made by a revoked key,
|
||||
"E" if the signature cannot be checked (e.g. missing key)
|
||||
and "N" for no signature
|
||||
- '%GS': show the name of the signer for 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
|
||||
- '%GP': show the fingerprint of the primary key whose subkey was used
|
||||
to sign a signed commit
|
||||
- '%gD': reflog selector, e.g., `refs/stash@{1}` or
|
||||
`refs/stash@{2 minutes ago`}; the format follows the rules described
|
||||
for the `-g` option. The portion before the `@` is the refname as
|
||||
given on the command line (so `git log -g refs/heads/master` would
|
||||
yield `refs/heads/master@{0}`).
|
||||
- '%gd': shortened reflog selector; same as `%gD`, but the refname
|
||||
portion is shortened for human readability (so `refs/heads/master`
|
||||
becomes just `master`).
|
||||
- '%gn': reflog identity name
|
||||
- '%gN': reflog identity name (respecting .mailmap, see
|
||||
linkgit:git-shortlog[1] or linkgit:git-blame[1])
|
||||
- '%ge': reflog identity email
|
||||
- '%gE': reflog identity email (respecting .mailmap, see
|
||||
linkgit:git-shortlog[1] or linkgit:git-blame[1])
|
||||
- '%gs': reflog subject
|
||||
- '%Cred': switch color to red
|
||||
- '%Cgreen': switch color to green
|
||||
- '%Cblue': switch color to blue
|
||||
- '%Creset': reset color
|
||||
- '%C(...)': color specification, as described under Values in the
|
||||
"CONFIGURATION FILE" section of linkgit:git-config[1].
|
||||
By default, colors are shown only when enabled for log output (by
|
||||
`color.diff`, `color.ui`, or `--color`, and respecting the `auto`
|
||||
settings of the former if we are going to a terminal). `%C(auto,...)`
|
||||
is accepted as a historical synonym for the default (e.g.,
|
||||
`%C(auto,red)`). Specifying `%C(always,...)` will show the colors
|
||||
even when color is not otherwise enabled (though consider
|
||||
just using `--color=always` to enable color for the whole output,
|
||||
including this format and anything else git might color). `auto`
|
||||
alone (i.e. `%C(auto)`) will turn on auto coloring on the next
|
||||
placeholders until the color is switched again.
|
||||
- '%m': left (`<`), right (`>`) or boundary (`-`) mark
|
||||
- '%n': newline
|
||||
- '%%': a raw '%'
|
||||
- '%x00': print a byte from a hex code
|
||||
- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
|
||||
linkgit:git-shortlog[1].
|
||||
- '%<(<N>[,trunc|ltrunc|mtrunc])': make the next placeholder take at
|
||||
least N columns, padding spaces on the right if necessary.
|
||||
Optionally truncate at the beginning (ltrunc), the middle (mtrunc)
|
||||
or the end (trunc) if the output is longer than N columns.
|
||||
Note that truncating only works correctly with N >= 2.
|
||||
- '%<|(<N>)': make the next placeholder take at least until Nth
|
||||
columns, padding spaces on the right if necessary
|
||||
- '%>(<N>)', '%>|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
|
||||
respectively, 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)
|
||||
- %(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.
|
||||
'%GG':: raw verification message from GPG for a signed commit
|
||||
'%G?':: show "G" for a good (valid) signature,
|
||||
"B" for a bad signature,
|
||||
"U" for a good signature with unknown validity,
|
||||
"X" for a good signature that has expired,
|
||||
"Y" for a good signature made by an expired key,
|
||||
"R" for a good signature made by a revoked key,
|
||||
"E" if the signature cannot be checked (e.g. missing key)
|
||||
and "N" for no signature
|
||||
'%GS':: show the name of the signer for 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
|
||||
'%GP':: show the fingerprint of the primary key whose subkey was used
|
||||
to sign a signed commit
|
||||
'%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2
|
||||
minutes ago`}; the format follows the rules described for the
|
||||
`-g` option. The portion before the `@` is the refname as
|
||||
given on the command line (so `git log -g refs/heads/master`
|
||||
would yield `refs/heads/master@{0}`).
|
||||
'%gd':: shortened reflog selector; same as `%gD`, but the refname
|
||||
portion is shortened for human readability (so
|
||||
`refs/heads/master` becomes just `master`).
|
||||
'%gn':: reflog identity name
|
||||
'%gN':: reflog identity name (respecting .mailmap, see
|
||||
linkgit:git-shortlog[1] or linkgit:git-blame[1])
|
||||
'%ge':: reflog identity email
|
||||
'%gE':: reflog identity email (respecting .mailmap, see
|
||||
linkgit:git-shortlog[1] or linkgit:git-blame[1])
|
||||
'%gs':: reflog subject
|
||||
'%(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:
|
||||
** 'key=<K>': only show trailers with specified key. Matching is done
|
||||
case-insensitively and trailing colon is optional. If option is
|
||||
given multiple times trailer lines matching any of the keys are
|
||||
shown. This option automatically enables the `only` option so that
|
||||
non-trailer lines in the trailer block are hidden. If that is not
|
||||
desired it can be disabled with `only=false`. E.g.,
|
||||
`%(trailers:key=Reviewed-by)` shows trailer lines with key
|
||||
`Reviewed-by`.
|
||||
** 'only[=val]': select whether non-trailer lines from the trailer
|
||||
block should be included. The `only` keyword may optionally be
|
||||
followed by an equal sign and one of `true`, `on`, `yes` to omit or
|
||||
`false`, `off`, `no` to show the non-trailer lines. If option is
|
||||
given without value it is enabled. If given multiple times the last
|
||||
value is used.
|
||||
** 'separator=<SEP>': specify a separator inserted between trailer
|
||||
lines. When this option is not given each trailer line is
|
||||
terminated with a line feed character. The string SEP may contain
|
||||
the literal formatting codes described above. To use comma as
|
||||
separator one must use `%x2C` as it would otherwise be parsed as
|
||||
next option. If separator option is given multiple times only the
|
||||
last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
|
||||
shows all trailer lines whose key is "Ticket" separated by a comma
|
||||
and a space.
|
||||
** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold`
|
||||
option was given. In same way as to for `only` it can be followed
|
||||
by an equal sign and explicit value. E.g.,
|
||||
`%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
|
||||
** 'valueonly[=val]': skip over the key part of the trailer line and only
|
||||
show the value part. Also this optionally allows explicit value.
|
||||
|
||||
NOTE: Some placeholders may depend on other options given to the
|
||||
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;
|
||||
}
|
||||
|
||||
static int match_placeholder_arg(const char *to_parse, const char *candidate,
|
||||
const char **end)
|
||||
static int match_placeholder_arg_value(const char *to_parse, const char *candidate,
|
||||
const char **end, const char **valuestart,
|
||||
size_t *valuelen)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (!(skip_prefix(to_parse, candidate, &p)))
|
||||
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 == ',') {
|
||||
*end = p + 1;
|
||||
return 1;
|
||||
|
@ -1075,6 +1088,47 @@ static int match_placeholder_arg(const char *to_parse, const char *candidate,
|
|||
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 */
|
||||
const char *placeholder,
|
||||
void *context)
|
||||
|
@ -1084,10 +1138,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
|||
const char *msg = c->message;
|
||||
struct commit_list *p;
|
||||
const char *arg;
|
||||
int ch;
|
||||
size_t res;
|
||||
char **slot;
|
||||
|
||||
/* these are independent of the commit */
|
||||
res = strbuf_expand_literal_cb(sb, placeholder, NULL);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
switch (placeholder[0]) {
|
||||
case 'C':
|
||||
if (starts_with(placeholder + 1, "(auto)")) {
|
||||
|
@ -1106,16 +1164,6 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
|||
*/
|
||||
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':
|
||||
if (placeholder[1] == '(') {
|
||||
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)) {
|
||||
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;
|
||||
|
||||
if (*arg == ':') {
|
||||
arg++;
|
||||
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;
|
||||
else if (match_placeholder_arg(arg, "unfold", &arg))
|
||||
opts.unfold = 1;
|
||||
else
|
||||
} else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) {
|
||||
char *fmt;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (*arg == ')') {
|
||||
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 */
|
||||
|
|
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,
|
||||
void *context)
|
||||
{
|
||||
|
|
8
strbuf.h
8
strbuf.h
|
@ -320,6 +320,14 @@ void strbuf_expand(struct strbuf *sb,
|
|||
expand_fn_t fn,
|
||||
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
|
||||
* 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_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' '
|
||||
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_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' '
|
||||
git commit --allow-empty -F - <<-\EOF &&
|
||||
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 process_trailer_options *opts)
|
||||
{
|
||||
size_t origlen = out->len;
|
||||
size_t i;
|
||||
|
||||
/* 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,
|
||||
info->trailer_end - info->trailer_start);
|
||||
return;
|
||||
|
@ -1147,15 +1148,29 @@ static void format_trailer_info(struct strbuf *out,
|
|||
struct strbuf val = STRBUF_INIT;
|
||||
|
||||
parse_trailer(&tok, &val, NULL, trailer, separator_pos);
|
||||
if (opts->unfold)
|
||||
unfold_value(&val);
|
||||
if (!opts->filter || opts->filter(&tok, opts->filter_data)) {
|
||||
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(&val);
|
||||
|
||||
} else if (!opts->only_trailers) {
|
||||
if (opts->separator && out->len != origlen) {
|
||||
strbuf_addbuf(out, opts->separator);
|
||||
}
|
||||
strbuf_addstr(out, trailer);
|
||||
if (opts->separator) {
|
||||
strbuf_rtrim(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,10 @@ struct process_trailer_options {
|
|||
int only_input;
|
||||
int unfold;
|
||||
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}
|
||||
|
|
Загрузка…
Ссылка в новой задаче