diff mbox series

[v3,3/6] revision: introduce struct to handle exclusions

Message ID 2a6a67df1d470bf790025d55095c237ddc6a6bd6.1667823042.git.ps@pks.im (mailing list archive)
State Superseded
Headers show
Series receive-pack: only use visible refs for connectivity check | expand

Commit Message

Patrick Steinhardt Nov. 7, 2022, 12:16 p.m. UTC
The functions that handle exclusion of refs work on a single string
list. We're about to add a second mechanism for excluding refs though,
and it makes sense to reuse much of the same architecture for both kinds
of exclusion.

Introduce a new `struct ref_exclusions` that encapsulates all the logic
related to excluding refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rev-parse.c |  8 ++++----
 revision.c          | 47 ++++++++++++++++++++-------------------------
 revision.h          | 22 +++++++++++++++------
 3 files changed, 41 insertions(+), 36 deletions(-)

Comments

Ævar Arnfjörð Bjarmason Nov. 7, 2022, 12:51 p.m. UTC | #1
On Mon, Nov 07 2022, Patrick Steinhardt wrote:

> [[PGP Signed Part:Undecided]]
> The functions that handle exclusion of refs work on a single string
> list. We're about to add a second mechanism for excluding refs though,
> and it makes sense to reuse much of the same architecture for both kinds
> of exclusion.
>
> Introduce a new `struct ref_exclusions` that encapsulates all the logic
> related to excluding refs.

I think it's a good change, but probably worth mentioning tha we're
moving the "excluded_refs" from being malloc'd to the "struct
string_list" being embedded in this new "struct ref_exclusions".

That change isn't necessary for hoisting it into a container struct, but
does make things nicer down the line.

>  	struct string_list_item *item;
> -
> -	if (!ref_excludes)
> -		return 0;
> -	for_each_string_list_item(item, ref_excludes) {
> +	for_each_string_list_item(item, &exclusions->excluded_refs) {
>  		if (!wildmatch(item->string, path, 0))
>  			return 1;

E.g. because here we don't care about the distinction between NULL and
!list->nr anymore, it *does* matter in some cases, but it's always nice
to be able to clearly distinguish the cases where we don't, such as this
one....

> -void clear_ref_exclusion(struct string_list **ref_excludes_p)
> +void init_ref_exclusions(struct ref_exclusions *exclusions)
>  {
> -	if (*ref_excludes_p) {
> -		string_list_clear(*ref_excludes_p, 0);
> -		free(*ref_excludes_p);
> -	}
> -	*ref_excludes_p = NULL;

...and this becomes much nicer.

Aside: There's some churn, and this diff is worse for the
rename-while-at-it of "clear_ref_exclusion" to "add_ref_exclusion", but
that's probably worth it to have the macro match the struct name etc.

> +	string_list_init_dup(&exclusions->excluded_refs);

Okey, so this is partly my fault for not following up on f196c1e908d
(revisions API users: use release_revisions() needing REV_INFO_INIT,
2022-04-13) :); But here:

If we keep this *_init() function don't duplicate what you're adding to
the macro, just init this in terms of the macro. See the two-line
examples in:

	git grep -W memcpy.*blank

But here (and this is the part that's mostly me) as we don't malloc this
anymore you're only needing to keep this init function for
repo_init_revisions().

So, probably too big a digression for a "while at it", but FWIW this on
top of your topic would do:
	
	 revision.c | 10 ++--------
	 revision.h | 10 +++++++---
	 2 files changed, 9 insertions(+), 11 deletions(-)
	
	diff --git a/revision.c b/revision.c
	index 45652f9b0bb..cf352d1fa43 100644
	--- a/revision.c
	+++ b/revision.c
	@@ -1534,12 +1534,6 @@ int ref_excluded(const struct ref_exclusions *exclusions, const char *path)
	 	return 0;
	 }
	 
	-void init_ref_exclusions(struct ref_exclusions *exclusions)
	-{
	-	string_list_init_dup(&exclusions->excluded_refs);
	-	string_list_init_dup(&exclusions->hidden_refs);
	-}
	-
	 void clear_ref_exclusions(struct ref_exclusions *exclusions)
	 {
	 	string_list_clear(&exclusions->excluded_refs, 0);
	@@ -1897,7 +1891,8 @@ void repo_init_revisions(struct repository *r,
	 			 struct rev_info *revs,
	 			 const char *prefix)
	 {
	-	memset(revs, 0, sizeof(*revs));
	+	struct rev_info blank = REV_INFO_INIT;
	+	memcpy(revs, &blank, sizeof(*revs));
	 
	 	revs->repo = r;
	 	revs->abbrev = DEFAULT_ABBREV;
	@@ -1933,7 +1928,6 @@ void repo_init_revisions(struct repository *r,
	 
	 	init_display_notes(&revs->notes_opt);
	 	list_objects_filter_init(&revs->filter);
	-	init_ref_exclusions(&revs->ref_excludes);
	 }
	 
	 static void add_pending_commit_list(struct rev_info *revs,
	diff --git a/revision.h b/revision.h
	index fef5e063d16..75b8ecc307b 100644
	--- a/revision.h
	+++ b/revision.h
	@@ -94,6 +94,10 @@ struct ref_exclusions {
	 	 */
	 	struct string_list hidden_refs;
	 };
	+#define REF_EXCLUSIONS_INIT { \
	+	.excluded_refs = STRING_LIST_INIT_DUP, \
	+	.hidden_refs = STRING_LIST_INIT_DUP, \
	+}
	 
	 struct oidset;
	 struct topo_walk_info;
	@@ -371,7 +375,9 @@ struct rev_info {
	  * called before release_revisions() the "struct rev_info" can be left
	  * uninitialized.
	  */
	-#define REV_INFO_INIT { 0 }
	+#define REV_INFO_INIT { \
	+	.ref_excludes = REF_EXCLUSIONS_INIT, \
	+}
	 
	 /**
	  * Initialize a rev_info structure with default values. The third parameter may
	@@ -455,10 +461,8 @@ void show_object_with_name(FILE *, struct object *, const char *);
	 /**
	  * Helpers to check if a reference should be excluded.
	  */
	-#define REF_EXCLUSIONS_INIT { .excluded_refs = STRING_LIST_INIT_DUP, .hidden_refs = STRING_LIST_INIT_DUP }
	 
	 int ref_excluded(const struct ref_exclusions *exclusions, const char *path);
	-void init_ref_exclusions(struct ref_exclusions *);
	 void clear_ref_exclusions(struct ref_exclusions *);
	 void add_ref_exclusion(struct ref_exclusions *, const char *exclude);
	 void exclude_hidden_refs(struct ref_exclusions *, const char *section);

But I'll submit that cleanup seperately, but for now let's not duplicate
your REF_EXCLUSIONS_INIT macro here in init_ref_exclusions(), just have
the function do what the macro is doing, now that we don't need the
malloc.

> -void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
> +void clear_ref_exclusions(struct ref_exclusions *exclusions)
>  {
> -	if (!*ref_excludes_p) {
> -		CALLOC_ARRAY(*ref_excludes_p, 1);
> -		(*ref_excludes_p)->strdup_strings = 1;
> -	}
> -	string_list_append(*ref_excludes_p, exclude);
> +	string_list_clear(&exclusions->excluded_refs, 0);

Also nicer.

>  static void add_pending_commit_list(struct rev_info *revs,
> @@ -2689,10 +2684,10 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
>  			init_all_refs_cb(&cb, revs, *flags);
>  			other_head_refs(handle_one_ref, &cb);
>  		}
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if (!strcmp(arg, "--branches")) {
>  		handle_refs(refs, revs, *flags, refs_for_each_branch_ref);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if (!strcmp(arg, "--bisect")) {
>  		read_bisect_terms(&term_bad, &term_good);
>  		handle_refs(refs, revs, *flags, for_each_bad_bisect_ref);
> @@ -2701,15 +2696,15 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
>  		revs->bisect = 1;
>  	} else if (!strcmp(arg, "--tags")) {
>  		handle_refs(refs, revs, *flags, refs_for_each_tag_ref);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if (!strcmp(arg, "--remotes")) {
>  		handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
>  		struct all_refs_cb cb;
>  		init_all_refs_cb(&cb, revs, *flags);
>  		for_each_glob_ref(handle_one_ref, optarg, &cb);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  		return argcount;
>  	} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
>  		add_ref_exclusion(&revs->ref_excludes, optarg);
> @@ -2718,17 +2713,17 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
>  		struct all_refs_cb cb;
>  		init_all_refs_cb(&cb, revs, *flags);
>  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if (skip_prefix(arg, "--tags=", &optarg)) {
>  		struct all_refs_cb cb;
>  		init_all_refs_cb(&cb, revs, *flags);
>  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);
>  	} else if (skip_prefix(arg, "--remotes=", &optarg)) {
>  		struct all_refs_cb cb;
>  		init_all_refs_cb(&cb, revs, *flags);
>  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
> -		clear_ref_exclusion(&revs->ref_excludes);
> +		clear_ref_exclusions(&revs->ref_excludes);

The churn I mentioned with the renaming, so maybe worth doing that as a
"prep" commit?

> +struct ref_exclusions {
> +	/*
> +	 * Excluded refs is a list of wildmatch patterns. If any of the
> +	 * patterns matches, the reference will be excluded.
> +	 */
> +	struct string_list excluded_refs;
> +};

Per the above POC diff though, please move...

>  struct oidset;
>  struct topo_walk_info;
>  
> @@ -103,7 +111,7 @@ struct rev_info {
>  	struct list_objects_filter_options filter;
>  
>  	/* excluding from --branches, --refs, etc. expansion */
> -	struct string_list *ref_excludes;
> +	struct ref_exclusions ref_excludes;
>  
>  	/* Basic information */
>  	const char *prefix;
> @@ -439,12 +447,14 @@ void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees)
>  void show_object_with_name(FILE *, struct object *, const char *);
>  
>  /**
> - * Helpers to check if a "struct string_list" item matches with
> - * wildmatch().
> + * Helpers to check if a reference should be excluded.
>   */
> -int ref_excluded(struct string_list *, const char *path);
> -void clear_ref_exclusion(struct string_list **);
> -void add_ref_exclusion(struct string_list **, const char *exclude);
> +#define REF_EXCLUSIONS_INIT { .excluded_refs = STRING_LIST_INIT_DUP }

...this macro to right after declaring the struct, which is what we
usually do, and will help in adding it to "REV_INFO_INIT" sooner than
later.

Also, at the end of your series this end up being overly long, so per
the diff-above (which is tot he end of the series), let's start by
line-wrapping it:

	#define ..._INIT { \
        	.member = ..._INIT, \
	}
Patrick Steinhardt Nov. 8, 2022, 9:11 a.m. UTC | #2
On Mon, Nov 07, 2022 at 01:51:51PM +0100, Ævar Arnfjörð Bjarmason wrote:
> 
> On Mon, Nov 07 2022, Patrick Steinhardt wrote:
[snip]
> > +	string_list_init_dup(&exclusions->excluded_refs);
> 
> Okey, so this is partly my fault for not following up on f196c1e908d
> (revisions API users: use release_revisions() needing REV_INFO_INIT,
> 2022-04-13) :); But here:
> 
> If we keep this *_init() function don't duplicate what you're adding to
> the macro, just init this in terms of the macro. See the two-line
> examples in:
> 
> 	git grep -W memcpy.*blank

Makes sense.

> But here (and this is the part that's mostly me) as we don't malloc this
> anymore you're only needing to keep this init function for
> repo_init_revisions().
> 
> So, probably too big a digression for a "while at it", but FWIW this on
> top of your topic would do:
> 	
> 	 revision.c | 10 ++--------
> 	 revision.h | 10 +++++++---
> 	 2 files changed, 9 insertions(+), 11 deletions(-)
> 	
> 	diff --git a/revision.c b/revision.c
> 	index 45652f9b0bb..cf352d1fa43 100644
> 	--- a/revision.c
> 	+++ b/revision.c
> 	@@ -1534,12 +1534,6 @@ int ref_excluded(const struct ref_exclusions *exclusions, const char *path)
> 	 	return 0;
> 	 }
> 	 
> 	-void init_ref_exclusions(struct ref_exclusions *exclusions)
> 	-{
> 	-	string_list_init_dup(&exclusions->excluded_refs);
> 	-	string_list_init_dup(&exclusions->hidden_refs);
> 	-}
> 	-
> 	 void clear_ref_exclusions(struct ref_exclusions *exclusions)
> 	 {
> 	 	string_list_clear(&exclusions->excluded_refs, 0);
> 	@@ -1897,7 +1891,8 @@ void repo_init_revisions(struct repository *r,
> 	 			 struct rev_info *revs,
> 	 			 const char *prefix)
> 	 {
> 	-	memset(revs, 0, sizeof(*revs));
> 	+	struct rev_info blank = REV_INFO_INIT;
> 	+	memcpy(revs, &blank, sizeof(*revs));
> 	 
> 	 	revs->repo = r;
> 	 	revs->abbrev = DEFAULT_ABBREV;
> 	@@ -1933,7 +1928,6 @@ void repo_init_revisions(struct repository *r,
> 	 
> 	 	init_display_notes(&revs->notes_opt);
> 	 	list_objects_filter_init(&revs->filter);
> 	-	init_ref_exclusions(&revs->ref_excludes);
> 	 }
> 	 
> 	 static void add_pending_commit_list(struct rev_info *revs,
> 	diff --git a/revision.h b/revision.h
> 	index fef5e063d16..75b8ecc307b 100644
> 	--- a/revision.h
> 	+++ b/revision.h
> 	@@ -94,6 +94,10 @@ struct ref_exclusions {
> 	 	 */
> 	 	struct string_list hidden_refs;
> 	 };
> 	+#define REF_EXCLUSIONS_INIT { \
> 	+	.excluded_refs = STRING_LIST_INIT_DUP, \
> 	+	.hidden_refs = STRING_LIST_INIT_DUP, \
> 	+}
> 	 
> 	 struct oidset;
> 	 struct topo_walk_info;
> 	@@ -371,7 +375,9 @@ struct rev_info {
> 	  * called before release_revisions() the "struct rev_info" can be left
> 	  * uninitialized.
> 	  */
> 	-#define REV_INFO_INIT { 0 }
> 	+#define REV_INFO_INIT { \
> 	+	.ref_excludes = REF_EXCLUSIONS_INIT, \
> 	+}
> 	 
> 	 /**
> 	  * Initialize a rev_info structure with default values. The third parameter may
> 	@@ -455,10 +461,8 @@ void show_object_with_name(FILE *, struct object *, const char *);
> 	 /**
> 	  * Helpers to check if a reference should be excluded.
> 	  */
> 	-#define REF_EXCLUSIONS_INIT { .excluded_refs = STRING_LIST_INIT_DUP, .hidden_refs = STRING_LIST_INIT_DUP }
> 	 
> 	 int ref_excluded(const struct ref_exclusions *exclusions, const char *path);
> 	-void init_ref_exclusions(struct ref_exclusions *);
> 	 void clear_ref_exclusions(struct ref_exclusions *);
> 	 void add_ref_exclusion(struct ref_exclusions *, const char *exclude);
> 	 void exclude_hidden_refs(struct ref_exclusions *, const char *section);
> 
> But I'll submit that cleanup seperately, but for now let's not duplicate
> your REF_EXCLUSIONS_INIT macro here in init_ref_exclusions(), just have
> the function do what the macro is doing, now that we don't need the
> malloc.

Great, thanks.

[snip]
> >  static void add_pending_commit_list(struct rev_info *revs,
> > @@ -2689,10 +2684,10 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
> >  			init_all_refs_cb(&cb, revs, *flags);
> >  			other_head_refs(handle_one_ref, &cb);
> >  		}
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if (!strcmp(arg, "--branches")) {
> >  		handle_refs(refs, revs, *flags, refs_for_each_branch_ref);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if (!strcmp(arg, "--bisect")) {
> >  		read_bisect_terms(&term_bad, &term_good);
> >  		handle_refs(refs, revs, *flags, for_each_bad_bisect_ref);
> > @@ -2701,15 +2696,15 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
> >  		revs->bisect = 1;
> >  	} else if (!strcmp(arg, "--tags")) {
> >  		handle_refs(refs, revs, *flags, refs_for_each_tag_ref);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if (!strcmp(arg, "--remotes")) {
> >  		handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
> >  		struct all_refs_cb cb;
> >  		init_all_refs_cb(&cb, revs, *flags);
> >  		for_each_glob_ref(handle_one_ref, optarg, &cb);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  		return argcount;
> >  	} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
> >  		add_ref_exclusion(&revs->ref_excludes, optarg);
> > @@ -2718,17 +2713,17 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
> >  		struct all_refs_cb cb;
> >  		init_all_refs_cb(&cb, revs, *flags);
> >  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if (skip_prefix(arg, "--tags=", &optarg)) {
> >  		struct all_refs_cb cb;
> >  		init_all_refs_cb(&cb, revs, *flags);
> >  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> >  	} else if (skip_prefix(arg, "--remotes=", &optarg)) {
> >  		struct all_refs_cb cb;
> >  		init_all_refs_cb(&cb, revs, *flags);
> >  		for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
> > -		clear_ref_exclusion(&revs->ref_excludes);
> > +		clear_ref_exclusions(&revs->ref_excludes);
> 
> The churn I mentioned with the renaming, so maybe worth doing that as a
> "prep" commit?

Hm. I don't think it's too bad, and it's weird to rename things already
without any clear justification why that's visible from the diff. Like,
there is no `struct ref_exclusions` yet, why rename it?

I'll retain this in a single commit if you don't mind, but amend the
commit message to explicitly mention the rename.

> > +struct ref_exclusions {
> > +	/*
> > +	 * Excluded refs is a list of wildmatch patterns. If any of the
> > +	 * patterns matches, the reference will be excluded.
> > +	 */
> > +	struct string_list excluded_refs;
> > +};
> 
> Per the above POC diff though, please move...
> 
> >  struct oidset;
> >  struct topo_walk_info;
> >  
> > @@ -103,7 +111,7 @@ struct rev_info {
> >  	struct list_objects_filter_options filter;
> >  
> >  	/* excluding from --branches, --refs, etc. expansion */
> > -	struct string_list *ref_excludes;
> > +	struct ref_exclusions ref_excludes;
> >  
> >  	/* Basic information */
> >  	const char *prefix;
> > @@ -439,12 +447,14 @@ void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees)
> >  void show_object_with_name(FILE *, struct object *, const char *);
> >  
> >  /**
> > - * Helpers to check if a "struct string_list" item matches with
> > - * wildmatch().
> > + * Helpers to check if a reference should be excluded.
> >   */
> > -int ref_excluded(struct string_list *, const char *path);
> > -void clear_ref_exclusion(struct string_list **);
> > -void add_ref_exclusion(struct string_list **, const char *exclude);
> > +#define REF_EXCLUSIONS_INIT { .excluded_refs = STRING_LIST_INIT_DUP }
> 
> ...this macro to right after declaring the struct, which is what we
> usually do, and will help in adding it to "REV_INFO_INIT" sooner than
> later.

Fair, will change.

> Also, at the end of your series this end up being overly long, so per
> the diff-above (which is tot he end of the series), let's start by
> line-wrapping it:
> 
> 	#define ..._INIT { \
>         	.member = ..._INIT, \
> 	}

Makes sense.

Patrick
diff mbox series

Patch

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 8f61050bde..7fa5b6991b 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -39,7 +39,7 @@  static int abbrev_ref_strict;
 static int output_sq;
 
 static int stuck_long;
-static struct string_list *ref_excludes;
+static struct ref_exclusions ref_excludes = REF_EXCLUSIONS_INIT;
 
 /*
  * Some arguments are relevant "revision" arguments,
@@ -198,7 +198,7 @@  static int show_default(void)
 static int show_reference(const char *refname, const struct object_id *oid,
 			  int flag UNUSED, void *cb_data UNUSED)
 {
-	if (ref_excluded(ref_excludes, refname))
+	if (ref_excluded(&ref_excludes, refname))
 		return 0;
 	show_rev(NORMAL, oid, refname);
 	return 0;
@@ -585,7 +585,7 @@  static void handle_ref_opt(const char *pattern, const char *prefix)
 		for_each_glob_ref_in(show_reference, pattern, prefix, NULL);
 	else
 		for_each_ref_in(prefix, show_reference, NULL);
-	clear_ref_exclusion(&ref_excludes);
+	clear_ref_exclusions(&ref_excludes);
 }
 
 enum format_type {
@@ -863,7 +863,7 @@  int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 			}
 			if (!strcmp(arg, "--all")) {
 				for_each_ref(show_reference, NULL);
-				clear_ref_exclusion(&ref_excludes);
+				clear_ref_exclusions(&ref_excludes);
 				continue;
 			}
 			if (skip_prefix(arg, "--disambiguate=", &arg)) {
diff --git a/revision.c b/revision.c
index be755670e2..e5eaaa24ba 100644
--- a/revision.c
+++ b/revision.c
@@ -1517,35 +1517,29 @@  static void add_rev_cmdline_list(struct rev_info *revs,
 	}
 }
 
-int ref_excluded(struct string_list *ref_excludes, const char *path)
+int ref_excluded(const struct ref_exclusions *exclusions, const char *path)
 {
 	struct string_list_item *item;
-
-	if (!ref_excludes)
-		return 0;
-	for_each_string_list_item(item, ref_excludes) {
+	for_each_string_list_item(item, &exclusions->excluded_refs) {
 		if (!wildmatch(item->string, path, 0))
 			return 1;
 	}
 	return 0;
 }
 
-void clear_ref_exclusion(struct string_list **ref_excludes_p)
+void init_ref_exclusions(struct ref_exclusions *exclusions)
 {
-	if (*ref_excludes_p) {
-		string_list_clear(*ref_excludes_p, 0);
-		free(*ref_excludes_p);
-	}
-	*ref_excludes_p = NULL;
+	string_list_init_dup(&exclusions->excluded_refs);
 }
 
-void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
+void clear_ref_exclusions(struct ref_exclusions *exclusions)
 {
-	if (!*ref_excludes_p) {
-		CALLOC_ARRAY(*ref_excludes_p, 1);
-		(*ref_excludes_p)->strdup_strings = 1;
-	}
-	string_list_append(*ref_excludes_p, exclude);
+	string_list_clear(&exclusions->excluded_refs, 0);
+}
+
+void add_ref_exclusion(struct ref_exclusions *exclusions, const char *exclude)
+{
+	string_list_append(&exclusions->excluded_refs, exclude);
 }
 
 struct all_refs_cb {
@@ -1563,7 +1557,7 @@  static int handle_one_ref(const char *path, const struct object_id *oid,
 	struct all_refs_cb *cb = cb_data;
 	struct object *object;
 
-	if (ref_excluded(cb->all_revs->ref_excludes, path))
+	if (ref_excluded(&cb->all_revs->ref_excludes, path))
 	    return 0;
 
 	object = get_reference(cb->all_revs, path, oid, cb->all_flags);
@@ -1901,6 +1895,7 @@  void repo_init_revisions(struct repository *r,
 
 	init_display_notes(&revs->notes_opt);
 	list_objects_filter_init(&revs->filter);
+	init_ref_exclusions(&revs->ref_excludes);
 }
 
 static void add_pending_commit_list(struct rev_info *revs,
@@ -2689,10 +2684,10 @@  static int handle_revision_pseudo_opt(struct rev_info *revs,
 			init_all_refs_cb(&cb, revs, *flags);
 			other_head_refs(handle_one_ref, &cb);
 		}
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--branches")) {
 		handle_refs(refs, revs, *flags, refs_for_each_branch_ref);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--bisect")) {
 		read_bisect_terms(&term_bad, &term_good);
 		handle_refs(refs, revs, *flags, for_each_bad_bisect_ref);
@@ -2701,15 +2696,15 @@  static int handle_revision_pseudo_opt(struct rev_info *revs,
 		revs->bisect = 1;
 	} else if (!strcmp(arg, "--tags")) {
 		handle_refs(refs, revs, *flags, refs_for_each_tag_ref);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--remotes")) {
 		handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
 		struct all_refs_cb cb;
 		init_all_refs_cb(&cb, revs, *flags);
 		for_each_glob_ref(handle_one_ref, optarg, &cb);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 		return argcount;
 	} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
 		add_ref_exclusion(&revs->ref_excludes, optarg);
@@ -2718,17 +2713,17 @@  static int handle_revision_pseudo_opt(struct rev_info *revs,
 		struct all_refs_cb cb;
 		init_all_refs_cb(&cb, revs, *flags);
 		for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (skip_prefix(arg, "--tags=", &optarg)) {
 		struct all_refs_cb cb;
 		init_all_refs_cb(&cb, revs, *flags);
 		for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (skip_prefix(arg, "--remotes=", &optarg)) {
 		struct all_refs_cb cb;
 		init_all_refs_cb(&cb, revs, *flags);
 		for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
-		clear_ref_exclusion(&revs->ref_excludes);
+		clear_ref_exclusions(&revs->ref_excludes);
 	} else if (!strcmp(arg, "--reflog")) {
 		add_reflogs_to_pending(revs, *flags);
 	} else if (!strcmp(arg, "--indexed-objects")) {
diff --git a/revision.h b/revision.h
index afe1b77985..87d6824c55 100644
--- a/revision.h
+++ b/revision.h
@@ -81,6 +81,14 @@  struct rev_cmdline_info {
 	} *rev;
 };
 
+struct ref_exclusions {
+	/*
+	 * Excluded refs is a list of wildmatch patterns. If any of the
+	 * patterns matches, the reference will be excluded.
+	 */
+	struct string_list excluded_refs;
+};
+
 struct oidset;
 struct topo_walk_info;
 
@@ -103,7 +111,7 @@  struct rev_info {
 	struct list_objects_filter_options filter;
 
 	/* excluding from --branches, --refs, etc. expansion */
-	struct string_list *ref_excludes;
+	struct ref_exclusions ref_excludes;
 
 	/* Basic information */
 	const char *prefix;
@@ -439,12 +447,14 @@  void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees)
 void show_object_with_name(FILE *, struct object *, const char *);
 
 /**
- * Helpers to check if a "struct string_list" item matches with
- * wildmatch().
+ * Helpers to check if a reference should be excluded.
  */
-int ref_excluded(struct string_list *, const char *path);
-void clear_ref_exclusion(struct string_list **);
-void add_ref_exclusion(struct string_list **, const char *exclude);
+#define REF_EXCLUSIONS_INIT { .excluded_refs = STRING_LIST_INIT_DUP }
+
+int ref_excluded(const struct ref_exclusions *exclusions, const char *path);
+void init_ref_exclusions(struct ref_exclusions *);
+void clear_ref_exclusions(struct ref_exclusions *);
+void add_ref_exclusion(struct ref_exclusions *, const char *exclude);
 
 /**
  * This function can be used if you want to add commit objects as revision