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 <m...@ikke.info>
Signed-off-by: Rafael Ascensão <rafa.al...@gmail.com>
---
 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

Reply via email to