Skip to content

Commit 86b56ff

Browse files
committed
Merge branch 'ak/pretty-decorate-more'
"git log --format" has been taught the %(decorate) placeholder. * ak/pretty-decorate-more: decorate: use commit color for HEAD arrow pretty: add pointer and tag options to %(decorate) pretty: add %(decorate[:<options>]) format decorate: color each token separately decorate: avoid some unnecessary color overhead decorate: refactor format_decorations() pretty-formats: enclose options in angle brackets pretty-formats: define "literal formatting code"
2 parents 174dfe4 + 1e63b34 commit 86b56ff

File tree

6 files changed

+219
-69
lines changed

6 files changed

+219
-69
lines changed

Documentation/pretty-formats.txt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ The placeholders are:
122122
- Placeholders that expand to a single literal character:
123123
'%n':: newline
124124
'%%':: a raw '%'
125-
'%x00':: print a byte from a hex code
125+
'%x00':: '%x' followed by two hexadecimal digits is replaced with a
126+
byte with the hexadecimal digits' value (we will call this
127+
"literal formatting code" in the rest of this document).
126128

127129
- Placeholders that affect formatting of later placeholders:
128130
'%Cred':: switch color to red
@@ -222,13 +224,30 @@ The placeholders are:
222224
linkgit:git-rev-list[1])
223225
'%d':: ref names, like the --decorate option of linkgit:git-log[1]
224226
'%D':: ref names without the " (", ")" wrapping.
225-
'%(describe[:options])':: human-readable name, like
226-
linkgit:git-describe[1]; empty string for
227-
undescribable commits. The `describe` string
228-
may be followed by a colon and zero or more
229-
comma-separated options. Descriptions can be
230-
inconsistent when tags are added or removed at
231-
the same time.
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+
** 'pointer=<value>': Shown between HEAD and the branch it points to, if any.
237+
Defaults to "{nbsp}`->`{nbsp}".
238+
** 'tag=<value>': Shown before tag names. Defaults to "`tag:`{nbsp}".
239+
240+
+
241+
For example, to produce decorations with no wrapping
242+
or tag annotations, and spaces as separators:
243+
+
244+
`%(decorate:prefix=,suffix=,tag=,separator= )`
245+
246+
'%(describe[:<options>])'::
247+
human-readable name, like linkgit:git-describe[1]; empty string for
248+
undescribable commits. The `describe` string may be followed by a colon and
249+
zero or more comma-separated options. Descriptions can be inconsistent when
250+
tags are added or removed at the same time.
232251
+
233252
** 'tags[=<bool-value>]': Instead of only considering annotated tags,
234253
consider lightweight tags as well.
@@ -281,13 +300,11 @@ endif::git-rev-list[]
281300
'%gE':: reflog identity email (respecting .mailmap, see
282301
linkgit:git-shortlog[1] or linkgit:git-blame[1])
283302
'%gs':: reflog subject
284-
'%(trailers[:options])':: display the trailers of the body as
285-
interpreted by
286-
linkgit:git-interpret-trailers[1]. The
287-
`trailers` string may be followed by a colon
288-
and zero or more comma-separated options.
289-
If any option is provided multiple times the
290-
last occurrence wins.
303+
'%(trailers[:<options>])'::
304+
display the trailers of the body as interpreted by
305+
linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
306+
a colon and zero or more comma-separated options. If any option is provided
307+
multiple times, the last occurrence wins.
291308
+
292309
** 'key=<key>': only show trailers with specified <key>. Matching is done
293310
case-insensitively and trailing colon is optional. If option is

log-tree.c

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -303,26 +303,43 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio
303303

304304
/*
305305
* The caller makes sure there is no funny color before calling.
306-
* format_decorations_extended makes sure the same after return.
306+
* format_decorations ensures the same after return.
307307
*/
308-
void format_decorations_extended(struct strbuf *sb,
308+
void format_decorations(struct strbuf *sb,
309309
const struct commit *commit,
310310
int use_color,
311-
const char *prefix,
312-
const char *separator,
313-
const char *suffix)
311+
const struct decoration_options *opts)
314312
{
315313
const struct name_decoration *decoration;
316314
const struct name_decoration *current_and_HEAD;
317-
const char *color_commit =
318-
diff_get_color(use_color, DIFF_COMMIT);
319-
const char *color_reset =
320-
decorate_get_color(use_color, DECORATION_NONE);
315+
const char *color_commit, *color_reset;
316+
317+
const char *prefix = " (";
318+
const char *suffix = ")";
319+
const char *separator = ", ";
320+
const char *pointer = " -> ";
321+
const char *tag = "tag: ";
321322

322323
decoration = get_name_decoration(&commit->object);
323324
if (!decoration)
324325
return;
325326

327+
if (opts) {
328+
if (opts->prefix)
329+
prefix = opts->prefix;
330+
if (opts->suffix)
331+
suffix = opts->suffix;
332+
if (opts->separator)
333+
separator = opts->separator;
334+
if (opts->pointer)
335+
pointer = opts->pointer;
336+
if (opts->tag)
337+
tag = opts->tag;
338+
}
339+
340+
color_commit = diff_get_color(use_color, DIFF_COMMIT);
341+
color_reset = decorate_get_color(use_color, DECORATION_NONE);
342+
326343
current_and_HEAD = current_pointed_by_HEAD(decoration);
327344
while (decoration) {
328345
/*
@@ -331,31 +348,44 @@ void format_decorations_extended(struct strbuf *sb,
331348
* appeared, skipping the entry for current.
332349
*/
333350
if (decoration != current_and_HEAD) {
334-
strbuf_addstr(sb, color_commit);
335-
strbuf_addstr(sb, prefix);
336-
strbuf_addstr(sb, color_reset);
337-
strbuf_addstr(sb, decorate_get_color(use_color, decoration->type));
338-
if (decoration->type == DECORATION_REF_TAG)
339-
strbuf_addstr(sb, "tag: ");
351+
const char *color =
352+
decorate_get_color(use_color, decoration->type);
353+
354+
if (*prefix) {
355+
strbuf_addstr(sb, color_commit);
356+
strbuf_addstr(sb, prefix);
357+
strbuf_addstr(sb, color_reset);
358+
}
340359

360+
if (*tag && decoration->type == DECORATION_REF_TAG) {
361+
strbuf_addstr(sb, color);
362+
strbuf_addstr(sb, tag);
363+
strbuf_addstr(sb, color_reset);
364+
}
365+
366+
strbuf_addstr(sb, color);
341367
show_name(sb, decoration);
368+
strbuf_addstr(sb, color_reset);
342369

343370
if (current_and_HEAD &&
344371
decoration->type == DECORATION_REF_HEAD) {
345-
strbuf_addstr(sb, " -> ");
372+
strbuf_addstr(sb, color_commit);
373+
strbuf_addstr(sb, pointer);
346374
strbuf_addstr(sb, color_reset);
347375
strbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));
348376
show_name(sb, current_and_HEAD);
377+
strbuf_addstr(sb, color_reset);
349378
}
350-
strbuf_addstr(sb, color_reset);
351379

352380
prefix = separator;
353381
}
354382
decoration = decoration->next;
355383
}
356-
strbuf_addstr(sb, color_commit);
357-
strbuf_addstr(sb, suffix);
358-
strbuf_addstr(sb, color_reset);
384+
if (*suffix) {
385+
strbuf_addstr(sb, color_commit);
386+
strbuf_addstr(sb, suffix);
387+
strbuf_addstr(sb, color_reset);
388+
}
359389
}
360390

361391
void show_decorations(struct rev_info *opt, struct commit *commit)
@@ -370,7 +400,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
370400
}
371401
if (!opt->show_decorations)
372402
return;
373-
format_decorations(&sb, commit, opt->diffopt.use_color);
403+
format_decorations(&sb, commit, opt->diffopt.use_color, NULL);
374404
fputs(sb.buf, opt->diffopt.file);
375405
strbuf_release(&sb);
376406
}

log-tree.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ struct decoration_filter {
1313
struct string_list *exclude_ref_config_pattern;
1414
};
1515

16+
struct decoration_options {
17+
char *prefix;
18+
char *suffix;
19+
char *separator;
20+
char *pointer;
21+
char *tag;
22+
};
23+
1624
int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
1725
int log_tree_diff_flush(struct rev_info *);
1826
int log_tree_commit(struct rev_info *, struct commit *);
1927
void show_log(struct rev_info *opt);
20-
void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
21-
int use_color,
22-
const char *prefix,
23-
const char *separator,
24-
const char *suffix);
25-
#define format_decorations(strbuf, commit, color) \
26-
format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")")
28+
void format_decorations(struct strbuf *sb, const struct commit *commit,
29+
int use_color, const struct decoration_options *opts);
2730
void show_decorations(struct rev_info *opt, struct commit *commit);
2831
void log_write_email_headers(struct rev_info *opt, struct commit *commit,
2932
const char **extra_headers_p,

pretty.c

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,8 +1252,8 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud)
12521252
return 0;
12531253
}
12541254

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)
12571257
{
12581258
char *fmt = xstrndup(argval, arglen);
12591259
const char *format = fmt;
@@ -1301,9 +1301,9 @@ int format_set_trailers_options(struct process_trailer_options *opts,
13011301
opts->filter_data = filter_list;
13021302
opts->only_trailers = 1;
13031303
} 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);
13051305
} 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);
13071307
} else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) &&
13081308
!match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) &&
13091309
!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)
13841384
return arg - start;
13851385
}
13861386

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+
parse_decoration_option(arg, "pointer", &opts->pointer) ||
1412+
parse_decoration_option(arg, "tag", &opts->tag))
1413+
;
1414+
}
1415+
1416+
static void free_decoration_options(const struct decoration_options *opts)
1417+
{
1418+
free(opts->prefix);
1419+
free(opts->suffix);
1420+
free(opts->separator);
1421+
free(opts->pointer);
1422+
free(opts->tag);
1423+
}
1424+
13871425
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
13881426
const char *placeholder,
13891427
void *context)
@@ -1537,11 +1575,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
15371575
strbuf_addstr(sb, get_revision_mark(NULL, commit));
15381576
return 1;
15391577
case 'd':
1540-
format_decorations(sb, commit, c->auto_color);
1578+
format_decorations(sb, commit, c->auto_color, NULL);
15411579
return 1;
15421580
case 'D':
1543-
format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
1544-
return 1;
1581+
{
1582+
const struct decoration_options opts = {
1583+
.prefix = "",
1584+
.suffix = ""
1585+
};
1586+
1587+
format_decorations(sb, commit, c->auto_color, &opts);
1588+
return 1;
1589+
}
15451590
case 'S': /* tag/branch like --source */
15461591
if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))
15471592
return 0;
@@ -1638,6 +1683,23 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
16381683
return 2;
16391684
}
16401685

1686+
if (skip_prefix(placeholder, "(decorate", &arg)) {
1687+
struct decoration_options opts = { NULL };
1688+
size_t ret = 0;
1689+
1690+
if (*arg == ':') {
1691+
arg++;
1692+
parse_decoration_options(&arg, &opts);
1693+
}
1694+
if (*arg == ')') {
1695+
format_decorations(sb, commit, c->auto_color, &opts);
1696+
ret = arg - placeholder + 1;
1697+
}
1698+
1699+
free_decoration_options(&opts);
1700+
return ret;
1701+
}
1702+
16411703
/* For the rest we have to parse the commit header. */
16421704
if (!c->commit_header_parsed) {
16431705
msg = c->message =

t/t4205-log-pretty-formats.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,38 @@ test_expect_success 'clean log decoration' '
576576
test_cmp expected actual1
577577
'
578578

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+
echo "HEAD->foo bar qux" >expect5 &&
606+
git log --format="%(decorate:prefix=,suffix=,separator= ,tag=,pointer=->)" \
607+
-1 >actual5 &&
608+
test_cmp expect5 actual5
609+
'
610+
579611
cat >trailers <<EOF
580612
Signed-off-by: A U Thor <[email protected]>
581613
Acked-by: A U Thor <[email protected]>

0 commit comments

Comments
 (0)