Skip to content

Commit 65516f5

Browse files
rafascgitster
authored andcommitted
log: add option to choose which refs to decorate
When `log --decorate` is used, git will decorate commits with all available refs. While in most cases this may give the desired effect, under some conditions it can lead to excessively verbose output. Introduce two command line options, `--decorate-refs=<pattern>` and `--decorate-refs-exclude=<pattern>` to allow the user to select which refs are used in decoration. When "--decorate-refs=<pattern>" is given, only the refs that match the pattern are used in decoration. The refs that match the pattern when "--decorate-refs-exclude=<pattern>" is given, are never used in decoration. These options follow the same convention for mixing negative and positive patterns across the system, assuming that the inclusive default is to match all refs available. (1) if there is no positive pattern given, pretend as if an inclusive default positive pattern was given; (2) for each candidate, reject it if it matches no positive pattern, or if it matches any one of the negative patterns. The rules for what is considered a match are slightly different from the rules used elsewhere. Commands like `log --glob` assume a trailing '/*' when glob chars are not present in the pattern. This makes it difficult to specify a single ref. On the other hand, commands like `describe --match --all` allow specifying exact refs, but do not have the convenience of allowing "shorthand refs" like 'refs/heads' or 'heads' to refer to 'refs/heads/*'. The commands introduced in this patch consider a match if: (a) the pattern contains globs chars, and regular pattern matching returns a match. (b) the pattern does not contain glob chars, and ref '<pattern>' exists, or if ref exists under '<pattern>/' This allows both behaviours (allowing single refs and shorthand refs) yet remaining compatible with existent commands. Helped-by: Kevin Daudt <me@ikke.info> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Rafael Ascensão <rafa.almas@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 14c63a9 commit 65516f5

File tree

9 files changed

+232
-11
lines changed

9 files changed

+232
-11
lines changed

Documentation/git-log.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ OPTIONS
3838
are shown as if 'short' were given, otherwise no ref names are
3939
shown. The default option is 'short'.
4040

41+
--decorate-refs=<pattern>::
42+
--decorate-refs-exclude=<pattern>::
43+
If no `--decorate-refs` is given, pretend as if all refs were
44+
included. For each candidate, do not use it for decoration if it
45+
matches any patterns given to `--decorate-refs-exclude` or if it
46+
doesn't match any of the patterns given to `--decorate-refs`.
47+
4148
--source::
4249
Print out the ref name given on the command line by which each
4350
commit was reached.

builtin/log.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
142142
struct userformat_want w;
143143
int quiet = 0, source = 0, mailmap = 0;
144144
static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
145+
static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
146+
static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
147+
struct decoration_filter decoration_filter = {&decorate_refs_include,
148+
&decorate_refs_exclude};
145149

146150
const struct option builtin_log_options[] = {
147151
OPT__QUIET(&quiet, N_("suppress diff output")),
148152
OPT_BOOL(0, "source", &source, N_("show source")),
149153
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
154+
OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
155+
N_("pattern"), N_("only decorate refs that match <pattern>")),
156+
OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
157+
N_("pattern"), N_("do not decorate refs that match <pattern>")),
150158
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
151159
PARSE_OPT_OPTARG, decorate_callback},
152160
OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
@@ -205,7 +213,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
205213

206214
if (decoration_style) {
207215
rev->show_decorations = 1;
208-
load_ref_decorations(decoration_style);
216+
load_ref_decorations(&decoration_filter, decoration_style);
209217
}
210218

211219
if (rev->line_level_traverse)

log-tree.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,12 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
9494
{
9595
struct object *obj;
9696
enum decoration_type type = DECORATION_NONE;
97+
struct decoration_filter *filter = (struct decoration_filter *)cb_data;
9798

98-
assert(cb_data == NULL);
99+
if (filter && !ref_filter_match(refname,
100+
filter->include_ref_pattern,
101+
filter->exclude_ref_pattern))
102+
return 0;
99103

100104
if (starts_with(refname, git_replace_ref_base)) {
101105
struct object_id original_oid;
@@ -148,15 +152,23 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data)
148152
return 0;
149153
}
150154

151-
void load_ref_decorations(int flags)
155+
void load_ref_decorations(struct decoration_filter *filter, int flags)
152156
{
153157
if (!decoration_loaded) {
154-
158+
if (filter) {
159+
struct string_list_item *item;
160+
for_each_string_list_item(item, filter->exclude_ref_pattern) {
161+
normalize_glob_ref(item, NULL, item->string);
162+
}
163+
for_each_string_list_item(item, filter->include_ref_pattern) {
164+
normalize_glob_ref(item, NULL, item->string);
165+
}
166+
}
155167
decoration_loaded = 1;
156168
decoration_flags = flags;
157-
for_each_ref(add_ref_decoration, NULL);
158-
head_ref(add_ref_decoration, NULL);
159-
for_each_commit_graft(add_graft_decoration, NULL);
169+
for_each_ref(add_ref_decoration, filter);
170+
head_ref(add_ref_decoration, filter);
171+
for_each_commit_graft(add_graft_decoration, filter);
160172
}
161173
}
162174

log-tree.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ struct log_info {
77
struct commit *commit, *parent;
88
};
99

10+
struct decoration_filter {
11+
struct string_list *include_ref_pattern, *exclude_ref_pattern;
12+
};
13+
1014
int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
1115
void init_log_tree_opt(struct rev_info *);
1216
int log_tree_diff_flush(struct rev_info *);
@@ -24,7 +28,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit);
2428
void log_write_email_headers(struct rev_info *opt, struct commit *commit,
2529
const char **extra_headers_p,
2630
int *need_8bit_cte_p);
27-
void load_ref_decorations(int flags);
31+
void load_ref_decorations(struct decoration_filter *filter, int flags);
2832

2933
#define FORMAT_PATCH_NAME_MAX 64
3034
void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);

pretty.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,11 +1186,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
11861186
strbuf_addstr(sb, get_revision_mark(NULL, commit));
11871187
return 1;
11881188
case 'd':
1189-
load_ref_decorations(DECORATE_SHORT_REFS);
1189+
load_ref_decorations(NULL, DECORATE_SHORT_REFS);
11901190
format_decorations(sb, commit, c->auto_color);
11911191
return 1;
11921192
case 'D':
1193-
load_ref_decorations(DECORATE_SHORT_REFS);
1193+
load_ref_decorations(NULL, DECORATE_SHORT_REFS);
11941194
format_decorations_extended(sb, commit, c->auto_color, "", ", ", "");
11951195
return 1;
11961196
case 'g': /* reflog info */

refs.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,50 @@ int ref_exists(const char *refname)
242242
return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
243243
}
244244

245+
static int match_ref_pattern(const char *refname,
246+
const struct string_list_item *item)
247+
{
248+
int matched = 0;
249+
if (item->util == NULL) {
250+
if (!wildmatch(item->string, refname, 0))
251+
matched = 1;
252+
} else {
253+
const char *rest;
254+
if (skip_prefix(refname, item->string, &rest) &&
255+
(!*rest || *rest == '/'))
256+
matched = 1;
257+
}
258+
return matched;
259+
}
260+
261+
int ref_filter_match(const char *refname,
262+
const struct string_list *include_patterns,
263+
const struct string_list *exclude_patterns)
264+
{
265+
struct string_list_item *item;
266+
267+
if (exclude_patterns && exclude_patterns->nr) {
268+
for_each_string_list_item(item, exclude_patterns) {
269+
if (match_ref_pattern(refname, item))
270+
return 0;
271+
}
272+
}
273+
274+
if (include_patterns && include_patterns->nr) {
275+
int found = 0;
276+
for_each_string_list_item(item, include_patterns) {
277+
if (match_ref_pattern(refname, item)) {
278+
found = 1;
279+
break;
280+
}
281+
}
282+
283+
if (!found)
284+
return 0;
285+
}
286+
return 1;
287+
}
288+
245289
static int filter_refs(const char *refname, const struct object_id *oid,
246290
int flags, void *data)
247291
{
@@ -369,6 +413,27 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
369413
return ret;
370414
}
371415

416+
void normalize_glob_ref(struct string_list_item *item, const char *prefix,
417+
const char *pattern)
418+
{
419+
struct strbuf normalized_pattern = STRBUF_INIT;
420+
421+
if (*pattern == '/')
422+
BUG("pattern must not start with '/'");
423+
424+
if (prefix) {
425+
strbuf_addstr(&normalized_pattern, prefix);
426+
}
427+
else if (!starts_with(pattern, "refs/"))
428+
strbuf_addstr(&normalized_pattern, "refs/");
429+
strbuf_addstr(&normalized_pattern, pattern);
430+
strbuf_strip_suffix(&normalized_pattern, "/");
431+
432+
item->string = strbuf_detach(&normalized_pattern, NULL);
433+
item->util = has_glob_specials(pattern) ? NULL : item->string;
434+
strbuf_release(&normalized_pattern);
435+
}
436+
372437
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
373438
const char *prefix, void *cb_data)
374439
{

refs.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,30 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
312312
int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
313313
int for_each_rawref(each_ref_fn fn, void *cb_data);
314314

315+
/*
316+
* Normalizes partial refs to their fully qualified form.
317+
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
318+
* <prefix> will default to 'refs/' if NULL.
319+
*
320+
* item.string will be set to the result.
321+
* item.util will be set to NULL if <pattern> contains glob characters, or
322+
* non-NULL if it doesn't.
323+
*/
324+
void normalize_glob_ref(struct string_list_item *item, const char *prefix,
325+
const char *pattern);
326+
327+
/*
328+
* Returns 0 if refname matches any of the exclude_patterns, or if it doesn't
329+
* match any of the include_patterns. Returns 1 otherwise.
330+
*
331+
* If pattern list is NULL or empty, matching against that list is skipped.
332+
* This has the effect of matching everything by default, unless the user
333+
* specifies rules otherwise.
334+
*/
335+
int ref_filter_match(const char *refname,
336+
const struct string_list *include_patterns,
337+
const struct string_list *exclude_patterns);
338+
315339
static inline const char *has_glob_specials(const char *pattern)
316340
{
317341
return strpbrk(pattern, "?*[");

revision.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1832,7 +1832,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
18321832
revs->simplify_by_decoration = 1;
18331833
revs->limited = 1;
18341834
revs->prune = 1;
1835-
load_ref_decorations(DECORATE_SHORT_REFS);
1835+
load_ref_decorations(NULL, DECORATE_SHORT_REFS);
18361836
} else if (!strcmp(arg, "--date-order")) {
18371837
revs->sort_order = REV_SORT_BY_COMMIT_DATE;
18381838
revs->topo_order = 1;

t/t4202-log.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,107 @@ test_expect_success 'log.decorate configuration' '
737737
738738
'
739739

740+
test_expect_success 'decorate-refs with glob' '
741+
cat >expect.decorate <<-\EOF &&
742+
Merge-tag-reach
743+
Merge-tags-octopus-a-and-octopus-b
744+
seventh
745+
octopus-b (octopus-b)
746+
octopus-a (octopus-a)
747+
reach
748+
EOF
749+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
750+
--decorate-refs="heads/octopus*" >actual &&
751+
test_cmp expect.decorate actual
752+
'
753+
754+
test_expect_success 'decorate-refs without globs' '
755+
cat >expect.decorate <<-\EOF &&
756+
Merge-tag-reach
757+
Merge-tags-octopus-a-and-octopus-b
758+
seventh
759+
octopus-b
760+
octopus-a
761+
reach (tag: reach)
762+
EOF
763+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
764+
--decorate-refs="tags/reach" >actual &&
765+
test_cmp expect.decorate actual
766+
'
767+
768+
test_expect_success 'multiple decorate-refs' '
769+
cat >expect.decorate <<-\EOF &&
770+
Merge-tag-reach
771+
Merge-tags-octopus-a-and-octopus-b
772+
seventh
773+
octopus-b (octopus-b)
774+
octopus-a (octopus-a)
775+
reach (tag: reach)
776+
EOF
777+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
778+
--decorate-refs="heads/octopus*" \
779+
--decorate-refs="tags/reach" >actual &&
780+
test_cmp expect.decorate actual
781+
'
782+
783+
test_expect_success 'decorate-refs-exclude with glob' '
784+
cat >expect.decorate <<-\EOF &&
785+
Merge-tag-reach (HEAD -> master)
786+
Merge-tags-octopus-a-and-octopus-b
787+
seventh (tag: seventh)
788+
octopus-b (tag: octopus-b)
789+
octopus-a (tag: octopus-a)
790+
reach (tag: reach, reach)
791+
EOF
792+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
793+
--decorate-refs-exclude="heads/octopus*" >actual &&
794+
test_cmp expect.decorate actual
795+
'
796+
797+
test_expect_success 'decorate-refs-exclude without globs' '
798+
cat >expect.decorate <<-\EOF &&
799+
Merge-tag-reach (HEAD -> master)
800+
Merge-tags-octopus-a-and-octopus-b
801+
seventh (tag: seventh)
802+
octopus-b (tag: octopus-b, octopus-b)
803+
octopus-a (tag: octopus-a, octopus-a)
804+
reach (reach)
805+
EOF
806+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
807+
--decorate-refs-exclude="tags/reach" >actual &&
808+
test_cmp expect.decorate actual
809+
'
810+
811+
test_expect_success 'multiple decorate-refs-exclude' '
812+
cat >expect.decorate <<-\EOF &&
813+
Merge-tag-reach (HEAD -> master)
814+
Merge-tags-octopus-a-and-octopus-b
815+
seventh (tag: seventh)
816+
octopus-b (tag: octopus-b)
817+
octopus-a (tag: octopus-a)
818+
reach (reach)
819+
EOF
820+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
821+
--decorate-refs-exclude="heads/octopus*" \
822+
--decorate-refs-exclude="tags/reach" >actual &&
823+
test_cmp expect.decorate actual
824+
'
825+
826+
test_expect_success 'decorate-refs and decorate-refs-exclude' '
827+
cat >expect.decorate <<-\EOF &&
828+
Merge-tag-reach (master)
829+
Merge-tags-octopus-a-and-octopus-b
830+
seventh
831+
octopus-b
832+
octopus-a
833+
reach (reach)
834+
EOF
835+
git log -n6 --decorate=short --pretty="tformat:%f%d" \
836+
--decorate-refs="heads/*" \
837+
--decorate-refs-exclude="heads/oc*" >actual &&
838+
test_cmp expect.decorate actual
839+
'
840+
740841
test_expect_success 'log.decorate config parsing' '
741842
git log --oneline --decorate=full >expect.full &&
742843
git log --oneline --decorate=short >expect.short &&

0 commit comments

Comments
 (0)