When `log --decorate` is used, git will decorate commits with all available refs. While in most cases this the desired effect, under some conditions it can lead to excessively verbose output. Using `--exclude=<pattern>` can help mitigate that verboseness by removing unnecessary 'branches' from the output. However, if the tip of an excluded ref points to an ancestor of a non-excluded ref, git will decorate it regardless. With `--decorate-refs=<pattern>`, only refs that match <pattern> are decorated while `--decorate-refs-exclude=<pattern>` allows to do the reverse, remove ref decorations that match <pattern> Both can be used together but --decorate-refs-exclude patterns have precedence over --decorate-refs patterns. The pattern follows similar rules as `--glob` except it doesn't assume a trailing '/*' if glob characters are missing. Both `--decorate-refs` and `--decorate-refs-exclude` can be used multiple times. Signed-off-by: Kevin Daudt <me@xxxxxxxxx> Signed-off-by: Rafael Ascensão <rafa.almas@xxxxxxxxx> --- Documentation/git-log.txt | 12 ++++++ builtin/log.c | 10 ++++- log-tree.c | 37 ++++++++++++++--- log-tree.h | 6 ++- pretty.c | 4 +- revision.c | 2 +- t/t4202-log.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 10 deletions(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 32246fdb0..314417d89 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -38,6 +38,18 @@ OPTIONS are shown as if 'short' were given, otherwise no ref names are shown. The default option is 'short'. +--decorate-refs=<pattern>:: + Only print ref names that match the specified pattern. Uses the same + rules as `git rev-list --glob` except it doesn't assume a trailing a + trailing '/{asterisk}' if pattern lacks '?', '{asterisk}', or '['. + `--decorate-refs-exlclude` has precedence. + +--decorate-refs-exclude=<pattern>:: + Do not print ref names that match the specified pattern. Uses the same + rules as `git rev-list --glob` except it doesn't assume a trailing a + trailing '/{asterisk}' if pattern lacks '?', '{asterisk}', or '['. + Has precedence over `--decorate-refs`. + --source:: Print out the ref name given on the command line by which each commit was reached. diff --git a/builtin/log.c b/builtin/log.c index d81a09051..3587c0055 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -143,11 +143,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct userformat_want w; int quiet = 0, source = 0, mailmap = 0; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; + static struct string_list decorate_refs_exclude = STRING_LIST_INIT_DUP; + static struct string_list decorate_refs_include = STRING_LIST_INIT_DUP; + struct ref_include_exclude_list ref_filter_lists = {&decorate_refs_include, + &decorate_refs_exclude}; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, + N_("ref"), N_("only decorate these refs")), + OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, + N_("ref"), N_("do not decorate these refs")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", @@ -206,7 +214,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (decoration_style) { rev->show_decorations = 1; - load_ref_decorations(decoration_style); + load_ref_decorations(decoration_style, &ref_filter_lists); } if (rev->line_level_traverse) diff --git a/log-tree.c b/log-tree.c index cea056234..8efc7ac3d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -94,9 +94,33 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, { struct object *obj; enum decoration_type type = DECORATION_NONE; + struct ref_include_exclude_list *filter = (struct ref_include_exclude_list *)cb_data; + struct string_list_item *item; + struct strbuf real_pattern = STRBUF_INIT; + + if(filter && filter->exclude->nr > 0) { + /* if current ref is on the exclude list skip */ + for_each_string_list_item(item, filter->exclude) { + strbuf_reset(&real_pattern); + normalize_glob_ref(&real_pattern, NULL, item->string, 0); + if (!wildmatch(real_pattern.buf, refname, 0)) + goto finish; + } + } - assert(cb_data == NULL); + if (filter && filter->include->nr > 0) { + /* if current ref is present on the include jump to decorate */ + for_each_string_list_item(item, filter->include) { + strbuf_reset(&real_pattern); + normalize_glob_ref(&real_pattern, NULL, item->string, 0); + if (!wildmatch(real_pattern.buf, refname, 0)) + goto decorate; + } + /* Filter was given, but no match was found, skip */ + goto finish; + } +decorate: if (starts_with(refname, git_replace_ref_base)) { struct object_id original_oid; if (!check_replace_refs) @@ -136,6 +160,9 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, parse_object(&obj->oid); add_name_decoration(DECORATION_REF_TAG, refname, obj); } + +finish: + strbuf_release(&real_pattern); return 0; } @@ -148,15 +175,15 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data) return 0; } -void load_ref_decorations(int flags) +void load_ref_decorations(int flags, struct ref_include_exclude_list *data) { if (!decoration_loaded) { decoration_loaded = 1; decoration_flags = flags; - for_each_ref(add_ref_decoration, NULL); - head_ref(add_ref_decoration, NULL); - for_each_commit_graft(add_graft_decoration, NULL); + for_each_ref(add_ref_decoration, data); + head_ref(add_ref_decoration, data); + for_each_commit_graft(add_graft_decoration, data); } } diff --git a/log-tree.h b/log-tree.h index 48f11fb74..66563af88 100644 --- a/log-tree.h +++ b/log-tree.h @@ -7,6 +7,10 @@ struct log_info { struct commit *commit, *parent; }; +struct ref_include_exclude_list { + struct string_list *include, *exclude; +}; + int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); @@ -24,7 +28,7 @@ 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, int *need_8bit_cte_p); -void load_ref_decorations(int flags); +void load_ref_decorations(int flags, struct ref_include_exclude_list *); #define FORMAT_PATCH_NAME_MAX 64 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *); diff --git a/pretty.c b/pretty.c index 2f6b0ae6c..87a6cc4f9 100644 --- a/pretty.c +++ b/pretty.c @@ -1186,11 +1186,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(DECORATE_SHORT_REFS, NULL); format_decorations(sb, commit, c->auto_color); return 1; case 'D': - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(DECORATE_SHORT_REFS, NULL); format_decorations_extended(sb, commit, c->auto_color, "", ", ", ""); return 1; case 'g': /* reflog info */ diff --git a/revision.c b/revision.c index d167223e6..298ff054b 100644 --- a/revision.c +++ b/revision.c @@ -1822,7 +1822,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->simplify_by_decoration = 1; revs->limited = 1; revs->prune = 1; - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(DECORATE_SHORT_REFS, NULL); } else if (!strcmp(arg, "--date-order")) { revs->sort_order = REV_SORT_BY_COMMIT_DATE; revs->topo_order = 1; diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 8f155da7a..e26d09a5c 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -737,6 +737,107 @@ test_expect_success 'log.decorate configuration' ' ' +test_expect_success 'decorate-refs with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach + EOF + git log -n6 --decorate=short --pretty="%f%d" \ + --decorate-refs="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty='tformat:%f%d' \ + --decorate-refs='heads/octopus*' \ + --decorate-refs='tags/reach' >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (tag: reach, reach) + EOF + git log -n6 --decorate=short --pretty="%f%d" \ + --decorate-refs-exclude="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b, octopus-b) + octopus-a (tag: octopus-a, octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty='tformat:%f%d' \ + --decorate-refs-exclude='heads/octopus*' \ + --decorate-refs-exclude='tags/reach' >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs and decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (master) + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (reach) + EOF + git log -n6 --decorate=short --pretty='tformat:%f%d' \ + --decorate-refs='heads/*' \ + --decorate-refs-exclude='heads/oc*' >actual && + test_cmp expect.decorate actual +' + test_expect_success 'log.decorate config parsing' ' git log --oneline --decorate=full >expect.full && git log --oneline --decorate=short >expect.short && -- 2.15.0