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
};