pretty: add %(decorate[:<options>]) format

Add %(decorate[:<options>]) format that lists ref names similarly to the %d format, but which allows the otherwise fixed prefix, suffix and separator strings to be customized. Omitted options default to the strings used in %d. Rename expand_separator() function used to expand %x literal formatting codes to expand_string_arg(), as it is now used on strings other than separators. Examples: - %(decorate) is equivalent to %d. - %(decorate:prefix=,suffix=) is equivalent to %D. - %(decorate:prefix=[,suffix=],separator=%x3B) produces a list enclosed in square brackets and separated by semicolons. Test the format in t4205-log-pretty-formats.sh and document it in pretty-formats.txt. Signed-off-by: Andy Koppe <andy.koppe@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Andy Koppe committed Aug 20, 2023 at 19:50 UTC a58dd835e9536cf9e7adcd404d008a44d29af804
3 files changed +92 -4
Documentation/pretty-formats.txt
+10
@@ -224,6 +224,16 @@ The placeholders are:
224 linkgit:git-rev-list[1])
225 '%d':: ref names, like the --decorate option of linkgit:git-log[1]
226 '%D':: ref names without the " (", ")" wrapping.
227 +'%(decorate[:<options>])'::
228 +ref names with custom decorations. The `decorate` string may be followed by a
229 +colon and zero or more comma-separated options. Option values may contain
230 +literal formatting codes. These must be used for commas (`%x2C`) and closing
231 +parentheses (`%x29`), due to their role in the option syntax.
232 ++
233 +** 'prefix=<value>': Shown before the list of ref names. Defaults to "{nbsp}`(`".
234 +** 'suffix=<value>': Shown after the list of ref names. Defaults to "`)`".
235 +** 'separator=<value>': Shown between ref names. Defaults to "`,`{nbsp}".
236 +
237 '%(describe[:<options>])'::
238 human-readable name, like linkgit:git-describe[1]; empty string for
239 undescribable commits. The `describe` string may be followed by a colon and
pretty.c
+55 -4
@@ -1252,8 +1252,8 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud)
1252 return 0;
1253 }
1254
1255 -static struct strbuf *expand_separator(struct strbuf *sb,
1256 - const char *argval, size_t arglen)
1255 +static struct strbuf *expand_string_arg(struct strbuf *sb,
1256 + const char *argval, size_t arglen)
1257 {
1258 char *fmt = xstrndup(argval, arglen);
1259 const char *format = fmt;
@@ -1301,9 +1301,9 @@ int format_set_trailers_options(struct process_trailer_options *opts,
1301 opts->filter_data = filter_list;
1302 opts->only_trailers = 1;
1303 } else if (match_placeholder_arg_value(*arg, "separator", arg, &argval, &arglen)) {
1304 - opts->separator = expand_separator(sepbuf, argval, arglen);
1304 + opts->separator = expand_string_arg(sepbuf, argval, arglen);
1305 } else if (match_placeholder_arg_value(*arg, "key_value_separator", arg, &argval, &arglen)) {
1306 - opts->key_value_separator = expand_separator(kvsepbuf, argval, arglen);
1306 + opts->key_value_separator = expand_string_arg(kvsepbuf, argval, arglen);
1307 } else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) &&
1308 !match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) &&
1309 !match_placeholder_bool_arg(*arg, "keyonly", arg, &opts->key_only) &&
@@ -1384,6 +1384,40 @@ static size_t parse_describe_args(const char *start, struct strvec *args)
1384 return arg - start;
1385 }
1386
1387 +
1388 +static int parse_decoration_option(const char **arg,
1389 + const char *name,
1390 + char **opt)
1391 +{
1392 + const char *argval;
1393 + size_t arglen;
1394 +
1395 + if (match_placeholder_arg_value(*arg, name, arg, &argval, &arglen)) {
1396 + struct strbuf sb = STRBUF_INIT;
1397 +
1398 + expand_string_arg(&sb, argval, arglen);
1399 + *opt = strbuf_detach(&sb, NULL);
1400 + return 1;
1401 + }
1402 + return 0;
1403 +}
1404 +
1405 +static void parse_decoration_options(const char **arg,
1406 + struct decoration_options *opts)
1407 +{
1408 + while (parse_decoration_option(arg, "prefix", &opts->prefix) ||
1409 + parse_decoration_option(arg, "suffix", &opts->suffix) ||
1410 + parse_decoration_option(arg, "separator", &opts->separator))
1411 + ;
1412 +}
1413 +
1414 +static void free_decoration_options(const struct decoration_options *opts)
1415 +{
1416 + free(opts->prefix);
1417 + free(opts->suffix);
1418 + free(opts->separator);
1419 +}
1420 +
1421 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1422 const char *placeholder,
1423 void *context)
@@ -1645,6 +1679,23 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1679 return 2;
1680 }
1681
1682 + if (skip_prefix(placeholder, "(decorate", &arg)) {
1683 + struct decoration_options opts = { NULL };
1684 + size_t ret = 0;
1685 +
1686 + if (*arg == ':') {
1687 + arg++;
1688 + parse_decoration_options(&arg, &opts);
1689 + }
1690 + if (*arg == ')') {
1691 + format_decorations(sb, commit, c->auto_color, &opts);
1692 + ret = arg - placeholder + 1;
1693 + }
1694 +
1695 + free_decoration_options(&opts);
1696 + return ret;
1697 + }
1698 +
1699 /* For the rest we have to parse the commit header. */
1700 if (!c->commit_header_parsed) {
1701 msg = c->message =
t/t4205-log-pretty-formats.sh
+27
@@ -576,6 +576,33 @@ test_expect_success 'clean log decoration' '
576 test_cmp expected actual1
577 '
578
579 +test_expect_success 'pretty format %decorate' '
580 + git checkout -b foo &&
581 + git commit --allow-empty -m "new commit" &&
582 + git tag bar &&
583 + git branch qux &&
584 +
585 + echo " (HEAD -> foo, tag: bar, qux)" >expect1 &&
586 + git log --format="%(decorate)" -1 >actual1 &&
587 + test_cmp expect1 actual1 &&
588 +
589 + echo "HEAD -> foo, tag: bar, qux" >expect2 &&
590 + git log --format="%(decorate:prefix=,suffix=)" -1 >actual2 &&
591 + test_cmp expect2 actual2 &&
592 +
593 + echo "[ HEAD -> foo; tag: bar; qux ]" >expect3 &&
594 + git log --format="%(decorate:prefix=[ ,suffix= ],separator=%x3B )" \
595 + -1 >actual3 &&
596 + test_cmp expect3 actual3 &&
597 +
598 + # Try with a typo (in "separator"), in which case the placeholder should
599 + # not be replaced.
600 + echo "%(decorate:prefix=[ ,suffix= ],separater=; )" >expect4 &&
601 + git log --format="%(decorate:prefix=[ ,suffix= ],separater=%x3B )" \
602 + -1 >actual4 &&
603 + test_cmp expect4 actual4
604 +'
605 +
606 cat >trailers <<EOF
607 Signed-off-by: A U Thor <author@example.com>
608 Acked-by: A U Thor <author@example.com>