aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/pretty-formats.txt47
-rw-r--r--log-tree.c72
-rw-r--r--log-tree.h17
-rw-r--r--pretty.c76
-rwxr-xr-xt/t4205-log-pretty-formats.sh32
-rwxr-xr-xt/t4207-log-decoration-colors.sh44
6 files changed, 219 insertions, 69 deletions
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 3b71334459..d38b4ab566 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -122,7 +122,9 @@ The placeholders are:
- Placeholders that expand to a single literal character:
'%n':: newline
'%%':: a raw '%'
-'%x00':: print a byte from a hex code
+'%x00':: '%x' followed by two hexadecimal digits is replaced with a
+ byte with the hexadecimal digits' value (we will call this
+ "literal formatting code" in the rest of this document).
- Placeholders that affect formatting of later placeholders:
'%Cred':: switch color to red
@@ -222,13 +224,30 @@ 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.
-'%(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 zero or more
- comma-separated options. Descriptions can be
- inconsistent when tags are added or removed at
- the same time.
+'%(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}".
+** 'pointer=<value>': Shown between HEAD and the branch it points to, if any.
+ Defaults to "{nbsp}`->`{nbsp}".
+** 'tag=<value>': Shown before tag names. Defaults to "`tag:`{nbsp}".
+
++
+For example, to produce decorations with no wrapping
+or tag annotations, and spaces as separators:
++
+`%(decorate:prefix=,suffix=,tag=,separator= )`
+
+'%(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
+zero or more comma-separated options. Descriptions can be inconsistent when
+tags are added or removed at the same time.
+
** 'tags[=<bool-value>]': Instead of only considering annotated tags,
consider lightweight tags as well.
@@ -281,13 +300,11 @@ endif::git-rev-list[]
'%gE':: reflog identity email (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1])
'%gs':: reflog subject
-'%(trailers[:options])':: display the trailers of the body as
- interpreted by
- linkgit:git-interpret-trailers[1]. The
- `trailers` string may be followed by a colon
- and zero or more comma-separated options.
- If any option is provided multiple times the
- last occurrence wins.
+'%(trailers[:<options>])'::
+display the trailers of the body as interpreted by
+linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
+a colon and zero or more comma-separated options. If any option is provided
+multiple times, the last occurrence wins.
+
** 'key=<key>': only show trailers with specified <key>. Matching is done
case-insensitively and trailing colon is optional. If option is
diff --git a/log-tree.c b/log-tree.c
index 208c69cbb7..504da6b519 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -303,26 +303,43 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio
/*
* The caller makes sure there is no funny color before calling.
- * format_decorations_extended makes sure the same after return.
+ * format_decorations ensures the same after return.
*/
-void format_decorations_extended(struct strbuf *sb,
+void format_decorations(struct strbuf *sb,
const struct commit *commit,
int use_color,
- const char *prefix,
- const char *separator,
- const char *suffix)
+ const struct decoration_options *opts)
{
const struct name_decoration *decoration;
const struct name_decoration *current_and_HEAD;
- const char *color_commit =
- diff_get_color(use_color, DIFF_COMMIT);
- const char *color_reset =
- decorate_get_color(use_color, DECORATION_NONE);
+ const char *color_commit, *color_reset;
+
+ const char *prefix = " (";
+ const char *suffix = ")";
+ const char *separator = ", ";
+ const char *pointer = " -> ";
+ const char *tag = "tag: ";
decoration = get_name_decoration(&commit->object);
if (!decoration)
return;
+ if (opts) {
+ if (opts->prefix)
+ prefix = opts->prefix;
+ if (opts->suffix)
+ suffix = opts->suffix;
+ if (opts->separator)
+ separator = opts->separator;
+ if (opts->pointer)
+ pointer = opts->pointer;
+ if (opts->tag)
+ tag = opts->tag;
+ }
+
+ color_commit = diff_get_color(use_color, DIFF_COMMIT);
+ color_reset = decorate_get_color(use_color, DECORATION_NONE);
+
current_and_HEAD = current_pointed_by_HEAD(decoration);
while (decoration) {
/*
@@ -331,31 +348,44 @@ void format_decorations_extended(struct strbuf *sb,
* appeared, skipping the entry for current.
*/
if (decoration != current_and_HEAD) {
- strbuf_addstr(sb, color_commit);
- strbuf_addstr(sb, prefix);
- strbuf_addstr(sb, color_reset);
- strbuf_addstr(sb, decorate_get_color(use_color, decoration->type));
- if (decoration->type == DECORATION_REF_TAG)
- strbuf_addstr(sb, "tag: ");
+ const char *color =
+ decorate_get_color(use_color, decoration->type);
+
+ if (*prefix) {
+ strbuf_addstr(sb, color_commit);
+ strbuf_addstr(sb, prefix);
+ strbuf_addstr(sb, color_reset);
+ }
+ if (*tag && decoration->type == DECORATION_REF_TAG) {
+ strbuf_addstr(sb, color);
+ strbuf_addstr(sb, tag);
+ strbuf_addstr(sb, color_reset);
+ }
+
+ strbuf_addstr(sb, color);
show_name(sb, decoration);
+ strbuf_addstr(sb, color_reset);
if (current_and_HEAD &&
decoration->type == DECORATION_REF_HEAD) {
- strbuf_addstr(sb, " -> ");
+ strbuf_addstr(sb, color_commit);
+ strbuf_addstr(sb, pointer);
strbuf_addstr(sb, color_reset);
strbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));
show_name(sb, current_and_HEAD);
+ strbuf_addstr(sb, color_reset);
}
- strbuf_addstr(sb, color_reset);
prefix = separator;
}
decoration = decoration->next;
}
- strbuf_addstr(sb, color_commit);
- strbuf_addstr(sb, suffix);
- strbuf_addstr(sb, color_reset);
+ if (*suffix) {
+ strbuf_addstr(sb, color_commit);
+ strbuf_addstr(sb, suffix);
+ strbuf_addstr(sb, color_reset);
+ }
}
void show_decorations(struct rev_info *opt, struct commit *commit)
@@ -370,7 +400,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
}
if (!opt->show_decorations)
return;
- format_decorations(&sb, commit, opt->diffopt.use_color);
+ format_decorations(&sb, commit, opt->diffopt.use_color, NULL);
fputs(sb.buf, opt->diffopt.file);
strbuf_release(&sb);
}
diff --git a/log-tree.h b/log-tree.h
index bdb6432815..41c776fea5 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -13,17 +13,20 @@ struct decoration_filter {
struct string_list *exclude_ref_config_pattern;
};
+struct decoration_options {
+ char *prefix;
+ char *suffix;
+ char *separator;
+ char *pointer;
+ char *tag;
+};
+
int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
int log_tree_diff_flush(struct rev_info *);
int log_tree_commit(struct rev_info *, struct commit *);
void show_log(struct rev_info *opt);
-void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
- int use_color,
- const char *prefix,
- const char *separator,
- const char *suffix);
-#define format_decorations(strbuf, commit, color) \
- format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")")
+void format_decorations(struct strbuf *sb, const struct commit *commit,
+ int use_color, const struct decoration_options *opts);
void show_decorations(struct rev_info *opt, struct commit *commit);
void log_write_email_headers(struct rev_info *opt, struct commit *commit,
const char **extra_headers_p,
diff --git a/pretty.c b/pretty.c
index 718530bbab..7f3abb676c 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,44 @@ 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) ||
+ parse_decoration_option(arg, "pointer", &opts->pointer) ||
+ parse_decoration_option(arg, "tag", &opts->tag))
+ ;
+}
+
+static void free_decoration_options(const struct decoration_options *opts)
+{
+ free(opts->prefix);
+ free(opts->suffix);
+ free(opts->separator);
+ free(opts->pointer);
+ free(opts->tag);
+}
+
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *placeholder,
void *context)
@@ -1537,11 +1575,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
strbuf_addstr(sb, get_revision_mark(NULL, commit));
return 1;
case 'd':
- format_decorations(sb, commit, c->auto_color);
+ format_decorations(sb, commit, c->auto_color, NULL);
return 1;
case 'D':
- format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
- return 1;
+ {
+ const struct decoration_options opts = {
+ .prefix = "",
+ .suffix = ""
+ };
+
+ format_decorations(sb, commit, c->auto_color, &opts);
+ return 1;
+ }
case 'S': /* tag/branch like --source */
if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))
return 0;
@@ -1638,6 +1683,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..16626e4fe9 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -576,6 +576,38 @@ 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 &&
+
+ echo "HEAD->foo bar qux" >expect5 &&
+ git log --format="%(decorate:prefix=,suffix=,separator= ,tag=,pointer=->)" \
+ -1 >actual5 &&
+ test_cmp expect5 actual5
+'
+
cat >trailers <<EOF
Signed-off-by: A U Thor <author@example.com>
Acked-by: A U Thor <author@example.com>
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index ded33a82e2..21986a866d 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -53,15 +53,17 @@ cmp_filtered_decorations () {
# to this test since it does not contain any decoration, hence --first-parent
test_expect_success 'commit decorations colored correctly' '
cat >expect <<-EOF &&
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \
-${c_reset}${c_branch}main${c_reset}${c_commit}, \
-${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \
-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B
-${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit}, \
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\
+${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \
+${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
+${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
+${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset} \
-On main: Changes to A.t
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
+${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.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
EOF
git log --first-parent --no-abbrev --decorate --oneline --color=always --all >actual &&
@@ -76,12 +78,14 @@ test_expect_success 'test coloring with replace-objects' '
git replace HEAD~1 HEAD~2 &&
cat >expect <<-EOF &&
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \
-${c_reset}${c_branch}main${c_reset}${c_commit}, \
-${c_reset}${c_tag}tag: D${c_reset}${c_commit})${c_reset} D
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: C${c_reset}${c_commit}, \
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\
+${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \
+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit})${c_reset} D
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
+${c_tag}tag: ${c_reset}${c_tag}C${c_reset}${c_commit}, \
${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} B
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A
+ ${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
EOF
git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&
@@ -100,13 +104,15 @@ test_expect_success 'test coloring with grafted commit' '
git replace --graft HEAD HEAD~2 &&
cat >expect <<-EOF &&
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \
-${c_reset}${c_branch}main${c_reset}${c_commit}, \
-${c_reset}${c_tag}tag: D${c_reset}${c_commit}, \
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\
+${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \
+${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit}, \
${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} D
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \
-${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B
- ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A
+ ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
+${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
+${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
+ ${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
EOF
git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual &&