git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v5 6/8] pretty: add %(decorate[:<options>]) format

From
Andy Koppe <andy.koppe@gmail.com>
Date
Aug 20, 2023, 18:50 UTC
Message-ID
<20230820185009.20095-7-andy.koppe@gmail.com>
In-Reply-To
<20230820185009.20095-1-andy.koppe@gmail.com>

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>
---
 Documentation/pretty-formats.txt | 10 ++++++
 pretty.c                         | 59 +++++++++++++++++++++++++++++---
 t/t4205-log-pretty-formats.sh    | 27 +++++++++++++++
 3 files changed, 92 insertions(+), 4 deletions(-)
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 851a9878e6..709d85af21 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -224,6 +224,16 @@ The placeholders are:
 	linkgit:git-rev-list[1])
 '%d':: ref names, like the --decorate option of linkgit:git-log[1]
 '%D':: ref names without the " (", ")" wrapping.
+'%(decorate[:<options>])'::
+ref names with custom decorations. The `decorate` string may be followed by a
+colon and zero or more comma-separated options. Option values may contain
+literal formatting codes. These must be used for commas (`%x2C`) and closing
+parentheses (`%x29`), due to their role in the option syntax.
++
+** 'prefix=<value>': Shown before the list of ref names.  Defaults to "{nbsp}`(`".
+** 'suffix=<value>': Shown after the list of ref names.  Defaults to "`)`".
+** 'separator=<value>': Shown between ref names.  Defaults to "`,`{nbsp}".
+
 '%(describe[:<options>])'::
 human-readable name, like linkgit:git-describe[1]; empty string for
 undescribable commits.  The `describe` string may be followed by a colon and
diff --git a/pretty.c b/pretty.c
index 69b6db3340..1639efe2f8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1252,8 +1252,8 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud)
 	return 0;
 }
 
-static struct strbuf *expand_separator(struct strbuf *sb,
-				       const char *argval, size_t arglen)
+static struct strbuf *expand_string_arg(struct strbuf *sb,
+					const char *argval, size_t arglen)
 {
 	char *fmt = xstrndup(argval, arglen);
 	const char *format = fmt;
@@ -1301,9 +1301,9 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 			opts->filter_data = filter_list;
 			opts->only_trailers = 1;
 		} else if (match_placeholder_arg_value(*arg, "separator", arg, &argval, &arglen)) {
-			opts->separator = expand_separator(sepbuf, argval, arglen);
+			opts->separator = expand_string_arg(sepbuf, argval, arglen);
 		} else if (match_placeholder_arg_value(*arg, "key_value_separator", arg, &argval, &arglen)) {
-			opts->key_value_separator = expand_separator(kvsepbuf, argval, arglen);
+			opts->key_value_separator = expand_string_arg(kvsepbuf, argval, arglen);
 		} else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) &&
 			   !match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) &&
 			   !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)
 	return arg - start;
 }
 
+
+static int parse_decoration_option(const char **arg,
+				   const char *name,
+				   char **opt)
+{
+	const char *argval;
+	size_t arglen;
+
+	if (match_placeholder_arg_value(*arg, name, arg, &argval, &arglen)) {
+		struct strbuf sb = STRBUF_INIT;
+
+		expand_string_arg(&sb, argval, arglen);
+		*opt = strbuf_detach(&sb, NULL);
+		return 1;
+	}
+	return 0;
+}
+
+static void parse_decoration_options(const char **arg,
+				     struct decoration_options *opts)
+{
+	while (parse_decoration_option(arg, "prefix", &opts->prefix) ||
+	       parse_decoration_option(arg, "suffix", &opts->suffix) ||
+	       parse_decoration_option(arg, "separator", &opts->separator))
+		;
+}
+
+static void free_decoration_options(const struct decoration_options *opts)
+{
+	free(opts->prefix);
+	free(opts->suffix);
+	free(opts->separator);
+}
+
 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 				const char *placeholder,
 				void *context)
@@ -1645,6 +1679,23 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		return 2;
 	}
 
+	if (skip_prefix(placeholder, "(decorate", &arg)) {
+		struct decoration_options opts = { NULL };
+		size_t ret = 0;
+
+		if (*arg == ':') {
+			arg++;
+			parse_decoration_options(&arg, &opts);
+		}
+		if (*arg == ')') {
+			format_decorations(sb, commit, c->auto_color, &opts);
+			ret = arg - placeholder + 1;
+		}
+
+		free_decoration_options(&opts);
+		return ret;
+	}
+
 	/* For the rest we have to parse the commit header. */
 	if (!c->commit_header_parsed) {
 		msg = c->message =
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index dd9035aa38..6ba399c5be 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -576,6 +576,33 @@ test_expect_success 'clean log decoration' '
 	test_cmp expected actual1
 '
 
+test_expect_success 'pretty format %decorate' '
+	git checkout -b foo &&
+	git commit --allow-empty -m "new commit" &&
+	git tag bar &&
+	git branch qux &&
+
+	echo " (HEAD -> foo, tag: bar, qux)" >expect1 &&
+	git log --format="%(decorate)" -1 >actual1 &&
+	test_cmp expect1 actual1 &&
+
+	echo "HEAD -> foo, tag: bar, qux" >expect2 &&
+	git log --format="%(decorate:prefix=,suffix=)" -1 >actual2 &&
+	test_cmp expect2 actual2 &&
+
+	echo "[ HEAD -> foo; tag: bar; qux ]" >expect3 &&
+	git log --format="%(decorate:prefix=[ ,suffix= ],separator=%x3B )" \
+		-1 >actual3 &&
+	test_cmp expect3 actual3 &&
+
+	# Try with a typo (in "separator"), in which case the placeholder should
+	# not be replaced.
+	echo "%(decorate:prefix=[ ,suffix= ],separater=; )" >expect4 &&
+	git log --format="%(decorate:prefix=[ ,suffix= ],separater=%x3B )" \
+		-1 >actual4 &&
+	test_cmp expect4 actual4
+'
+
 cat >trailers <<EOF
 Signed-off-by: A U Thor <author@example.com>
 Acked-by: A U Thor <author@example.com>
-- 
2.42.0-rc2
Previous: Andy KoppeNext: Andy Koppe
Message 54 of 59 in “pretty: add %(decorate[:<options>]) format”
  1. pretty: add %(decorate[:<options>]) formatAndy Koppe, Jul 15, 2023
  2. pretty: add %(decorate[:<options>]) formatAndy Koppe, Jul 15, 2023
  3. Junio C HamanoJul 17, 2023
  4. Junio C HamanoJul 18, 2023
  5. Andy KoppeAug 11, 2023
  6. Glen ChooJul 19, 2023
  7. Phillip WoodJul 23, 2023
  8. Andy KoppeAug 11, 2023
  9. Junio C HamanoAug 11, 2023
  10. Andy KoppeAug 11, 2023
  11. Junio C HamanoAug 12, 2023
  12. Andy KoppeAug 11, 2023
  13. Junio C HamanoAug 15, 2023
  14. Andy KoppeAug 15, 2023
  15. Junio C HamanoAug 15, 2023
  16. main != master at github.com/git/gitAndy Koppe, Aug 15, 2023
  17. Taylor BlauAug 15, 2023
  18. Jeff KingAug 16, 2023
  19. rsbecker@nexbridge.comAug 16, 2023
  20. Junio C HamanoAug 18, 2023
  21. Johannes SchindelinAug 21, 2023
  22. Junio C HamanoAug 21, 2023
  23. ci: avoid building from the same commit in parallelJunio C Hamano, Aug 22, 2023
  24. Junio C HamanoAug 22, 2023
  25. Johannes SchindelinAug 22, 2023
  26. Junio C HamanoAug 22, 2023
  27. Johannes SchindelinAug 23, 2023
  28. Junio C HamanoAug 23, 2023
  29. Junio C HamanoAug 23, 2023
  30. Johannes SchindelinAug 25, 2023
  31. 1/7 pretty-formats: define "literal formatting code"Andy Koppe, Aug 10, 2023
  32. 2/7 pretty-formats: enclose options in angle bracketsAndy Koppe, Aug 10, 2023
  33. 4/7 decorate: avoid some unnecessary color overheadAndy Koppe, Aug 10, 2023
  34. 3/7 decorate: refactor format_decorations()Andy Koppe, Aug 10, 2023
  35. 6/7 pretty: add %(decorate[:<options>]) formatAndy Koppe, Aug 10, 2023
  36. 5/7 decorate: color each token separatelyAndy Koppe, Aug 10, 2023
  37. 7/7 pretty: add pointer and tag options to %(decorate)Andy Koppe, Aug 10, 2023
  38. Junio C HamanoAug 16, 2023
  39. 0/8 pretty: add %(decorate[:<options>]) formatAndy Koppe, Aug 20, 2023
  40. 1/8 pretty-formats: define "literal formatting code"Andy Koppe, Aug 20, 2023
  41. 2/8 pretty-formats: enclose options in angle bracketsAndy Koppe, Aug 20, 2023
  42. 5/8 decorate: color each token separatelyAndy Koppe, Aug 20, 2023
  43. 4/8 decorate: avoid some unnecessary color overheadAndy Koppe, Aug 20, 2023
  44. 3/8 decorate: refactor format_decorations()Andy Koppe, Aug 20, 2023
  45. 6/8 pretty: add %(decorate[:<options>]) formatAndy Koppe, Aug 20, 2023
  46. 7/8 pretty: add pointer and tag options to %(decorate)Andy Koppe, Aug 20, 2023
  47. 8/8 decorate: use commit color for HEAD arrowAndy Koppe, Aug 20, 2023
  48. 0/8 pretty: add %(decorate[:<options>]) formatAndy Koppe, Aug 20, 2023
  49. 1/8 pretty-formats: define "literal formatting code"Andy Koppe, Aug 20, 2023
  50. 2/8 pretty-formats: enclose options in angle bracketsAndy Koppe, Aug 20, 2023
  51. 4/8 decorate: avoid some unnecessary color overheadAndy Koppe, Aug 20, 2023
  52. 5/8 decorate: color each token separatelyAndy Koppe, Aug 20, 2023
  53. 3/8 decorate: refactor format_decorations()Andy Koppe, Aug 20, 2023
  54. 6/8 pretty: add %(decorate[:<options>]) formatAndy Koppe, Aug 20, 2023
  55. 8/8 decorate: use commit color for HEAD arrowAndy Koppe, Aug 20, 2023
  56. 7/8 pretty: add pointer and tag options to %(decorate)Andy Koppe, Aug 20, 2023
  57. Junio C HamanoAug 29, 2023
  58. Andy KoppeSep 1, 2023
  59. Junio C HamanoAug 21, 2023

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.