* [PATCH 00/17] refs: unify `refs_for_each_*()` functions
@ 2026-02-20 8:24 Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 01/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
` (18 more replies)
0 siblings, 19 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Hi,
we currently have 14 different `refs_for_each_*()` functions, with each
of them doing slightly different things. This makes for a confusing API
surface, and because the API is not built for extension we have to add a
new function every now and then to handle another esoteric edge case
that will ultimately only have at most a handful of callers.
This design isn't really sensible in my opinion, and this patch series
aims to fix that. Instead of having a dozen different functions, it
introduces a new `refs_for_each_ref_ext()` function that simply takes an
options structure as input. From thereon, callers can mix and match the
parameters that they care about.
The patch series is structured like this:
- Patches 1 to 5 introduce some preliminary cleanups.
- Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move
more functionality into it. This also fixes a performance bug that
we have in one of the implementations.
- Patch 10 adds some more verification for options that would have
caught the bugs in ps/for-each-ref-in-fixes.
- The remaining patches drop 7 out of 14 functions and replace them
with `refs_for_each_ref_ext()`. It results in a bit of churn, so
while I think this churn is worth it, I consider these patches to be
optional.
The patch series is built on top of 73fd77805f (The 5th batch,
2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:
simplify string_list memory handling, 2026-02-19) merged into it.
Thanks!
Patrick
---
Patrick Steinhardt (17):
refs: move `refs_head_ref_namespaced()`
refs: move `do_for_each_ref_flags` further up
refs: rename `do_for_each_ref_flags`
refs: rename `each_ref_fn`
refs: remove unused `refs_for_each_include_root_ref()`
refs: introduce `refs_for_each_ref_ext`
refs: speed up `refs_for_each_glob_ref_in()`
refs: generalize `refs_for_each_namespaced_ref()`
refs: generalize `refs_for_each_fullref_in_prefixes()`
refs: improve verification for-each-ref options
refs: replace `refs_for_each_ref_in()`
refs: replace `refs_for_each_rawref()`
refs: replace `refs_for_each_rawref_in()`
refs: replace `refs_for_each_glob_ref_in()`
refs: replace `refs_for_each_glob_ref()`
refs: replace `refs_for_each_namespaced_ref()`
refs: replace `refs_for_each_fullref_in()`
bisect.c | 16 ++-
builtin/bisect.c | 34 ++++--
builtin/describe.c | 7 +-
builtin/fetch.c | 7 +-
builtin/fsck.c | 7 +-
builtin/receive-pack.c | 8 +-
builtin/remote.c | 8 +-
builtin/rev-parse.c | 37 ++++---
builtin/show-ref.c | 21 ++--
fetch-pack.c | 15 ++-
http-backend.c | 8 +-
ls-refs.c | 11 +-
notes.c | 7 +-
pack-bitmap.c | 15 +--
pack-bitmap.h | 2 +-
ref-filter.c | 19 ++--
refs.c | 256 ++++++++++++++++++++++------------------------
refs.h | 198 +++++++++++++++++------------------
refs/files-backend.c | 19 ++--
refs/iterator.c | 2 +-
refs/packed-backend.c | 8 +-
refs/reftable-backend.c | 10 +-
revision.c | 46 ++++++---
submodule.c | 2 +-
t/helper/test-ref-store.c | 15 ++-
upload-pack.c | 13 ++-
worktree.c | 2 +-
worktree.h | 2 +-
28 files changed, 437 insertions(+), 358 deletions(-)
---
base-commit: dbbe43524e0814c1f93325795ed6aa26eb6e587e
change-id: 20260220-pks-refs-for-each-unification-7572c694cfc0
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH 01/17] refs: move `refs_head_ref_namespaced()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 8:05 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 02/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
` (17 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The function `refs_head_ref_namespaced()` is somewhat special when
compared to most of the other functions that take a callback function:
while `refs_for_each_*()` functions yield multiple refs, we only yield
at most the HEAD ref of the current function. As such, the function is
related to `refs_head_ref()` and not to the for-each functions.
Move the function to be located next to `refs_head_ref()` to clarify.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/refs.h b/refs.h
index f16b1b697b..62e8ef61e7 100644
--- a/refs.h
+++ b/refs.h
@@ -413,6 +413,9 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);
*/
int refs_head_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
+int refs_head_ref_namespaced(struct ref_store *refs,
+ each_ref_fn fn, void *cb_data);
+
int refs_for_each_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
@@ -456,8 +459,6 @@ int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
const char *pattern, const char *prefix, void *cb_data);
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 02/17] refs: move `do_for_each_ref_flags` further up
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 01/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 03/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
` (16 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Move the `do_for_each_ref_flags` enum further up. This prepares for
subsequent changes, where the flags will be used by more functions.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.h | 74 +++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/refs.h b/refs.h
index 62e8ef61e7..40974c017b 100644
--- a/refs.h
+++ b/refs.h
@@ -402,6 +402,43 @@ int reference_get_peeled_oid(struct repository *repo,
*/
typedef int each_ref_fn(const struct reference *ref, void *cb_data);
+/*
+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
+ * which feeds it).
+ */
+enum do_for_each_ref_flags {
+ /*
+ * Include broken references in a do_for_each_ref*() iteration, which
+ * would normally be omitted. This includes both refs that point to
+ * missing objects (a true repository corruption), ones with illegal
+ * names (which we prefer not to expose to callers), as well as
+ * dangling symbolic refs (i.e., those that point to a non-existent
+ * ref; this is not a corruption, but as they have no valid oid, we
+ * omit them from normal iteration results).
+ */
+ DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+
+ /*
+ * Only include per-worktree refs in a do_for_each_ref*() iteration.
+ * Normally this will be used with a files ref_store, since that's
+ * where all reference backends will presumably store their
+ * per-worktree refs.
+ */
+ DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+
+ /*
+ * Omit dangling symrefs from output; this only has an effect with
+ * INCLUDE_BROKEN, since they are otherwise not included at all.
+ */
+ DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+
+ /*
+ * Include root refs i.e. HEAD and pseudorefs along with the regular
+ * refs.
+ */
+ DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
+};
+
/*
* The following functions invoke the specified callback function for
* each reference indicated. If the function ever returns a nonzero
@@ -1332,43 +1369,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
*/
struct ref_iterator;
-/*
- * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
- * which feeds it).
- */
-enum do_for_each_ref_flags {
- /*
- * Include broken references in a do_for_each_ref*() iteration, which
- * would normally be omitted. This includes both refs that point to
- * missing objects (a true repository corruption), ones with illegal
- * names (which we prefer not to expose to callers), as well as
- * dangling symbolic refs (i.e., those that point to a non-existent
- * ref; this is not a corruption, but as they have no valid oid, we
- * omit them from normal iteration results).
- */
- DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
-
- /*
- * Only include per-worktree refs in a do_for_each_ref*() iteration.
- * Normally this will be used with a files ref_store, since that's
- * where all reference backends will presumably store their
- * per-worktree refs.
- */
- DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
-
- /*
- * Omit dangling symrefs from output; this only has an effect with
- * INCLUDE_BROKEN, since they are otherwise not included at all.
- */
- DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
-
- /*
- * Include root refs i.e. HEAD and pseudorefs along with the regular
- * refs.
- */
- DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
-};
-
/*
* Return an iterator that goes over each reference in `refs` for
* which the refname begins with prefix. If trim is non-zero, then
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 03/17] refs: rename `do_for_each_ref_flags`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 01/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 02/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 04/17] refs: rename `each_ref_fn` Patrick Steinhardt
` (15 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The enum `do_for_each_ref_flags` and its individual values don't match
to our current best practices when it comes to naming things. Rename it
to `refs_for_each_flag`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
ref-filter.c | 2 +-
refs.c | 20 ++++++++++----------
refs.h | 12 ++++++------
refs/files-backend.c | 12 ++++++------
refs/packed-backend.c | 8 ++++----
refs/reftable-backend.c | 10 +++++-----
6 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/ref-filter.c b/ref-filter.c
index 3917c4ccd9..4bc54ebd9d 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2810,7 +2810,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
if (filter->kind & FILTER_REFS_ROOT_REFS) {
/* In this case, we want to print all refs including root refs. */
return for_each_fullref_with_seek(filter, cb, cb_data,
- DO_FOR_EACH_INCLUDE_ROOT_REFS);
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS);
}
if (!filter->match_as_path) {
diff --git a/refs.c b/refs.c
index 600913b99f..0cad3b4759 100644
--- a/refs.c
+++ b/refs.c
@@ -1812,7 +1812,7 @@ struct ref_iterator *refs_ref_iterator_begin(
const char *prefix,
const char **exclude_patterns,
int trim,
- enum do_for_each_ref_flags flags)
+ enum refs_for_each_flag flags)
{
struct ref_iterator *iter;
struct strvec normalized_exclude_patterns = STRVEC_INIT;
@@ -1834,14 +1834,14 @@ struct ref_iterator *refs_ref_iterator_begin(
exclude_patterns = normalized_exclude_patterns.v;
}
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) {
static int ref_paranoia = -1;
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
if (ref_paranoia) {
- flags |= DO_FOR_EACH_INCLUDE_BROKEN;
- flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+ flags |= REFS_FOR_EACH_INCLUDE_BROKEN;
+ flags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS;
}
}
@@ -1861,7 +1861,7 @@ struct ref_iterator *refs_ref_iterator_begin(
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
each_ref_fn fn, int trim,
- enum do_for_each_ref_flags flags, void *cb_data)
+ enum refs_for_each_flag flags, void *cb_data)
{
struct ref_iterator *iter;
@@ -1897,7 +1897,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
strlen(git_replace_ref_base),
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int refs_for_each_namespaced_ref(struct ref_store *refs,
@@ -1929,14 +1929,14 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, 0,
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
void *cb_data)
{
return do_for_each_ref(refs, "", NULL, fn, 0,
- DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
}
static int qsort_strcmp(const void *va, const void *vb)
@@ -2748,7 +2748,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
if (!iter)
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
else if (ref_iterator_seek(iter, dirname.buf,
REF_ITERATOR_SEEK_SET_PREFIX) < 0)
goto cleanup;
@@ -3288,7 +3288,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* ensure that there are no concurrent writes.
*/
ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
- DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
&data);
if (ret < 0)
goto done;
diff --git a/refs.h b/refs.h
index 40974c017b..8ac1ef7a8b 100644
--- a/refs.h
+++ b/refs.h
@@ -406,7 +406,7 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);
* These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
* which feeds it).
*/
-enum do_for_each_ref_flags {
+enum refs_for_each_flag {
/*
* Include broken references in a do_for_each_ref*() iteration, which
* would normally be omitted. This includes both refs that point to
@@ -416,7 +416,7 @@ enum do_for_each_ref_flags {
* ref; this is not a corruption, but as they have no valid oid, we
* omit them from normal iteration results).
*/
- DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+ REFS_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
/*
* Only include per-worktree refs in a do_for_each_ref*() iteration.
@@ -424,19 +424,19 @@ enum do_for_each_ref_flags {
* where all reference backends will presumably store their
* per-worktree refs.
*/
- DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+ REFS_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
/*
* Omit dangling symrefs from output; this only has an effect with
* INCLUDE_BROKEN, since they are otherwise not included at all.
*/
- DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+ REFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
/*
* Include root refs i.e. HEAD and pseudorefs along with the regular
* refs.
*/
- DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
};
/*
@@ -1378,7 +1378,7 @@ struct ref_iterator;
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
const char *prefix, const char **exclude_patterns,
- int trim, enum do_for_each_ref_flags flags);
+ int trim, enum refs_for_each_flag flags);
/*
* Advance the iterator to the first or next item and return ITER_OK.
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b1b13b41f6..6c98e14414 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -439,7 +439,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,
dir = get_ref_dir(refs->loose->root);
- if (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS)
+ if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS)
add_root_refs(refs, dir);
/*
@@ -955,17 +955,17 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
int ok;
while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,
NULL) != REF_WORKTREE_CURRENT)
continue;
- if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
+ if ((iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
(iter->iter0->ref.flags & REF_ISSYMREF) &&
(iter->iter0->ref.flags & REF_ISBROKEN))
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->iter0->ref.name,
iter->repo,
iter->iter0->ref.oid,
@@ -1012,7 +1012,7 @@ static struct ref_iterator *files_ref_iterator_begin(
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
@@ -1050,7 +1050,7 @@ static struct ref_iterator *files_ref_iterator_begin(
*/
packed_iter = refs_ref_iterator_begin(
refs->packed_ref_store, prefix, exclude_patterns, 0,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 59b3ecb9d6..5ef4ae32b8 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -982,11 +982,11 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
const char *refname = iter->base.ref.name;
const char *prefix = iter->prefix;
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
!is_per_worktree_ref(iter->base.ref.name))
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->base.ref.name, iter->repo,
&iter->oid, iter->flags))
continue;
@@ -1159,7 +1159,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin");
@@ -1401,7 +1401,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
* of updates is exhausted, leave i set to updates->nr.
*/
iter = packed_ref_iterator_begin(&refs->base, "", NULL,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
ref_iterator_free(iter);
iter = NULL;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 5611808ad7..34bc074dd3 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -662,7 +662,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
* the root refs are to be included. We emulate the same behaviour here.
*/
if (!starts_with(iter->ref.refname, "refs/") &&
- !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&
+ !(iter->flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS &&
is_root_ref(iter->ref.refname))) {
continue;
}
@@ -676,7 +676,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
if (iter->exclude_patterns && should_exclude_current_ref(iter))
continue;
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=
REF_WORKTREE_CURRENT)
continue;
@@ -714,12 +714,12 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
flags |= REF_BAD_NAME | REF_ISBROKEN;
}
- if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS &&
+ if (iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS &&
flags & REF_ISSYMREF &&
flags & REF_ISBROKEN)
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->ref.refname, refs->base.repo,
&iter->oid, flags))
continue;
@@ -871,7 +871,7 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
struct reftable_ref_store *refs;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin");
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 04/17] refs: rename `each_ref_fn`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (2 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 03/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 8:07 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
` (14 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Similar to the preceding commit, rename `each_ref_fn` to better match
our current best practices around how we name things.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
pack-bitmap.c | 2 +-
pack-bitmap.h | 2 +-
ref-filter.c | 6 +++---
refs.c | 36 ++++++++++++++++++------------------
refs.h | 40 ++++++++++++++++++++--------------------
refs/iterator.c | 2 +-
revision.c | 8 ++++----
submodule.c | 2 +-
upload-pack.c | 2 +-
worktree.c | 2 +-
worktree.h | 2 +-
11 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 1c93871484..efef7081e6 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -3324,7 +3324,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)
}
void for_each_preferred_bitmap_tip(struct repository *repo,
- each_ref_fn cb, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
struct string_list_item *item;
const struct string_list *preferred_tips;
diff --git a/pack-bitmap.h b/pack-bitmap.h
index d0611d0481..a95e1c2d11 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -105,7 +105,7 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
* "pack.preferBitmapTips" and invoke the callback on each function.
*/
void for_each_preferred_bitmap_tip(struct repository *repo,
- each_ref_fn cb, void *cb_data);
+ refs_for_each_cb cb, void *cb_data);
#define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL"
diff --git a/ref-filter.c b/ref-filter.c
index 4bc54ebd9d..049e845a19 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2781,7 +2781,7 @@ static int start_ref_iterator_after(struct ref_iterator *iter, const char *marke
return ret;
}
-static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
+static int for_each_fullref_with_seek(struct ref_filter *filter, refs_for_each_cb cb,
void *cb_data, unsigned int flags)
{
struct ref_iterator *iter;
@@ -2804,7 +2804,7 @@ static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
* pattern match, so the callback still has to match each ref individually.
*/
static int for_each_fullref_in_pattern(struct ref_filter *filter,
- each_ref_fn cb,
+ refs_for_each_cb cb,
void *cb_data)
{
if (filter->kind & FILTER_REFS_ROOT_REFS) {
@@ -3303,7 +3303,7 @@ void filter_is_base(struct repository *r,
free(bases);
}
-static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
+static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for_each_cb fn, void *cb_data)
{
const char *prefix = NULL;
int ret = 0;
diff --git a/refs.c b/refs.c
index 0cad3b4759..e9ac0a7101 100644
--- a/refs.c
+++ b/refs.c
@@ -445,7 +445,7 @@ char *refs_resolve_refdup(struct ref_store *refs,
struct for_each_ref_filter {
const char *pattern;
const char *prefix;
- each_ref_fn *fn;
+ refs_for_each_cb *fn;
void *cb_data;
};
@@ -527,22 +527,22 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
-int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
}
-int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
}
-int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
}
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
int ret = 0;
@@ -590,7 +590,7 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, const char *prefix, void *cb_data)
{
struct strbuf real_pattern = STRBUF_INIT;
@@ -620,7 +620,7 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
return ret;
}
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data)
{
return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
@@ -1788,7 +1788,7 @@ const char *find_descendant_ref(const char *dirname,
return NULL;
}
-int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct object_id oid;
int flag;
@@ -1860,7 +1860,7 @@ struct ref_iterator *refs_ref_iterator_begin(
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, int trim,
+ refs_for_each_cb fn, int trim,
enum refs_for_each_flag flags, void *cb_data)
{
struct ref_iterator *iter;
@@ -1874,25 +1874,25 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
return do_for_each_ref_iterator(iter, fn, cb_data);
}
-int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
}
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
}
-int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
@@ -1902,7 +1902,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct strbuf prefix = STRBUF_INIT;
@@ -1920,19 +1920,19 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return ret;
}
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_rawref_in(refs, "", fn, cb_data);
}
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, 0,
REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data)
{
return do_for_each_ref(refs, "", NULL, fn, 0,
@@ -2001,7 +2001,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
const char *namespace,
const char **patterns,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct string_list prefixes = STRING_LIST_INIT_DUP;
diff --git a/refs.h b/refs.h
index 8ac1ef7a8b..e37574009b 100644
--- a/refs.h
+++ b/refs.h
@@ -170,7 +170,7 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err);
*
* peel_object(r, oid, &peeled);
*
- * with the "oid" value given to the each_ref_fn callback, except
+ * with the "oid" value given to the refs_for_each_cb callback, except
* that some ref storage may be able to answer the query without
* actually loading the object in memory.
*/
@@ -329,7 +329,7 @@ int check_tag_ref(struct strbuf *sb, const char *name);
struct ref_transaction;
/*
- * Bit values set in the flags argument passed to each_ref_fn() and
+ * Bit values set in the flags argument passed to refs_for_each_cb() and
* stored in ref_iterator::flags. Other bits are for internal use
* only:
*/
@@ -400,7 +400,7 @@ int reference_get_peeled_oid(struct repository *repo,
* argument is only guaranteed to be valid for the duration of a
* single callback invocation.
*/
-typedef int each_ref_fn(const struct reference *ref, void *cb_data);
+typedef int refs_for_each_cb(const struct reference *ref, void *cb_data);
/*
* These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
@@ -449,22 +449,22 @@ enum refs_for_each_flag {
* stop the iteration. Returned references are sorted.
*/
int refs_head_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_head_ref_namespaced(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_branch_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_remote_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_replace_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/*
* references matching any pattern in "exclude_patterns" are omitted from the
@@ -472,7 +472,7 @@ int refs_for_each_replace_ref(struct ref_store *refs,
*/
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/**
* iterate all refs in "patterns" by partitioning patterns into disjoint sets
@@ -487,13 +487,13 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
const char *namespace,
const char **patterns,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/* iterates all refs that match the specified glob pattern. */
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data);
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, const char *prefix, void *cb_data);
/*
@@ -502,17 +502,17 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
*/
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/*
* Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
*/
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data);
/*
@@ -1427,6 +1427,6 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);
* iterator style.
*/
int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
#endif /* REFS_H */
diff --git a/refs/iterator.c b/refs/iterator.c
index d79aa5ec82..d5cacde51b 100644
--- a/refs/iterator.c
+++ b/refs/iterator.c
@@ -423,7 +423,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
}
int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
int retval = 0, ok;
diff --git a/revision.c b/revision.c
index 29972c3a19..8c206830d5 100644
--- a/revision.c
+++ b/revision.c
@@ -1646,7 +1646,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
static void handle_refs(struct ref_store *refs,
struct rev_info *revs, unsigned flags,
- int (*for_each)(struct ref_store *, each_ref_fn, void *))
+ int (*for_each)(struct ref_store *, refs_for_each_cb, void *))
{
struct all_refs_cb cb;
@@ -2728,7 +2728,7 @@ void revision_opts_finish(struct rev_info *revs)
}
}
-static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
+static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data, const char *term)
{
struct strbuf bisect_refs = STRBUF_INIT;
@@ -2739,12 +2739,12 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
return status;
}
-static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_bad_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return for_each_bisect_ref(refs, fn, cb_data, term_bad);
}
-static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_good_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return for_each_bisect_ref(refs, fn, cb_data, term_good);
}
diff --git a/submodule.c b/submodule.c
index 508938e4da..4f9aaa2c75 100644
--- a/submodule.c
+++ b/submodule.c
@@ -101,7 +101,7 @@ int is_staging_gitmodules_ok(struct index_state *istate)
}
static int for_each_remote_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_remote_ref(repo_get_submodule_ref_store(the_repository,
submodule),
diff --git a/upload-pack.c b/upload-pack.c
index 2d2b70cbf2..7fe397b0d0 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -607,7 +607,7 @@ static int allow_hidden_refs(enum allow_uor allow_uor)
return !(allow_uor & (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
}
-static void for_each_namespaced_ref_1(each_ref_fn fn,
+static void for_each_namespaced_ref_1(refs_for_each_cb fn,
struct upload_pack_data *data)
{
const char **excludes = NULL;
diff --git a/worktree.c b/worktree.c
index 9308389cb6..bf8c54c04d 100644
--- a/worktree.c
+++ b/worktree.c
@@ -575,7 +575,7 @@ void strbuf_worktree_ref(const struct worktree *wt,
strbuf_addstr(sb, refname);
}
-int other_head_refs(each_ref_fn fn, void *cb_data)
+int other_head_refs(refs_for_each_cb fn, void *cb_data)
{
struct worktree **worktrees, **p;
struct strbuf refname = STRBUF_INIT;
diff --git a/worktree.h b/worktree.h
index e4bcccdc0a..12484a91a7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -191,7 +191,7 @@ int is_shared_symref(const struct worktree *wt,
* Similar to head_ref() for all HEADs _except_ one from the current
* worktree, which is covered by head_ref().
*/
-int other_head_refs(each_ref_fn fn, void *cb_data);
+int other_head_refs(refs_for_each_cb fn, void *cb_data);
int is_worktree_being_rebased(const struct worktree *wt, const char *target);
int is_worktree_being_bisected(const struct worktree *wt, const char *target);
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (3 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 04/17] refs: rename `each_ref_fn` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 10:29 ` Oswald Buddenhagen
2026-02-20 8:24 ` [PATCH 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
` (13 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Remove the unused `refs_for_each_include_root_ref()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 7 -------
refs.h | 6 ------
2 files changed, 13 deletions(-)
diff --git a/refs.c b/refs.c
index e9ac0a7101..a45cc61211 100644
--- a/refs.c
+++ b/refs.c
@@ -1932,13 +1932,6 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
- void *cb_data)
-{
- return do_for_each_ref(refs, "", NULL, fn, 0,
- REFS_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index e37574009b..5190e98b2c 100644
--- a/refs.h
+++ b/refs.h
@@ -509,12 +509,6 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
-/*
- * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
- */
-int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
- void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 06/17] refs: introduce `refs_for_each_ref_ext`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (4 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 8:14 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
` (12 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
In the refs subsystem we have a proliferation of functions that all
iterate through references. (Almost) all of these functions internally
call `do_for_each_ref()` and provide slightly different arguments so
that one can control different aspects of its behaviour. This approach
doesn't really scale: every time there is a slightly different use case
for iterating through refs we create another new function.
This combinatorial explosion doesn't make a lot of sense: it leads to
confusing interfaces and heightens the maintenance burden.
Refactor the code to become more composable by:
- Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.
- Introducing an options structure that lets the caller control
individual options.
This gives us a much better foundation to build on going forward.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 78 ++++++++++++++++++++++++++++++++++++++++--------------------------
refs.h | 29 +++++++++++++++++++++++++
2 files changed, 77 insertions(+), 30 deletions(-)
diff --git a/refs.c b/refs.c
index a45cc61211..ec9e466381 100644
--- a/refs.c
+++ b/refs.c
@@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(
return iter;
}
-static int do_for_each_ref(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb fn, int trim,
- enum refs_for_each_flag flags, void *cb_data)
+int refs_for_each_ref_ext(struct ref_store *refs,
+ refs_for_each_cb cb, void *cb_data,
+ const struct refs_for_each_ref_options *opts)
{
struct ref_iterator *iter;
if (!refs)
return 0;
- iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
- flags);
+ iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
+ opts->exclude_patterns,
+ opts->trim_prefix, opts->flags);
- return do_for_each_ref_iterator(iter, fn, cb_data);
+ return do_for_each_ref_iterator(iter, cb, cb_data);
}
-int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
+ struct refs_for_each_ref_options opts = { 0 };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .exclude_patterns = exclude_patterns,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
- return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
- strlen(git_replace_ref_base),
- REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = git_replace_ref_base,
+ .trim_prefix = strlen(git_replace_ref_base),
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct strbuf prefix = STRBUF_INIT;
int ret;
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- get_git_namespace(),
- &namespaced_exclude_patterns);
-
+ opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
+ get_git_namespace(),
+ &namespaced_exclude_patterns);
strbuf_addf(&prefix, "%srefs/", get_git_namespace());
- ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
+ opts.prefix = prefix.buf;
+
+ ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
strvec_clear(&namespaced_exclude_patterns);
strbuf_release(&prefix);
@@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d
}
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, NULL, fn, 0,
- REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
static int qsort_strcmp(const void *va, const void *vb)
@@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
struct strbuf *errbuf)
{
struct ref_store *old_refs = NULL, *new_refs = NULL;
+ struct refs_for_each_ref_options for_each_ref_opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
struct ref_transaction *transaction = NULL;
struct strbuf new_gitdir = STRBUF_INIT;
struct migration_data data = {
@@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
data.errbuf = errbuf;
/*
- * We need to use the internal `do_for_each_ref()` here so that we can
+ * We need to use `refs_for_each_ref_ext()` here so that we can
* also include broken refs and symrefs. These would otherwise be
* skipped silently.
*
@@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* allow for a central lock due to its design. It's thus on the user to
* ensure that there are no concurrent writes.
*/
- ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
- REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
- &data);
+ ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
if (ret < 0)
goto done;
diff --git a/refs.h b/refs.h
index 5190e98b2c..bb9c64a51c 100644
--- a/refs.h
+++ b/refs.h
@@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,
int refs_head_ref_namespaced(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
+
+struct refs_for_each_ref_options {
+ /* Only iterate over references that have this given prefix. */
+ const char *prefix;
+
+ /*
+ * Exclude any references that match any of these patterns on a
+ * best-effort basis. The caller needs to be prepared for the exclude
+ * patterns to be ignored.
+ *
+ * The array must be terminated with a NULL sentinel value.
+ */
+ const char **exclude_patterns;
+
+ /*
+ * The number of bytes to trim from the refname. Note that the trimmed
+ * bytes must not cause the reference to become empty. As such, this
+ * field should typically only be set when one uses a `prefix` ending
+ * in a slash.
+ */
+ size_t trim_prefix;
+
+ /* Flags that change which refs will be included. */
+ enum refs_for_each_flag flags;
+};
+
int refs_for_each_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
+int refs_for_each_ref_ext(struct ref_store *refs,
+ refs_for_each_cb cb, void *cb_data,
+ const struct refs_for_each_ref_options *opts);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (5 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 8:27 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
` (11 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The function `refs_for_each_glob_ref_in()` can be used to iterate
through all refs in a specific prefix with globbing. The logic to handle
this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up
a callback function that knows to filter out refs that _don't_ match the
given globbing pattern.
The way we do this is somewhat inefficient though: even though the
function is expected to only yield refs in the given prefix, we still
end up iterating through _all_ references, regardless of whether or not
their name matches the given prefix.
Extend `refs_for_each_ref_ext()` so that it can handle patterns and
adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to
use the same callback-based infrastructure to filter individual refs via
the globbing pattern, but we can now also use the other functionality of
the `_ext()` variant.
Most importantly, this means that we now properly handle the prefix.
This results in a performance improvement when using a prefix where a
significant majority of refs exists outside of the prefix. The following
benchmark is an extreme case, with 1 million refs that exist outside the
prefix and a single ref that exists inside it:
Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)
Time (mean ± σ): 115.9 ms ± 0.7 ms [User: 113.0 ms, System: 2.4 ms]
Range (min … max): 114.9 ms … 117.8 ms 25 runs
Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)
Time (mean ± σ): 1.1 ms ± 0.1 ms [User: 0.3 ms, System: 0.7 ms]
Range (min … max): 1.0 ms … 2.3 ms 2092 runs
Summary
git rev-parse --branches=refs/heads/* (rev = HEAD) ran
107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 69 ++++++++++++++++++++++++++++++++++++++----------------------------
refs.h | 10 ++++++++++
2 files changed, 50 insertions(+), 29 deletions(-)
diff --git a/refs.c b/refs.c
index ec9e466381..ac34bbe6c1 100644
--- a/refs.c
+++ b/refs.c
@@ -590,40 +590,23 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, const char *prefix, void *cb_data)
{
- struct strbuf real_pattern = STRBUF_INIT;
- struct for_each_ref_filter filter;
- int ret;
-
- if (!prefix && !starts_with(pattern, "refs/"))
- strbuf_addstr(&real_pattern, "refs/");
- else if (prefix)
- strbuf_addstr(&real_pattern, prefix);
- strbuf_addstr(&real_pattern, pattern);
-
- if (!has_glob_specials(pattern)) {
- /* Append implied '/' '*' if not present. */
- strbuf_complete(&real_pattern, '/');
- /* No need to check for '*', there is none. */
- strbuf_addch(&real_pattern, '*');
- }
-
- filter.pattern = real_pattern.buf;
- filter.prefix = prefix;
- filter.fn = fn;
- filter.cb_data = cb_data;
- ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
-
- strbuf_release(&real_pattern);
- return ret;
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, void *cb_data)
{
- return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
const char *prettify_refname(const char *name)
@@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts)
{
+ struct strbuf real_pattern = STRBUF_INIT;
+ struct for_each_ref_filter filter;
struct ref_iterator *iter;
+ int ret;
if (!refs)
return 0;
+ if (opts->pattern) {
+ if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
+ strbuf_addstr(&real_pattern, "refs/");
+ else if (opts->prefix)
+ strbuf_addstr(&real_pattern, opts->prefix);
+ strbuf_addstr(&real_pattern, opts->pattern);
+
+ if (!has_glob_specials(opts->pattern)) {
+ /* Append implied '/' '*' if not present. */
+ strbuf_complete(&real_pattern, '/');
+ /* No need to check for '*', there is none. */
+ strbuf_addch(&real_pattern, '*');
+ }
+
+ filter.pattern = real_pattern.buf;
+ filter.prefix = opts->prefix;
+ filter.fn = cb;
+ filter.cb_data = cb_data;
+
+ cb = for_each_filter_refs;
+ cb_data = &filter;
+ }
+
iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
opts->exclude_patterns,
opts->trim_prefix, opts->flags);
- return do_for_each_ref_iterator(iter, cb, cb_data);
+ ret = do_for_each_ref_iterator(iter, cb, cb_data);
+ strbuf_release(&real_pattern);
+ return ret;
}
int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
diff --git a/refs.h b/refs.h
index bb9c64a51c..a66dbf3865 100644
--- a/refs.h
+++ b/refs.h
@@ -458,6 +458,16 @@ struct refs_for_each_ref_options {
/* Only iterate over references that have this given prefix. */
const char *prefix;
+ /*
+ * A globbing pattern that can be used to only yield refs that match.
+ * If given, refs will be matched against the pattern with
+ * `wildmatch()`.
+ *
+ * If the pattern doesn't contain any globbing characters then it is
+ * treated as if it was ending with "/" and "*".
+ */
+ const char *pattern;
+
/*
* Exclude any references that match any of these patterns on a
* best-effort basis. The caller needs to be prepared for the exclude
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (6 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 9:02 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
` (10 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The function `refs_for_each_namespaced_ref()` iterates through all
references that are part of the current ref namespace. This namespace
can be configured by setting the `GIT_NAMESPACE` environment variable
and is then retrieved by calling `get_git_namespace()`.
If a namespace is configured, then we:
- Obviously only yield refs that exist in this namespace.
- Rewrite exclude patterns so that they work for the given namespace,
if any namespace is currently configured.
Port this logic to `refs_for_each_ref_ext()` by adding a new `namespace`
field to the options structure. This gives callers more flexibility as
they can decide by themselves whether they want to use the globally
configured or an arbitrary other namespace.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 47 +++++++++++++++++++++++++++++------------------
refs.h | 6 ++++++
2 files changed, 35 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index ac34bbe6c1..99994879d9 100644
--- a/refs.c
+++ b/refs.c
@@ -1845,9 +1845,13 @@ int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts)
{
+ struct strvec namespaced_exclude_patterns = STRVEC_INIT;
+ struct strbuf namespaced_prefix = STRBUF_INIT;
struct strbuf real_pattern = STRBUF_INIT;
struct for_each_ref_filter filter;
struct ref_iterator *iter;
+ const char **exclude_patterns;
+ const char *prefix;
int ret;
if (!refs)
@@ -1876,11 +1880,29 @@ int refs_for_each_ref_ext(struct ref_store *refs,
cb_data = &filter;
}
- iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
- opts->exclude_patterns,
+ if (opts->namespace) {
+ strbuf_addstr(&namespaced_prefix, opts->namespace);
+ if (opts->prefix)
+ strbuf_addstr(&namespaced_prefix, opts->prefix);
+ else
+ strbuf_addstr(&namespaced_prefix, "refs/");
+
+ prefix = namespaced_prefix.buf;
+ exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,
+ opts->namespace,
+ &namespaced_exclude_patterns);
+ } else {
+ prefix = opts->prefix ? opts->prefix : "";
+ exclude_patterns = opts->exclude_patterns;
+ }
+
+ iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
opts->trim_prefix, opts->flags);
ret = do_for_each_ref_iterator(iter, cb, cb_data);
+
+ strvec_clear(&namespaced_exclude_patterns);
+ strbuf_release(&namespaced_prefix);
strbuf_release(&real_pattern);
return ret;
}
@@ -1927,22 +1949,11 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
refs_for_each_cb cb, void *cb_data)
{
- struct refs_for_each_ref_options opts = { 0 };
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct strbuf prefix = STRBUF_INIT;
- int ret;
-
- opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- get_git_namespace(),
- &namespaced_exclude_patterns);
- strbuf_addf(&prefix, "%srefs/", get_git_namespace());
- opts.prefix = prefix.buf;
-
- ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-
- strvec_clear(&namespaced_exclude_patterns);
- strbuf_release(&prefix);
- return ret;
+ struct refs_for_each_ref_options opts = {
+ .exclude_patterns = exclude_patterns,
+ .namespace = get_git_namespace(),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
diff --git a/refs.h b/refs.h
index a66dbf3865..2bde60aa0e 100644
--- a/refs.h
+++ b/refs.h
@@ -468,6 +468,12 @@ struct refs_for_each_ref_options {
*/
const char *pattern;
+ /*
+ * If set, only yield refs part of the configured namespace. Exclude
+ * patterns will be rewritten to apply to the namespace.
+ */
+ const char *namespace;
+
/*
* Exclude any references that match any of these patterns on a
* best-effort basis. The caller needs to be prepared for the exclude
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (7 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 9:06 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
` (9 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The function `refs_for_each_fullref_in_prefixes()` can be used to
iterate over all references part of any of the user-provided prefixes.
In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it
knows to handle the case well where multiple of the passed-in prefixes
start with a common prefix by computing longest common prefixes and then
iterating over those.
While we could move this logic into `refs_for_each_ref_ext()`, this one
feels somewhat special as we perform multiple iterations. But what we
_can_ do is to generalize how this function works: instead of accepting
only a small handful of parameters, we can have it accept the full
options structure.
One obvious exception is that the caller must not provide a prefix via
the options. But this case can be easily detected.
Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
ls-refs.c | 11 +++++++----
ref-filter.c | 11 +++++++----
refs.c | 39 +++++++++++++++------------------------
refs.h | 16 +++++-----------
4 files changed, 34 insertions(+), 43 deletions(-)
diff --git a/ls-refs.c b/ls-refs.c
index 8641281b86..9759826ca7 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,
int ls_refs(struct repository *r, struct packet_reader *request)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct ls_refs_data data;
memset(&data, 0, sizeof(data));
@@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)
send_possibly_unborn_head(&data);
if (!data.prefixes.nr)
strvec_push(&data.prefixes, "");
- refs_for_each_fullref_in_prefixes(get_main_ref_store(r),
- get_git_namespace(), data.prefixes.v,
- hidden_refs_to_excludes(&data.hidden_refs),
- send_ref, &data);
+
+ opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);
+ opts.namespace = get_git_namespace();
+
+ refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,
+ &opts, send_ref, &data);
packet_fflush(stdout);
strvec_clear(&data.prefixes);
strbuf_release(&data.buf);
diff --git a/ref-filter.c b/ref-filter.c
index 049e845a19..7c682e0a33 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
refs_for_each_cb cb,
void *cb_data)
{
+ struct refs_for_each_ref_options opts = {
+ .exclude_patterns = filter->exclude.v,
+ };
+
if (filter->kind & FILTER_REFS_ROOT_REFS) {
/* In this case, we want to print all refs including root refs. */
return for_each_fullref_with_seek(filter, cb, cb_data,
@@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
return for_each_fullref_with_seek(filter, cb, cb_data, 0);
}
- return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),
- NULL, filter->name_patterns,
- filter->exclude.v,
- cb, cb_data);
+ return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),
+ filter->name_patterns, &opts,
+ cb, cb_data);
}
/*
diff --git a/refs.c b/refs.c
index 99994879d9..20d34faeb5 100644
--- a/refs.c
+++ b/refs.c
@@ -2029,40 +2029,31 @@ static void find_longest_prefixes(struct string_list *out,
strbuf_release(&prefix);
}
-int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
- const char *namespace,
- const char **patterns,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
+ const char **prefixes,
+ const struct refs_for_each_ref_options *opts,
+ refs_for_each_cb cb, void *cb_data)
{
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct string_list prefixes = STRING_LIST_INIT_DUP;
+ struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
- struct strbuf buf = STRBUF_INIT;
- int ret = 0, namespace_len;
+ int ret = 0;
- find_longest_prefixes(&prefixes, patterns);
+ if (opts->prefix)
+ BUG("refs_for_each_ref_in_prefixes called with specific prefix");
- if (namespace)
- strbuf_addstr(&buf, namespace);
- namespace_len = buf.len;
+ find_longest_prefixes(&longest_prefixes, prefixes);
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- namespace,
- &namespaced_exclude_patterns);
+ for_each_string_list_item(prefix, &longest_prefixes) {
+ struct refs_for_each_ref_options prefix_opts = *opts;
+ prefix_opts.prefix = prefix->string;
- for_each_string_list_item(prefix, &prefixes) {
- strbuf_addstr(&buf, prefix->string);
- ret = refs_for_each_fullref_in(ref_store, buf.buf,
- exclude_patterns, fn, cb_data);
+ ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
+ &prefix_opts);
if (ret)
break;
- strbuf_setlen(&buf, namespace_len);
}
- strvec_clear(&namespaced_exclude_patterns);
- string_list_clear(&prefixes, 0);
- strbuf_release(&buf);
+ string_list_clear(&longest_prefixes, 0);
return ret;
}
diff --git a/refs.h b/refs.h
index 2bde60aa0e..65d1665c72 100644
--- a/refs.h
+++ b/refs.h
@@ -520,19 +520,13 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
/**
- * iterate all refs in "patterns" by partitioning patterns into disjoint sets
+ * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
* and iterating the longest-common prefix of each set.
- *
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- *
- * callers should be prepared to ignore references that they did not ask for.
*/
-int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
- const char *namespace,
- const char **patterns,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
+int refs_for_each_ref_in_prefixes(struct ref_store *refs,
+ const char **prefixes,
+ const struct refs_for_each_ref_options *opts,
+ refs_for_each_cb cb, void *cb_data);
/* iterates all refs that match the specified glob pattern. */
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 10/17] refs: improve verification for-each-ref options
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (8 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 9:09 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
` (8 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Improve verification of the passed-in for-each-ref options:
- Require that the `refs` store must be given. It's arguably very
surprising that we simply return successfully in case the ref store
is a `NULL` pointer.
- When expected to trim ref prefixes we will `BUG()` in case the
refname would become empty or in case we're expected to trim a
longer prefix than the refname is long. As such, this case is only
guaranteed to _not_ `BUG()` in case the caller also specified a
prefix. And furthermore, that prefix must end in a trailing slash,
as otherwise it may produce an exact match that could lead us to
trim to the empty string.
An audit shows that there are no callsites that rely on either of these
behaviours, so this should not result in a functional change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/refs.c b/refs.c
index 20d34faeb5..3b676432b4 100644
--- a/refs.c
+++ b/refs.c
@@ -1855,7 +1855,18 @@ int refs_for_each_ref_ext(struct ref_store *refs,
int ret;
if (!refs)
- return 0;
+ BUG("no refs passed");
+
+ if (opts->trim_prefix) {
+ size_t prefix_len;
+
+ if (!opts->prefix)
+ BUG("trimming only allowed with a prefix");
+
+ prefix_len = strlen(opts->prefix);
+ if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')
+ BUG("ref pattern must end in a trailing slash when trimming");
+ }
if (opts->pattern) {
if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 11/17] refs: replace `refs_for_each_ref_in()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (9 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 9:11 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
` (7 subsequent siblings)
18 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
bisect.c | 8 ++++++--
builtin/rev-parse.c | 13 +++++++++----
pack-bitmap.c | 13 +++++++------
refs.c | 34 ++++++++++++++++++----------------
refs.h | 2 --
t/helper/test-ref-store.c | 7 +++++--
6 files changed, 45 insertions(+), 32 deletions(-)
diff --git a/bisect.c b/bisect.c
index 2bdad4ee42..296836c154 100644
--- a/bisect.c
+++ b/bisect.c
@@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED)
static int read_bisect_refs(void)
{
- return refs_for_each_ref_in(get_main_ref_store(the_repository),
- "refs/bisect/", register_ref, NULL);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ .trim_prefix = strlen("refs/bisect/"),
+ };
+ return refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ register_ref, NULL, &opts);
}
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 9032cc6327..02703f2fb8 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
static void handle_ref_opt(const char *pattern, const char *prefix)
{
- if (pattern)
+ if (pattern) {
refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
show_reference, pattern, prefix,
NULL);
- else
- refs_for_each_ref_in(get_main_ref_store(the_repository),
- prefix, show_reference, NULL);
+ } else {
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
+ }
clear_ref_exclusions(&ref_excludes);
}
diff --git a/pack-bitmap.c b/pack-bitmap.c
index efef7081e6..22419bfb33 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -3326,6 +3326,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)
void for_each_preferred_bitmap_tip(struct repository *repo,
refs_for_each_cb cb, void *cb_data)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct string_list_item *item;
const struct string_list *preferred_tips;
struct strbuf buf = STRBUF_INIT;
@@ -3335,16 +3336,16 @@ void for_each_preferred_bitmap_tip(struct repository *repo,
return;
for_each_string_list_item(item, preferred_tips) {
- const char *pattern = item->string;
+ opts.prefix = item->string;
- if (!ends_with(pattern, "/")) {
+ if (!ends_with(opts.prefix, "/")) {
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s/", pattern);
- pattern = buf.buf;
+ strbuf_addf(&buf, "%s/", opts.prefix);
+ opts.prefix = buf.buf;
}
- refs_for_each_ref_in(get_main_ref_store(repo),
- pattern, cb, cb_data);
+ refs_for_each_ref_ext(get_main_ref_store(repo),
+ cb, cb_data, &opts);
}
strbuf_release(&buf);
diff --git a/refs.c b/refs.c
index 3b676432b4..b42a1bfa3f 100644
--- a/refs.c
+++ b/refs.c
@@ -527,19 +527,31 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
-int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
+ .trim_prefix = strlen("refs/tags/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
+ .trim_prefix = strlen("refs/heads/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
+ .trim_prefix = strlen("refs/remotes/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
@@ -1924,16 +1936,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .trim_prefix = strlen(prefix),
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
refs_for_each_cb cb, void *cb_data)
diff --git a/refs.h b/refs.h
index 65d1665c72..c9f8d3e6cb 100644
--- a/refs.h
+++ b/refs.h
@@ -500,8 +500,6 @@ int refs_for_each_ref(struct ref_store *refs,
int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts);
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
int refs_for_each_branch_ref(struct ref_store *refs,
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index b1215947c5..a2ef1b6949 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -163,8 +163,11 @@ static int each_ref(const struct reference *ref, void *cb_data UNUSED)
static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
{
const char *prefix = notnull(*argv++, "prefix");
-
- return refs_for_each_ref_in(refs, prefix, each_ref, NULL);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
}
static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 12/17] refs: replace `refs_for_each_rawref()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (10 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
` (6 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_rawref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/describe.c | 7 +++++--
builtin/fsck.c | 7 +++++--
fetch-pack.c | 15 +++++++++++----
refs.c | 10 ++++------
refs.h | 1 -
refs/files-backend.c | 7 +++++--
6 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/builtin/describe.c b/builtin/describe.c
index abfe3525a5..bffeed13a3 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -641,6 +641,9 @@ int cmd_describe(int argc,
const char *prefix,
struct repository *repo UNUSED )
{
+ struct refs_for_each_ref_options for_each_ref_opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
int contains = 0;
struct option options[] = {
OPT_BOOL(0, "contains", &contains, N_("find the tag that comes after the commit")),
@@ -738,8 +741,8 @@ int cmd_describe(int argc,
}
hashmap_init(&names, commit_name_neq, NULL, 0);
- refs_for_each_rawref(get_main_ref_store(the_repository), get_name,
- NULL);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ get_name, NULL, &for_each_ref_opts);
if (!hashmap_get_size(&names) && !always)
die(_("No names found, cannot describe anything."));
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 0512f78a87..24cdb657f5 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -598,6 +598,9 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
struct worktree **worktrees, **p;
const char *head_points_at;
struct object_id head_oid;
@@ -623,8 +626,8 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
return;
}
- refs_for_each_rawref(get_main_ref_store(the_repository),
- snapshot_ref, snap);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ snapshot_ref, snap, &opts);
worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
diff --git a/fetch-pack.c b/fetch-pack.c
index 40316c9a34..570caa03fa 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -292,11 +292,14 @@ static int next_flush(int stateless_rpc, int count)
static void mark_tips(struct fetch_negotiator *negotiator,
const struct oid_array *negotiation_tips)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
int i;
if (!negotiation_tips) {
- refs_for_each_rawref(get_main_ref_store(the_repository),
- rev_list_insert_ref_oid, negotiator);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ rev_list_insert_ref_oid, negotiator, &opts);
return;
}
@@ -792,8 +795,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
*/
trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL);
if (!args->deepen) {
- refs_for_each_rawref(get_main_ref_store(the_repository),
- mark_complete_oid, NULL);
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ mark_complete_oid, NULL, &opts);
for_each_cached_alternate(NULL, mark_alternate_complete);
if (cutoff)
mark_recent_complete_commits(args, cutoff);
diff --git a/refs.c b/refs.c
index b42a1bfa3f..5739804870 100644
--- a/refs.c
+++ b/refs.c
@@ -524,7 +524,10 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
.indent = indent,
.dry_run = dry_run,
};
- refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ refs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts);
}
int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
@@ -1969,11 +1972,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
-{
- return refs_for_each_rawref_in(refs, "", fn, cb_data);
-}
-
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb cb, void *cb_data)
{
diff --git a/refs.h b/refs.h
index c9f8d3e6cb..7a0c9c3693 100644
--- a/refs.h
+++ b/refs.h
@@ -542,7 +542,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 6c98e14414..ab96760781 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3149,6 +3149,9 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
@@ -3173,8 +3176,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
* so here we really only check that none of the references
* that we are creating already exists.
*/
- if (refs_for_each_rawref(&refs->base, ref_present,
- &transaction->refnames))
+ if (refs_for_each_ref_ext(&refs->base, ref_present,
+ &transaction->refnames, &opts))
BUG("initial ref transaction called with existing refs");
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 13/17] refs: replace `refs_for_each_rawref_in()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (11 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
` (5 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_rawref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/remote.c | 8 ++++++--
refs.c | 10 ----------
refs.h | 4 ----
3 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index ace390c671..0fddaa1773 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -912,6 +912,9 @@ static int mv(int argc, const char **argv, const char *prefix,
old_remote_context.buf);
if (refspecs_need_update) {
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
0, &err);
if (!rename.transaction)
@@ -923,9 +926,10 @@ static int mv(int argc, const char **argv, const char *prefix,
strbuf_reset(&buf);
strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name);
+ opts.prefix = buf.buf;
- result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf,
- rename_one_ref, &rename);
+ result = refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ rename_one_ref, &rename, &opts);
if (result < 0)
die(_("queueing remote ref renames failed: %s"), rename.err->buf);
diff --git a/refs.c b/refs.c
index 5739804870..454c9dd017 100644
--- a/refs.c
+++ b/refs.c
@@ -1972,16 +1972,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index 7a0c9c3693..2915ff081a 100644
--- a/refs.h
+++ b/refs.h
@@ -541,10 +541,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
refs_for_each_cb fn, void *cb_data);
-/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (12 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
` (4 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_glob_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/bisect.c | 34 ++++++++++++++++++++++++----------
builtin/rev-parse.c | 9 ++++++---
refs.c | 10 ----------
refs.h | 3 ---
revision.c | 27 ++++++++++++++++++---------
5 files changed, 48 insertions(+), 35 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 4cc118fb57..774abb3620 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -422,13 +422,16 @@ static void bisect_status(struct bisect_state *state,
{
char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
char *good_glob = xstrfmt("%s-*", terms->term_good);
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
+ };
if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))
state->nr_bad = 1;
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr,
- good_glob, "refs/bisect/",
- (void *) &state->nr_good);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ inc_nr, &state->nr_good, &opts);
free(good_glob);
free(bad_ref);
@@ -562,6 +565,9 @@ static int add_bisect_ref(const struct reference *ref, void *cb)
static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
{
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ };
int res = 0;
struct add_bisect_ref_data cb = { revs };
char *good = xstrfmt("%s-*", terms->term_good);
@@ -581,11 +587,16 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
reset_revision_walk();
repo_init_revisions(the_repository, revs, NULL);
setup_revisions(0, NULL, revs, NULL);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- add_bisect_ref, bad, "refs/bisect/", &cb);
+
+ opts.pattern = bad;
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_bisect_ref, &cb, &opts);
+
cb.object_flags = UNINTERESTING;
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- add_bisect_ref, good, "refs/bisect/", &cb);
+ opts.pattern = good;
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_bisect_ref, &cb, &opts);
+
if (prepare_revision_walk(revs))
res = error(_("revision walk setup failed"));
@@ -1191,10 +1202,13 @@ static int verify_good(const struct bisect_terms *terms, const char *command)
char *good_glob = xstrfmt("%s-*", terms->term_good);
int no_checkout = refs_ref_exists(get_main_ref_store(the_repository),
"BISECT_HEAD");
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
+ };
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- get_first_good, good_glob, "refs/bisect/",
- &good_rev);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ get_first_good, &good_rev, &opts);
free(good_glob);
if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev))
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 02703f2fb8..206f5bda39 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -614,9 +614,12 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
static void handle_ref_opt(const char *pattern, const char *prefix)
{
if (pattern) {
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- show_reference, pattern, prefix,
- NULL);
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
} else {
struct refs_for_each_ref_options opts = {
.prefix = prefix,
diff --git a/refs.c b/refs.c
index 454c9dd017..1fa16facb8 100644
--- a/refs.c
+++ b/refs.c
@@ -605,16 +605,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
- const char *pattern, const char *prefix, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .pattern = pattern,
- .prefix = prefix,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, void *cb_data)
{
diff --git a/refs.h b/refs.h
index 2915ff081a..1d21999ae1 100644
--- a/refs.h
+++ b/refs.h
@@ -530,9 +530,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data);
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
- const char *pattern, const char *prefix, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
diff --git a/revision.c b/revision.c
index 8c206830d5..da7737ce28 100644
--- a/revision.c
+++ b/revision.c
@@ -2827,34 +2827,43 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
exclude_hidden_refs(&revs->ref_excludes, optarg);
return argcount;
} else if (skip_prefix(arg, "--branches=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--branches");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/heads/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--tags=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--tags");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/tags/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--remotes=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--remotes");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/remotes/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (!strcmp(arg, "--reflog")) {
add_reflogs_to_pending(revs, *flags);
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 15/17] refs: replace `refs_for_each_glob_ref()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (13 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
` (3 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_glob_ref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/fetch.c | 7 +++++--
notes.c | 7 +++++--
refs.c | 9 ---------
refs.h | 4 ----
revision.c | 7 +++++--
5 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a3bc7e9380..a3323fbfd7 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1542,6 +1542,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
for (i = 0; i < negotiation_tip.nr; i++) {
const char *s = negotiation_tip.items[i].string;
+ struct refs_for_each_ref_options opts = {
+ .pattern = s,
+ };
int old_nr;
if (!has_glob_specials(s)) {
struct object_id oid;
@@ -1553,8 +1556,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
continue;
}
old_nr = oids->nr;
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- add_oid, s, oids);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_oid, oids, &opts);
if (old_nr == oids->nr)
warning("ignoring --negotiation-tip=%s because it does not match any refs",
s);
diff --git a/notes.c b/notes.c
index 090c48bbd5..51a7ef9f83 100644
--- a/notes.c
+++ b/notes.c
@@ -952,8 +952,11 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
{
assert(list->strdup_strings);
if (has_glob_specials(glob)) {
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- string_list_add_one_ref, glob, list);
+ struct refs_for_each_ref_options opts = {
+ .pattern = glob,
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ string_list_add_one_ref, list, &opts);
} else {
struct object_id oid;
if (repo_get_oid(the_repository, glob, &oid))
diff --git a/refs.c b/refs.c
index 1fa16facb8..e11ee94013 100644
--- a/refs.c
+++ b/refs.c
@@ -605,15 +605,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
- const char *pattern, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .pattern = pattern,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
const char *prettify_refname(const char *name)
{
if (skip_prefix(name, "refs/heads/", &name) ||
diff --git a/refs.h b/refs.h
index 1d21999ae1..f408367b01 100644
--- a/refs.h
+++ b/refs.h
@@ -526,10 +526,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
const struct refs_for_each_ref_options *opts,
refs_for_each_cb cb, void *cb_data);
-/* iterates all refs that match the specified glob pattern. */
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
- const char *pattern, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
diff --git a/revision.c b/revision.c
index da7737ce28..8a6a871b1c 100644
--- a/revision.c
+++ b/revision.c
@@ -2814,10 +2814,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
clear_ref_exclusions(&revs->ref_excludes);
} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
+ struct refs_for_each_ref_options opts = {
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- handle_one_ref, optarg, &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
return argcount;
} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (14 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
` (2 subsequent siblings)
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_namespaced_ref()` with the newly
introduced `refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
http-backend.c | 8 ++++++--
refs.c | 11 -----------
refs.h | 8 --------
upload-pack.c | 11 +++++++----
4 files changed, 13 insertions(+), 25 deletions(-)
diff --git a/http-backend.c b/http-backend.c
index 0122146df6..1a171c5c5a 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -565,9 +565,13 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)
run_service(argv, 0);
} else {
+ struct refs_for_each_ref_options opts = {
+ .namespace = get_git_namespace(),
+ };
+
select_getanyfile(hdr);
- refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
- NULL, show_text_ref, &buf);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_text_ref, &buf, &opts);
send_strbuf(hdr, "text/plain", &buf);
}
strbuf_release(&buf);
diff --git a/refs.c b/refs.c
index e11ee94013..28142fa967 100644
--- a/refs.c
+++ b/refs.c
@@ -1942,17 +1942,6 @@ int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_namespaced_ref(struct ref_store *refs,
- const char **exclude_patterns,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .exclude_patterns = exclude_patterns,
- .namespace = get_git_namespace(),
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index f408367b01..eae45ce15a 100644
--- a/refs.h
+++ b/refs.h
@@ -526,14 +526,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
const struct refs_for_each_ref_options *opts,
refs_for_each_cb cb, void *cb_data);
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_namespaced_ref(struct ref_store *refs,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
diff --git a/upload-pack.c b/upload-pack.c
index 7fe397b0d0..d21f0577f9 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -610,7 +610,10 @@ static int allow_hidden_refs(enum allow_uor allow_uor)
static void for_each_namespaced_ref_1(refs_for_each_cb fn,
struct upload_pack_data *data)
{
- const char **excludes = NULL;
+ struct refs_for_each_ref_options opts = {
+ .namespace = get_git_namespace(),
+ };
+
/*
* If `data->allow_uor` allows fetching hidden refs, we need to
* mark all references (including hidden ones), to check in
@@ -621,10 +624,10 @@ static void for_each_namespaced_ref_1(refs_for_each_cb fn,
* hidden references.
*/
if (allow_hidden_refs(data->allow_uor))
- excludes = hidden_refs_to_excludes(&data->hidden_refs);
+ opts.exclude_patterns = hidden_refs_to_excludes(&data->hidden_refs);
- refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
- excludes, fn, data);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ fn, data, &opts);
}
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH 17/17] refs: replace `refs_for_each_fullref_in()`
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (15 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
@ 2026-02-20 8:24 ` Patrick Steinhardt
2026-02-23 9:14 ` [PATCH 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
18 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 8:24 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Replace calls to `refs_for_each_fullref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
bisect.c | 8 +++++---
builtin/receive-pack.c | 8 ++++----
builtin/rev-parse.c | 15 +++++++--------
builtin/show-ref.c | 21 +++++++++++++--------
refs.c | 11 -----------
refs.h | 8 --------
revision.c | 4 +++-
t/helper/test-ref-store.c | 8 +++++---
8 files changed, 37 insertions(+), 46 deletions(-)
diff --git a/bisect.c b/bisect.c
index 296836c154..ef17a442e5 100644
--- a/bisect.c
+++ b/bisect.c
@@ -1190,13 +1190,15 @@ static int mark_for_removal(const struct reference *ref, void *cb_data)
int bisect_clean_state(void)
{
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ };
int result = 0;
/* There may be some refs packed during bisection */
struct string_list refs_for_removal = STRING_LIST_INIT_DUP;
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/", NULL, mark_for_removal,
- &refs_for_removal);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ mark_for_removal, &refs_for_removal, &opts);
string_list_append(&refs_for_removal, "BISECT_HEAD");
string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV");
result = refs_delete_refs(get_main_ref_store(the_repository),
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 4c0112b4bc..8c5ad5b81e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -343,9 +343,9 @@ static void show_one_alternate_ref(const struct object_id *oid,
static void write_head_info(void)
{
+ struct refs_for_each_ref_options opts = { 0 };
static struct oidset seen = OIDSET_INIT;
struct strvec excludes_vector = STRVEC_INIT;
- const char **exclude_patterns;
/*
* We need access to the reference names both with and without their
@@ -353,12 +353,12 @@ static void write_head_info(void)
* thus have to adapt exclude patterns to carry the namespace prefix
* ourselves.
*/
- exclude_patterns = get_namespaced_exclude_patterns(
+ opts.exclude_patterns = get_namespaced_exclude_patterns(
hidden_refs_to_excludes(&hidden_refs),
get_git_namespace(), &excludes_vector);
- refs_for_each_fullref_in(get_main_ref_store(the_repository), "",
- exclude_patterns, show_ref_cb, &seen);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref_cb, &seen, &opts);
odb_for_each_alternate_ref(the_repository->objects,
show_one_alternate_ref, &seen);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 206f5bda39..6c73631733 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -939,14 +939,13 @@ int cmd_rev_parse(int argc,
continue;
}
if (!strcmp(arg, "--bisect")) {
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/bad",
- NULL, show_reference,
- NULL);
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/good",
- NULL, anti_reference,
- NULL);
+ struct refs_for_each_ref_options opts = { 0 };
+ opts.prefix = "refs/bisect/bad";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
+ opts.prefix = "refs/bisect/good";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ anti_reference, NULL, &opts);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 4d4984e4e0..5d31acea7c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -215,14 +215,19 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
refs_head_ref(get_main_ref_store(the_repository), show_ref,
&show_ref_data);
if (opts->branches_only || opts->tags_only) {
- if (opts->branches_only)
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/heads/", NULL,
- show_ref, &show_ref_data);
- if (opts->tags_only)
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/tags/", NULL, show_ref,
- &show_ref_data);
+ struct refs_for_each_ref_options for_each_ref_opts = { 0 };
+
+ if (opts->branches_only) {
+ for_each_ref_opts.prefix = "refs/heads/";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref, &show_ref_data, &for_each_ref_opts);
+ }
+
+ if (opts->tags_only) {
+ for_each_ref_opts.prefix = "refs/tags/";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref, &show_ref_data, &for_each_ref_opts);
+ }
} else {
refs_for_each_ref(get_main_ref_store(the_repository),
show_ref, &show_ref_data);
diff --git a/refs.c b/refs.c
index 28142fa967..781fe2ed82 100644
--- a/refs.c
+++ b/refs.c
@@ -1920,17 +1920,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .exclude_patterns = exclude_patterns,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
diff --git a/refs.h b/refs.h
index eae45ce15a..d7332a01f9 100644
--- a/refs.h
+++ b/refs.h
@@ -509,14 +509,6 @@ int refs_for_each_remote_ref(struct ref_store *refs,
int refs_for_each_replace_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
-
/**
* Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
* and iterating the longest-common prefix of each set.
diff --git a/revision.c b/revision.c
index 8a6a871b1c..76c170e92d 100644
--- a/revision.c
+++ b/revision.c
@@ -2731,10 +2731,12 @@ void revision_opts_finish(struct rev_info *revs)
static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data, const char *term)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct strbuf bisect_refs = STRBUF_INIT;
int status;
strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
- status = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data);
+ opts.prefix = bisect_refs.buf;
+ status = refs_for_each_ref_ext(refs, fn, cb_data, &opts);
strbuf_release(&bisect_refs);
return status;
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a2ef1b6949..74edf2029a 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -173,10 +173,12 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)
{
const char *prefix = notnull(*argv++, "prefix");
- const char **exclude_patterns = argv;
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .exclude_patterns = argv,
+ };
- return refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref,
- NULL);
+ return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
}
static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
--
2.53.0.414.gf7e9f6c205.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* Re: [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`
2026-02-20 8:24 ` [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
@ 2026-02-20 10:29 ` Oswald Buddenhagen
2026-02-20 12:05 ` Patrick Steinhardt
0 siblings, 1 reply; 53+ messages in thread
From: Oswald Buddenhagen @ 2026-02-20 10:29 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak
On Fri, Feb 20, 2026 at 09:24:09AM +0100, Patrick Steinhardt wrote:
>Remove the unused `refs_for_each_include_root_ref()` function.
>
at first sight it would seem sensible to move this to the start of the
series to reduce churn.
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`
2026-02-20 10:29 ` Oswald Buddenhagen
@ 2026-02-20 12:05 ` Patrick Steinhardt
0 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-20 12:05 UTC (permalink / raw)
To: Oswald Buddenhagen; +Cc: git, Karthik Nayak
On Fri, Feb 20, 2026 at 11:29:38AM +0100, Oswald Buddenhagen wrote:
> On Fri, Feb 20, 2026 at 09:24:09AM +0100, Patrick Steinhardt wrote:
> > Remove the unused `refs_for_each_include_root_ref()` function.
> >
> at first sight it would seem sensible to move this to the start of the
> series to reduce churn.
Right, that makes sense indeed. I've queued that change locally and will
send it out with the next version, thanks!
Patrick
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 01/17] refs: move `refs_head_ref_namespaced()`
2026-02-20 8:24 ` [PATCH 01/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
@ 2026-02-23 8:05 ` Karthik Nayak
2026-02-23 10:46 ` Patrick Steinhardt
0 siblings, 1 reply; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 8:05 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 1728 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> The function `refs_head_ref_namespaced()` is somewhat special when
> compared to most of the other functions that take a callback function:
> while `refs_for_each_*()` functions yield multiple refs, we only yield
Perhaps swap s/we/refs_head_ref_namespaced() will/
> at most the HEAD ref of the current function. As such, the function is
Should this be s/function/namespace?
> related to `refs_head_ref()` and not to the for-each functions.
>
> Move the function to be located next to `refs_head_ref()` to clarify.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> refs.h | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/refs.h b/refs.h
> index f16b1b697b..62e8ef61e7 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -413,6 +413,9 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);
> */
> int refs_head_ref(struct ref_store *refs,
> each_ref_fn fn, void *cb_data);
> +int refs_head_ref_namespaced(struct ref_store *refs,
> + each_ref_fn fn, void *cb_data);
> +
> int refs_for_each_ref(struct ref_store *refs,
> each_ref_fn fn, void *cb_data);
> int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
> @@ -456,8 +459,6 @@ int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
> int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
> const char *pattern, const char *prefix, void *cb_data);
>
> -int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);
> -
> /*
> * references matching any pattern in "exclude_patterns" are omitted from the
> * result set on a best-effort basis.
>
> --
> 2.53.0.414.gf7e9f6c205.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 04/17] refs: rename `each_ref_fn`
2026-02-20 8:24 ` [PATCH 04/17] refs: rename `each_ref_fn` Patrick Steinhardt
@ 2026-02-23 8:07 ` Karthik Nayak
0 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 8:07 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 242 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Similar to the preceding commit, rename `each_ref_fn` to better match
> our current best practices around how we name things.
>
This and the previous commit are renames and make sense to me.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 06/17] refs: introduce `refs_for_each_ref_ext`
2026-02-20 8:24 ` [PATCH 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
@ 2026-02-23 8:14 ` Karthik Nayak
0 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 8:14 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 8834 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> In the refs subsystem we have a proliferation of functions that all
> iterate through references. (Almost) all of these functions internally
> call `do_for_each_ref()` and provide slightly different arguments so
> that one can control different aspects of its behaviour. This approach
> doesn't really scale: every time there is a slightly different use case
> for iterating through refs we create another new function.
>
> This combinatorial explosion doesn't make a lot of sense: it leads to
> confusing interfaces and heightens the maintenance burden.
>
> Refactor the code to become more composable by:
>
> - Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.
>
> - Introducing an options structure that lets the caller control
> individual options.
>
> This gives us a much better foundation to build on going forward.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> refs.c | 78 ++++++++++++++++++++++++++++++++++++++++--------------------------
> refs.h | 29 +++++++++++++++++++++++++
> 2 files changed, 77 insertions(+), 30 deletions(-)
>
> diff --git a/refs.c b/refs.c
> index a45cc61211..ec9e466381 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(
> return iter;
> }
>
> -static int do_for_each_ref(struct ref_store *refs, const char *prefix,
> - const char **exclude_patterns,
> - refs_for_each_cb fn, int trim,
> - enum refs_for_each_flag flags, void *cb_data)
> +int refs_for_each_ref_ext(struct ref_store *refs,
> + refs_for_each_cb cb, void *cb_data,
> + const struct refs_for_each_ref_options *opts)
So instead of passing prefix, exclude_patterns and trim, we pass in a
struct which contains the options. Ok.
We also rename fn to cb, since that is more legible.
> {
> struct ref_iterator *iter;
>
> if (!refs)
> return 0;
>
> - iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
> - flags);
> + iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
> + opts->exclude_patterns,
> + opts->trim_prefix, opts->flags);
>
> - return do_for_each_ref_iterator(iter, fn, cb_data);
> + return do_for_each_ref_iterator(iter, cb, cb_data);
> }
>
> -int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
> +int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
> {
> - return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
> + struct refs_for_each_ref_options opts = { 0 };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
> - refs_for_each_cb fn, void *cb_data)
> + refs_for_each_cb cb, void *cb_data)
> {
> - return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
> + struct refs_for_each_ref_options opts = {
> + .prefix = prefix,
> + .trim_prefix = strlen(prefix),
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
> const char **exclude_patterns,
> - refs_for_each_cb fn, void *cb_data)
> + refs_for_each_cb cb, void *cb_data)
> {
> - return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
> + struct refs_for_each_ref_options opts = {
> + .prefix = prefix,
> + .exclude_patterns = exclude_patterns,
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> -int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
> +int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
> {
> const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
> - return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
> - strlen(git_replace_ref_base),
> - REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
> + struct refs_for_each_ref_options opts = {
> + .prefix = git_replace_ref_base,
> + .trim_prefix = strlen(git_replace_ref_base),
> + .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> int refs_for_each_namespaced_ref(struct ref_store *refs,
> const char **exclude_patterns,
> - refs_for_each_cb fn, void *cb_data)
> + refs_for_each_cb cb, void *cb_data)
> {
> + struct refs_for_each_ref_options opts = { 0 };
> struct strvec namespaced_exclude_patterns = STRVEC_INIT;
> struct strbuf prefix = STRBUF_INIT;
> int ret;
>
> - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
> - get_git_namespace(),
> - &namespaced_exclude_patterns);
> -
> + opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
> + get_git_namespace(),
> + &namespaced_exclude_patterns);
> strbuf_addf(&prefix, "%srefs/", get_git_namespace());
> - ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
> + opts.prefix = prefix.buf;
> +
> + ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
>
> strvec_clear(&namespaced_exclude_patterns);
> strbuf_release(&prefix);
> @@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d
> }
>
> int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
> - refs_for_each_cb fn, void *cb_data)
> + refs_for_each_cb cb, void *cb_data)
> {
> - return do_for_each_ref(refs, prefix, NULL, fn, 0,
> - REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
> + struct refs_for_each_ref_options opts = {
> + .prefix = prefix,
> + .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> static int qsort_strcmp(const void *va, const void *vb)
> @@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
> struct strbuf *errbuf)
> {
> struct ref_store *old_refs = NULL, *new_refs = NULL;
> + struct refs_for_each_ref_options for_each_ref_opts = {
> + .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
> + };
> struct ref_transaction *transaction = NULL;
> struct strbuf new_gitdir = STRBUF_INIT;
> struct migration_data data = {
> @@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
> data.errbuf = errbuf;
>
> /*
> - * We need to use the internal `do_for_each_ref()` here so that we can
> + * We need to use `refs_for_each_ref_ext()` here so that we can
> * also include broken refs and symrefs. These would otherwise be
> * skipped silently.
> *
> @@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
> * allow for a central lock due to its design. It's thus on the user to
> * ensure that there are no concurrent writes.
> */
> - ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
> - REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
> - &data);
> + ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
> if (ret < 0)
> goto done;
>
Then we modify all the callees to use the new options struct instead of
passing in the arguments individually. Seems good.
> diff --git a/refs.h b/refs.h
> index 5190e98b2c..bb9c64a51c 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,
> int refs_head_ref_namespaced(struct ref_store *refs,
> refs_for_each_cb fn, void *cb_data);
>
> +
> +struct refs_for_each_ref_options {
> + /* Only iterate over references that have this given prefix. */
> + const char *prefix;
> +
> + /*
> + * Exclude any references that match any of these patterns on a
> + * best-effort basis. The caller needs to be prepared for the exclude
> + * patterns to be ignored.
> + *
> + * The array must be terminated with a NULL sentinel value.
> + */
> + const char **exclude_patterns;
> +
> + /*
> + * The number of bytes to trim from the refname. Note that the trimmed
> + * bytes must not cause the reference to become empty. As such, this
> + * field should typically only be set when one uses a `prefix` ending
> + * in a slash.
> + */
> + size_t trim_prefix;
> +
> + /* Flags that change which refs will be included. */
> + enum refs_for_each_flag flags;
> +};
> +
> int refs_for_each_ref(struct ref_store *refs,
> refs_for_each_cb fn, void *cb_data);
> +int refs_for_each_ref_ext(struct ref_store *refs,
> + refs_for_each_cb cb, void *cb_data,
> + const struct refs_for_each_ref_options *opts);
> int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
> refs_for_each_cb fn, void *cb_data);
> int refs_for_each_tag_ref(struct ref_store *refs,
>
> --
> 2.53.0.414.gf7e9f6c205.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`
2026-02-20 8:24 ` [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
@ 2026-02-23 8:27 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
0 siblings, 1 reply; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 8:27 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 6825 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> The function `refs_for_each_glob_ref_in()` can be used to iterate
> through all refs in a specific prefix with globbing. The logic to handle
> this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up
> a callback function that knows to filter out refs that _don't_ match the
> given globbing pattern.
>
> The way we do this is somewhat inefficient though: even though the
> function is expected to only yield refs in the given prefix, we still
> end up iterating through _all_ references, regardless of whether or not
> their name matches the given prefix.
>
So currently instead of relying on the backends to do the prefix
matching, the function uses its own callback to do the prefix matching.
> Extend `refs_for_each_ref_ext()` so that it can handle patterns and
> adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to
> use the same callback-based infrastructure to filter individual refs via
> the globbing pattern, but we can now also use the other functionality of
> the `_ext()` variant.
>
So this change, ensures we don't do the filtering for prefix match
ourselves and allows the backend to do it.
> Most importantly, this means that we now properly handle the prefix.
> This results in a performance improvement when using a prefix where a
> significant majority of refs exists outside of the prefix. The following
> benchmark is an extreme case, with 1 million refs that exist outside the
> prefix and a single ref that exists inside it:
>
> Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)
> Time (mean ± σ): 115.9 ms ± 0.7 ms [User: 113.0 ms, System: 2.4 ms]
> Range (min … max): 114.9 ms … 117.8 ms 25 runs
>
> Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)
> Time (mean ± σ): 1.1 ms ± 0.1 ms [User: 0.3 ms, System: 0.7 ms]
> Range (min … max): 1.0 ms … 2.3 ms 2092 runs
>
> Summary
> git rev-parse --branches=refs/heads/* (rev = HEAD) ran
> 107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)
>
Nice. That's a really neat bump in speed.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> refs.c | 69 ++++++++++++++++++++++++++++++++++++++----------------------------
> refs.h | 10 ++++++++++
> 2 files changed, 50 insertions(+), 29 deletions(-)
>
> diff --git a/refs.c b/refs.c
> index ec9e466381..ac34bbe6c1 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -590,40 +590,23 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
> strbuf_release(&normalized_pattern);
> }
>
> -int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
> +int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
> const char *pattern, const char *prefix, void *cb_data)
> {
> - struct strbuf real_pattern = STRBUF_INIT;
> - struct for_each_ref_filter filter;
> - int ret;
> -
> - if (!prefix && !starts_with(pattern, "refs/"))
> - strbuf_addstr(&real_pattern, "refs/");
> - else if (prefix)
> - strbuf_addstr(&real_pattern, prefix);
> - strbuf_addstr(&real_pattern, pattern);
> -
> - if (!has_glob_specials(pattern)) {
> - /* Append implied '/' '*' if not present. */
> - strbuf_complete(&real_pattern, '/');
> - /* No need to check for '*', there is none. */
> - strbuf_addch(&real_pattern, '*');
> - }
> -
> - filter.pattern = real_pattern.buf;
> - filter.prefix = prefix;
> - filter.fn = fn;
> - filter.cb_data = cb_data;
> - ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
> -
> - strbuf_release(&real_pattern);
> - return ret;
> + struct refs_for_each_ref_options opts = {
> + .pattern = pattern,
> + .prefix = prefix,
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> -int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
> +int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
> const char *pattern, void *cb_data)
> {
> - return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
> + struct refs_for_each_ref_options opts = {
> + .pattern = pattern,
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> const char *prettify_refname(const char *name)
> @@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,
> refs_for_each_cb cb, void *cb_data,
> const struct refs_for_each_ref_options *opts)
> {
> + struct strbuf real_pattern = STRBUF_INIT;
> + struct for_each_ref_filter filter;
> struct ref_iterator *iter;
> + int ret;
>
> if (!refs)
> return 0;
>
> + if (opts->pattern) {
> + if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
> + strbuf_addstr(&real_pattern, "refs/");
> + else if (opts->prefix)
> + strbuf_addstr(&real_pattern, opts->prefix);
> + strbuf_addstr(&real_pattern, opts->pattern);
> +
> + if (!has_glob_specials(opts->pattern)) {
> + /* Append implied '/' '*' if not present. */
> + strbuf_complete(&real_pattern, '/');
> + /* No need to check for '*', there is none. */
> + strbuf_addch(&real_pattern, '*');
> + }
> +
> + filter.pattern = real_pattern.buf;
> + filter.prefix = opts->prefix;
Can't we now remove this option and cleanup `for_each_filter_refs()` to
remove prefix trimming?
> + filter.fn = cb;
> + filter.cb_data = cb_data;
> +
> + cb = for_each_filter_refs;
> + cb_data = &filter;
> + }
> +
> iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
> opts->exclude_patterns,
> opts->trim_prefix, opts->flags);
>
> - return do_for_each_ref_iterator(iter, cb, cb_data);
> + ret = do_for_each_ref_iterator(iter, cb, cb_data);
> + strbuf_release(&real_pattern);
> + return ret;
> }
>
> int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
> diff --git a/refs.h b/refs.h
> index bb9c64a51c..a66dbf3865 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -458,6 +458,16 @@ struct refs_for_each_ref_options {
> /* Only iterate over references that have this given prefix. */
> const char *prefix;
>
> + /*
> + * A globbing pattern that can be used to only yield refs that match.
> + * If given, refs will be matched against the pattern with
> + * `wildmatch()`.
> + *
> + * If the pattern doesn't contain any globbing characters then it is
> + * treated as if it was ending with "/" and "*".
> + */
> + const char *pattern;
> +
> /*
> * Exclude any references that match any of these patterns on a
> * best-effort basis. The caller needs to be prepared for the exclude
>
> --
> 2.53.0.414.gf7e9f6c205.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`
2026-02-20 8:24 ` [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
@ 2026-02-23 9:02 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
0 siblings, 1 reply; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 9:02 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 4544 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> The function `refs_for_each_namespaced_ref()` iterates through all
> references that are part of the current ref namespace. This namespace
> can be configured by setting the `GIT_NAMESPACE` environment variable
> and is then retrieved by calling `get_git_namespace()`.
>
> If a namespace is configured, then we:
>
> - Obviously only yield refs that exist in this namespace.
>
> - Rewrite exclude patterns so that they work for the given namespace,
> if any namespace is currently configured.
>
> Port this logic to `refs_for_each_ref_ext()` by adding a new `namespace`
> field to the options structure. This gives callers more flexibility as
> they can decide by themselves whether they want to use the globally
> configured or an arbitrary other namespace.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> refs.c | 47 +++++++++++++++++++++++++++++------------------
> refs.h | 6 ++++++
> 2 files changed, 35 insertions(+), 18 deletions(-)
>
> diff --git a/refs.c b/refs.c
> index ac34bbe6c1..99994879d9 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1845,9 +1845,13 @@ int refs_for_each_ref_ext(struct ref_store *refs,
> refs_for_each_cb cb, void *cb_data,
> const struct refs_for_each_ref_options *opts)
> {
> + struct strvec namespaced_exclude_patterns = STRVEC_INIT;
> + struct strbuf namespaced_prefix = STRBUF_INIT;
> struct strbuf real_pattern = STRBUF_INIT;
> struct for_each_ref_filter filter;
> struct ref_iterator *iter;
> + const char **exclude_patterns;
> + const char *prefix;
> int ret;
>
> if (!refs)
> @@ -1876,11 +1880,29 @@ int refs_for_each_ref_ext(struct ref_store *refs,
> cb_data = &filter;
> }
>
> - iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
> - opts->exclude_patterns,
> + if (opts->namespace) {
> + strbuf_addstr(&namespaced_prefix, opts->namespace);
> + if (opts->prefix)
> + strbuf_addstr(&namespaced_prefix, opts->prefix);
> + else
> + strbuf_addstr(&namespaced_prefix, "refs/");
> +
So if the namespace is 'foo', we'll have the namespace folder as
'refs/namespace/foo', and a prefix of 'refs/heads/' would mean that the
'namespaced_prefix' is now 'refs/namespace/foo/refs/heads'. Looks good.
> + prefix = namespaced_prefix.buf;
> + exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,
> + opts->namespace,
> + &namespaced_exclude_patterns);
> + } else {
> + prefix = opts->prefix ? opts->prefix : "";
> + exclude_patterns = opts->exclude_patterns;
> + }
> +
> + iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
> opts->trim_prefix, opts->flags);
>
> ret = do_for_each_ref_iterator(iter, cb, cb_data);
> +
> + strvec_clear(&namespaced_exclude_patterns);
> + strbuf_release(&namespaced_prefix);
> strbuf_release(&real_pattern);
> return ret;
> }
> @@ -1927,22 +1949,11 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
> const char **exclude_patterns,
> refs_for_each_cb cb, void *cb_data)
> {
> - struct refs_for_each_ref_options opts = { 0 };
> - struct strvec namespaced_exclude_patterns = STRVEC_INIT;
> - struct strbuf prefix = STRBUF_INIT;
> - int ret;
> -
> - opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
> - get_git_namespace(),
> - &namespaced_exclude_patterns);
> - strbuf_addf(&prefix, "%srefs/", get_git_namespace());
> - opts.prefix = prefix.buf;
> -
> - ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> -
> - strvec_clear(&namespaced_exclude_patterns);
> - strbuf_release(&prefix);
> - return ret;
> + struct refs_for_each_ref_options opts = {
> + .exclude_patterns = exclude_patterns,
> + .namespace = get_git_namespace(),
> + };
> + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
> }
>
> int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
> diff --git a/refs.h b/refs.h
> index a66dbf3865..2bde60aa0e 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -468,6 +468,12 @@ struct refs_for_each_ref_options {
> */
> const char *pattern;
>
> + /*
> + * If set, only yield refs part of the configured namespace. Exclude
> + * patterns will be rewritten to apply to the namespace.
> + */
> + const char *namespace;
Nit: should we also mention how prefix is appended to namespace?
> +
> /*
> * Exclude any references that match any of these patterns on a
> * best-effort basis. The caller needs to be prepared for the exclude
>
> --
> 2.53.0.414.gf7e9f6c205.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`
2026-02-20 8:24 ` [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
@ 2026-02-23 9:06 ` Karthik Nayak
0 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 9:06 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 5437 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> The function `refs_for_each_fullref_in_prefixes()` can be used to
> iterate over all references part of any of the user-provided prefixes.
> In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it
> knows to handle the case well where multiple of the passed-in prefixes
> start with a common prefix by computing longest common prefixes and then
> iterating over those.
>
> While we could move this logic into `refs_for_each_ref_ext()`, this one
> feels somewhat special as we perform multiple iterations. But what we
> _can_ do is to generalize how this function works: instead of accepting
> only a small handful of parameters, we can have it accept the full
> options structure.
>
> One obvious exception is that the caller must not provide a prefix via
> the options. But this case can be easily detected.
>
> Refactor the code accordingly.
>
I've attempted the refactoring that this series is doing a couple of
times, I always got stuck up on how to integrate this function with the
rest. This is a novel approach.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> ls-refs.c | 11 +++++++----
> ref-filter.c | 11 +++++++----
> refs.c | 39 +++++++++++++++------------------------
> refs.h | 16 +++++-----------
> 4 files changed, 34 insertions(+), 43 deletions(-)
>
> diff --git a/ls-refs.c b/ls-refs.c
> index 8641281b86..9759826ca7 100644
> --- a/ls-refs.c
> +++ b/ls-refs.c
> @@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,
>
> int ls_refs(struct repository *r, struct packet_reader *request)
> {
> + struct refs_for_each_ref_options opts = { 0 };
> struct ls_refs_data data;
>
> memset(&data, 0, sizeof(data));
> @@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)
> send_possibly_unborn_head(&data);
> if (!data.prefixes.nr)
> strvec_push(&data.prefixes, "");
> - refs_for_each_fullref_in_prefixes(get_main_ref_store(r),
> - get_git_namespace(), data.prefixes.v,
> - hidden_refs_to_excludes(&data.hidden_refs),
> - send_ref, &data);
> +
> + opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);
> + opts.namespace = get_git_namespace();
> +
> + refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,
> + &opts, send_ref, &data);
> packet_fflush(stdout);
> strvec_clear(&data.prefixes);
> strbuf_release(&data.buf);
> diff --git a/ref-filter.c b/ref-filter.c
> index 049e845a19..7c682e0a33 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
> refs_for_each_cb cb,
> void *cb_data)
> {
> + struct refs_for_each_ref_options opts = {
> + .exclude_patterns = filter->exclude.v,
> + };
> +
> if (filter->kind & FILTER_REFS_ROOT_REFS) {
> /* In this case, we want to print all refs including root refs. */
> return for_each_fullref_with_seek(filter, cb, cb_data,
> @@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
> return for_each_fullref_with_seek(filter, cb, cb_data, 0);
> }
>
> - return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),
> - NULL, filter->name_patterns,
> - filter->exclude.v,
> - cb, cb_data);
> + return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),
> + filter->name_patterns, &opts,
> + cb, cb_data);
> }
>
> /*
> diff --git a/refs.c b/refs.c
> index 99994879d9..20d34faeb5 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2029,40 +2029,31 @@ static void find_longest_prefixes(struct string_list *out,
> strbuf_release(&prefix);
> }
>
> -int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
> - const char *namespace,
> - const char **patterns,
> - const char **exclude_patterns,
> - refs_for_each_cb fn, void *cb_data)
> +int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
> + const char **prefixes,
> + const struct refs_for_each_ref_options *opts,
> + refs_for_each_cb cb, void *cb_data)
> {
> - struct strvec namespaced_exclude_patterns = STRVEC_INIT;
> - struct string_list prefixes = STRING_LIST_INIT_DUP;
> + struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
> struct string_list_item *prefix;
> - struct strbuf buf = STRBUF_INIT;
> - int ret = 0, namespace_len;
> + int ret = 0;
>
> - find_longest_prefixes(&prefixes, patterns);
> + if (opts->prefix)
> + BUG("refs_for_each_ref_in_prefixes called with specific prefix");
>
> - if (namespace)
> - strbuf_addstr(&buf, namespace);
> - namespace_len = buf.len;
> + find_longest_prefixes(&longest_prefixes, prefixes);
>
> - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
> - namespace,
> - &namespaced_exclude_patterns);
> + for_each_string_list_item(prefix, &longest_prefixes) {
> + struct refs_for_each_ref_options prefix_opts = *opts;
> + prefix_opts.prefix = prefix->string;
>
> - for_each_string_list_item(prefix, &prefixes) {
> - strbuf_addstr(&buf, prefix->string);
> - ret = refs_for_each_fullref_in(ref_store, buf.buf,
> - exclude_patterns, fn, cb_data);
> + ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
> + &prefix_opts);
Okay so it still calls `refs_for_each_ref_ext()` on the longest prefix.
Makes sense.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 10/17] refs: improve verification for-each-ref options
2026-02-20 8:24 ` [PATCH 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
@ 2026-02-23 9:09 ` Karthik Nayak
0 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 9:09 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 1842 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Improve verification of the passed-in for-each-ref options:
>
> - Require that the `refs` store must be given. It's arguably very
> surprising that we simply return successfully in case the ref store
> is a `NULL` pointer.
>
> - When expected to trim ref prefixes we will `BUG()` in case the
> refname would become empty or in case we're expected to trim a
> longer prefix than the refname is long. As such, this case is only
> guaranteed to _not_ `BUG()` in case the caller also specified a
> prefix. And furthermore, that prefix must end in a trailing slash,
> as otherwise it may produce an exact match that could lead us to
> trim to the empty string.
>
> An audit shows that there are no callsites that rely on either of these
> behaviours, so this should not result in a functional change.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> refs.c | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/refs.c b/refs.c
> index 20d34faeb5..3b676432b4 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1855,7 +1855,18 @@ int refs_for_each_ref_ext(struct ref_store *refs,
> int ret;
>
> if (!refs)
> - return 0;
> + BUG("no refs passed");
> +
Nit: s/refs/ref store/, mostly from a readability point, but since this
is a BUG(), I think its okay to leave as is.
> + if (opts->trim_prefix) {
> + size_t prefix_len;
> +
> + if (!opts->prefix)
> + BUG("trimming only allowed with a prefix");
> +
> + prefix_len = strlen(opts->prefix);
> + if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')
> + BUG("ref pattern must end in a trailing slash when trimming");
> + }
>
> if (opts->pattern) {
> if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
>
> --
> 2.53.0.414.gf7e9f6c205.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`
2026-02-20 8:24 ` [PATCH 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
@ 2026-02-23 9:11 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
0 siblings, 1 reply; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 9:11 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 2293 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Replace calls to `refs_for_each_ref_in()` with the newly introduced
> `refs_for_each_ref_ext()` function.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> bisect.c | 8 ++++++--
> builtin/rev-parse.c | 13 +++++++++----
> pack-bitmap.c | 13 +++++++------
> refs.c | 34 ++++++++++++++++++----------------
> refs.h | 2 --
> t/helper/test-ref-store.c | 7 +++++--
> 6 files changed, 45 insertions(+), 32 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index 2bdad4ee42..296836c154 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED)
>
> static int read_bisect_refs(void)
> {
> - return refs_for_each_ref_in(get_main_ref_store(the_repository),
> - "refs/bisect/", register_ref, NULL);
> + struct refs_for_each_ref_options opts = {
> + .prefix = "refs/bisect/",
> + .trim_prefix = strlen("refs/bisect/"),
> + };
> + return refs_for_each_ref_ext(get_main_ref_store(the_repository),
> + register_ref, NULL, &opts);
> }
>
> static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index 9032cc6327..02703f2fb8 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
>
> static void handle_ref_opt(const char *pattern, const char *prefix)
> {
> - if (pattern)
> + if (pattern) {
> refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
> show_reference, pattern, prefix,
> NULL);
> - else
> - refs_for_each_ref_in(get_main_ref_store(the_repository),
> - prefix, show_reference, NULL);
> + } else {
> + struct refs_for_each_ref_options opts = {
> + .prefix = prefix,
> + .trim_prefix = strlen(prefix),
Tangent: I wonder if it makes sense to make `trim_prefix` a bool and
then internally trim strlen(prefix). Is there a usecase where
`.trim_prefix != strlen(prefix)`?
> + };
> + refs_for_each_ref_ext(get_main_ref_store(the_repository),
> + show_reference, NULL, &opts);
> + }
> clear_ref_exclusions(&ref_excludes);
> }
>
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 00/17] refs: unify `refs_for_each_*()` functions
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (16 preceding siblings ...)
2026-02-20 8:24 ` [PATCH 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
@ 2026-02-23 9:14 ` Karthik Nayak
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
18 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 9:14 UTC (permalink / raw)
To: Patrick Steinhardt, git
[-- Attachment #1: Type: text/plain, Size: 1738 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> we currently have 14 different `refs_for_each_*()` functions, with each
> of them doing slightly different things. This makes for a confusing API
> surface, and because the API is not built for extension we have to add a
> new function every now and then to handle another esoteric edge case
> that will ultimately only have at most a handful of callers.
>
> This design isn't really sensible in my opinion, and this patch series
> aims to fix that. Instead of having a dozen different functions, it
> introduces a new `refs_for_each_ref_ext()` function that simply takes an
> options structure as input. From thereon, callers can mix and match the
> parameters that they care about.
>
> The patch series is structured like this:
>
> - Patches 1 to 5 introduce some preliminary cleanups.
>
> - Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move
> more functionality into it. This also fixes a performance bug that
> we have in one of the implementations.
>
> - Patch 10 adds some more verification for options that would have
> caught the bugs in ps/for-each-ref-in-fixes.
>
> - The remaining patches drop 7 out of 14 functions and replace them
> with `refs_for_each_ref_ext()`. It results in a bit of churn, so
> while I think this churn is worth it, I consider these patches to be
> optional.
>
> The patch series is built on top of 73fd77805f (The 5th batch,
> 2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:
> simplify string_list memory handling, 2026-02-19) merged into it.
>
> Thanks!
>
> Patrick
>
I'm really happy with the patches, I have some small nits/questions, but
it looks good otherwise.
Thanks,
Karthik
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 01/17] refs: move `refs_head_ref_namespaced()`
2026-02-23 8:05 ` Karthik Nayak
@ 2026-02-23 10:46 ` Patrick Steinhardt
0 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 10:46 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Feb 23, 2026 at 03:05:27AM -0500, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > The function `refs_head_ref_namespaced()` is somewhat special when
> > compared to most of the other functions that take a callback function:
> > while `refs_for_each_*()` functions yield multiple refs, we only yield
>
> Perhaps swap s/we/refs_head_ref_namespaced() will/
>
> > at most the HEAD ref of the current function. As such, the function is
>
> Should this be s/function/namespace?
Yup. Will adapt both, thanks!
Patrick
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`
2026-02-23 8:27 ` Karthik Nayak
@ 2026-02-23 10:48 ` Patrick Steinhardt
0 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 10:48 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Feb 23, 2026 at 12:27:15AM -0800, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/refs.c b/refs.c
> > index ec9e466381..ac34bbe6c1 100644
> > --- a/refs.c
> > +++ b/refs.c
> > @@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,
> > refs_for_each_cb cb, void *cb_data,
> > const struct refs_for_each_ref_options *opts)
> > {
> > + struct strbuf real_pattern = STRBUF_INIT;
> > + struct for_each_ref_filter filter;
> > struct ref_iterator *iter;
> > + int ret;
> >
> > if (!refs)
> > return 0;
> >
> > + if (opts->pattern) {
> > + if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
> > + strbuf_addstr(&real_pattern, "refs/");
> > + else if (opts->prefix)
> > + strbuf_addstr(&real_pattern, opts->prefix);
> > + strbuf_addstr(&real_pattern, opts->pattern);
> > +
> > + if (!has_glob_specials(opts->pattern)) {
> > + /* Append implied '/' '*' if not present. */
> > + strbuf_complete(&real_pattern, '/');
> > + /* No need to check for '*', there is none. */
> > + strbuf_addch(&real_pattern, '*');
> > + }
> > +
> > + filter.pattern = real_pattern.buf;
> > + filter.prefix = opts->prefix;
>
> Can't we now remove this option and cleanup `for_each_filter_refs()` to
> remove prefix trimming?
No, unfortunately not. This is because the glob pattern is expected to
match on the full refname, so if we were to strip the refname before we
pass it to the `filter` callback then we wouldn't be able to do the call
to wildmatch anymore.
But the stripping part is still a bit funky after my refactoring, as we
unconditionall strip the prefix right now. This is the expected
behaviour, but it is somewhat surprising I guess. I'll rework this part
a bit, thanks!
Patrick
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`
2026-02-23 9:02 ` Karthik Nayak
@ 2026-02-23 10:48 ` Patrick Steinhardt
0 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 10:48 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Feb 23, 2026 at 01:02:56AM -0800, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/refs.h b/refs.h
> > index a66dbf3865..2bde60aa0e 100644
> > --- a/refs.h
> > +++ b/refs.h
> > @@ -468,6 +468,12 @@ struct refs_for_each_ref_options {
> > */
> > const char *pattern;
> >
> > + /*
> > + * If set, only yield refs part of the configured namespace. Exclude
> > + * patterns will be rewritten to apply to the namespace.
> > + */
> > + const char *namespace;
>
> Nit: should we also mention how prefix is appended to namespace?
Yeah, makes sense, will do.
Patrick
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`
2026-02-23 9:11 ` Karthik Nayak
@ 2026-02-23 10:48 ` Patrick Steinhardt
2026-02-23 13:35 ` Karthik Nayak
0 siblings, 1 reply; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 10:48 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Feb 23, 2026 at 04:11:52AM -0500, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> > index 9032cc6327..02703f2fb8 100644
> > --- a/builtin/rev-parse.c
> > +++ b/builtin/rev-parse.c
> > @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
> >
> > static void handle_ref_opt(const char *pattern, const char *prefix)
> > {
> > - if (pattern)
> > + if (pattern) {
> > refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
> > show_reference, pattern, prefix,
> > NULL);
> > - else
> > - refs_for_each_ref_in(get_main_ref_store(the_repository),
> > - prefix, show_reference, NULL);
> > + } else {
> > + struct refs_for_each_ref_options opts = {
> > + .prefix = prefix,
> > + .trim_prefix = strlen(prefix),
>
> Tangent: I wonder if it makes sense to make `trim_prefix` a bool and
> then internally trim strlen(prefix). Is there a usecase where
> `.trim_prefix != strlen(prefix)`?
I don't think there is right now, and I cannot think about any myself.
How about we leave this as a #leftoverbit though?
Patrick
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
` (17 preceding siblings ...)
2026-02-23 9:14 ` [PATCH 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
` (17 more replies)
18 siblings, 18 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Hi,
we currently have 14 different `refs_for_each_*()` functions, with each
of them doing slightly different things. This makes for a confusing API
surface, and because the API is not built for extension we have to add a
new function every now and then to handle another esoteric edge case
that will ultimately only have at most a handful of callers.
This design isn't really sensible in my opinion, and this patch series
aims to fix that. Instead of having a dozen different functions, it
introduces a new `refs_for_each_ref_ext()` function that simply takes an
options structure as input. From thereon, callers can mix and match the
parameters that they care about.
The patch series is structured like this:
- Patches 1 to 5 introduce some preliminary cleanups.
- Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move
more functionality into it. This also fixes a performance bug that
we have in one of the implementations.
- Patch 10 adds some more verification for options that would have
caught the bugs in ps/for-each-ref-in-fixes.
- The remaining patches drop 7 out of 14 functions and replace them
with `refs_for_each_ref_ext()`. It results in a bit of churn, so
while I think this churn is worth it, I consider these patches to be
optional.
The patch series is built on top of 73fd77805f (The 5th batch,
2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:
simplify string_list memory handling, 2026-02-19) merged into it.
Changes in v2:
- Move the removal of `refs_for_each_include_root_ref()` to the
beginning of the series to avoid some unnecessary churn.
- Some commit message improvements.
- Make the converted version of `refs_for_each_glob_ref_in()` fit into
the new calling conventions a bit better. The function was still
stripping the prefix unconditionally for example, which I've now
changed.
- Link to v1: https://lore.kernel.org/r/20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (17):
refs: remove unused `refs_for_each_include_root_ref()`
refs: move `refs_head_ref_namespaced()`
refs: move `do_for_each_ref_flags` further up
refs: rename `do_for_each_ref_flags`
refs: rename `each_ref_fn`
refs: introduce `refs_for_each_ref_ext`
refs: speed up `refs_for_each_glob_ref_in()`
refs: generalize `refs_for_each_namespaced_ref()`
refs: generalize `refs_for_each_fullref_in_prefixes()`
refs: improve verification for-each-ref options
refs: replace `refs_for_each_ref_in()`
refs: replace `refs_for_each_rawref()`
refs: replace `refs_for_each_rawref_in()`
refs: replace `refs_for_each_glob_ref_in()`
refs: replace `refs_for_each_glob_ref()`
refs: replace `refs_for_each_namespaced_ref()`
refs: replace `refs_for_each_fullref_in()`
bisect.c | 16 ++-
builtin/bisect.c | 37 +++++--
builtin/describe.c | 7 +-
builtin/fetch.c | 7 +-
builtin/fsck.c | 7 +-
builtin/receive-pack.c | 8 +-
builtin/remote.c | 8 +-
builtin/rev-parse.c | 38 ++++---
builtin/show-ref.c | 21 ++--
fetch-pack.c | 15 ++-
http-backend.c | 8 +-
ls-refs.c | 11 +-
notes.c | 7 +-
pack-bitmap.c | 15 +--
pack-bitmap.h | 2 +-
ref-filter.c | 19 ++--
refs.c | 271 ++++++++++++++++++++++------------------------
refs.h | 199 +++++++++++++++++-----------------
refs/files-backend.c | 19 ++--
refs/iterator.c | 2 +-
refs/packed-backend.c | 8 +-
refs/reftable-backend.c | 10 +-
revision.c | 49 ++++++---
submodule.c | 2 +-
t/helper/test-ref-store.c | 15 ++-
upload-pack.c | 13 ++-
worktree.c | 2 +-
worktree.h | 2 +-
28 files changed, 457 insertions(+), 361 deletions(-)
Range-diff versus v1:
-: ---------- > 1: 97473a19a8 refs: remove unused `refs_for_each_include_root_ref()`
1: 312fde9bc7 ! 2: 625d8bde9d refs: move `refs_head_ref_namespaced()`
@@ Commit message
The function `refs_head_ref_namespaced()` is somewhat special when
compared to most of the other functions that take a callback function:
- while `refs_for_each_*()` functions yield multiple refs, we only yield
- at most the HEAD ref of the current function. As such, the function is
- related to `refs_head_ref()` and not to the for-each functions.
+ while `refs_for_each_*()` functions yield multiple refs,
+ `refs_heasd_ref_namespaced()` will only yield at most the HEAD ref of
+ the current namespace. As such, the function is related to
+ `refs_head_ref()` and not to the for-each functions.
Move the function to be located next to `refs_head_ref()` to clarify.
2: fd0fa20a37 = 3: 2f5a6e7d27 refs: move `do_for_each_ref_flags` further up
3: f11af1c8e7 ! 4: a817b11091 refs: rename `do_for_each_ref_flags`
@@ refs.c: int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
+ REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
- int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
- void *cb_data)
- {
- return do_for_each_ref(refs, "", NULL, fn, 0,
-- DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
-+ REFS_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
- }
-
static int qsort_strcmp(const void *va, const void *vb)
@@ refs.c: enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
4: 37be4c5f59 ! 5: f06b4fc5e4 refs: rename `each_ref_fn`
@@ refs.c: int refs_for_each_namespaced_ref(struct ref_store *refs,
{
return do_for_each_ref(refs, prefix, NULL, fn, 0,
REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
- }
-
--int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
-+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
- void *cb_data)
- {
- return do_for_each_ref(refs, "", NULL, fn, 0,
@@ refs.c: int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
const char *namespace,
const char **patterns,
@@ refs.h: int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+ refs_for_each_cb fn, void *cb_data);
/*
- * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
- */
--int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
-+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,
- void *cb_data);
-
- /*
+ * Normalizes partial refs to their fully qualified form.
@@ refs.h: void ref_iterator_free(struct ref_iterator *ref_iterator);
* iterator style.
*/
5: d70867c5f6 < -: ---------- refs: remove unused `refs_for_each_include_root_ref()`
6: b22f654698 = 6: 58620a64dd refs: introduce `refs_for_each_ref_ext`
7: 0a050b61f7 ! 7: 2ca0ddb23a refs: speed up `refs_for_each_glob_ref_in()`
@@ Commit message
Signed-off-by: Patrick Steinhardt <ps@pks.im>
## refs.c ##
+@@ refs.c: char *refs_resolve_refdup(struct ref_store *refs,
+ /* The argument to for_each_filter_refs */
+ struct for_each_ref_filter {
+ const char *pattern;
+- const char *prefix;
++ size_t trim_prefix;
+ refs_for_each_cb *fn;
+ void *cb_data;
+ };
+@@ refs.c: static int for_each_filter_refs(const struct reference *ref, void *data)
+
+ if (wildmatch(filter->pattern, ref->name, 0))
+ return 0;
+- if (filter->prefix) {
++ if (filter->trim_prefix) {
+ struct reference skipped = *ref;
+- skip_prefix(skipped.name, filter->prefix, &skipped.name);
++ if (strlen(skipped.name) <= filter->trim_prefix)
++ BUG("attempt to trim too many characters");
++ skipped.name += filter->trim_prefix;
+ return filter->fn(&skipped, filter->cb_data);
+ } else {
+ return filter->fn(ref, filter->cb_data);
@@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
@@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefi
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
++ .trim_prefix = prefix ? strlen(prefix) : 0,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
@@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,
+ struct strbuf real_pattern = STRBUF_INIT;
+ struct for_each_ref_filter filter;
struct ref_iterator *iter;
++ size_t trim_prefix = opts->trim_prefix;
+ int ret;
if (!refs)
@@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,
+ }
+
+ filter.pattern = real_pattern.buf;
-+ filter.prefix = opts->prefix;
++ filter.trim_prefix = opts->trim_prefix;
+ filter.fn = cb;
+ filter.cb_data = cb_data;
+
++ /*
++ * We need to trim the prefix in the callback function as the
++ * pattern is expected to match on the full refname.
++ */
++ trim_prefix = 0;
++
+ cb = for_each_filter_refs;
+ cb_data = &filter;
+ }
+
iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
opts->exclude_patterns,
- opts->trim_prefix, opts->flags);
+- opts->trim_prefix, opts->flags);
++ trim_prefix, opts->flags);
- return do_for_each_ref_iterator(iter, cb, cb_data);
+ ret = do_for_each_ref_iterator(iter, cb, cb_data);
8: b15d334f14 ! 8: 01a640a61d refs: generalize `refs_for_each_namespaced_ref()`
@@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,
struct strbuf real_pattern = STRBUF_INIT;
struct for_each_ref_filter filter;
struct ref_iterator *iter;
+ size_t trim_prefix = opts->trim_prefix;
+ const char **exclude_patterns;
+ const char *prefix;
int ret;
@@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,
+ }
+
+ iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
- opts->trim_prefix, opts->flags);
+ trim_prefix, opts->flags);
ret = do_for_each_ref_iterator(iter, cb, cb_data);
+
@@ refs.h: struct refs_for_each_ref_options {
+ /*
+ * If set, only yield refs part of the configured namespace. Exclude
-+ * patterns will be rewritten to apply to the namespace.
++ * patterns will be rewritten to apply to the namespace, and the prefix
++ * will be considered relative to the namespace.
+ */
+ const char *namespace;
+
9: 2e63b1ab88 = 9: 241030d7ad refs: generalize `refs_for_each_fullref_in_prefixes()`
10: b408e5c1f0 ! 10: 548aae78f0 refs: improve verification for-each-ref options
@@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,
if (!refs)
- return 0;
-+ BUG("no refs passed");
++ BUG("no ref store passed");
+
+ if (opts->trim_prefix) {
+ size_t prefix_len;
11: 5c9401df32 = 11: b0a5c835be refs: replace `refs_for_each_ref_in()`
12: 39a5f1ef21 = 12: 59f5632719 refs: replace `refs_for_each_rawref()`
13: 5568ee95d0 = 13: 8b99f6e38c refs: replace `refs_for_each_rawref_in()`
14: d73e6362ae ! 14: ed75a64569 refs: replace `refs_for_each_glob_ref_in()`
@@ builtin/bisect.c: static void bisect_status(struct bisect_state *state,
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
++ .trim_prefix = strlen("refs/bisect/"),
+ };
if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))
@@ builtin/bisect.c: static int add_bisect_ref(const struct reference *ref, void *c
{
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
++ .trim_prefix = strlen("refs/bisect/"),
+ };
int res = 0;
struct add_bisect_ref_data cb = { revs };
@@ builtin/bisect.c: static int verify_good(const struct bisect_terms *terms, const
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
++ .trim_prefix = strlen("refs/bisect/"),
+ };
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
@@ builtin/rev-parse.c: static int opt_with_value(const char *arg, const char *opt,
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
++ .trim_prefix = prefix ? strlen(prefix) : 0,
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
@@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefi
- struct refs_for_each_ref_options opts = {
- .pattern = pattern,
- .prefix = prefix,
+- .trim_prefix = prefix ? strlen(prefix) : 0,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
@@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,
} else if (skip_prefix(arg, "--branches=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
++ .trim_prefix = strlen("refs/heads/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
@@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,
} else if (skip_prefix(arg, "--tags=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
++ .trim_prefix = strlen("refs/tags/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
@@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,
} else if (skip_prefix(arg, "--remotes=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
++ .trim_prefix = strlen("refs/remotes/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
15: c957d80f2d = 15: 6256a04ecf refs: replace `refs_for_each_glob_ref()`
16: 229d69d91c = 16: ef356544bf refs: replace `refs_for_each_namespaced_ref()`
17: 4e0fe9f805 = 17: 4616cdc618 refs: replace `refs_for_each_fullref_in()`
---
base-commit: dbbe43524e0814c1f93325795ed6aa26eb6e587e
change-id: 20260220-pks-refs-for-each-unification-7572c694cfc0
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 02/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
` (16 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Remove the unused `refs_for_each_include_root_ref()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 7 -------
refs.h | 6 ------
2 files changed, 13 deletions(-)
diff --git a/refs.c b/refs.c
index 600913b99f..466398494f 100644
--- a/refs.c
+++ b/refs.c
@@ -1932,13 +1932,6 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
- void *cb_data)
-{
- return do_for_each_ref(refs, "", NULL, fn, 0,
- DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index f16b1b697b..1fdb809343 100644
--- a/refs.h
+++ b/refs.h
@@ -471,12 +471,6 @@ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data);
-/*
- * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
- */
-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
- void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 02/17] refs: move `refs_head_ref_namespaced()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 03/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
` (15 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
The function `refs_head_ref_namespaced()` is somewhat special when
compared to most of the other functions that take a callback function:
while `refs_for_each_*()` functions yield multiple refs,
`refs_heasd_ref_namespaced()` will only yield at most the HEAD ref of
the current namespace. As such, the function is related to
`refs_head_ref()` and not to the for-each functions.
Move the function to be located next to `refs_head_ref()` to clarify.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/refs.h b/refs.h
index 1fdb809343..718212a5d7 100644
--- a/refs.h
+++ b/refs.h
@@ -413,6 +413,9 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);
*/
int refs_head_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
+int refs_head_ref_namespaced(struct ref_store *refs,
+ each_ref_fn fn, void *cb_data);
+
int refs_for_each_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
@@ -456,8 +459,6 @@ int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
const char *pattern, const char *prefix, void *cb_data);
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 03/17] refs: move `do_for_each_ref_flags` further up
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 02/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 04/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
` (14 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Move the `do_for_each_ref_flags` enum further up. This prepares for
subsequent changes, where the flags will be used by more functions.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.h | 74 +++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 37 insertions(+), 37 deletions(-)
diff --git a/refs.h b/refs.h
index 718212a5d7..6fd7a706b5 100644
--- a/refs.h
+++ b/refs.h
@@ -402,6 +402,43 @@ int reference_get_peeled_oid(struct repository *repo,
*/
typedef int each_ref_fn(const struct reference *ref, void *cb_data);
+/*
+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
+ * which feeds it).
+ */
+enum do_for_each_ref_flags {
+ /*
+ * Include broken references in a do_for_each_ref*() iteration, which
+ * would normally be omitted. This includes both refs that point to
+ * missing objects (a true repository corruption), ones with illegal
+ * names (which we prefer not to expose to callers), as well as
+ * dangling symbolic refs (i.e., those that point to a non-existent
+ * ref; this is not a corruption, but as they have no valid oid, we
+ * omit them from normal iteration results).
+ */
+ DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+
+ /*
+ * Only include per-worktree refs in a do_for_each_ref*() iteration.
+ * Normally this will be used with a files ref_store, since that's
+ * where all reference backends will presumably store their
+ * per-worktree refs.
+ */
+ DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+
+ /*
+ * Omit dangling symrefs from output; this only has an effect with
+ * INCLUDE_BROKEN, since they are otherwise not included at all.
+ */
+ DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+
+ /*
+ * Include root refs i.e. HEAD and pseudorefs along with the regular
+ * refs.
+ */
+ DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
+};
+
/*
* The following functions invoke the specified callback function for
* each reference indicated. If the function ever returns a nonzero
@@ -1326,43 +1363,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,
*/
struct ref_iterator;
-/*
- * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
- * which feeds it).
- */
-enum do_for_each_ref_flags {
- /*
- * Include broken references in a do_for_each_ref*() iteration, which
- * would normally be omitted. This includes both refs that point to
- * missing objects (a true repository corruption), ones with illegal
- * names (which we prefer not to expose to callers), as well as
- * dangling symbolic refs (i.e., those that point to a non-existent
- * ref; this is not a corruption, but as they have no valid oid, we
- * omit them from normal iteration results).
- */
- DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
-
- /*
- * Only include per-worktree refs in a do_for_each_ref*() iteration.
- * Normally this will be used with a files ref_store, since that's
- * where all reference backends will presumably store their
- * per-worktree refs.
- */
- DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
-
- /*
- * Omit dangling symrefs from output; this only has an effect with
- * INCLUDE_BROKEN, since they are otherwise not included at all.
- */
- DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
-
- /*
- * Include root refs i.e. HEAD and pseudorefs along with the regular
- * refs.
- */
- DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
-};
-
/*
* Return an iterator that goes over each reference in `refs` for
* which the refname begins with prefix. If trim is non-zero, then
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 04/17] refs: rename `do_for_each_ref_flags`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (2 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 03/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 05/17] refs: rename `each_ref_fn` Patrick Steinhardt
` (13 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
The enum `do_for_each_ref_flags` and its individual values don't match
to our current best practices when it comes to naming things. Rename it
to `refs_for_each_flag`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
ref-filter.c | 2 +-
refs.c | 18 +++++++++---------
refs.h | 12 ++++++------
refs/files-backend.c | 12 ++++++------
refs/packed-backend.c | 8 ++++----
refs/reftable-backend.c | 10 +++++-----
6 files changed, 31 insertions(+), 31 deletions(-)
diff --git a/ref-filter.c b/ref-filter.c
index 3917c4ccd9..4bc54ebd9d 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2810,7 +2810,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
if (filter->kind & FILTER_REFS_ROOT_REFS) {
/* In this case, we want to print all refs including root refs. */
return for_each_fullref_with_seek(filter, cb, cb_data,
- DO_FOR_EACH_INCLUDE_ROOT_REFS);
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS);
}
if (!filter->match_as_path) {
diff --git a/refs.c b/refs.c
index 466398494f..52a680797a 100644
--- a/refs.c
+++ b/refs.c
@@ -1812,7 +1812,7 @@ struct ref_iterator *refs_ref_iterator_begin(
const char *prefix,
const char **exclude_patterns,
int trim,
- enum do_for_each_ref_flags flags)
+ enum refs_for_each_flag flags)
{
struct ref_iterator *iter;
struct strvec normalized_exclude_patterns = STRVEC_INIT;
@@ -1834,14 +1834,14 @@ struct ref_iterator *refs_ref_iterator_begin(
exclude_patterns = normalized_exclude_patterns.v;
}
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) {
static int ref_paranoia = -1;
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
if (ref_paranoia) {
- flags |= DO_FOR_EACH_INCLUDE_BROKEN;
- flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+ flags |= REFS_FOR_EACH_INCLUDE_BROKEN;
+ flags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS;
}
}
@@ -1861,7 +1861,7 @@ struct ref_iterator *refs_ref_iterator_begin(
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
each_ref_fn fn, int trim,
- enum do_for_each_ref_flags flags, void *cb_data)
+ enum refs_for_each_flag flags, void *cb_data)
{
struct ref_iterator *iter;
@@ -1897,7 +1897,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
strlen(git_replace_ref_base),
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int refs_for_each_namespaced_ref(struct ref_store *refs,
@@ -1929,7 +1929,7 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, 0,
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
static int qsort_strcmp(const void *va, const void *vb)
@@ -2741,7 +2741,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
if (!iter)
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
else if (ref_iterator_seek(iter, dirname.buf,
REF_ITERATOR_SEEK_SET_PREFIX) < 0)
goto cleanup;
@@ -3281,7 +3281,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* ensure that there are no concurrent writes.
*/
ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
- DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
&data);
if (ret < 0)
goto done;
diff --git a/refs.h b/refs.h
index 6fd7a706b5..2ae4a6e75b 100644
--- a/refs.h
+++ b/refs.h
@@ -406,7 +406,7 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);
* These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
* which feeds it).
*/
-enum do_for_each_ref_flags {
+enum refs_for_each_flag {
/*
* Include broken references in a do_for_each_ref*() iteration, which
* would normally be omitted. This includes both refs that point to
@@ -416,7 +416,7 @@ enum do_for_each_ref_flags {
* ref; this is not a corruption, but as they have no valid oid, we
* omit them from normal iteration results).
*/
- DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+ REFS_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
/*
* Only include per-worktree refs in a do_for_each_ref*() iteration.
@@ -424,19 +424,19 @@ enum do_for_each_ref_flags {
* where all reference backends will presumably store their
* per-worktree refs.
*/
- DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+ REFS_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
/*
* Omit dangling symrefs from output; this only has an effect with
* INCLUDE_BROKEN, since they are otherwise not included at all.
*/
- DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+ REFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
/*
* Include root refs i.e. HEAD and pseudorefs along with the regular
* refs.
*/
- DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
+ REFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),
};
/*
@@ -1372,7 +1372,7 @@ struct ref_iterator;
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
const char *prefix, const char **exclude_patterns,
- int trim, enum do_for_each_ref_flags flags);
+ int trim, enum refs_for_each_flag flags);
/*
* Advance the iterator to the first or next item and return ITER_OK.
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b1b13b41f6..6c98e14414 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -439,7 +439,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,
dir = get_ref_dir(refs->loose->root);
- if (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS)
+ if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS)
add_root_refs(refs, dir);
/*
@@ -955,17 +955,17 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
int ok;
while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,
NULL) != REF_WORKTREE_CURRENT)
continue;
- if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
+ if ((iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
(iter->iter0->ref.flags & REF_ISSYMREF) &&
(iter->iter0->ref.flags & REF_ISBROKEN))
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->iter0->ref.name,
iter->repo,
iter->iter0->ref.oid,
@@ -1012,7 +1012,7 @@ static struct ref_iterator *files_ref_iterator_begin(
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
@@ -1050,7 +1050,7 @@ static struct ref_iterator *files_ref_iterator_begin(
*/
packed_iter = refs_ref_iterator_begin(
refs->packed_ref_store, prefix, exclude_patterns, 0,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 59b3ecb9d6..5ef4ae32b8 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -982,11 +982,11 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
const char *refname = iter->base.ref.name;
const char *prefix = iter->prefix;
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
!is_per_worktree_ref(iter->base.ref.name))
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->base.ref.name, iter->repo,
&iter->oid, iter->flags))
continue;
@@ -1159,7 +1159,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin");
@@ -1401,7 +1401,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
* of updates is exhausted, leave i set to updates->nr.
*/
iter = packed_ref_iterator_begin(&refs->base, "", NULL,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
ref_iterator_free(iter);
iter = NULL;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 5611808ad7..34bc074dd3 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -662,7 +662,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
* the root refs are to be included. We emulate the same behaviour here.
*/
if (!starts_with(iter->ref.refname, "refs/") &&
- !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&
+ !(iter->flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS &&
is_root_ref(iter->ref.refname))) {
continue;
}
@@ -676,7 +676,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
if (iter->exclude_patterns && should_exclude_current_ref(iter))
continue;
- if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&
parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=
REF_WORKTREE_CURRENT)
continue;
@@ -714,12 +714,12 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
flags |= REF_BAD_NAME | REF_ISBROKEN;
}
- if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS &&
+ if (iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS &&
flags & REF_ISSYMREF &&
flags & REF_ISBROKEN)
continue;
- if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->ref.refname, refs->base.repo,
&iter->oid, flags))
continue;
@@ -871,7 +871,7 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
struct reftable_ref_store *refs;
unsigned int required_flags = REF_STORE_READ;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))
required_flags |= REF_STORE_ODB;
refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin");
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 05/17] refs: rename `each_ref_fn`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (3 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 04/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
` (12 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Similar to the preceding commit, rename `each_ref_fn` to better match
our current best practices around how we name things.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
pack-bitmap.c | 2 +-
pack-bitmap.h | 2 +-
ref-filter.c | 6 +++---
refs.c | 34 +++++++++++++++++-----------------
refs.h | 38 +++++++++++++++++++-------------------
refs/iterator.c | 2 +-
revision.c | 8 ++++----
submodule.c | 2 +-
upload-pack.c | 2 +-
worktree.c | 2 +-
worktree.h | 2 +-
11 files changed, 50 insertions(+), 50 deletions(-)
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 1c93871484..efef7081e6 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -3324,7 +3324,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)
}
void for_each_preferred_bitmap_tip(struct repository *repo,
- each_ref_fn cb, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
struct string_list_item *item;
const struct string_list *preferred_tips;
diff --git a/pack-bitmap.h b/pack-bitmap.h
index d0611d0481..a95e1c2d11 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -105,7 +105,7 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
* "pack.preferBitmapTips" and invoke the callback on each function.
*/
void for_each_preferred_bitmap_tip(struct repository *repo,
- each_ref_fn cb, void *cb_data);
+ refs_for_each_cb cb, void *cb_data);
#define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL"
diff --git a/ref-filter.c b/ref-filter.c
index 4bc54ebd9d..049e845a19 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2781,7 +2781,7 @@ static int start_ref_iterator_after(struct ref_iterator *iter, const char *marke
return ret;
}
-static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
+static int for_each_fullref_with_seek(struct ref_filter *filter, refs_for_each_cb cb,
void *cb_data, unsigned int flags)
{
struct ref_iterator *iter;
@@ -2804,7 +2804,7 @@ static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,
* pattern match, so the callback still has to match each ref individually.
*/
static int for_each_fullref_in_pattern(struct ref_filter *filter,
- each_ref_fn cb,
+ refs_for_each_cb cb,
void *cb_data)
{
if (filter->kind & FILTER_REFS_ROOT_REFS) {
@@ -3303,7 +3303,7 @@ void filter_is_base(struct repository *r,
free(bases);
}
-static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
+static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for_each_cb fn, void *cb_data)
{
const char *prefix = NULL;
int ret = 0;
diff --git a/refs.c b/refs.c
index 52a680797a..a45cc61211 100644
--- a/refs.c
+++ b/refs.c
@@ -445,7 +445,7 @@ char *refs_resolve_refdup(struct ref_store *refs,
struct for_each_ref_filter {
const char *pattern;
const char *prefix;
- each_ref_fn *fn;
+ refs_for_each_cb *fn;
void *cb_data;
};
@@ -527,22 +527,22 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
-int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
}
-int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
}
-int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
}
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
int ret = 0;
@@ -590,7 +590,7 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, const char *prefix, void *cb_data)
{
struct strbuf real_pattern = STRBUF_INIT;
@@ -620,7 +620,7 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
return ret;
}
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data)
{
return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
@@ -1788,7 +1788,7 @@ const char *find_descendant_ref(const char *dirname,
return NULL;
}
-int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct object_id oid;
int flag;
@@ -1860,7 +1860,7 @@ struct ref_iterator *refs_ref_iterator_begin(
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, int trim,
+ refs_for_each_cb fn, int trim,
enum refs_for_each_flag flags, void *cb_data)
{
struct ref_iterator *iter;
@@ -1874,25 +1874,25 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
return do_for_each_ref_iterator(iter, fn, cb_data);
}
-int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
}
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
}
-int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
@@ -1902,7 +1902,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct strbuf prefix = STRBUF_INIT;
@@ -1920,13 +1920,13 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return ret;
}
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_rawref_in(refs, "", fn, cb_data);
}
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, 0,
REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
@@ -1994,7 +1994,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
const char *namespace,
const char **patterns,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct string_list prefixes = STRING_LIST_INIT_DUP;
diff --git a/refs.h b/refs.h
index 2ae4a6e75b..5190e98b2c 100644
--- a/refs.h
+++ b/refs.h
@@ -170,7 +170,7 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err);
*
* peel_object(r, oid, &peeled);
*
- * with the "oid" value given to the each_ref_fn callback, except
+ * with the "oid" value given to the refs_for_each_cb callback, except
* that some ref storage may be able to answer the query without
* actually loading the object in memory.
*/
@@ -329,7 +329,7 @@ int check_tag_ref(struct strbuf *sb, const char *name);
struct ref_transaction;
/*
- * Bit values set in the flags argument passed to each_ref_fn() and
+ * Bit values set in the flags argument passed to refs_for_each_cb() and
* stored in ref_iterator::flags. Other bits are for internal use
* only:
*/
@@ -400,7 +400,7 @@ int reference_get_peeled_oid(struct repository *repo,
* argument is only guaranteed to be valid for the duration of a
* single callback invocation.
*/
-typedef int each_ref_fn(const struct reference *ref, void *cb_data);
+typedef int refs_for_each_cb(const struct reference *ref, void *cb_data);
/*
* These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
@@ -449,22 +449,22 @@ enum refs_for_each_flag {
* stop the iteration. Returned references are sorted.
*/
int refs_head_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_head_ref_namespaced(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_branch_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_remote_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
int refs_for_each_replace_ref(struct ref_store *refs,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/*
* references matching any pattern in "exclude_patterns" are omitted from the
@@ -472,7 +472,7 @@ int refs_for_each_replace_ref(struct ref_store *refs,
*/
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/**
* iterate all refs in "patterns" by partitioning patterns into disjoint sets
@@ -487,13 +487,13 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
const char *namespace,
const char **patterns,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/* iterates all refs that match the specified glob pattern. */
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data);
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, const char *prefix, void *cb_data);
/*
@@ -502,12 +502,12 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
*/
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
/*
* Normalizes partial refs to their fully qualified form.
@@ -1421,6 +1421,6 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);
* iterator style.
*/
int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data);
+ refs_for_each_cb fn, void *cb_data);
#endif /* REFS_H */
diff --git a/refs/iterator.c b/refs/iterator.c
index d79aa5ec82..d5cacde51b 100644
--- a/refs/iterator.c
+++ b/refs/iterator.c
@@ -423,7 +423,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
}
int do_for_each_ref_iterator(struct ref_iterator *iter,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
int retval = 0, ok;
diff --git a/revision.c b/revision.c
index 29972c3a19..8c206830d5 100644
--- a/revision.c
+++ b/revision.c
@@ -1646,7 +1646,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
static void handle_refs(struct ref_store *refs,
struct rev_info *revs, unsigned flags,
- int (*for_each)(struct ref_store *, each_ref_fn, void *))
+ int (*for_each)(struct ref_store *, refs_for_each_cb, void *))
{
struct all_refs_cb cb;
@@ -2728,7 +2728,7 @@ void revision_opts_finish(struct rev_info *revs)
}
}
-static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
+static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data, const char *term)
{
struct strbuf bisect_refs = STRBUF_INIT;
@@ -2739,12 +2739,12 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
return status;
}
-static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_bad_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return for_each_bisect_ref(refs, fn, cb_data, term_bad);
}
-static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+static int for_each_good_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
return for_each_bisect_ref(refs, fn, cb_data, term_good);
}
diff --git a/submodule.c b/submodule.c
index 508938e4da..4f9aaa2c75 100644
--- a/submodule.c
+++ b/submodule.c
@@ -101,7 +101,7 @@ int is_staging_gitmodules_ok(struct index_state *istate)
}
static int for_each_remote_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data)
+ refs_for_each_cb fn, void *cb_data)
{
return refs_for_each_remote_ref(repo_get_submodule_ref_store(the_repository,
submodule),
diff --git a/upload-pack.c b/upload-pack.c
index 2d2b70cbf2..7fe397b0d0 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -607,7 +607,7 @@ static int allow_hidden_refs(enum allow_uor allow_uor)
return !(allow_uor & (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));
}
-static void for_each_namespaced_ref_1(each_ref_fn fn,
+static void for_each_namespaced_ref_1(refs_for_each_cb fn,
struct upload_pack_data *data)
{
const char **excludes = NULL;
diff --git a/worktree.c b/worktree.c
index 9308389cb6..bf8c54c04d 100644
--- a/worktree.c
+++ b/worktree.c
@@ -575,7 +575,7 @@ void strbuf_worktree_ref(const struct worktree *wt,
strbuf_addstr(sb, refname);
}
-int other_head_refs(each_ref_fn fn, void *cb_data)
+int other_head_refs(refs_for_each_cb fn, void *cb_data)
{
struct worktree **worktrees, **p;
struct strbuf refname = STRBUF_INIT;
diff --git a/worktree.h b/worktree.h
index e4bcccdc0a..12484a91a7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -191,7 +191,7 @@ int is_shared_symref(const struct worktree *wt,
* Similar to head_ref() for all HEADs _except_ one from the current
* worktree, which is covered by head_ref().
*/
-int other_head_refs(each_ref_fn fn, void *cb_data);
+int other_head_refs(refs_for_each_cb fn, void *cb_data);
int is_worktree_being_rebased(const struct worktree *wt, const char *target);
int is_worktree_being_bisected(const struct worktree *wt, const char *target);
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 06/17] refs: introduce `refs_for_each_ref_ext`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (4 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 05/17] refs: rename `each_ref_fn` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
` (11 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
In the refs subsystem we have a proliferation of functions that all
iterate through references. (Almost) all of these functions internally
call `do_for_each_ref()` and provide slightly different arguments so
that one can control different aspects of its behaviour. This approach
doesn't really scale: every time there is a slightly different use case
for iterating through refs we create another new function.
This combinatorial explosion doesn't make a lot of sense: it leads to
confusing interfaces and heightens the maintenance burden.
Refactor the code to become more composable by:
- Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.
- Introducing an options structure that lets the caller control
individual options.
This gives us a much better foundation to build on going forward.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 78 ++++++++++++++++++++++++++++++++++++++++--------------------------
refs.h | 29 +++++++++++++++++++++++++
2 files changed, 77 insertions(+), 30 deletions(-)
diff --git a/refs.c b/refs.c
index a45cc61211..ec9e466381 100644
--- a/refs.c
+++ b/refs.c
@@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(
return iter;
}
-static int do_for_each_ref(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb fn, int trim,
- enum refs_for_each_flag flags, void *cb_data)
+int refs_for_each_ref_ext(struct ref_store *refs,
+ refs_for_each_cb cb, void *cb_data,
+ const struct refs_for_each_ref_options *opts)
{
struct ref_iterator *iter;
if (!refs)
return 0;
- iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
- flags);
+ iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
+ opts->exclude_patterns,
+ opts->trim_prefix, opts->flags);
- return do_for_each_ref_iterator(iter, fn, cb_data);
+ return do_for_each_ref_iterator(iter, cb, cb_data);
}
-int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
+ struct refs_for_each_ref_options opts = { 0 };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .exclude_patterns = exclude_patterns,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
- return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
- strlen(git_replace_ref_base),
- REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = git_replace_ref_base,
+ .trim_prefix = strlen(git_replace_ref_base),
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct strbuf prefix = STRBUF_INIT;
int ret;
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- get_git_namespace(),
- &namespaced_exclude_patterns);
-
+ opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
+ get_git_namespace(),
+ &namespaced_exclude_patterns);
strbuf_addf(&prefix, "%srefs/", get_git_namespace());
- ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
+ opts.prefix = prefix.buf;
+
+ ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
strvec_clear(&namespaced_exclude_patterns);
strbuf_release(&prefix);
@@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d
}
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data)
+ refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, NULL, fn, 0,
- REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
static int qsort_strcmp(const void *va, const void *vb)
@@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
struct strbuf *errbuf)
{
struct ref_store *old_refs = NULL, *new_refs = NULL;
+ struct refs_for_each_ref_options for_each_ref_opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
struct ref_transaction *transaction = NULL;
struct strbuf new_gitdir = STRBUF_INIT;
struct migration_data data = {
@@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
data.errbuf = errbuf;
/*
- * We need to use the internal `do_for_each_ref()` here so that we can
+ * We need to use `refs_for_each_ref_ext()` here so that we can
* also include broken refs and symrefs. These would otherwise be
* skipped silently.
*
@@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* allow for a central lock due to its design. It's thus on the user to
* ensure that there are no concurrent writes.
*/
- ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
- REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
- &data);
+ ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
if (ret < 0)
goto done;
diff --git a/refs.h b/refs.h
index 5190e98b2c..bb9c64a51c 100644
--- a/refs.h
+++ b/refs.h
@@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,
int refs_head_ref_namespaced(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
+
+struct refs_for_each_ref_options {
+ /* Only iterate over references that have this given prefix. */
+ const char *prefix;
+
+ /*
+ * Exclude any references that match any of these patterns on a
+ * best-effort basis. The caller needs to be prepared for the exclude
+ * patterns to be ignored.
+ *
+ * The array must be terminated with a NULL sentinel value.
+ */
+ const char **exclude_patterns;
+
+ /*
+ * The number of bytes to trim from the refname. Note that the trimmed
+ * bytes must not cause the reference to become empty. As such, this
+ * field should typically only be set when one uses a `prefix` ending
+ * in a slash.
+ */
+ size_t trim_prefix;
+
+ /* Flags that change which refs will be included. */
+ enum refs_for_each_flag flags;
+};
+
int refs_for_each_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
+int refs_for_each_ref_ext(struct ref_store *refs,
+ refs_for_each_cb cb, void *cb_data,
+ const struct refs_for_each_ref_options *opts);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 07/17] refs: speed up `refs_for_each_glob_ref_in()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (5 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
` (10 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
The function `refs_for_each_glob_ref_in()` can be used to iterate
through all refs in a specific prefix with globbing. The logic to handle
this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up
a callback function that knows to filter out refs that _don't_ match the
given globbing pattern.
The way we do this is somewhat inefficient though: even though the
function is expected to only yield refs in the given prefix, we still
end up iterating through _all_ references, regardless of whether or not
their name matches the given prefix.
Extend `refs_for_each_ref_ext()` so that it can handle patterns and
adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to
use the same callback-based infrastructure to filter individual refs via
the globbing pattern, but we can now also use the other functionality of
the `_ext()` variant.
Most importantly, this means that we now properly handle the prefix.
This results in a performance improvement when using a prefix where a
significant majority of refs exists outside of the prefix. The following
benchmark is an extreme case, with 1 million refs that exist outside the
prefix and a single ref that exists inside it:
Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)
Time (mean ± σ): 115.9 ms ± 0.7 ms [User: 113.0 ms, System: 2.4 ms]
Range (min … max): 114.9 ms … 117.8 ms 25 runs
Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)
Time (mean ± σ): 1.1 ms ± 0.1 ms [User: 0.3 ms, System: 0.7 ms]
Range (min … max): 1.0 ms … 2.3 ms 2092 runs
Summary
git rev-parse --branches=refs/heads/* (rev = HEAD) ran
107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 87 +++++++++++++++++++++++++++++++++++++++++-------------------------
refs.h | 10 ++++++++
2 files changed, 64 insertions(+), 33 deletions(-)
diff --git a/refs.c b/refs.c
index ec9e466381..e4402d787f 100644
--- a/refs.c
+++ b/refs.c
@@ -444,7 +444,7 @@ char *refs_resolve_refdup(struct ref_store *refs,
/* The argument to for_each_filter_refs */
struct for_each_ref_filter {
const char *pattern;
- const char *prefix;
+ size_t trim_prefix;
refs_for_each_cb *fn;
void *cb_data;
};
@@ -475,9 +475,11 @@ static int for_each_filter_refs(const struct reference *ref, void *data)
if (wildmatch(filter->pattern, ref->name, 0))
return 0;
- if (filter->prefix) {
+ if (filter->trim_prefix) {
struct reference skipped = *ref;
- skip_prefix(skipped.name, filter->prefix, &skipped.name);
+ if (strlen(skipped.name) <= filter->trim_prefix)
+ BUG("attempt to trim too many characters");
+ skipped.name += filter->trim_prefix;
return filter->fn(&skipped, filter->cb_data);
} else {
return filter->fn(ref, filter->cb_data);
@@ -590,40 +592,24 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, const char *prefix, void *cb_data)
{
- struct strbuf real_pattern = STRBUF_INIT;
- struct for_each_ref_filter filter;
- int ret;
-
- if (!prefix && !starts_with(pattern, "refs/"))
- strbuf_addstr(&real_pattern, "refs/");
- else if (prefix)
- strbuf_addstr(&real_pattern, prefix);
- strbuf_addstr(&real_pattern, pattern);
-
- if (!has_glob_specials(pattern)) {
- /* Append implied '/' '*' if not present. */
- strbuf_complete(&real_pattern, '/');
- /* No need to check for '*', there is none. */
- strbuf_addch(&real_pattern, '*');
- }
-
- filter.pattern = real_pattern.buf;
- filter.prefix = prefix;
- filter.fn = fn;
- filter.cb_data = cb_data;
- ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
-
- strbuf_release(&real_pattern);
- return ret;
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
+ .trim_prefix = prefix ? strlen(prefix) : 0,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, void *cb_data)
{
- return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
const char *prettify_refname(const char *name)
@@ -1862,16 +1848,51 @@ int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts)
{
+ struct strbuf real_pattern = STRBUF_INIT;
+ struct for_each_ref_filter filter;
struct ref_iterator *iter;
+ size_t trim_prefix = opts->trim_prefix;
+ int ret;
if (!refs)
return 0;
+ if (opts->pattern) {
+ if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
+ strbuf_addstr(&real_pattern, "refs/");
+ else if (opts->prefix)
+ strbuf_addstr(&real_pattern, opts->prefix);
+ strbuf_addstr(&real_pattern, opts->pattern);
+
+ if (!has_glob_specials(opts->pattern)) {
+ /* Append implied '/' '*' if not present. */
+ strbuf_complete(&real_pattern, '/');
+ /* No need to check for '*', there is none. */
+ strbuf_addch(&real_pattern, '*');
+ }
+
+ filter.pattern = real_pattern.buf;
+ filter.trim_prefix = opts->trim_prefix;
+ filter.fn = cb;
+ filter.cb_data = cb_data;
+
+ /*
+ * We need to trim the prefix in the callback function as the
+ * pattern is expected to match on the full refname.
+ */
+ trim_prefix = 0;
+
+ cb = for_each_filter_refs;
+ cb_data = &filter;
+ }
+
iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
opts->exclude_patterns,
- opts->trim_prefix, opts->flags);
+ trim_prefix, opts->flags);
- return do_for_each_ref_iterator(iter, cb, cb_data);
+ ret = do_for_each_ref_iterator(iter, cb, cb_data);
+ strbuf_release(&real_pattern);
+ return ret;
}
int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
diff --git a/refs.h b/refs.h
index bb9c64a51c..a66dbf3865 100644
--- a/refs.h
+++ b/refs.h
@@ -458,6 +458,16 @@ struct refs_for_each_ref_options {
/* Only iterate over references that have this given prefix. */
const char *prefix;
+ /*
+ * A globbing pattern that can be used to only yield refs that match.
+ * If given, refs will be matched against the pattern with
+ * `wildmatch()`.
+ *
+ * If the pattern doesn't contain any globbing characters then it is
+ * treated as if it was ending with "/" and "*".
+ */
+ const char *pattern;
+
/*
* Exclude any references that match any of these patterns on a
* best-effort basis. The caller needs to be prepared for the exclude
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 08/17] refs: generalize `refs_for_each_namespaced_ref()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (6 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
` (9 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
The function `refs_for_each_namespaced_ref()` iterates through all
references that are part of the current ref namespace. This namespace
can be configured by setting the `GIT_NAMESPACE` environment variable
and is then retrieved by calling `get_git_namespace()`.
If a namespace is configured, then we:
- Obviously only yield refs that exist in this namespace.
- Rewrite exclude patterns so that they work for the given namespace,
if any namespace is currently configured.
Port this logic to `refs_for_each_ref_ext()` by adding a new `namespace`
field to the options structure. This gives callers more flexibility as
they can decide by themselves whether they want to use the globally
configured or an arbitrary other namespace.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 47 +++++++++++++++++++++++++++++------------------
refs.h | 7 +++++++
2 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index e4402d787f..0d0f0edbfb 100644
--- a/refs.c
+++ b/refs.c
@@ -1848,10 +1848,14 @@ int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts)
{
+ struct strvec namespaced_exclude_patterns = STRVEC_INIT;
+ struct strbuf namespaced_prefix = STRBUF_INIT;
struct strbuf real_pattern = STRBUF_INIT;
struct for_each_ref_filter filter;
struct ref_iterator *iter;
size_t trim_prefix = opts->trim_prefix;
+ const char **exclude_patterns;
+ const char *prefix;
int ret;
if (!refs)
@@ -1886,11 +1890,29 @@ int refs_for_each_ref_ext(struct ref_store *refs,
cb_data = &filter;
}
- iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
- opts->exclude_patterns,
+ if (opts->namespace) {
+ strbuf_addstr(&namespaced_prefix, opts->namespace);
+ if (opts->prefix)
+ strbuf_addstr(&namespaced_prefix, opts->prefix);
+ else
+ strbuf_addstr(&namespaced_prefix, "refs/");
+
+ prefix = namespaced_prefix.buf;
+ exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,
+ opts->namespace,
+ &namespaced_exclude_patterns);
+ } else {
+ prefix = opts->prefix ? opts->prefix : "";
+ exclude_patterns = opts->exclude_patterns;
+ }
+
+ iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
trim_prefix, opts->flags);
ret = do_for_each_ref_iterator(iter, cb, cb_data);
+
+ strvec_clear(&namespaced_exclude_patterns);
+ strbuf_release(&namespaced_prefix);
strbuf_release(&real_pattern);
return ret;
}
@@ -1937,22 +1959,11 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
refs_for_each_cb cb, void *cb_data)
{
- struct refs_for_each_ref_options opts = { 0 };
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct strbuf prefix = STRBUF_INIT;
- int ret;
-
- opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- get_git_namespace(),
- &namespaced_exclude_patterns);
- strbuf_addf(&prefix, "%srefs/", get_git_namespace());
- opts.prefix = prefix.buf;
-
- ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-
- strvec_clear(&namespaced_exclude_patterns);
- strbuf_release(&prefix);
- return ret;
+ struct refs_for_each_ref_options opts = {
+ .exclude_patterns = exclude_patterns,
+ .namespace = get_git_namespace(),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
diff --git a/refs.h b/refs.h
index a66dbf3865..5a5fb4e1e4 100644
--- a/refs.h
+++ b/refs.h
@@ -468,6 +468,13 @@ struct refs_for_each_ref_options {
*/
const char *pattern;
+ /*
+ * If set, only yield refs part of the configured namespace. Exclude
+ * patterns will be rewritten to apply to the namespace, and the prefix
+ * will be considered relative to the namespace.
+ */
+ const char *namespace;
+
/*
* Exclude any references that match any of these patterns on a
* best-effort basis. The caller needs to be prepared for the exclude
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (7 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
` (8 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
The function `refs_for_each_fullref_in_prefixes()` can be used to
iterate over all references part of any of the user-provided prefixes.
In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it
knows to handle the case well where multiple of the passed-in prefixes
start with a common prefix by computing longest common prefixes and then
iterating over those.
While we could move this logic into `refs_for_each_ref_ext()`, this one
feels somewhat special as we perform multiple iterations. But what we
_can_ do is to generalize how this function works: instead of accepting
only a small handful of parameters, we can have it accept the full
options structure.
One obvious exception is that the caller must not provide a prefix via
the options. But this case can be easily detected.
Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
ls-refs.c | 11 +++++++----
ref-filter.c | 11 +++++++----
refs.c | 39 +++++++++++++++------------------------
refs.h | 16 +++++-----------
4 files changed, 34 insertions(+), 43 deletions(-)
diff --git a/ls-refs.c b/ls-refs.c
index 8641281b86..9759826ca7 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,
int ls_refs(struct repository *r, struct packet_reader *request)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct ls_refs_data data;
memset(&data, 0, sizeof(data));
@@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)
send_possibly_unborn_head(&data);
if (!data.prefixes.nr)
strvec_push(&data.prefixes, "");
- refs_for_each_fullref_in_prefixes(get_main_ref_store(r),
- get_git_namespace(), data.prefixes.v,
- hidden_refs_to_excludes(&data.hidden_refs),
- send_ref, &data);
+
+ opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);
+ opts.namespace = get_git_namespace();
+
+ refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,
+ &opts, send_ref, &data);
packet_fflush(stdout);
strvec_clear(&data.prefixes);
strbuf_release(&data.buf);
diff --git a/ref-filter.c b/ref-filter.c
index 049e845a19..7c682e0a33 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
refs_for_each_cb cb,
void *cb_data)
{
+ struct refs_for_each_ref_options opts = {
+ .exclude_patterns = filter->exclude.v,
+ };
+
if (filter->kind & FILTER_REFS_ROOT_REFS) {
/* In this case, we want to print all refs including root refs. */
return for_each_fullref_with_seek(filter, cb, cb_data,
@@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
return for_each_fullref_with_seek(filter, cb, cb_data, 0);
}
- return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),
- NULL, filter->name_patterns,
- filter->exclude.v,
- cb, cb_data);
+ return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),
+ filter->name_patterns, &opts,
+ cb, cb_data);
}
/*
diff --git a/refs.c b/refs.c
index 0d0f0edbfb..0aa3b68dd9 100644
--- a/refs.c
+++ b/refs.c
@@ -2039,40 +2039,31 @@ static void find_longest_prefixes(struct string_list *out,
strbuf_release(&prefix);
}
-int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
- const char *namespace,
- const char **patterns,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data)
+int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
+ const char **prefixes,
+ const struct refs_for_each_ref_options *opts,
+ refs_for_each_cb cb, void *cb_data)
{
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct string_list prefixes = STRING_LIST_INIT_DUP;
+ struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
- struct strbuf buf = STRBUF_INIT;
- int ret = 0, namespace_len;
+ int ret = 0;
- find_longest_prefixes(&prefixes, patterns);
+ if (opts->prefix)
+ BUG("refs_for_each_ref_in_prefixes called with specific prefix");
- if (namespace)
- strbuf_addstr(&buf, namespace);
- namespace_len = buf.len;
+ find_longest_prefixes(&longest_prefixes, prefixes);
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- namespace,
- &namespaced_exclude_patterns);
+ for_each_string_list_item(prefix, &longest_prefixes) {
+ struct refs_for_each_ref_options prefix_opts = *opts;
+ prefix_opts.prefix = prefix->string;
- for_each_string_list_item(prefix, &prefixes) {
- strbuf_addstr(&buf, prefix->string);
- ret = refs_for_each_fullref_in(ref_store, buf.buf,
- exclude_patterns, fn, cb_data);
+ ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
+ &prefix_opts);
if (ret)
break;
- strbuf_setlen(&buf, namespace_len);
}
- strvec_clear(&namespaced_exclude_patterns);
- string_list_clear(&prefixes, 0);
- strbuf_release(&buf);
+ string_list_clear(&longest_prefixes, 0);
return ret;
}
diff --git a/refs.h b/refs.h
index 5a5fb4e1e4..faed63aa81 100644
--- a/refs.h
+++ b/refs.h
@@ -521,19 +521,13 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
/**
- * iterate all refs in "patterns" by partitioning patterns into disjoint sets
+ * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
* and iterating the longest-common prefix of each set.
- *
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- *
- * callers should be prepared to ignore references that they did not ask for.
*/
-int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
- const char *namespace,
- const char **patterns,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
+int refs_for_each_ref_in_prefixes(struct ref_store *refs,
+ const char **prefixes,
+ const struct refs_for_each_ref_options *opts,
+ refs_for_each_cb cb, void *cb_data);
/* iterates all refs that match the specified glob pattern. */
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 10/17] refs: improve verification for-each-ref options
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (8 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
` (7 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Improve verification of the passed-in for-each-ref options:
- Require that the `refs` store must be given. It's arguably very
surprising that we simply return successfully in case the ref store
is a `NULL` pointer.
- When expected to trim ref prefixes we will `BUG()` in case the
refname would become empty or in case we're expected to trim a
longer prefix than the refname is long. As such, this case is only
guaranteed to _not_ `BUG()` in case the caller also specified a
prefix. And furthermore, that prefix must end in a trailing slash,
as otherwise it may produce an exact match that could lead us to
trim to the empty string.
An audit shows that there are no callsites that rely on either of these
behaviours, so this should not result in a functional change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/refs.c b/refs.c
index 0aa3b68dd9..a57eafd6de 100644
--- a/refs.c
+++ b/refs.c
@@ -1859,7 +1859,18 @@ int refs_for_each_ref_ext(struct ref_store *refs,
int ret;
if (!refs)
- return 0;
+ BUG("no ref store passed");
+
+ if (opts->trim_prefix) {
+ size_t prefix_len;
+
+ if (!opts->prefix)
+ BUG("trimming only allowed with a prefix");
+
+ prefix_len = strlen(opts->prefix);
+ if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')
+ BUG("ref pattern must end in a trailing slash when trimming");
+ }
if (opts->pattern) {
if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 11/17] refs: replace `refs_for_each_ref_in()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (9 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
` (6 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
bisect.c | 8 ++++++--
builtin/rev-parse.c | 13 +++++++++----
pack-bitmap.c | 13 +++++++------
refs.c | 34 ++++++++++++++++++----------------
refs.h | 2 --
t/helper/test-ref-store.c | 7 +++++--
6 files changed, 45 insertions(+), 32 deletions(-)
diff --git a/bisect.c b/bisect.c
index 2bdad4ee42..296836c154 100644
--- a/bisect.c
+++ b/bisect.c
@@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED)
static int read_bisect_refs(void)
{
- return refs_for_each_ref_in(get_main_ref_store(the_repository),
- "refs/bisect/", register_ref, NULL);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ .trim_prefix = strlen("refs/bisect/"),
+ };
+ return refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ register_ref, NULL, &opts);
}
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 9032cc6327..02703f2fb8 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
static void handle_ref_opt(const char *pattern, const char *prefix)
{
- if (pattern)
+ if (pattern) {
refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
show_reference, pattern, prefix,
NULL);
- else
- refs_for_each_ref_in(get_main_ref_store(the_repository),
- prefix, show_reference, NULL);
+ } else {
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
+ }
clear_ref_exclusions(&ref_excludes);
}
diff --git a/pack-bitmap.c b/pack-bitmap.c
index efef7081e6..22419bfb33 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -3326,6 +3326,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)
void for_each_preferred_bitmap_tip(struct repository *repo,
refs_for_each_cb cb, void *cb_data)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct string_list_item *item;
const struct string_list *preferred_tips;
struct strbuf buf = STRBUF_INIT;
@@ -3335,16 +3336,16 @@ void for_each_preferred_bitmap_tip(struct repository *repo,
return;
for_each_string_list_item(item, preferred_tips) {
- const char *pattern = item->string;
+ opts.prefix = item->string;
- if (!ends_with(pattern, "/")) {
+ if (!ends_with(opts.prefix, "/")) {
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s/", pattern);
- pattern = buf.buf;
+ strbuf_addf(&buf, "%s/", opts.prefix);
+ opts.prefix = buf.buf;
}
- refs_for_each_ref_in(get_main_ref_store(repo),
- pattern, cb, cb_data);
+ refs_for_each_ref_ext(get_main_ref_store(repo),
+ cb, cb_data, &opts);
}
strbuf_release(&buf);
diff --git a/refs.c b/refs.c
index a57eafd6de..7b1ef769c0 100644
--- a/refs.c
+++ b/refs.c
@@ -529,19 +529,31 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
-int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
+ .trim_prefix = strlen("refs/tags/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
+ .trim_prefix = strlen("refs/heads/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
+ .trim_prefix = strlen("refs/remotes/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
@@ -1934,16 +1946,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .trim_prefix = strlen(prefix),
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
refs_for_each_cb cb, void *cb_data)
diff --git a/refs.h b/refs.h
index faed63aa81..7a3bc9e5b7 100644
--- a/refs.h
+++ b/refs.h
@@ -501,8 +501,6 @@ int refs_for_each_ref(struct ref_store *refs,
int refs_for_each_ref_ext(struct ref_store *refs,
refs_for_each_cb cb, void *cb_data,
const struct refs_for_each_ref_options *opts);
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data);
int refs_for_each_tag_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
int refs_for_each_branch_ref(struct ref_store *refs,
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index b1215947c5..a2ef1b6949 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -163,8 +163,11 @@ static int each_ref(const struct reference *ref, void *cb_data UNUSED)
static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
{
const char *prefix = notnull(*argv++, "prefix");
-
- return refs_for_each_ref_in(refs, prefix, each_ref, NULL);
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .trim_prefix = strlen(prefix),
+ };
+ return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
}
static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 12/17] refs: replace `refs_for_each_rawref()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (10 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
` (5 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_rawref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/describe.c | 7 +++++--
builtin/fsck.c | 7 +++++--
fetch-pack.c | 15 +++++++++++----
refs.c | 10 ++++------
refs.h | 1 -
refs/files-backend.c | 7 +++++--
6 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/builtin/describe.c b/builtin/describe.c
index abfe3525a5..bffeed13a3 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -641,6 +641,9 @@ int cmd_describe(int argc,
const char *prefix,
struct repository *repo UNUSED )
{
+ struct refs_for_each_ref_options for_each_ref_opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
int contains = 0;
struct option options[] = {
OPT_BOOL(0, "contains", &contains, N_("find the tag that comes after the commit")),
@@ -738,8 +741,8 @@ int cmd_describe(int argc,
}
hashmap_init(&names, commit_name_neq, NULL, 0);
- refs_for_each_rawref(get_main_ref_store(the_repository), get_name,
- NULL);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ get_name, NULL, &for_each_ref_opts);
if (!hashmap_get_size(&names) && !always)
die(_("No names found, cannot describe anything."));
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 0512f78a87..24cdb657f5 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -598,6 +598,9 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
struct worktree **worktrees, **p;
const char *head_points_at;
struct object_id head_oid;
@@ -623,8 +626,8 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
return;
}
- refs_for_each_rawref(get_main_ref_store(the_repository),
- snapshot_ref, snap);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ snapshot_ref, snap, &opts);
worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
diff --git a/fetch-pack.c b/fetch-pack.c
index 40316c9a34..570caa03fa 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -292,11 +292,14 @@ static int next_flush(int stateless_rpc, int count)
static void mark_tips(struct fetch_negotiator *negotiator,
const struct oid_array *negotiation_tips)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
int i;
if (!negotiation_tips) {
- refs_for_each_rawref(get_main_ref_store(the_repository),
- rev_list_insert_ref_oid, negotiator);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ rev_list_insert_ref_oid, negotiator, &opts);
return;
}
@@ -792,8 +795,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
*/
trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL);
if (!args->deepen) {
- refs_for_each_rawref(get_main_ref_store(the_repository),
- mark_complete_oid, NULL);
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ mark_complete_oid, NULL, &opts);
for_each_cached_alternate(NULL, mark_alternate_complete);
if (cutoff)
mark_recent_complete_commits(args, cutoff);
diff --git a/refs.c b/refs.c
index 7b1ef769c0..791654a0f6 100644
--- a/refs.c
+++ b/refs.c
@@ -526,7 +526,10 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
.indent = indent,
.dry_run = dry_run,
};
- refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ refs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts);
}
int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
@@ -1979,11 +1982,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
-{
- return refs_for_each_rawref_in(refs, "", fn, cb_data);
-}
-
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb cb, void *cb_data)
{
diff --git a/refs.h b/refs.h
index 7a3bc9e5b7..01dc3c2fd4 100644
--- a/refs.h
+++ b/refs.h
@@ -543,7 +543,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);
int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
refs_for_each_cb fn, void *cb_data);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 6c98e14414..ab96760781 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3149,6 +3149,9 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err)
{
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
@@ -3173,8 +3176,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
* so here we really only check that none of the references
* that we are creating already exists.
*/
- if (refs_for_each_rawref(&refs->base, ref_present,
- &transaction->refnames))
+ if (refs_for_each_ref_ext(&refs->base, ref_present,
+ &transaction->refnames, &opts))
BUG("initial ref transaction called with existing refs");
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 13/17] refs: replace `refs_for_each_rawref_in()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (11 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
` (4 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_rawref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/remote.c | 8 ++++++--
refs.c | 10 ----------
refs.h | 4 ----
3 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index ace390c671..0fddaa1773 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -912,6 +912,9 @@ static int mv(int argc, const char **argv, const char *prefix,
old_remote_context.buf);
if (refspecs_need_update) {
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
0, &err);
if (!rename.transaction)
@@ -923,9 +926,10 @@ static int mv(int argc, const char **argv, const char *prefix,
strbuf_reset(&buf);
strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name);
+ opts.prefix = buf.buf;
- result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf,
- rename_one_ref, &rename);
+ result = refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ rename_one_ref, &rename, &opts);
if (result < 0)
die(_("queueing remote ref renames failed: %s"), rename.err->buf);
diff --git a/refs.c b/refs.c
index 791654a0f6..172d4cf941 100644
--- a/refs.c
+++ b/refs.c
@@ -1982,16 +1982,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index 01dc3c2fd4..673d4ccce5 100644
--- a/refs.h
+++ b/refs.h
@@ -542,10 +542,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
refs_for_each_cb fn, void *cb_data);
-/* can be used to learn about broken ref and symref */
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- refs_for_each_cb fn, void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 14/17] refs: replace `refs_for_each_glob_ref_in()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (12 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
` (3 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_glob_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/bisect.c | 37 +++++++++++++++++++++++++++----------
builtin/rev-parse.c | 10 +++++++---
refs.c | 11 -----------
refs.h | 3 ---
revision.c | 30 +++++++++++++++++++++---------
5 files changed, 55 insertions(+), 36 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 4cc118fb57..4520e585d0 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -422,13 +422,17 @@ static void bisect_status(struct bisect_state *state,
{
char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
char *good_glob = xstrfmt("%s-*", terms->term_good);
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
+ .trim_prefix = strlen("refs/bisect/"),
+ };
if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))
state->nr_bad = 1;
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr,
- good_glob, "refs/bisect/",
- (void *) &state->nr_good);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ inc_nr, &state->nr_good, &opts);
free(good_glob);
free(bad_ref);
@@ -562,6 +566,10 @@ static int add_bisect_ref(const struct reference *ref, void *cb)
static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
{
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ .trim_prefix = strlen("refs/bisect/"),
+ };
int res = 0;
struct add_bisect_ref_data cb = { revs };
char *good = xstrfmt("%s-*", terms->term_good);
@@ -581,11 +589,16 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
reset_revision_walk();
repo_init_revisions(the_repository, revs, NULL);
setup_revisions(0, NULL, revs, NULL);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- add_bisect_ref, bad, "refs/bisect/", &cb);
+
+ opts.pattern = bad;
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_bisect_ref, &cb, &opts);
+
cb.object_flags = UNINTERESTING;
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- add_bisect_ref, good, "refs/bisect/", &cb);
+ opts.pattern = good;
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_bisect_ref, &cb, &opts);
+
if (prepare_revision_walk(revs))
res = error(_("revision walk setup failed"));
@@ -1191,10 +1204,14 @@ static int verify_good(const struct bisect_terms *terms, const char *command)
char *good_glob = xstrfmt("%s-*", terms->term_good);
int no_checkout = refs_ref_exists(get_main_ref_store(the_repository),
"BISECT_HEAD");
+ struct refs_for_each_ref_options opts = {
+ .pattern = good_glob,
+ .prefix = "refs/bisect/",
+ .trim_prefix = strlen("refs/bisect/"),
+ };
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- get_first_good, good_glob, "refs/bisect/",
- &good_rev);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ get_first_good, &good_rev, &opts);
free(good_glob);
if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev))
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 02703f2fb8..61a3f0fdb9 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -614,9 +614,13 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
static void handle_ref_opt(const char *pattern, const char *prefix)
{
if (pattern) {
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- show_reference, pattern, prefix,
- NULL);
+ struct refs_for_each_ref_options opts = {
+ .pattern = pattern,
+ .prefix = prefix,
+ .trim_prefix = prefix ? strlen(prefix) : 0,
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
} else {
struct refs_for_each_ref_options opts = {
.prefix = prefix,
diff --git a/refs.c b/refs.c
index 172d4cf941..b4ef4ffff0 100644
--- a/refs.c
+++ b/refs.c
@@ -607,17 +607,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
- const char *pattern, const char *prefix, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .pattern = pattern,
- .prefix = prefix,
- .trim_prefix = prefix ? strlen(prefix) : 0,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
const char *pattern, void *cb_data)
{
diff --git a/refs.h b/refs.h
index 673d4ccce5..3fa2c11c1f 100644
--- a/refs.h
+++ b/refs.h
@@ -531,9 +531,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
const char *pattern, void *cb_data);
-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
- const char *pattern, const char *prefix, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
diff --git a/revision.c b/revision.c
index 8c206830d5..074a75b859 100644
--- a/revision.c
+++ b/revision.c
@@ -2827,34 +2827,46 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
exclude_hidden_refs(&revs->ref_excludes, optarg);
return argcount;
} else if (skip_prefix(arg, "--branches=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
+ .trim_prefix = strlen("refs/heads/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--branches");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/heads/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--tags=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
+ .trim_prefix = strlen("refs/tags/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--tags");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/tags/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--remotes=", &optarg)) {
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
+ .trim_prefix = strlen("refs/remotes/"),
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
if (revs->ref_excludes.hidden_refs_configured)
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--remotes");
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
- handle_one_ref, optarg,
- "refs/remotes/", &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
} else if (!strcmp(arg, "--reflog")) {
add_reflogs_to_pending(revs, *flags);
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 15/17] refs: replace `refs_for_each_glob_ref()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (13 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
` (2 subsequent siblings)
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_glob_ref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/fetch.c | 7 +++++--
notes.c | 7 +++++--
refs.c | 9 ---------
refs.h | 4 ----
revision.c | 7 +++++--
5 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a3bc7e9380..a3323fbfd7 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1542,6 +1542,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
for (i = 0; i < negotiation_tip.nr; i++) {
const char *s = negotiation_tip.items[i].string;
+ struct refs_for_each_ref_options opts = {
+ .pattern = s,
+ };
int old_nr;
if (!has_glob_specials(s)) {
struct object_id oid;
@@ -1553,8 +1556,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
continue;
}
old_nr = oids->nr;
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- add_oid, s, oids);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ add_oid, oids, &opts);
if (old_nr == oids->nr)
warning("ignoring --negotiation-tip=%s because it does not match any refs",
s);
diff --git a/notes.c b/notes.c
index 090c48bbd5..51a7ef9f83 100644
--- a/notes.c
+++ b/notes.c
@@ -952,8 +952,11 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
{
assert(list->strdup_strings);
if (has_glob_specials(glob)) {
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- string_list_add_one_ref, glob, list);
+ struct refs_for_each_ref_options opts = {
+ .pattern = glob,
+ };
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ string_list_add_one_ref, list, &opts);
} else {
struct object_id oid;
if (repo_get_oid(the_repository, glob, &oid))
diff --git a/refs.c b/refs.c
index b4ef4ffff0..ca7fc7289b 100644
--- a/refs.c
+++ b/refs.c
@@ -607,15 +607,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
- const char *pattern, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .pattern = pattern,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
const char *prettify_refname(const char *name)
{
if (skip_prefix(name, "refs/heads/", &name) ||
diff --git a/refs.h b/refs.h
index 3fa2c11c1f..b63775fa35 100644
--- a/refs.h
+++ b/refs.h
@@ -527,10 +527,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
const struct refs_for_each_ref_options *opts,
refs_for_each_cb cb, void *cb_data);
-/* iterates all refs that match the specified glob pattern. */
-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
- const char *pattern, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
diff --git a/revision.c b/revision.c
index 074a75b859..4ddb3370c6 100644
--- a/revision.c
+++ b/revision.c
@@ -2814,10 +2814,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
clear_ref_exclusions(&revs->ref_excludes);
} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
+ struct refs_for_each_ref_options opts = {
+ .pattern = optarg,
+ };
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags);
- refs_for_each_glob_ref(get_main_ref_store(the_repository),
- handle_one_ref, optarg, &cb);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ handle_one_ref, &cb, &opts);
clear_ref_exclusions(&revs->ref_excludes);
return argcount;
} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 16/17] refs: replace `refs_for_each_namespaced_ref()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (14 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
2026-02-24 13:14 ` [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_namespaced_ref()` with the newly
introduced `refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
http-backend.c | 8 ++++++--
refs.c | 11 -----------
refs.h | 8 --------
upload-pack.c | 11 +++++++----
4 files changed, 13 insertions(+), 25 deletions(-)
diff --git a/http-backend.c b/http-backend.c
index 0122146df6..1a171c5c5a 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -565,9 +565,13 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)
run_service(argv, 0);
} else {
+ struct refs_for_each_ref_options opts = {
+ .namespace = get_git_namespace(),
+ };
+
select_getanyfile(hdr);
- refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
- NULL, show_text_ref, &buf);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_text_ref, &buf, &opts);
send_strbuf(hdr, "text/plain", &buf);
}
strbuf_release(&buf);
diff --git a/refs.c b/refs.c
index ca7fc7289b..35a4925ac4 100644
--- a/refs.c
+++ b/refs.c
@@ -1951,17 +1951,6 @@ int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_namespaced_ref(struct ref_store *refs,
- const char **exclude_patterns,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .exclude_patterns = exclude_patterns,
- .namespace = get_git_namespace(),
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
static int qsort_strcmp(const void *va, const void *vb)
{
const char *a = *(const char **)va;
diff --git a/refs.h b/refs.h
index b63775fa35..1b468c4ffb 100644
--- a/refs.h
+++ b/refs.h
@@ -527,14 +527,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,
const struct refs_for_each_ref_options *opts,
refs_for_each_cb cb, void *cb_data);
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_namespaced_ref(struct ref_store *refs,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
-
/*
* Normalizes partial refs to their fully qualified form.
* Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.
diff --git a/upload-pack.c b/upload-pack.c
index 7fe397b0d0..d21f0577f9 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -610,7 +610,10 @@ static int allow_hidden_refs(enum allow_uor allow_uor)
static void for_each_namespaced_ref_1(refs_for_each_cb fn,
struct upload_pack_data *data)
{
- const char **excludes = NULL;
+ struct refs_for_each_ref_options opts = {
+ .namespace = get_git_namespace(),
+ };
+
/*
* If `data->allow_uor` allows fetching hidden refs, we need to
* mark all references (including hidden ones), to check in
@@ -621,10 +624,10 @@ static void for_each_namespaced_ref_1(refs_for_each_cb fn,
* hidden references.
*/
if (allow_hidden_refs(data->allow_uor))
- excludes = hidden_refs_to_excludes(&data->hidden_refs);
+ opts.exclude_patterns = hidden_refs_to_excludes(&data->hidden_refs);
- refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
- excludes, fn, data);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ fn, data, &opts);
}
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v2 17/17] refs: replace `refs_for_each_fullref_in()`
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (15 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
@ 2026-02-23 11:59 ` Patrick Steinhardt
2026-02-24 13:14 ` [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
17 siblings, 0 replies; 53+ messages in thread
From: Patrick Steinhardt @ 2026-02-23 11:59 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Oswald Buddenhagen
Replace calls to `refs_for_each_fullref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
bisect.c | 8 +++++---
builtin/receive-pack.c | 8 ++++----
builtin/rev-parse.c | 15 +++++++--------
builtin/show-ref.c | 21 +++++++++++++--------
refs.c | 11 -----------
refs.h | 8 --------
revision.c | 4 +++-
t/helper/test-ref-store.c | 8 +++++---
8 files changed, 37 insertions(+), 46 deletions(-)
diff --git a/bisect.c b/bisect.c
index 296836c154..ef17a442e5 100644
--- a/bisect.c
+++ b/bisect.c
@@ -1190,13 +1190,15 @@ static int mark_for_removal(const struct reference *ref, void *cb_data)
int bisect_clean_state(void)
{
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/bisect/",
+ };
int result = 0;
/* There may be some refs packed during bisection */
struct string_list refs_for_removal = STRING_LIST_INIT_DUP;
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/", NULL, mark_for_removal,
- &refs_for_removal);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ mark_for_removal, &refs_for_removal, &opts);
string_list_append(&refs_for_removal, "BISECT_HEAD");
string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV");
result = refs_delete_refs(get_main_ref_store(the_repository),
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 4c0112b4bc..8c5ad5b81e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -343,9 +343,9 @@ static void show_one_alternate_ref(const struct object_id *oid,
static void write_head_info(void)
{
+ struct refs_for_each_ref_options opts = { 0 };
static struct oidset seen = OIDSET_INIT;
struct strvec excludes_vector = STRVEC_INIT;
- const char **exclude_patterns;
/*
* We need access to the reference names both with and without their
@@ -353,12 +353,12 @@ static void write_head_info(void)
* thus have to adapt exclude patterns to carry the namespace prefix
* ourselves.
*/
- exclude_patterns = get_namespaced_exclude_patterns(
+ opts.exclude_patterns = get_namespaced_exclude_patterns(
hidden_refs_to_excludes(&hidden_refs),
get_git_namespace(), &excludes_vector);
- refs_for_each_fullref_in(get_main_ref_store(the_repository), "",
- exclude_patterns, show_ref_cb, &seen);
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref_cb, &seen, &opts);
odb_for_each_alternate_ref(the_repository->objects,
show_one_alternate_ref, &seen);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 61a3f0fdb9..01a62800e8 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -940,14 +940,13 @@ int cmd_rev_parse(int argc,
continue;
}
if (!strcmp(arg, "--bisect")) {
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/bad",
- NULL, show_reference,
- NULL);
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/bisect/good",
- NULL, anti_reference,
- NULL);
+ struct refs_for_each_ref_options opts = { 0 };
+ opts.prefix = "refs/bisect/bad";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_reference, NULL, &opts);
+ opts.prefix = "refs/bisect/good";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ anti_reference, NULL, &opts);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 4d4984e4e0..5d31acea7c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -215,14 +215,19 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
refs_head_ref(get_main_ref_store(the_repository), show_ref,
&show_ref_data);
if (opts->branches_only || opts->tags_only) {
- if (opts->branches_only)
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/heads/", NULL,
- show_ref, &show_ref_data);
- if (opts->tags_only)
- refs_for_each_fullref_in(get_main_ref_store(the_repository),
- "refs/tags/", NULL, show_ref,
- &show_ref_data);
+ struct refs_for_each_ref_options for_each_ref_opts = { 0 };
+
+ if (opts->branches_only) {
+ for_each_ref_opts.prefix = "refs/heads/";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref, &show_ref_data, &for_each_ref_opts);
+ }
+
+ if (opts->tags_only) {
+ for_each_ref_opts.prefix = "refs/tags/";
+ refs_for_each_ref_ext(get_main_ref_store(the_repository),
+ show_ref, &show_ref_data, &for_each_ref_opts);
+ }
} else {
refs_for_each_ref(get_main_ref_store(the_repository),
show_ref, &show_ref_data);
diff --git a/refs.c b/refs.c
index 35a4925ac4..af51a648d5 100644
--- a/refs.c
+++ b/refs.c
@@ -1929,17 +1929,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data
return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb cb, void *cb_data)
-{
- struct refs_for_each_ref_options opts = {
- .prefix = prefix,
- .exclude_patterns = exclude_patterns,
- };
- return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
-}
-
int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
diff --git a/refs.h b/refs.h
index 1b468c4ffb..9b5d57a9b7 100644
--- a/refs.h
+++ b/refs.h
@@ -510,14 +510,6 @@ int refs_for_each_remote_ref(struct ref_store *refs,
int refs_for_each_replace_ref(struct ref_store *refs,
refs_for_each_cb fn, void *cb_data);
-/*
- * references matching any pattern in "exclude_patterns" are omitted from the
- * result set on a best-effort basis.
- */
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- refs_for_each_cb fn, void *cb_data);
-
/**
* Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
* and iterating the longest-common prefix of each set.
diff --git a/revision.c b/revision.c
index 4ddb3370c6..0136ef64f5 100644
--- a/revision.c
+++ b/revision.c
@@ -2731,10 +2731,12 @@ void revision_opts_finish(struct rev_info *revs)
static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,
void *cb_data, const char *term)
{
+ struct refs_for_each_ref_options opts = { 0 };
struct strbuf bisect_refs = STRBUF_INIT;
int status;
strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
- status = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data);
+ opts.prefix = bisect_refs.buf;
+ status = refs_for_each_ref_ext(refs, fn, cb_data, &opts);
strbuf_release(&bisect_refs);
return status;
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a2ef1b6949..74edf2029a 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -173,10 +173,12 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)
{
const char *prefix = notnull(*argv++, "prefix");
- const char **exclude_patterns = argv;
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ .exclude_patterns = argv,
+ };
- return refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref,
- NULL);
+ return refs_for_each_ref_ext(refs, each_ref, NULL, &opts);
}
static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
--
2.53.0.536.g309c995771.dirty
^ permalink raw reply related [flat|nested] 53+ messages in thread
* Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`
2026-02-23 10:48 ` Patrick Steinhardt
@ 2026-02-23 13:35 ` Karthik Nayak
0 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-23 13:35 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 1261 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Feb 23, 2026 at 04:11:52AM -0500, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> > diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
>> > index 9032cc6327..02703f2fb8 100644
>> > --- a/builtin/rev-parse.c
>> > +++ b/builtin/rev-parse.c
>> > @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
>> >
>> > static void handle_ref_opt(const char *pattern, const char *prefix)
>> > {
>> > - if (pattern)
>> > + if (pattern) {
>> > refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
>> > show_reference, pattern, prefix,
>> > NULL);
>> > - else
>> > - refs_for_each_ref_in(get_main_ref_store(the_repository),
>> > - prefix, show_reference, NULL);
>> > + } else {
>> > + struct refs_for_each_ref_options opts = {
>> > + .prefix = prefix,
>> > + .trim_prefix = strlen(prefix),
>>
>> Tangent: I wonder if it makes sense to make `trim_prefix` a bool and
>> then internally trim strlen(prefix). Is there a usecase where
>> `.trim_prefix != strlen(prefix)`?
>
> I don't think there is right now, and I cannot think about any myself.
> How about we leave this as a #leftoverbit though?
>
> Patrick
Sure!
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
` (16 preceding siblings ...)
2026-02-23 11:59 ` [PATCH v2 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
@ 2026-02-24 13:14 ` Karthik Nayak
17 siblings, 0 replies; 53+ messages in thread
From: Karthik Nayak @ 2026-02-24 13:14 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Oswald Buddenhagen
[-- Attachment #1: Type: text/plain, Size: 2188 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> we currently have 14 different `refs_for_each_*()` functions, with each
> of them doing slightly different things. This makes for a confusing API
> surface, and because the API is not built for extension we have to add a
> new function every now and then to handle another esoteric edge case
> that will ultimately only have at most a handful of callers.
>
> This design isn't really sensible in my opinion, and this patch series
> aims to fix that. Instead of having a dozen different functions, it
> introduces a new `refs_for_each_ref_ext()` function that simply takes an
> options structure as input. From thereon, callers can mix and match the
> parameters that they care about.
>
> The patch series is structured like this:
>
> - Patches 1 to 5 introduce some preliminary cleanups.
>
> - Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move
> more functionality into it. This also fixes a performance bug that
> we have in one of the implementations.
>
> - Patch 10 adds some more verification for options that would have
> caught the bugs in ps/for-each-ref-in-fixes.
>
> - The remaining patches drop 7 out of 14 functions and replace them
> with `refs_for_each_ref_ext()`. It results in a bit of churn, so
> while I think this churn is worth it, I consider these patches to be
> optional.
>
> The patch series is built on top of 73fd77805f (The 5th batch,
> 2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:
> simplify string_list memory handling, 2026-02-19) merged into it.
>
> Changes in v2:
> - Move the removal of `refs_for_each_include_root_ref()` to the
> beginning of the series to avoid some unnecessary churn.
> - Some commit message improvements.
> - Make the converted version of `refs_for_each_glob_ref_in()` fit into
> the new calling conventions a bit better. The function was still
> stripping the prefix unconditionally for example, which I've now
> changed.
> - Link to v1: https://lore.kernel.org/r/20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im
>
The range-diff looks good. I'm happy with this version! :)
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 53+ messages in thread
end of thread, other threads:[~2026-02-24 13:14 UTC | newest]
Thread overview: 53+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-20 8:24 [PATCH 00/17] refs: unify `refs_for_each_*()` functions Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 01/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
2026-02-23 8:05 ` Karthik Nayak
2026-02-23 10:46 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 02/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 03/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 04/17] refs: rename `each_ref_fn` Patrick Steinhardt
2026-02-23 8:07 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
2026-02-20 10:29 ` Oswald Buddenhagen
2026-02-20 12:05 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
2026-02-23 8:14 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
2026-02-23 8:27 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
2026-02-23 9:02 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
2026-02-23 9:06 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
2026-02-23 9:09 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
2026-02-23 9:11 ` Karthik Nayak
2026-02-23 10:48 ` Patrick Steinhardt
2026-02-23 13:35 ` Karthik Nayak
2026-02-20 8:24 ` [PATCH 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
2026-02-20 8:24 ` [PATCH 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
2026-02-23 9:14 ` [PATCH 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
2026-02-23 11:59 ` [PATCH v2 " Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 02/17] refs: move `refs_head_ref_namespaced()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 03/17] refs: move `do_for_each_ref_flags` further up Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 04/17] refs: rename `do_for_each_ref_flags` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 05/17] refs: rename `each_ref_fn` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 06/17] refs: introduce `refs_for_each_ref_ext` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 07/17] refs: speed up `refs_for_each_glob_ref_in()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 08/17] refs: generalize `refs_for_each_namespaced_ref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 10/17] refs: improve verification for-each-ref options Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 11/17] refs: replace `refs_for_each_ref_in()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 12/17] refs: replace `refs_for_each_rawref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 13/17] refs: replace `refs_for_each_rawref_in()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 14/17] refs: replace `refs_for_each_glob_ref_in()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 15/17] refs: replace `refs_for_each_glob_ref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 16/17] refs: replace `refs_for_each_namespaced_ref()` Patrick Steinhardt
2026-02-23 11:59 ` [PATCH v2 17/17] refs: replace `refs_for_each_fullref_in()` Patrick Steinhardt
2026-02-24 13:14 ` [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions Karthik Nayak
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox