pretty: support normalization options for %(trailers)

The interpret-trailers command recently learned some options to make its output easier to parse (for a caller whose only interested in picking out the trailer values). But it's not very efficient for asking for the trailers of many commits in a single invocation. We already have "%(trailers)" to do that, but it doesn't know about unfolding or omitting non-trailers. Let's plumb those options through, so you can have the best of both. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Aug 15, 2017 at 06:25 UTC 58311c66fd316dff8f2c68a634ca0cf968227870
4 files changed +79 -6
Documentation/pretty-formats.txt
+4 -1
@@ -201,7 +201,10 @@ endif::git-rev-list[]
201 - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)'
202 respectively, but padding both sides (i.e. the text is centered)
203 - %(trailers): display the trailers of the body as interpreted by
204 - linkgit:git-interpret-trailers[1]
204 + linkgit:git-interpret-trailers[1]. If the `:only` option is given,
205 + omit non-trailer lines from the trailer block. If the `:unfold`
206 + option is given, behave as if interpret-trailer's `--unfold` option
207 + was given. E.g., `%(trailers:only:unfold)` to do both.
208
209 NOTE: Some placeholders may depend on other options given to the
210 revision traversal engine. For example, the `%g*` reflog options will
pretty.c
+12 -3
@@ -1044,6 +1044,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1044 const struct commit *commit = c->commit;
1045 const char *msg = c->message;
1046 struct commit_list *p;
1047 + const char *arg;
1048 int ch;
1049
1050 /* these are independent of the commit */
@@ -1262,10 +1263,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1263 return 1;
1264 }
1265
1265 - if (starts_with(placeholder, "(trailers)")) {
1266 + if (skip_prefix(placeholder, "(trailers", &arg)) {
1267 struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
1267 - format_trailers_from_commit(sb, msg + c->subject_off, &opts);
1268 - return strlen("(trailers)");
1268 + while (*arg == ':') {
1269 + if (skip_prefix(arg, ":only", &arg))
1270 + opts.only_trailers = 1;
1271 + else if (skip_prefix(arg, ":unfold", &arg))
1272 + opts.unfold = 1;
1273 + }
1274 + if (*arg == ')') {
1275 + format_trailers_from_commit(sb, msg + c->subject_off, &opts);
1276 + return arg - placeholder + 1;
1277 + }
1278 }
1279
1280 return 0; /* unknown placeholder */
t/t4205-log-pretty-formats.sh
+33
@@ -543,6 +543,10 @@ Signed-off-by: A U Thor
543 <author@example.com>
544 EOF
545
546 +unfold () {
547 + perl -0pe 's/\n\s+/ /'
548 +}
549 +
550 test_expect_success 'set up trailer tests' '
551 echo "Some contents" >trailerfile &&
552 git add trailerfile &&
@@ -565,4 +569,33 @@ test_expect_success 'pretty format %(trailers) shows trailers' '
569 test_cmp expect actual
570 '
571
572 +test_expect_success '%(trailers:only) shows only "key: value" trailers' '
573 + git log --no-walk --pretty="%(trailers:only)" >actual &&
574 + {
575 + grep -v patch.description <trailers &&
576 + echo
577 + } >expect &&
578 + test_cmp expect actual
579 +'
580 +
581 +test_expect_success '%(trailers:unfold) unfolds trailers' '
582 + git log --no-walk --pretty="%(trailers:unfold)" >actual &&
583 + {
584 + unfold <trailers &&
585 + echo
586 + } >expect &&
587 + test_cmp expect actual
588 +'
589 +
590 +test_expect_success ':only and :unfold work together' '
591 + git log --no-walk --pretty="%(trailers:only:unfold)" >actual &&
592 + git log --no-walk --pretty="%(trailers:unfold:only)" >reverse &&
593 + test_cmp actual reverse &&
594 + {
595 + grep -v patch.description <trailers | unfold &&
596 + echo
597 + } >expect &&
598 + test_cmp expect actual
599 +'
600 +
601 test_done
trailer.c
+30 -2
@@ -1095,8 +1095,36 @@ static void format_trailer_info(struct strbuf *out,
1095 const struct trailer_info *info,
1096 const struct process_trailer_options *opts)
1097 {
1098 - strbuf_add(out, info->trailer_start,
1099 - info->trailer_end - info->trailer_start);
1098 + int i;
1099 +
1100 + /* If we want the whole block untouched, we can take the fast path. */
1101 + if (!opts->only_trailers && !opts->unfold) {
1102 + strbuf_add(out, info->trailer_start,
1103 + info->trailer_end - info->trailer_start);
1104 + return;
1105 + }
1106 +
1107 + for (i = 0; i < info->trailer_nr; i++) {
1108 + char *trailer = info->trailers[i];
1109 + int separator_pos = find_separator(trailer, separators);
1110 +
1111 + if (separator_pos >= 1) {
1112 + struct strbuf tok = STRBUF_INIT;
1113 + struct strbuf val = STRBUF_INIT;
1114 +
1115 + parse_trailer(&tok, &val, NULL, trailer, separator_pos);
1116 + if (opts->unfold)
1117 + unfold_value(&val);
1118 +
1119 + strbuf_addf(out, "%s: %s\n", tok.buf, val.buf);
1120 + strbuf_release(&tok);
1121 + strbuf_release(&val);
1122 +
1123 + } else if (!opts->only_trailers) {
1124 + strbuf_addstr(out, trailer);
1125 + }
1126 + }
1127 +
1128 }
1129
1130 void format_trailers_from_commit(struct strbuf *out, const char *msg,