Merge branch 'ab/trailers-extra-format'

The "--format=%(trailers)" mechanism gets enhanced to make it
easier to design output for machine consumption.

* ab/trailers-extra-format:
  pretty format %(trailers): add a "key_value_separator"
  pretty format %(trailers): add a "keyonly"
  pretty-format %(trailers): fix broken standalone "valueonly"
  pretty format %(trailers) doc: avoid repetition
  pretty format %(trailers) test: split a long line
This commit is contained in:
Junio C Hamano 2021-01-06 23:33:43 -08:00
Родитель c977ff4407 058761f1c1
Коммит b62bbd3580
5 изменённых файлов: 143 добавлений и 21 удалений

Просмотреть файл

@ -252,7 +252,15 @@ endif::git-rev-list[]
interpreted by interpreted by
linkgit:git-interpret-trailers[1]. The linkgit:git-interpret-trailers[1]. The
`trailers` string may be followed by a colon `trailers` string may be followed by a colon
and zero or more comma-separated options: and zero or more comma-separated options.
If any option is provided multiple times the
last occurance wins.
+
The boolean options accept an optional value `[=<BOOL>]`. The values
`true`, `false`, `on`, `off` etc. are all accepted. See the "boolean"
sub-section in "EXAMPLES" in linkgit:git-config[1]. If a boolean
option is given with no value, it's enabled.
+
** 'key=<K>': only show trailers with specified key. Matching is done ** 'key=<K>': only show trailers with specified key. Matching is done
case-insensitively and trailing colon is optional. If option is case-insensitively and trailing colon is optional. If option is
given multiple times trailer lines matching any of the keys are given multiple times trailer lines matching any of the keys are
@ -261,27 +269,25 @@ endif::git-rev-list[]
desired it can be disabled with `only=false`. E.g., desired it can be disabled with `only=false`. E.g.,
`%(trailers:key=Reviewed-by)` shows trailer lines with key `%(trailers:key=Reviewed-by)` shows trailer lines with key
`Reviewed-by`. `Reviewed-by`.
** 'only[=val]': select whether non-trailer lines from the trailer ** 'only[=<BOOL>]': select whether non-trailer lines from the trailer
block should be included. The `only` keyword may optionally be block should be included.
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 ** 'separator=<SEP>': specify a separator inserted between trailer
lines. When this option is not given each trailer line is lines. When this option is not given each trailer line is
terminated with a line feed character. The string SEP may contain terminated with a line feed character. The string SEP may contain
the literal formatting codes described above. To use comma as the literal formatting codes described above. To use comma as
separator one must use `%x2C` as it would otherwise be parsed as separator one must use `%x2C` as it would otherwise be parsed as
next option. If separator option is given multiple times only the next option. E.g., `%(trailers:key=Ticket,separator=%x2C )`
last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
shows all trailer lines whose key is "Ticket" separated by a comma shows all trailer lines whose key is "Ticket" separated by a comma
and a space. and a space.
** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold` ** 'unfold[=<BOOL>]': make it behave as if interpret-trailer's `--unfold`
option was given. In same way as to for `only` it can be followed option was given. E.g.,
by an equal sign and explicit value. E.g.,
`%(trailers:only,unfold=true)` unfolds and shows all trailer lines. `%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
** 'valueonly[=val]': skip over the key part of the trailer line and only ** 'keyonly[=<BOOL>]': only show the key part of the trailer.
show the value part. Also this optionally allows explicit value. ** 'valueonly[=<BOOL>]': only show the value part of the trailer.
** 'key_value_separator=<SEP>': specify a separator inserted between
trailer lines. When this option is not given each trailer key-value
pair is separated by ": ". Otherwise it shares the same semantics
as 'separator=<SEP>' above.
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

Просмотреть файл

@ -1418,6 +1418,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
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 string_list filter_list = STRING_LIST_INIT_NODUP;
struct strbuf sepbuf = STRBUF_INIT; struct strbuf sepbuf = STRBUF_INIT;
struct strbuf kvsepbuf = STRBUF_INIT;
size_t ret = 0; size_t ret = 0;
opts.no_divider = 1; opts.no_divider = 1;
@ -1449,8 +1450,17 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL); strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
free(fmt); free(fmt);
opts.separator = &sepbuf; opts.separator = &sepbuf;
} else if (match_placeholder_arg_value(arg, "key_value_separator", &arg, &argval, &arglen)) {
char *fmt;
strbuf_reset(&kvsepbuf);
fmt = xstrndup(argval, arglen);
strbuf_expand(&kvsepbuf, fmt, strbuf_expand_literal_cb, NULL);
free(fmt);
opts.key_value_separator = &kvsepbuf;
} else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && } 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, "unfold", &arg, &opts.unfold) &&
!match_placeholder_bool_arg(arg, "keyonly", &arg, &opts.key_only) &&
!match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only)) !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only))
break; break;
} }

Просмотреть файл

@ -605,6 +605,12 @@ test_expect_success 'pretty format %(trailers) shows trailers' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'pretty format %(trailers:) enables no options' '
git log --no-walk --pretty="%(trailers:)" >actual &&
# "expect" the same as the test above
test_cmp expect actual
'
test_expect_success '%(trailers:only) shows only "key: value" trailers' ' test_expect_success '%(trailers:only) shows only "key: value" trailers' '
git log --no-walk --pretty="%(trailers:only)" >actual && git log --no-walk --pretty="%(trailers:only)" >actual &&
{ {
@ -709,19 +715,101 @@ test_expect_success '%(trailers:key) without value is error' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success '%(trailers:keyonly) shows only keys' '
git log --no-walk --pretty="format:%(trailers:keyonly)" >actual &&
test_write_lines \
"Signed-off-by" \
"Acked-by" \
"[ v2 updated patch description ]" \
"Signed-off-by" >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=foo,keyonly) shows only key' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by,keyonly)" >actual &&
echo "Acked-by" >expect &&
test_cmp expect actual
'
test_expect_success '%(trailers:key=foo,valueonly) shows only value' ' test_expect_success '%(trailers:key=foo,valueonly) shows only value' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual && git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual &&
echo "A U Thor <author@example.com>" >expect && echo "A U Thor <author@example.com>" >expect &&
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'pretty format %(trailers:separator) changes separator' ' test_expect_success '%(trailers:valueonly) shows only values' '
git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual && git log --no-walk --pretty="format:%(trailers:valueonly)" >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_write_lines \
"A U Thor <author@example.com>" \
"A U Thor <author@example.com>" \
"[ v2 updated patch description ]" \
"A U Thor" \
" <author@example.com>" >expect &&
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' ' test_expect_success '%(trailers:key=foo,keyonly,valueonly) shows nothing' '
git log --no-walk --pretty="format:%(trailers:key=Acked-by,keyonly,valueonly)" >actual &&
echo >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:separator) changes separator' '
git log --no-walk --pretty=format:"X%(trailers:separator=%x00)X" >actual &&
(
printf "XSigned-off-by: A U Thor <author@example.com>\0" &&
printf "Acked-by: A U Thor <author@example.com>\0" &&
printf "[ v2 updated patch description ]\0" &&
printf "Signed-off-by: A U Thor\n <author@example.com>X"
) >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:separator=X,unfold) 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>\0" &&
printf "Acked-by: A U Thor <author@example.com>\0" &&
printf "[ v2 updated patch description ]\0" &&
printf "Signed-off-by: A U Thor <author@example.com>X"
) >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key_value_separator) changes key-value separator' '
git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00)X" >actual &&
(
printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
printf "Acked-by\0A U Thor <author@example.com>\n" &&
printf "[ v2 updated patch description ]\n" &&
printf "Signed-off-by\0A U Thor\n <author@example.com>\nX"
) >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:key_value_separator,unfold) changes key-value separator' '
git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00,unfold)X" >actual &&
(
printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
printf "Acked-by\0A U Thor <author@example.com>\n" &&
printf "[ v2 updated patch description ]\n" &&
printf "Signed-off-by\0A U Thor <author@example.com>\nX"
) >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers:separator,key_value_separator) changes both separators' '
git log --no-walk --pretty=format:"%(trailers:separator=%x00,key_value_separator=%x00%x00,unfold)" >actual &&
(
printf "Signed-off-by\0\0A U Thor <author@example.com>\0" &&
printf "Acked-by\0\0A U Thor <author@example.com>\0" &&
printf "[ v2 updated patch description ]\0" &&
printf "Signed-off-by\0\0A U Thor <author@example.com>"
) >expect &&
test_cmp expect actual
'
test_expect_success 'pretty format %(trailers) combining separator/key/keyonly/valueonly' '
git commit --allow-empty -F - <<-\EOF && git commit --allow-empty -F - <<-\EOF &&
Important fix Important fix
@ -748,6 +836,13 @@ test_expect_success 'pretty format %(trailers) combining separator/key/valueonly
"Does not close any tickets" \ "Does not close any tickets" \
"Another fix #567, #890" \ "Another fix #567, #890" \
"Important fix #1234" >expect && "Important fix #1234" >expect &&
test_cmp expect actual &&
git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,keyonly)" HEAD~3.. >actual &&
test_write_lines \
"Does not close any tickets" \
"Another fix Closes, Closes" \
"Important fix Closes" >expect &&
test_cmp expect actual test_cmp expect actual
' '

Просмотреть файл

@ -1131,7 +1131,9 @@ static void format_trailer_info(struct strbuf *out,
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 && !opts->filter && !opts->separator) { if (!opts->only_trailers && !opts->unfold && !opts->filter &&
!opts->separator && !opts->key_only && !opts->value_only &&
!opts->key_value_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;
@ -1153,8 +1155,15 @@ static void format_trailer_info(struct strbuf *out,
if (opts->separator && out->len != origlen) if (opts->separator && out->len != origlen)
strbuf_addbuf(out, opts->separator); strbuf_addbuf(out, opts->separator);
if (!opts->value_only) if (!opts->value_only)
strbuf_addf(out, "%s: ", tok.buf); strbuf_addbuf(out, &tok);
strbuf_addbuf(out, &val); if (!opts->key_only && !opts->value_only) {
if (opts->key_value_separator)
strbuf_addbuf(out, opts->key_value_separator);
else
strbuf_addstr(out, ": ");
}
if (!opts->key_only)
strbuf_addbuf(out, &val);
if (!opts->separator) if (!opts->separator)
strbuf_addch(out, '\n'); strbuf_addch(out, '\n');
} }

Просмотреть файл

@ -71,8 +71,10 @@ struct process_trailer_options {
int only_input; int only_input;
int unfold; int unfold;
int no_divider; int no_divider;
int key_only;
int value_only; int value_only;
const struct strbuf *separator; const struct strbuf *separator;
const struct strbuf *key_value_separator;
int (*filter)(const struct strbuf *, void *); int (*filter)(const struct strbuf *, void *);
void *filter_data; void *filter_data;
}; };