Merge branch 'tb/show-trailers-in-ref-filter'

"git for-each-ref --format=..." learned a new format element,
%(trailers), to show only the commit log trailer part of the log
message.

* tb/show-trailers-in-ref-filter:
  ref-filter.c: parse trailers arguments with %(contents) atom
  ref-filter.c: use trailer_opts to format trailers
  t6300: refactor %(trailers) tests
  doc: use "`<literal>`"-style quoting for literal strings
  doc: 'trailers' is the preferred way to format trailers
  t4205: unfold across multiple lines
maint
Junio C Hamano 2017-10-11 14:52:22 +09:00
commit b03cd16613
4 changed files with 122 additions and 17 deletions

View File

@ -218,11 +218,15 @@ and `date` to extract the named component.
The complete message in a commit and tag object is `contents`. The complete message in a commit and tag object is `contents`.
Its first line is `contents:subject`, where subject is the concatenation Its first line is `contents:subject`, where subject is the concatenation
of all lines of the commit message up to the first blank line. The next of all lines of the commit message up to the first blank line. The next
line is 'contents:body', where body is all of the lines after the first line is `contents:body`, where body is all of the lines after the first
blank line. The optional GPG signature is `contents:signature`. The blank line. The optional GPG signature is `contents:signature`. The
first `N` lines of the message is obtained using `contents:lines=N`. first `N` lines of the message is obtained using `contents:lines=N`.
Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1] Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1]
are obtained as 'contents:trailers'. are obtained as `trailers` (or by using the historical alias
`contents:trailers`). Non-trailer lines from the trailer block can be omitted
with `trailers:only`. Whitespace-continuations can be removed from trailers so
that each trailer appears on a line by itself with its full content with
`trailers:unfold`. Both can be used together as `trailers:unfold,only`.


For sorting purposes, fields with numeric values sort in numeric order For sorting purposes, fields with numeric values sort in numeric order
(`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`). (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`).

View File

@ -82,6 +82,7 @@ static struct used_atom {
} remote_ref; } remote_ref;
struct { struct {
enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option; enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
struct process_trailer_options trailer_opts;
unsigned int nlines; unsigned int nlines;
} contents; } contents;
struct { struct {
@ -182,9 +183,23 @@ static void subject_atom_parser(const struct ref_format *format, struct used_ato


static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{ {
if (arg) struct string_list params = STRING_LIST_INIT_DUP;
die(_("%%(trailers) does not take arguments")); int i;

if (arg) {
string_list_split(&params, arg, ',', -1);
for (i = 0; i < params.nr; i++) {
const char *s = params.items[i].string;
if (!strcmp(s, "unfold"))
atom->u.contents.trailer_opts.unfold = 1;
else if (!strcmp(s, "only"))
atom->u.contents.trailer_opts.only_trailers = 1;
else
die(_("unknown %%(trailers) argument: %s"), s);
}
}
atom->u.contents.option = C_TRAILERS; atom->u.contents.option = C_TRAILERS;
string_list_clear(&params, 0);
} }


static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
@ -197,9 +212,10 @@ static void contents_atom_parser(const struct ref_format *format, struct used_at
atom->u.contents.option = C_SIG; atom->u.contents.option = C_SIG;
else if (!strcmp(arg, "subject")) else if (!strcmp(arg, "subject"))
atom->u.contents.option = C_SUB; atom->u.contents.option = C_SUB;
else if (!strcmp(arg, "trailers")) else if (skip_prefix(arg, "trailers", &arg)) {
atom->u.contents.option = C_TRAILERS; skip_prefix(arg, ":", &arg);
else if (skip_prefix(arg, "lines=", &arg)) { trailers_atom_parser(format, atom, *arg ? arg : NULL);
} else if (skip_prefix(arg, "lines=", &arg)) {
atom->u.contents.option = C_LINES; atom->u.contents.option = C_LINES;
if (strtoul_ui(arg, 10, &atom->u.contents.nlines)) if (strtoul_ui(arg, 10, &atom->u.contents.nlines))
die(_("positive value expected contents:lines=%s"), arg); die(_("positive value expected contents:lines=%s"), arg);
@ -1048,7 +1064,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
name++; name++;
if (strcmp(name, "subject") && if (strcmp(name, "subject") &&
strcmp(name, "body") && strcmp(name, "body") &&
strcmp(name, "trailers") && !starts_with(name, "trailers") &&
!starts_with(name, "contents")) !starts_with(name, "contents"))
continue; continue;
if (!subpos) if (!subpos)
@ -1073,13 +1089,12 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines); append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines);
v->s = strbuf_detach(&s, NULL); v->s = strbuf_detach(&s, NULL);
} else if (atom->u.contents.option == C_TRAILERS) { } else if (atom->u.contents.option == C_TRAILERS) {
struct trailer_info info; struct strbuf s = STRBUF_INIT;


/* Search for trailer info */ /* Format the trailer info according to the trailer_opts given */
trailer_info_get(&info, subpos); format_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);
v->s = xmemdupz(info.trailer_start,
info.trailer_end - info.trailer_start); v->s = strbuf_detach(&s, NULL);
trailer_info_release(&info);
} else if (atom->u.contents.option == C_BARE) } else if (atom->u.contents.option == C_BARE)
v->s = xstrdup(subpos); v->s = xstrdup(subpos);
} }

View File

@ -544,7 +544,7 @@ Signed-off-by: A U Thor
EOF EOF


unfold () { unfold () {
perl -0pe 's/\n\s+/ /' perl -0pe 's/\n\s+/ /g'
} }


test_expect_success 'set up trailer tests' ' test_expect_success 'set up trailer tests' '

View File

@ -605,18 +605,104 @@ test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
cat >trailers <<EOF cat >trailers <<EOF
Reviewed-by: A U Thor <author@example.com> Reviewed-by: A U Thor <author@example.com>
Signed-off-by: A U Thor <author@example.com> Signed-off-by: A U Thor <author@example.com>
[ v2 updated patch description ]
Acked-by: A U Thor
<author@example.com>
EOF EOF


test_expect_success 'basic atom: head contents:trailers' ' unfold () {
perl -0pe 's/\n\s+/ /g'
}

test_expect_success 'set up trailers for next test' '
echo "Some contents" > two && echo "Some contents" > two &&
git add two && git add two &&
git commit -F - <<-EOF && git commit -F - <<-EOF
trailers: this commit message has trailers trailers: this commit message has trailers


Some message contents Some message contents


$(cat trailers) $(cat trailers)
EOF EOF
'

test_expect_success '%(trailers:unfold) unfolds trailers' '
git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual &&
{
unfold <trailers
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(trailers:only) shows only "key: value" trailers' '
git for-each-ref --format="%(trailers:only)" refs/heads/master >actual &&
{
grep -v patch.description <trailers &&
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(trailers:only) and %(trailers:unfold) work together' '
git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual &&
git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >reverse &&
test_cmp actual reverse &&
{
grep -v patch.description <trailers | unfold &&
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(contents:trailers:unfold) unfolds trailers' '
git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual &&
{
unfold <trailers
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(contents:trailers:only) shows only "key: value" trailers' '
git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual &&
{
grep -v patch.description <trailers &&
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(contents:trailers:only) and %(contents:trailers:unfold) work together' '
git for-each-ref --format="%(contents:trailers:only,unfold)" refs/heads/master >actual &&
git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >reverse &&
test_cmp actual reverse &&
{
grep -v patch.description <trailers | unfold &&
echo
} >expect &&
test_cmp expect actual
'

test_expect_success '%(trailers) rejects unknown trailers arguments' '
# error message cannot be checked under i18n
cat >expect <<-EOF &&
fatal: unknown %(trailers) argument: unsupported
EOF
test_must_fail git for-each-ref --format="%(trailers:unsupported)" 2>actual &&
test_i18ncmp expect actual
'

test_expect_success '%(contents:trailers) rejects unknown trailers arguments' '
# error message cannot be checked under i18n
cat >expect <<-EOF &&
fatal: unknown %(trailers) argument: unsupported
EOF
test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual &&
test_i18ncmp expect actual
'

test_expect_success 'basic atom: head contents:trailers' '
git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual && git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual &&
sanitize_pgp <actual >actual.clean && sanitize_pgp <actual >actual.clean &&
# git for-each-ref ends with a blank line # git for-each-ref ends with a blank line