{"thread":{"id":"59974","subject":"[PATCH] pretty: add %r format specifier for showing refs","startedAt":"2023-07-12T11:07:50Z","lastAt":"2023-07-12T20:56:39Z","messageCount":5,"participants":["Andy Koppe","Eric Sunshine","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"479404","messageId":"20230712110732.8274-1-andy.koppe@gmail.com","threadId":"59974","inReplyTo":null,"subject":"[PATCH] pretty: add %r format specifier for showing refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2023-07-12T11:07:32Z","receivedAt":"2023-07-12T11:07:50Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"This lists refs similarly to the %D decoration format, but separates\nthe refs with spaces only and omits \"tag:\" annotations. It's intended\nprimarily for color output, where tags are already distinguished by\ncolor.\n\nRefactor format_decorations() to take an enum decoration_format argument\nthat determines the prefix, separator and suffix as well as the tag\nannotation.\n\nFor %d and %D, wrap the \"tag:\" annotation and the actual tag in separate\ncolor controls, because otherwise the tag ends up uncolored when %w\nwidth formatting breaks a line between the annotation and tag.\n\nAmend t4207-log-decoration-colors.sh to reflect the added color\ncontrols, and t4202-log.sh to test the %r format.\n---\n Documentation/pretty-formats.txt |  3 +-\n log-tree.c                       | 52 +++++++++++++++++++++-----------\n log-tree.h                       | 15 ++++-----\n pretty.c                         |  7 +++--\n t/t4202-log.sh                   |  7 ++++-\n t/t4207-log-decoration-colors.sh | 22 +++++++-------\n 6 files changed, 67 insertions(+), 39 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 3b71334459..d2ae898c79 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -221,7 +221,8 @@ The placeholders are:\n '%ch':: committer date, human style (like the `--date=human` option of\n \tlinkgit:git-rev-list[1])\n '%d':: ref names, like the --decorate option of linkgit:git-log[1]\n-'%D':: ref names without the \" (\", \")\" wrapping.\n+'%D':: ref names without the \" (\", \")\" wrapping\n+'%r':: ref names only, separated by spaces\n '%(describe[:options])':: human-readable name, like\n \t\t\t  linkgit:git-describe[1]; empty string for\n \t\t\t  undescribable commits.  The `describe` string\ndiff --git a/log-tree.c b/log-tree.c\nindex f4b22a60cc..80850b3a03 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -301,14 +301,12 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio\n \n /*\n  * The caller makes sure there is no funny color before calling.\n- * format_decorations_extended makes sure the same after return.\n+ * format_decorations makes sure the same after return.\n  */\n-void format_decorations_extended(struct strbuf *sb,\n+void format_decorations(struct strbuf *sb,\n \t\t\tconst struct commit *commit,\n \t\t\tint use_color,\n-\t\t\tconst char *prefix,\n-\t\t\tconst char *separator,\n-\t\t\tconst char *suffix)\n+\t\t\tenum decoration_format format)\n {\n \tconst struct name_decoration *decoration;\n \tconst struct name_decoration *current_and_HEAD;\n@@ -316,11 +314,18 @@ void format_decorations_extended(struct strbuf *sb,\n \t\tdiff_get_color(use_color, DIFF_COMMIT);\n \tconst char *color_reset =\n \t\tdecorate_get_color(use_color, DECORATION_NONE);\n+\tint first = 1;\n \n \tdecoration = get_name_decoration(&commit->object);\n \tif (!decoration)\n \t\treturn;\n \n+\tif (format == DECO_FMT_WRAPPED) {\n+\t\tstrbuf_addstr(sb, color_commit);\n+\t\tstrbuf_addstr(sb, \" (\");\n+\t\tstrbuf_addstr(sb, color_reset);\n+\t}\n+\n \tcurrent_and_HEAD = current_pointed_by_HEAD(decoration);\n \twhile (decoration) {\n \t\t/*\n@@ -329,13 +334,25 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t * appeared, skipping the entry for current.\n \t\t */\n \t\tif (decoration != current_and_HEAD) {\n-\t\t\tstrbuf_addstr(sb, color_commit);\n-\t\t\tstrbuf_addstr(sb, prefix);\n-\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, decoration->type));\n-\t\t\tif (decoration->type == DECORATION_REF_TAG)\n-\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n+\t\t\tconst char *color = decorate_get_color(use_color, decoration->type);\n+\t\t\tif (!first) {\n+\t\t\t\tif (format == DECO_FMT_BARE)\n+\t\t\t\t\tstrbuf_addstr(sb, \" \");\n+\t\t\t\telse {\n+\t\t\t\t\tstrbuf_addstr(sb, color_commit);\n+\t\t\t\t\tstrbuf_addstr(sb, \", \");\n+\t\t\t\t\tstrbuf_addstr(sb, color_reset);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfirst = 0;\n \n+\t\t\tif (format != DECO_FMT_BARE &&\n+\t\t\t    decoration->type == DECORATION_REF_TAG) {\n+\t\t\t\tstrbuf_addstr(sb, color);\n+\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n+\t\t\t\tstrbuf_addstr(sb, color_reset);\n+\t\t\t}\n+\t\t\tstrbuf_addstr(sb, color);\n \t\t\tshow_name(sb, decoration);\n \n \t\t\tif (current_and_HEAD &&\n@@ -346,14 +363,15 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t\t\tshow_name(sb, current_and_HEAD);\n \t\t\t}\n \t\t\tstrbuf_addstr(sb, color_reset);\n-\n-\t\t\tprefix = separator;\n \t\t}\n \t\tdecoration = decoration->next;\n \t}\n-\tstrbuf_addstr(sb, color_commit);\n-\tstrbuf_addstr(sb, suffix);\n-\tstrbuf_addstr(sb, color_reset);\n+\n+\tif (format == DECO_FMT_WRAPPED) {\n+\t\tstrbuf_addstr(sb, color_commit);\n+\t\tstrbuf_addstr(sb, \")\");\n+\t\tstrbuf_addstr(sb, color_reset);\n+\t}\n }\n \n void show_decorations(struct rev_info *opt, struct commit *commit)\n@@ -368,7 +386,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit)\n \t}\n \tif (!opt->show_decorations)\n \t\treturn;\n-\tformat_decorations(&sb, commit, opt->diffopt.use_color);\n+\tformat_decorations(&sb, commit, opt->diffopt.use_color, DECO_FMT_WRAPPED);\n \tfputs(sb.buf, opt->diffopt.file);\n \tstrbuf_release(&sb);\n }\ndiff --git a/log-tree.h b/log-tree.h\nindex e7e4641cf8..5aa8908c65 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -13,17 +13,18 @@ struct decoration_filter {\n \tstruct string_list *exclude_ref_config_pattern;\n };\n \n+enum decoration_format {\n+  DECO_FMT_BARE = 0,\n+  DECO_FMT_UNWRAPPED,\n+  DECO_FMT_WRAPPED,\n+};\n+\n int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);\n int log_tree_diff_flush(struct rev_info *);\n int log_tree_commit(struct rev_info *, struct commit *);\n void show_log(struct rev_info *opt);\n-void format_decorations_extended(struct strbuf *sb, const struct commit *commit,\n-\t\t\t     int use_color,\n-\t\t\t     const char *prefix,\n-\t\t\t     const char *separator,\n-\t\t\t     const char *suffix);\n-#define format_decorations(strbuf, commit, color) \\\n-\t\t\t     format_decorations_extended((strbuf), (commit), (color), \" (\", \", \", \")\")\n+void format_decorations(struct strbuf *sb, const struct commit *commit,\n+\t\t\tint use_color, enum decoration_format format);\n void show_decorations(struct rev_info *opt, struct commit *commit);\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\ndiff --git a/pretty.c b/pretty.c\nindex 0bb938021b..88b041df85 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1526,10 +1526,13 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\tstrbuf_addstr(sb, get_revision_mark(NULL, commit));\n \t\treturn 1;\n \tcase 'd':\n-\t\tformat_decorations(sb, commit, c->auto_color);\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_WRAPPED);\n \t\treturn 1;\n \tcase 'D':\n-\t\tformat_decorations_extended(sb, commit, c->auto_color, \"\", \", \", \"\");\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_UNWRAPPED);\n+\t\treturn 1;\n+\tcase 'r':\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_BARE);\n \t\treturn 1;\n \tcase 'S':\t\t/* tag/branch like --source */\n \t\tif (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex ae73aef922..9e0871df24 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -2325,7 +2325,12 @@ test_expect_success 'log --decorate includes all levels of tag annotated tags' '\n \tHEAD -> branch, tag: lightweight, tag: double-1, tag: double-0, tag: annotated\n \tEOF\n \tgit log -1 --format=\"%D\" >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tcat >expect2 <<-\\EOF &&\n+\tHEAD -> branch lightweight double-1 double-0 annotated\n+\tEOF\n+\tgit log -1 --format=\"%r\" >actual2 &&\n+\ttest_cmp expect2 actual2\n '\n \n test_expect_success 'log --decorate does not include things outside filter' '\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex ded33a82e2..7effc0813f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -55,13 +55,13 @@ test_expect_success 'commit decorations colored correctly' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B\n-${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n+${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n ${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset} \\\n On main: Changes to A.t\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n \tEOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always --all >actual &&\n@@ -78,10 +78,10 @@ test_expect_success 'test coloring with replace-objects' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: D${c_reset}${c_commit})${c_reset} D\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: C${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit})${c_reset} D\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}C${c_reset}${c_commit}, \\\n ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} B\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n EOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&\n@@ -102,11 +102,11 @@ test_expect_success 'test coloring with grafted commit' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: D${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit}, \\\n ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} D\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n \tEOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&\n-- \n2.41.0\n\n"},{"id":"479414","messageId":"CAPig+cT3tWnSfk+ZoRWU=JTGctMiE38fH5V1ebpP7L1bGsfU4Q@mail.gmail.com","threadId":"59974","inReplyTo":"20230712110732.8274-1-andy.koppe@gmail.com","subject":"Re: [PATCH] pretty: add %r format specifier for showing refs","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-07-12T16:56:35Z","receivedAt":"2023-07-12T16:56:50Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Not a proper review... just running my eye quickly over the patch...\n\nOn Wed, Jul 12, 2023 at 7:17 AM Andy Koppe <andy.koppe@gmail.com> wrote:\n> This lists refs similarly to the %D decoration format, but separates\n> the refs with spaces only and omits \"tag:\" annotations. It's intended\n> primarily for color output, where tags are already distinguished by\n> color.\n>\n> Refactor format_decorations() to take an enum decoration_format argument\n> that determines the prefix, separator and suffix as well as the tag\n> annotation.\n>\n> For %d and %D, wrap the \"tag:\" annotation and the actual tag in separate\n> color controls, because otherwise the tag ends up uncolored when %w\n> width formatting breaks a line between the annotation and tag.\n>\n> Amend t4207-log-decoration-colors.sh to reflect the added color\n> controls, and t4202-log.sh to test the %r format.\n> ---\n\nMissing sign-off.\n\n> diff --git a/log-tree.h b/log-tree.h\n> @@ -13,17 +13,18 @@ struct decoration_filter {\n> +enum decoration_format {\n> +  DECO_FMT_BARE = 0,\n> +  DECO_FMT_UNWRAPPED,\n> +  DECO_FMT_WRAPPED,\n> +};\n\nIndent with TAB, not spaces.\n\nIs this enum name a bit too generic for a public header? A quick scan\nof other enums in the project shows that they usually incorporate the\n\"subsystem\" into their names somehow (often as a prefix); for\ninstance, \"enum apply_ws_ignore\", \"enum bisect_error\".\n"},{"id":"479417","messageId":"xmqqa5w1t2kp.fsf@gitster.g","threadId":"59974","inReplyTo":"CAPig+cT3tWnSfk+ZoRWU=JTGctMiE38fH5V1ebpP7L1bGsfU4Q@mail.gmail.com","subject":"Re: [PATCH] pretty: add %r format specifier for showing refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-12T18:19:34Z","receivedAt":"2023-07-12T18:19:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Not a proper review... just running my eye quickly over the patch...\n> ...\n>> Amend t4207-log-decoration-colors.sh to reflect the added color\n>> controls, and t4202-log.sh to test the %r format.\n>> ---\n>\n> Missing sign-off.\n>\n>> diff --git a/log-tree.h b/log-tree.h\n>> @@ -13,17 +13,18 @@ struct decoration_filter {\n>> +enum decoration_format {\n>> +  DECO_FMT_BARE = 0,\n>> +  DECO_FMT_UNWRAPPED,\n>> +  DECO_FMT_WRAPPED,\n>> +};\n>\n> Indent with TAB, not spaces.\n>\n> Is this enum name a bit too generic for a public header? A quick scan\n> of other enums in the project shows that they usually incorporate the\n> \"subsystem\" into their names somehow (often as a prefix); for\n> instance, \"enum apply_ws_ignore\", \"enum bisect_error\".\n\nEverything you said makes sense.\n\nBut more importantly, I doubt the wisdom of adding any more %<single\nletter> placeholders to the vocabulary.  Even though I personally do\nnot see any need for variants other than just the plain \"%d\" to show\nthe \"decorate\" information (if you want anything else, just\npost-process the output), if we really want to, the way we should\nextend the format placeholders is to add %(decorate:<options>) that\nis extensible enough that it can produce the identical output as\nexisting \"%d\" and \"%D\" placeholders do, and add new ones as a new\noption to %(decorate).\n\nThanks.\n"},{"id":"479423","messageId":"CAHWeT-agn87wc82xdMzB07Y=xe6H-yR_oxS_CGf2tE-szQ=T-Q@mail.gmail.com","threadId":"59974","inReplyTo":"xmqqa5w1t2kp.fsf@gitster.g","subject":"Re: [PATCH] pretty: add %r format specifier for showing refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2023-07-12T20:47:37Z","receivedAt":"2023-07-12T20:47:55Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"> Eric Sunshine <sunshine@sunshineco.com> writes:\n>\n>> Not a proper review... just running my eye quickly over the patch...\n>> ...\n>> Missing sign-off.\n>>\n>> Indent with TAB, not spaces.\n\nThanks for the check, and apologies for those avoidable mistakes. Must\nremember to run checkpatch …\n\nI'll send a corrected patch, if only for completeness.\n\n>>> +enum decoration_format {\n>> Is this enum name a bit too generic for a public header? A quick scan\n>> of other enums in the project shows that they usually incorporate the\n>> \"subsystem\" into their names somehow (often as a prefix); for\n>> instance, \"enum apply_ws_ignore\", \"enum bisect_error\".\n\nI took existing decoration-related types as precedent, in particular\nenum decoration_type and structs decoration_entry and\ndecoration_filter, whereby the latter is in the same header.\n\nJunio C Hamano <gitster@pobox.com> writes:\n> But more importantly, I doubt the wisdom of adding any more %<single\n> letter> placeholders to the vocabulary.  Even though I personally do\n> not see any need for variants other than just the plain \"%d\" to show\n> the \"decorate\" information (if you want anything else, just\n> post-process the output)\n\nThe proposed %r placeholder basically is the minimised version of %d,\nwhich could save space in one-line logs and generally reduce visual\nnoise in custom log formats. Post-processing is rather more difficult\nand error-prone than a built-in feature.\n\n> if we really want to, the way we should\n> extend the format placeholders is to add %(decorate:<options>) that\n> is extensible enough that it can produce the identical output as\n> existing \"%d\" and \"%D\" placeholders do, and add new ones as a new\n> option to %(decorate).\n\nI'd be happy to look into that.\n\nWhat have you got in mind for the <options>?\n\nSomething like:\n  %(decorate) for %d\n  %(decorate:unwrapped) for %D\n  %(decorate:bare) instead of the proposed %r\n\nOr something with separate options for each element, similar to the\nseparator option of %(trailers)?\n\n%r might look as follows, with a space for the separator and empty\nstrings for the other elements:\n\n  %(decorate:prefix=,separator= ,suffix=,tag=)\n\n(Each option would default to its %d value if not specified.)\n\nThanks,\nAndy\n"},{"id":"479425","messageId":"20230712205608.1806-1-andy.koppe@gmail.com","threadId":"59974","inReplyTo":"CAPig+cT3tWnSfk+ZoRWU=JTGctMiE38fH5V1ebpP7L1bGsfU4Q@mail.gmail.com","subject":"[PATCH v2] pretty: add %r format specifier for showing refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2023-07-12T20:56:08Z","receivedAt":"2023-07-12T20:56:39Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"This lists refs similarly to the %D decoration format, but separates\nthe refs with spaces only and omits \"tag:\" annotations. It's intended\nprimarily for color output, where tags are already distinguished by\ncolor.\n\nRefactor format_decorations() to take an enum decoration_format argument\nthat determines the prefix, separator and suffix as well as the tag\nannotation.\n\nFor %d and %D, wrap the \"tag:\" annotation and the actual tag in separate\ncolor controls, because otherwise the tag ends up uncolored when %w\nwidth formatting breaks a line between the annotation and tag.\n\nAmend t4207-log-decoration-colors.sh to reflect the added color\ncontrols, and t4202-log.sh to test the %r format.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\nCI: https://github.com/ak2/git/actions/runs/5535093717\nAll passed apart from unrelated errors for MacOS.\n\n Documentation/pretty-formats.txt |  3 +-\n log-tree.c                       | 53 ++++++++++++++++++++++----------\n log-tree.h                       | 15 ++++-----\n pretty.c                         |  7 +++--\n t/t4202-log.sh                   |  7 ++++-\n t/t4207-log-decoration-colors.sh | 32 +++++++++++--------\n 6 files changed, 76 insertions(+), 41 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 3b71334459..d2ae898c79 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -221,7 +221,8 @@ The placeholders are:\n '%ch':: committer date, human style (like the `--date=human` option of\n \tlinkgit:git-rev-list[1])\n '%d':: ref names, like the --decorate option of linkgit:git-log[1]\n-'%D':: ref names without the \" (\", \")\" wrapping.\n+'%D':: ref names without the \" (\", \")\" wrapping\n+'%r':: ref names only, separated by spaces\n '%(describe[:options])':: human-readable name, like\n \t\t\t  linkgit:git-describe[1]; empty string for\n \t\t\t  undescribable commits.  The `describe` string\ndiff --git a/log-tree.c b/log-tree.c\nindex f4b22a60cc..33dcb31858 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -301,14 +301,12 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio\n \n /*\n  * The caller makes sure there is no funny color before calling.\n- * format_decorations_extended makes sure the same after return.\n+ * format_decorations makes sure the same after return.\n  */\n-void format_decorations_extended(struct strbuf *sb,\n+void format_decorations(struct strbuf *sb,\n \t\t\tconst struct commit *commit,\n \t\t\tint use_color,\n-\t\t\tconst char *prefix,\n-\t\t\tconst char *separator,\n-\t\t\tconst char *suffix)\n+\t\t\tenum decoration_format format)\n {\n \tconst struct name_decoration *decoration;\n \tconst struct name_decoration *current_and_HEAD;\n@@ -316,11 +314,18 @@ void format_decorations_extended(struct strbuf *sb,\n \t\tdiff_get_color(use_color, DIFF_COMMIT);\n \tconst char *color_reset =\n \t\tdecorate_get_color(use_color, DECORATION_NONE);\n+\tint first = 1;\n \n \tdecoration = get_name_decoration(&commit->object);\n \tif (!decoration)\n \t\treturn;\n \n+\tif (format == DECO_FMT_WRAPPED) {\n+\t\tstrbuf_addstr(sb, color_commit);\n+\t\tstrbuf_addstr(sb, \" (\");\n+\t\tstrbuf_addstr(sb, color_reset);\n+\t}\n+\n \tcurrent_and_HEAD = current_pointed_by_HEAD(decoration);\n \twhile (decoration) {\n \t\t/*\n@@ -329,13 +334,26 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t * appeared, skipping the entry for current.\n \t\t */\n \t\tif (decoration != current_and_HEAD) {\n-\t\t\tstrbuf_addstr(sb, color_commit);\n-\t\t\tstrbuf_addstr(sb, prefix);\n-\t\t\tstrbuf_addstr(sb, color_reset);\n-\t\t\tstrbuf_addstr(sb, decorate_get_color(use_color, decoration->type));\n-\t\t\tif (decoration->type == DECORATION_REF_TAG)\n-\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n+\t\t\tconst char *color = decorate_get_color(use_color, decoration->type);\n \n+\t\t\tif (!first) {\n+\t\t\t\tif (format == DECO_FMT_BARE)\n+\t\t\t\t\tstrbuf_addstr(sb, \" \");\n+\t\t\t\telse {\n+\t\t\t\t\tstrbuf_addstr(sb, color_commit);\n+\t\t\t\t\tstrbuf_addstr(sb, \", \");\n+\t\t\t\t\tstrbuf_addstr(sb, color_reset);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfirst = 0;\n+\n+\t\t\tif (format != DECO_FMT_BARE &&\n+\t\t\t    decoration->type == DECORATION_REF_TAG) {\n+\t\t\t\tstrbuf_addstr(sb, color);\n+\t\t\t\tstrbuf_addstr(sb, \"tag: \");\n+\t\t\t\tstrbuf_addstr(sb, color_reset);\n+\t\t\t}\n+\t\t\tstrbuf_addstr(sb, color);\n \t\t\tshow_name(sb, decoration);\n \n \t\t\tif (current_and_HEAD &&\n@@ -346,14 +364,15 @@ void format_decorations_extended(struct strbuf *sb,\n \t\t\t\tshow_name(sb, current_and_HEAD);\n \t\t\t}\n \t\t\tstrbuf_addstr(sb, color_reset);\n-\n-\t\t\tprefix = separator;\n \t\t}\n \t\tdecoration = decoration->next;\n \t}\n-\tstrbuf_addstr(sb, color_commit);\n-\tstrbuf_addstr(sb, suffix);\n-\tstrbuf_addstr(sb, color_reset);\n+\n+\tif (format == DECO_FMT_WRAPPED) {\n+\t\tstrbuf_addstr(sb, color_commit);\n+\t\tstrbuf_addstr(sb, \")\");\n+\t\tstrbuf_addstr(sb, color_reset);\n+\t}\n }\n \n void show_decorations(struct rev_info *opt, struct commit *commit)\n@@ -368,7 +387,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit)\n \t}\n \tif (!opt->show_decorations)\n \t\treturn;\n-\tformat_decorations(&sb, commit, opt->diffopt.use_color);\n+\tformat_decorations(&sb, commit, opt->diffopt.use_color, DECO_FMT_WRAPPED);\n \tfputs(sb.buf, opt->diffopt.file);\n \tstrbuf_release(&sb);\n }\ndiff --git a/log-tree.h b/log-tree.h\nindex e7e4641cf8..345d1e80dc 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -13,17 +13,18 @@ struct decoration_filter {\n \tstruct string_list *exclude_ref_config_pattern;\n };\n \n+enum decoration_format {\n+\tDECO_FMT_BARE = 0,\n+\tDECO_FMT_UNWRAPPED,\n+\tDECO_FMT_WRAPPED,\n+};\n+\n int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);\n int log_tree_diff_flush(struct rev_info *);\n int log_tree_commit(struct rev_info *, struct commit *);\n void show_log(struct rev_info *opt);\n-void format_decorations_extended(struct strbuf *sb, const struct commit *commit,\n-\t\t\t     int use_color,\n-\t\t\t     const char *prefix,\n-\t\t\t     const char *separator,\n-\t\t\t     const char *suffix);\n-#define format_decorations(strbuf, commit, color) \\\n-\t\t\t     format_decorations_extended((strbuf), (commit), (color), \" (\", \", \", \")\")\n+void format_decorations(struct strbuf *sb, const struct commit *commit,\n+\t\t\tint use_color, enum decoration_format format);\n void show_decorations(struct rev_info *opt, struct commit *commit);\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\ndiff --git a/pretty.c b/pretty.c\nindex 0bb938021b..88b041df85 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1526,10 +1526,13 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\tstrbuf_addstr(sb, get_revision_mark(NULL, commit));\n \t\treturn 1;\n \tcase 'd':\n-\t\tformat_decorations(sb, commit, c->auto_color);\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_WRAPPED);\n \t\treturn 1;\n \tcase 'D':\n-\t\tformat_decorations_extended(sb, commit, c->auto_color, \"\", \", \", \"\");\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_UNWRAPPED);\n+\t\treturn 1;\n+\tcase 'r':\n+\t\tformat_decorations(sb, commit, c->auto_color, DECO_FMT_BARE);\n \t\treturn 1;\n \tcase 'S':\t\t/* tag/branch like --source */\n \t\tif (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex ae73aef922..9e0871df24 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -2325,7 +2325,12 @@ test_expect_success 'log --decorate includes all levels of tag annotated tags' '\n \tHEAD -> branch, tag: lightweight, tag: double-1, tag: double-0, tag: annotated\n \tEOF\n \tgit log -1 --format=\"%D\" >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tcat >expect2 <<-\\EOF &&\n+\tHEAD -> branch lightweight double-1 double-0 annotated\n+\tEOF\n+\tgit log -1 --format=\"%r\" >actual2 &&\n+\ttest_cmp expect2 actual2\n '\n \n test_expect_success 'log --decorate does not include things outside filter' '\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex ded33a82e2..3a4eedc494 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -55,13 +55,15 @@ test_expect_success 'commit decorations colored correctly' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B\n-${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n+${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n ${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset} \\\n-On main: Changes to A.t\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n \tEOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always --all >actual &&\n@@ -78,10 +80,12 @@ test_expect_success 'test coloring with replace-objects' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: D${c_reset}${c_commit})${c_reset} D\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: C${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit})${c_reset} D\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}C${c_reset}${c_commit}, \\\n ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} B\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n EOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&\n@@ -102,11 +106,13 @@ test_expect_success 'test coloring with grafted commit' '\n \tcat >expect <<-EOF &&\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \\\n ${c_reset}${c_branch}main${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: D${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit}, \\\n ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} D\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \\\n-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B\n-\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n+\t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n+${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A\n \tEOF\n \n \tgit log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&\n-- \n2.41.0\n\n"}]}