pretty: allow showing specific trailers

Adds a new "key=X" option to "%(trailers)" which will cause it to only print trailer lines which match any of the specified keys. Signed-off-by: Anders Waldenborg <anders@0x63.nu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Anders Waldenborg committed Jan 28, 2019 at 22:33 UTC 250bea0c1652ad546cd0455852bd734e4820ec46
5 files changed +107 -6
Documentation/pretty-formats.txt
+8
@@ -225,6 +225,14 @@ endif::git-rev-list[]
225 linkgit:git-interpret-trailers[1]. The
226 `trailers` string may be followed by a colon
227 and zero or more comma-separated options:
228 +** 'key=<K>': only show trailers with specified key. Matching is done
229 + case-insensitively and trailing colon is optional. If option is
230 + given multiple times trailer lines matching any of the keys are
231 + shown. This option automatically enables the `only` option so that
232 + non-trailer lines in the trailer block are hidden. If that is not
233 + desired it can be disabled with `only=false`. E.g.,
234 + `%(trailers:key=Reviewed-by)` shows trailer lines with key
235 + `Reviewed-by`.
236 ** 'only[=val]': select whether non-trailer lines from the trailer
237 block should be included. The `only` keyword may optionally be
238 followed by an equal sign and one of `true`, `on`, `yes` to omit or
pretty.c
+34 -2
@@ -1115,6 +1115,19 @@ static int match_placeholder_bool_arg(const char *to_parse, const char *candidat
1115 return 1;
1116 }
1117
1118 +static int format_trailer_match_cb(const struct strbuf *key, void *ud)
1119 +{
1120 + const struct string_list *list = ud;
1121 + const struct string_list_item *item;
1122 +
1123 + for_each_string_list_item (item, list) {
1124 + if (key->len == (uintptr_t)item->util &&
1125 + !strncasecmp(item->string, key->buf, key->len))
1126 + return 1;
1127 + }
1128 + return 0;
1129 +}
1130 +
1131 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1132 const char *placeholder,
1133 void *context)
@@ -1353,6 +1366,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1366
1367 if (skip_prefix(placeholder, "(trailers", &arg)) {
1368 struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
1369 + struct string_list filter_list = STRING_LIST_INIT_NODUP;
1370 size_t ret = 0;
1371
1372 opts.no_divider = 1;
@@ -1360,8 +1374,24 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1374 if (*arg == ':') {
1375 arg++;
1376 for (;;) {
1363 - if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
1364 - !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold))
1377 + const char *argval;
1378 + size_t arglen;
1379 +
1380 + if (match_placeholder_arg_value(arg, "key", &arg, &argval, &arglen)) {
1381 + uintptr_t len = arglen;
1382 +
1383 + if (!argval)
1384 + goto trailer_out;
1385 +
1386 + if (len && argval[len - 1] == ':')
1387 + len--;
1388 + string_list_append(&filter_list, argval)->util = (char *)len;
1389 +
1390 + opts.filter = format_trailer_match_cb;
1391 + opts.filter_data = &filter_list;
1392 + opts.only_trailers = 1;
1393 + } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
1394 + !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold))
1395 break;
1396 }
1397 }
@@ -1369,6 +1399,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1399 format_trailers_from_commit(sb, msg + c->subject_off, &opts);
1400 ret = arg - placeholder + 1;
1401 }
1402 + trailer_out:
1403 + string_list_clear(&filter_list, 0);
1404 return ret;
1405 }
1406
t/t4205-log-pretty-formats.sh
+57
@@ -616,6 +616,63 @@ test_expect_success ':only and :unfold work together' '
616 test_cmp expect actual
617 '
618
619 +test_expect_success 'pretty format %(trailers:key=foo) shows that trailer' '
620 + git log --no-walk --pretty="format:%(trailers:key=Acked-by)" >actual &&
621 + echo "Acked-by: A U Thor <author@example.com>" >expect &&
622 + test_cmp expect actual
623 +'
624 +
625 +test_expect_success 'pretty format %(trailers:key=foo) is case insensitive' '
626 + git log --no-walk --pretty="format:%(trailers:key=AcKed-bY)" >actual &&
627 + echo "Acked-by: A U Thor <author@example.com>" >expect &&
628 + test_cmp expect actual
629 +'
630 +
631 +test_expect_success 'pretty format %(trailers:key=foo:) trailing colon also works' '
632 + git log --no-walk --pretty="format:%(trailers:key=Acked-by:)" >actual &&
633 + echo "Acked-by: A U Thor <author@example.com>" >expect &&
634 + test_cmp expect actual
635 +'
636 +
637 +test_expect_success 'pretty format %(trailers:key=foo) multiple keys' '
638 + git log --no-walk --pretty="format:%(trailers:key=Acked-by:,key=Signed-off-By)" >actual &&
639 + grep -v patch.description <trailers >expect &&
640 + test_cmp expect actual
641 +'
642 +
643 +test_expect_success '%(trailers:key=nonexistant) becomes empty' '
644 + git log --no-walk --pretty="x%(trailers:key=Nacked-by)x" >actual &&
645 + echo "xx" >expect &&
646 + test_cmp expect actual
647 +'
648 +
649 +test_expect_success '%(trailers:key=foo) handles multiple lines even if folded' '
650 + git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by)" >actual &&
651 + grep -v patch.description <trailers | grep -v Acked-by >expect &&
652 + test_cmp expect actual
653 +'
654 +
655 +test_expect_success '%(trailers:key=foo,unfold) properly unfolds' '
656 + git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by,unfold)" >actual &&
657 + unfold <trailers | grep Signed-off-by >expect &&
658 + test_cmp expect actual
659 +'
660 +
661 +test_expect_success 'pretty format %(trailers:key=foo,only=no) also includes nontrailer lines' '
662 + git log --no-walk --pretty="format:%(trailers:key=Acked-by,only=no)" >actual &&
663 + {
664 + echo "Acked-by: A U Thor <author@example.com>" &&
665 + grep patch.description <trailers
666 + } >expect &&
667 + test_cmp expect actual
668 +'
669 +
670 +test_expect_success '%(trailers:key) without value is error' '
671 + git log --no-walk --pretty="tformat:%(trailers:key)" >actual &&
672 + echo "%(trailers:key)" >expect &&
673 + test_cmp expect actual
674 +'
675 +
676 test_expect_success 'trailer parsing not fooled by --- line' '
677 git commit --allow-empty -F - <<-\EOF &&
678 this is the subject
trailer.c
+6 -4
@@ -1132,7 +1132,7 @@ static void format_trailer_info(struct strbuf *out,
1132 size_t i;
1133
1134 /* If we want the whole block untouched, we can take the fast path. */
1135 - if (!opts->only_trailers && !opts->unfold) {
1135 + if (!opts->only_trailers && !opts->unfold && !opts->filter) {
1136 strbuf_add(out, info->trailer_start,
1137 info->trailer_end - info->trailer_start);
1138 return;
@@ -1147,10 +1147,12 @@ static void format_trailer_info(struct strbuf *out,
1147 struct strbuf val = STRBUF_INIT;
1148
1149 parse_trailer(&tok, &val, NULL, trailer, separator_pos);
1150 - if (opts->unfold)
1151 - unfold_value(&val);
1150 + if (!opts->filter || opts->filter(&tok, opts->filter_data)) {
1151 + if (opts->unfold)
1152 + unfold_value(&val);
1153
1153 - strbuf_addf(out, "%s: %s\n", tok.buf, val.buf);
1154 + strbuf_addf(out, "%s: %s\n", tok.buf, val.buf);
1155 + }
1156 strbuf_release(&tok);
1157 strbuf_release(&val);
1158
trailer.h
+2
@@ -72,6 +72,8 @@ struct process_trailer_options {
72 int only_input;
73 int unfold;
74 int no_divider;
75 + int (*filter)(const struct strbuf *, void *);
76 + void *filter_data;
77 };
78
79 #define PROCESS_TRAILER_OPTIONS_INIT {0}