From 57fa3161a0ba4fb5e17dba8244bc854d91ae98bf Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:35 +0100 Subject: [PATCH 01/17] refs: remove unused `refs_for_each_include_root_ref()` Remove the unused `refs_for_each_include_root_ref()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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 to the if it doesn't start with 'refs/'. From 7543920af2f1a48ed4656e38a442125ef60cc3b0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:36 +0100 Subject: [PATCH 02/17] refs: move `refs_head_ref_namespaced()` 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 Signed-off-by: Junio C Hamano --- 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. From 1c3aff34b88f132ba3d489dbd9ff30f2604bb871 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:37 +0100 Subject: [PATCH 03/17] refs: move `do_for_each_ref_flags` further up 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 Signed-off-by: Junio C Hamano --- 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 From 8f0720a5a781562fb1f750b351e14129fc8930ea Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:38 +0100 Subject: [PATCH 04/17] refs: rename `do_for_each_ref_flags` 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 Signed-off-by: Junio C Hamano --- 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"); From 635f08b7394b9dda013a0b78f4db11348dc7717b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:39 +0100 Subject: [PATCH 05/17] refs: rename `each_ref_fn` 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 Signed-off-by: Junio C Hamano --- 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); From aefcc9b367016581743e57adf667ee4d56691bb1 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:40 +0100 Subject: [PATCH 06/17] refs: introduce `refs_for_each_ref_ext` 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 Signed-off-by: Junio C Hamano --- 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, From daf01b1366ca644d45374451560aeeb4fc8a7765 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:41 +0100 Subject: [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Junio C Hamano --- 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 From 5387919327574b5067f7efd986fca8793c95c71a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:42 +0100 Subject: [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()` 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 Signed-off-by: Junio C Hamano --- 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 From f503bb7dc96ee92623ade8d60eed401ecfddae0f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:43 +0100 Subject: [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()` 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 Signed-off-by: Junio C Hamano --- 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, From 5507200b504f478516bf93767ac3ed3bebed7226 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:44 +0100 Subject: [PATCH 10/17] refs: improve verification for-each-ref options 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 Signed-off-by: Junio C Hamano --- 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/")) From 00be226f1f2a1036ea3920f8700b23b7cc55bf57 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:45 +0100 Subject: [PATCH 11/17] refs: replace `refs_for_each_ref_in()` Replace calls to `refs_for_each_ref_in()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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) From 67034081adc956c4dc8b51f59a9b70b8861c4642 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:46 +0100 Subject: [PATCH 12/17] refs: replace `refs_for_each_rawref()` Replace calls to `refs_for_each_rawref()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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, From 5ef6d593f18ac6b1e7540eed45f5d795d5a82faa Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:47 +0100 Subject: [PATCH 13/17] refs: replace `refs_for_each_rawref_in()` Replace calls to `refs_for_each_rawref_in()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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 to the if it doesn't start with 'refs/'. From 4091d2989353aaf14080ee64ee2e94b60ceaf18d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:48 +0100 Subject: [PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()` Replace calls to `refs_for_each_glob_ref_in()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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); From 3fc1ad03c6243b44c2dcab480acaced44921b1c5 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:49 +0100 Subject: [PATCH 15/17] refs: replace `refs_for_each_glob_ref()` Replace calls to `refs_for_each_glob_ref()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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))) { From 96c35a9ba5f1b5afac1e30ca93815dcd540d8b16 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:50 +0100 Subject: [PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()` Replace calls to `refs_for_each_namespaced_ref()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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 to the 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); } From 1dd4f1e43f8f11ebb13c1b9edbd91219a134443d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 23 Feb 2026 12:59:51 +0100 Subject: [PATCH 17/17] refs: replace `refs_for_each_fullref_in()` Replace calls to `refs_for_each_fullref_in()` with the newly introduced `refs_for_each_ref_ext()` function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- 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)