pretty format %(trailers): add a "key_value_separator"

Add a "key_value_separator" option to the "%(trailers)" pretty format, to go along with the existing "separator" argument. In combination these two options make it trivial to produce machine-readable (e.g. \0 and \0\0-delimited) format output. As elaborated on in a previous commit which added "keyonly" it was needlessly tedious to extract structured data from "%(trailers)" before the addition of this "key_value_separator" option. As seen by the test being added here extracting this data now becomes trivial. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Dec 9, 2020 at 16:52 UTC 058761f1c19b58afd6906672fc013327eb2096c6
5 files changed +55 -3
Documentation/pretty-formats.txt
+4
@@ -284,6 +284,10 @@ option is given with no value, it's enabled.
284 `%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
285 ** 'keyonly[=<BOOL>]': only show the key part of the trailer.
286 ** 'valueonly[=<BOOL>]': only show the value part of the trailer.
287 +** 'key_value_separator=<SEP>': specify a separator inserted between
288 + trailer lines. When this option is not given each trailer key-value
289 + pair is separated by ": ". Otherwise it shares the same semantics
290 + as 'separator=<SEP>' above.
291
292 NOTE: Some placeholders may depend on other options given to the
293 revision traversal engine. For example, the `%g*` reflog options will
pretty.c
+9
@@ -1418,6 +1418,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1418 struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
1419 struct string_list filter_list = STRING_LIST_INIT_NODUP;
1420 struct strbuf sepbuf = STRBUF_INIT;
1421 + struct strbuf kvsepbuf = STRBUF_INIT;
1422 size_t ret = 0;
1423
1424 opts.no_divider = 1;
@@ -1449,6 +1450,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1450 strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
1451 free(fmt);
1452 opts.separator = &sepbuf;
1453 + } else if (match_placeholder_arg_value(arg, "key_value_separator", &arg, &argval, &arglen)) {
1454 + char *fmt;
1455 +
1456 + strbuf_reset(&kvsepbuf);
1457 + fmt = xstrndup(argval, arglen);
1458 + strbuf_expand(&kvsepbuf, fmt, strbuf_expand_literal_cb, NULL);
1459 + free(fmt);
1460 + opts.key_value_separator = &kvsepbuf;
1461 } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
1462 !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) &&
1463 !match_placeholder_bool_arg(arg, "keyonly", &arg, &opts.key_only) &&
t/t4205-log-pretty-formats.sh
+33
@@ -776,6 +776,39 @@ test_expect_success 'pretty format %(trailers:separator=X,unfold) changes separa
776 test_cmp expect actual
777 '
778
779 +test_expect_success 'pretty format %(trailers:key_value_separator) changes key-value separator' '
780 + git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00)X" >actual &&
781 + (
782 + printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
783 + printf "Acked-by\0A U Thor <author@example.com>\n" &&
784 + printf "[ v2 updated patch description ]\n" &&
785 + printf "Signed-off-by\0A U Thor\n <author@example.com>\nX"
786 + ) >expect &&
787 + test_cmp expect actual
788 +'
789 +
790 +test_expect_success 'pretty format %(trailers:key_value_separator,unfold) changes key-value separator' '
791 + git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00,unfold)X" >actual &&
792 + (
793 + printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
794 + printf "Acked-by\0A U Thor <author@example.com>\n" &&
795 + printf "[ v2 updated patch description ]\n" &&
796 + printf "Signed-off-by\0A U Thor <author@example.com>\nX"
797 + ) >expect &&
798 + test_cmp expect actual
799 +'
800 +
801 +test_expect_success 'pretty format %(trailers:separator,key_value_separator) changes both separators' '
802 + git log --no-walk --pretty=format:"%(trailers:separator=%x00,key_value_separator=%x00%x00,unfold)" >actual &&
803 + (
804 + printf "Signed-off-by\0\0A U Thor <author@example.com>\0" &&
805 + printf "Acked-by\0\0A U Thor <author@example.com>\0" &&
806 + printf "[ v2 updated patch description ]\0" &&
807 + printf "Signed-off-by\0\0A U Thor <author@example.com>"
808 + ) >expect &&
809 + test_cmp expect actual
810 +'
811 +
812 test_expect_success 'pretty format %(trailers) combining separator/key/keyonly/valueonly' '
813 git commit --allow-empty -F - <<-\EOF &&
814 Important fix
trailer.c
+8 -3
@@ -1132,7 +1132,8 @@ static void format_trailer_info(struct strbuf *out,
1132
1133 /* If we want the whole block untouched, we can take the fast path. */
1134 if (!opts->only_trailers && !opts->unfold && !opts->filter &&
1135 - !opts->separator && !opts->key_only && !opts->value_only) {
1135 + !opts->separator && !opts->key_only && !opts->value_only &&
1136 + !opts->key_value_separator) {
1137 strbuf_add(out, info->trailer_start,
1138 info->trailer_end - info->trailer_start);
1139 return;
@@ -1155,8 +1156,12 @@ static void format_trailer_info(struct strbuf *out,
1156 strbuf_addbuf(out, opts->separator);
1157 if (!opts->value_only)
1158 strbuf_addbuf(out, &tok);
1158 - if (!opts->key_only && !opts->value_only)
1159 - strbuf_addstr(out, ": ");
1159 + if (!opts->key_only && !opts->value_only) {
1160 + if (opts->key_value_separator)
1161 + strbuf_addbuf(out, opts->key_value_separator);
1162 + else
1163 + strbuf_addstr(out, ": ");
1164 + }
1165 if (!opts->key_only)
1166 strbuf_addbuf(out, &val);
1167 if (!opts->separator)
trailer.h
+1
@@ -74,6 +74,7 @@ struct process_trailer_options {
74 int key_only;
75 int value_only;
76 const struct strbuf *separator;
77 + const struct strbuf *key_value_separator;
78 int (*filter)(const struct strbuf *, void *);
79 void *filter_data;
80 };