pretty: add support for separator option in %(trailers)

By default trailer lines are terminated by linebreaks ('\n'). By specifying the new 'separator' option they will instead be separated by user provided string and have separator semantics rather than terminator semantics. The separator string can contain the literal formatting codes %n and %xNN allowing it to be things that are otherwise hard to type such as %x00, or comma and end-parenthesis which would break parsing. E.g: $ git log --pretty='%(trailers:key=Reviewed-by,valueonly,separator=%x00)' 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 0b691d8685131c2c10e1a2cf2acc9b8920c5365f
5 files changed +69 -2
Documentation/pretty-formats.txt
+9
@@ -239,6 +239,15 @@ endif::git-rev-list[]
239 `false`, `off`, `no` to show the non-trailer lines. If option is
240 given without value it is enabled. If given multiple times the last
241 value is used.
242 +** 'separator=<SEP>': specify a separator inserted between trailer
243 + lines. When this option is not given each trailer line is
244 + terminated with a line feed character. The string SEP may contain
245 + the literal formatting codes described above. To use comma as
246 + separator one must use `%x2C` as it would otherwise be parsed as
247 + next option. If separator option is given multiple times only the
248 + last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
249 + shows all trailer lines whose key is "Ticket" separated by a comma
250 + and a space.
251 ** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold`
252 option was given. In same way as to for `only` it can be followed
253 by an equal sign and explicit value. E.g.,
pretty.c
+10
@@ -1361,6 +1361,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1361 if (skip_prefix(placeholder, "(trailers", &arg)) {
1362 struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
1363 struct string_list filter_list = STRING_LIST_INIT_NODUP;
1364 + struct strbuf sepbuf = STRBUF_INIT;
1365 size_t ret = 0;
1366
1367 opts.no_divider = 1;
@@ -1384,6 +1385,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1385 opts.filter = format_trailer_match_cb;
1386 opts.filter_data = &filter_list;
1387 opts.only_trailers = 1;
1388 + } else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) {
1389 + char *fmt;
1390 +
1391 + strbuf_reset(&sepbuf);
1392 + fmt = xstrndup(argval, arglen);
1393 + strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
1394 + free(fmt);
1395 + opts.separator = &sepbuf;
1396 } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
1397 !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) &&
1398 !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only))
@@ -1396,6 +1405,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1405 }
1406 trailer_out:
1407 string_list_clear(&filter_list, 0);
1408 + strbuf_release(&sepbuf);
1409 return ret;
1410 }
1411
t/t4205-log-pretty-formats.sh
+36
@@ -679,6 +679,42 @@ test_expect_success '%(trailers:key=foo,valueonly) shows only value' '
679 test_cmp expect actual
680 '
681
682 +test_expect_success 'pretty format %(trailers:separator) changes separator' '
683 + git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual &&
684 + 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 &&
685 + test_cmp expect actual
686 +'
687 +
688 +test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' '
689 + git commit --allow-empty -F - <<-\EOF &&
690 + Important fix
691 +
692 + The fix is explained here
693 +
694 + Closes: #1234
695 + EOF
696 +
697 + git commit --allow-empty -F - <<-\EOF &&
698 + Another fix
699 +
700 + The fix is explained here
701 +
702 + Closes: #567
703 + Closes: #890
704 + EOF
705 +
706 + git commit --allow-empty -F - <<-\EOF &&
707 + Does not close any tickets
708 + EOF
709 +
710 + git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,valueonly)" HEAD~3.. >actual &&
711 + test_write_lines \
712 + "Does not close any tickets" \
713 + "Another fix #567, #890" \
714 + "Important fix #1234" >expect &&
715 + test_cmp expect actual
716 +'
717 +
718 test_expect_success 'trailer parsing not fooled by --- line' '
719 git commit --allow-empty -F - <<-\EOF &&
720 this is the subject
trailer.c
+13 -2
@@ -1129,10 +1129,11 @@ static void format_trailer_info(struct strbuf *out,
1129 const struct trailer_info *info,
1130 const struct process_trailer_options *opts)
1131 {
1132 + size_t origlen = out->len;
1133 size_t i;
1134
1135 /* If we want the whole block untouched, we can take the fast path. */
1135 - if (!opts->only_trailers && !opts->unfold && !opts->filter) {
1136 + if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) {
1137 strbuf_add(out, info->trailer_start,
1138 info->trailer_end - info->trailer_start);
1139 return;
@@ -1150,16 +1151,26 @@ static void format_trailer_info(struct strbuf *out,
1151 if (!opts->filter || opts->filter(&tok, opts->filter_data)) {
1152 if (opts->unfold)
1153 unfold_value(&val);
1154 +
1155 + if (opts->separator && out->len != origlen)
1156 + strbuf_addbuf(out, opts->separator);
1157 if (!opts->value_only)
1158 strbuf_addf(out, "%s: ", tok.buf);
1159 strbuf_addbuf(out, &val);
1156 - strbuf_addch(out, '\n');
1160 + if (!opts->separator)
1161 + strbuf_addch(out, '\n');
1162 }
1163 strbuf_release(&tok);
1164 strbuf_release(&val);
1165
1166 } else if (!opts->only_trailers) {
1167 + if (opts->separator && out->len != origlen) {
1168 + strbuf_addbuf(out, opts->separator);
1169 + }
1170 strbuf_addstr(out, trailer);
1171 + if (opts->separator) {
1172 + strbuf_rtrim(out);
1173 + }
1174 }
1175 }
1176
trailer.h
+1
@@ -73,6 +73,7 @@ struct process_trailer_options {
73 int unfold;
74 int no_divider;
75 int value_only;
76 + const struct strbuf *separator;
77 int (*filter)(const struct strbuf *, void *);
78 void *filter_data;
79 };