Merge branch 'tb/show-trailers-in-ref-filter'
authorJunio C Hamano <gitster@pobox.com>
Wed, 11 Oct 2017 05:52:22 +0000 (14:52 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 11 Oct 2017 05:52:22 +0000 (14:52 +0900)
"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

1  2 
Documentation/git-for-each-ref.txt
ref-filter.c
t/t6300-for-each-ref.sh
index cbd0a6212a62a618252e7b56397239d4066dcbd7,4a2c851e6c365212706d0e6f474156b955c0d3e2..1d420e4cde8230de00aae583a296128fdd59140f
@@@ -57,11 -57,6 +57,11 @@@ OPTION
        `xx`; for example `%00` interpolates to `\0` (NUL),
        `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
  
 +--color[=<when>]:
 +      Respect any colors specified in the `--format` option. The
 +      `<when>` field must be one of `always`, `never`, or `auto` (if
 +      `<when>` is absent, behave as if `always` was given).
 +
  --shell::
  --perl::
  --python::
@@@ -218,11 -213,15 +218,15 @@@ and `date` to extract the named compone
  The complete message in a commit and tag object is `contents`.
  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
- 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
  first `N` lines of the message is obtained using `contents:lines=N`.
  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
  (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`).
diff --combined ref-filter.c
index 45a3be83402066a5bf7507dde50f0dc07efbf645,85625fe2d2f89ebe23d387de8c8aaac28ab036f7..e728b15b3aeec8e8a9154d4ee4a4284cf06b5931
@@@ -82,6 -82,7 +82,7 @@@ static struct used_atom 
                } remote_ref;
                struct {
                        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;
                } contents;
                struct {
@@@ -182,9 -183,23 +183,23 @@@ static void subject_atom_parser(const s
  
  static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
  {
-       if (arg)
-               die(_("%%(trailers) does not take arguments"));
+       struct string_list params = STRING_LIST_INIT_DUP;
+       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;
+       string_list_clear(&params, 0);
  }
  
  static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
                atom->u.contents.option = C_SIG;
        else if (!strcmp(arg, "subject"))
                atom->u.contents.option = C_SUB;
-       else if (!strcmp(arg, "trailers"))
-               atom->u.contents.option = C_TRAILERS;
-       else if (skip_prefix(arg, "lines=", &arg)) {
+       else if (skip_prefix(arg, "trailers", &arg)) {
+               skip_prefix(arg, ":", &arg);
+               trailers_atom_parser(format, atom, *arg ? arg : NULL);
+       } else if (skip_prefix(arg, "lines=", &arg)) {
                atom->u.contents.option = C_LINES;
                if (strtoul_ui(arg, 10, &atom->u.contents.nlines))
                        die(_("positive value expected contents:lines=%s"), arg);
@@@ -295,7 -311,9 +311,7 @@@ static void if_atom_parser(const struc
  
  static void head_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
  {
 -      struct object_id unused;
 -
 -      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL);
 +      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
  }
  
  static struct {
@@@ -413,16 -431,8 +429,16 @@@ static int parse_ref_filter_atom(const 
        REALLOC_ARRAY(used_atom, used_atom_cnt);
        used_atom[at].name = xmemdupz(atom, ep - atom);
        used_atom[at].type = valid_atom[i].cmp_type;
 -      if (arg)
 +      if (arg) {
                arg = used_atom[at].name + (arg - atom) + 1;
 +              if (!*arg) {
 +                      /*
 +                       * Treat empty sub-arguments list as NULL (i.e.,
 +                       * "%(atom:)" is equivalent to "%(atom)").
 +                       */
 +                      arg = NULL;
 +              }
 +      }
        memset(&used_atom[at].u, 0, sizeof(used_atom[at].u));
        if (valid_atom[i].parser)
                valid_atom[i].parser(format, &used_atom[at], arg);
@@@ -1048,7 -1058,7 +1064,7 @@@ static void grab_sub_body_contents(stru
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
-                   strcmp(name, "trailers") &&
+                   !starts_with(name, "trailers") &&
                    !starts_with(name, "contents"))
                        continue;
                if (!subpos)
                        append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines);
                        v->s = strbuf_detach(&s, NULL);
                } else if (atom->u.contents.option == C_TRAILERS) {
-                       struct trailer_info info;
+                       struct strbuf s = STRBUF_INIT;
  
-                       /* Search for trailer info */
-                       trailer_info_get(&info, subpos);
-                       v->s = xmemdupz(info.trailer_start,
-                                       info.trailer_end - info.trailer_start);
-                       trailer_info_release(&info);
+                       /* Format the trailer info according to the trailer_opts given */
+                       format_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);
+                       v->s = strbuf_detach(&s, NULL);
                } else if (atom->u.contents.option == C_BARE)
                        v->s = xstrdup(subpos);
        }
@@@ -1323,8 -1332,9 +1338,8 @@@ static void populate_value(struct ref_a
        ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
  
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
 -              struct object_id unused1;
                ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
 -                                           unused1.hash, NULL);
 +                                           NULL, NULL);
                if (!ref->symref)
                        ref->symref = "";
        }
diff --combined t/t6300-for-each-ref.sh
index a6f51a50033d1e0d25b5adb64ad38142d7f136e0,3bdfa0255900cb578810be09e69d0a43075812fa..416ff7d0b832d2898173dd2ac83201cf8e02955b
@@@ -51,7 -51,6 +51,7 @@@ test_atom() 
  }
  
  test_atom head refname refs/heads/master
 +test_atom head refname: refs/heads/master
  test_atom head refname:short master
  test_atom head refname:lstrip=1 heads/master
  test_atom head refname:lstrip=2 master
@@@ -426,7 -425,8 +426,7 @@@ test_expect_success 'set up color tests
  '
  
  test_expect_success TTY '%(color) shows color with a tty' '
 -      test_terminal env TERM=vt100 \
 -              git for-each-ref --format="$color_format" >actual.raw &&
 +      test_terminal git for-each-ref --format="$color_format" >actual.raw &&
        test_decode_color <actual.raw >actual &&
        test_cmp expected.color actual
  '
@@@ -436,8 -436,8 +436,8 @@@ test_expect_success '%(color) does not 
        test_cmp expected.bare actual
  '
  
 -test_expect_success 'color.ui=always can override tty check' '
 -      git -c color.ui=always for-each-ref --format="$color_format" >actual.raw &&
 +test_expect_success '--color can override tty check' '
 +      git for-each-ref --color --format="$color_format" >actual.raw &&
        test_decode_color <actual.raw >actual &&
        test_cmp expected.color actual
  '
@@@ -605,18 -605,104 +605,104 @@@ test_expect_success 'do not dereferenc
  cat >trailers <<EOF
  Reviewed-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
  
- 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 &&
        git add two &&
-       git commit -F - <<-EOF &&
+       git commit -F - <<-EOF
        trailers: this commit message has trailers
  
        Some message contents
  
        $(cat trailers)
        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 &&
        sanitize_pgp <actual >actual.clean &&
        # git for-each-ref ends with a blank line