{"thread":{"id":"65028","subject":"[PATCH 00/17] refs: unify `refs_for_each_*()` functions","startedAt":"2026-02-20T08:24:25Z","lastAt":"2026-02-23T13:35:21Z","messageCount":34,"participants":["Patrick Steinhardt","Oswald Buddenhagen","Karthik Nayak"],"isPatch":true,"patchVersion":1,"patchTotal":17},"messages":[{"id":"536470","messageId":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","threadId":"65028","inReplyTo":null,"subject":"[PATCH 00/17] refs: unify `refs_for_each_*()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:04Z","receivedAt":"2026-02-20T08:24:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nwe currently have 14 different `refs_for_each_*()` functions, with each\nof them doing slightly different things. This makes for a confusing API\nsurface, and because the API is not built for extension we have to add a\nnew function every now and then to handle another esoteric edge case\nthat will ultimately only have at most a handful of callers.\n\nThis design isn't really sensible in my opinion, and this patch series\naims to fix that. Instead of having a dozen different functions, it\nintroduces a new `refs_for_each_ref_ext()` function that simply takes an\noptions structure as input. From thereon, callers can mix and match the\nparameters that they care about.\n\nThe patch series is structured like this:\n\n  - Patches 1 to 5 introduce some preliminary cleanups.\n\n  - Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move\n    more functionality into it. This also fixes a performance bug that\n    we have in one of the implementations.\n\n  - Patch 10 adds some more verification for options that would have\n    caught the bugs in ps/for-each-ref-in-fixes.\n\n  - The remaining patches drop 7 out of 14 functions and replace them\n    with `refs_for_each_ref_ext()`. It results in a bit of churn, so\n    while I think this churn is worth it, I consider these patches to be\n    optional.\n\nThe patch series is built on top of 73fd77805f (The 5th batch,\n2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:\nsimplify string_list memory handling, 2026-02-19) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (17):\n      refs: move `refs_head_ref_namespaced()`\n      refs: move `do_for_each_ref_flags` further up\n      refs: rename `do_for_each_ref_flags`\n      refs: rename `each_ref_fn`\n      refs: remove unused `refs_for_each_include_root_ref()`\n      refs: introduce `refs_for_each_ref_ext`\n      refs: speed up `refs_for_each_glob_ref_in()`\n      refs: generalize `refs_for_each_namespaced_ref()`\n      refs: generalize `refs_for_each_fullref_in_prefixes()`\n      refs: improve verification for-each-ref options\n      refs: replace `refs_for_each_ref_in()`\n      refs: replace `refs_for_each_rawref()`\n      refs: replace `refs_for_each_rawref_in()`\n      refs: replace `refs_for_each_glob_ref_in()`\n      refs: replace `refs_for_each_glob_ref()`\n      refs: replace `refs_for_each_namespaced_ref()`\n      refs: replace `refs_for_each_fullref_in()`\n\n bisect.c                  |  16 ++-\n builtin/bisect.c          |  34 ++++--\n builtin/describe.c        |   7 +-\n builtin/fetch.c           |   7 +-\n builtin/fsck.c            |   7 +-\n builtin/receive-pack.c    |   8 +-\n builtin/remote.c          |   8 +-\n builtin/rev-parse.c       |  37 ++++---\n builtin/show-ref.c        |  21 ++--\n fetch-pack.c              |  15 ++-\n http-backend.c            |   8 +-\n ls-refs.c                 |  11 +-\n notes.c                   |   7 +-\n pack-bitmap.c             |  15 +--\n pack-bitmap.h             |   2 +-\n ref-filter.c              |  19 ++--\n refs.c                    | 256 ++++++++++++++++++++++------------------------\n refs.h                    | 198 +++++++++++++++++------------------\n refs/files-backend.c      |  19 ++--\n refs/iterator.c           |   2 +-\n refs/packed-backend.c     |   8 +-\n refs/reftable-backend.c   |  10 +-\n revision.c                |  46 ++++++---\n submodule.c               |   2 +-\n t/helper/test-ref-store.c |  15 ++-\n upload-pack.c             |  13 ++-\n worktree.c                |   2 +-\n worktree.h                |   2 +-\n 28 files changed, 437 insertions(+), 358 deletions(-)\n\n\n---\nbase-commit: dbbe43524e0814c1f93325795ed6aa26eb6e587e\nchange-id: 20260220-pks-refs-for-each-unification-7572c694cfc0\n\n"},{"id":"536471","messageId":"20260220-pks-refs-for-each-unification-v1-1-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 01/17] refs: move `refs_head_ref_namespaced()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:05Z","receivedAt":"2026-02-20T08:24:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `refs_head_ref_namespaced()` is somewhat special when\ncompared to most of the other functions that take a callback function:\nwhile `refs_for_each_*()` functions yield multiple refs, we only yield\nat most the HEAD ref of the current function. As such, the function is\nrelated to `refs_head_ref()` and not to the for-each functions.\n\nMove the function to be located next to `refs_head_ref()` to clarify.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex f16b1b697b..62e8ef61e7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -413,6 +413,9 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n  */\n int refs_head_ref(struct ref_store *refs,\n \t\t  each_ref_fn fn, void *cb_data);\n+int refs_head_ref_namespaced(struct ref_store *refs,\n+\t\t\t     each_ref_fn fn, void *cb_data);\n+\n int refs_for_each_ref(struct ref_store *refs,\n \t\t      each_ref_fn fn, void *cb_data);\n int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n@@ -456,8 +459,6 @@ int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,\n int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n \t\t\t      const char *pattern, const char *prefix, void *cb_data);\n \n-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);\n-\n /*\n  * references matching any pattern in \"exclude_patterns\" are omitted from the\n  * result set on a best-effort basis.\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536472","messageId":"20260220-pks-refs-for-each-unification-v1-2-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 02/17] refs: move `do_for_each_ref_flags` further up","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:06Z","receivedAt":"2026-02-20T08:24:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Move the `do_for_each_ref_flags` enum further up. This prepares for\nsubsequent changes, where the flags will be used by more functions.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 74 +++++++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 37 insertions(+), 37 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 62e8ef61e7..40974c017b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -402,6 +402,43 @@ int reference_get_peeled_oid(struct repository *repo,\n  */\n typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n \n+/*\n+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),\n+ * which feeds it).\n+ */\n+enum do_for_each_ref_flags {\n+\t/*\n+\t * Include broken references in a do_for_each_ref*() iteration, which\n+\t * would normally be omitted. This includes both refs that point to\n+\t * missing objects (a true repository corruption), ones with illegal\n+\t * names (which we prefer not to expose to callers), as well as\n+\t * dangling symbolic refs (i.e., those that point to a non-existent\n+\t * ref; this is not a corruption, but as they have no valid oid, we\n+\t * omit them from normal iteration results).\n+\t */\n+\tDO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),\n+\n+\t/*\n+\t * Only include per-worktree refs in a do_for_each_ref*() iteration.\n+\t * Normally this will be used with a files ref_store, since that's\n+\t * where all reference backends will presumably store their\n+\t * per-worktree refs.\n+\t */\n+\tDO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),\n+\n+\t/*\n+\t * Omit dangling symrefs from output; this only has an effect with\n+\t * INCLUDE_BROKEN, since they are otherwise not included at all.\n+\t */\n+\tDO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n+\n+\t/*\n+\t * Include root refs i.e. HEAD and pseudorefs along with the regular\n+\t * refs.\n+\t */\n+\tDO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n+};\n+\n /*\n  * The following functions invoke the specified callback function for\n  * each reference indicated.  If the function ever returns a nonzero\n@@ -1332,43 +1369,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  */\n struct ref_iterator;\n \n-/*\n- * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),\n- * which feeds it).\n- */\n-enum do_for_each_ref_flags {\n-\t/*\n-\t * Include broken references in a do_for_each_ref*() iteration, which\n-\t * would normally be omitted. This includes both refs that point to\n-\t * missing objects (a true repository corruption), ones with illegal\n-\t * names (which we prefer not to expose to callers), as well as\n-\t * dangling symbolic refs (i.e., those that point to a non-existent\n-\t * ref; this is not a corruption, but as they have no valid oid, we\n-\t * omit them from normal iteration results).\n-\t */\n-\tDO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),\n-\n-\t/*\n-\t * Only include per-worktree refs in a do_for_each_ref*() iteration.\n-\t * Normally this will be used with a files ref_store, since that's\n-\t * where all reference backends will presumably store their\n-\t * per-worktree refs.\n-\t */\n-\tDO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),\n-\n-\t/*\n-\t * Omit dangling symrefs from output; this only has an effect with\n-\t * INCLUDE_BROKEN, since they are otherwise not included at all.\n-\t */\n-\tDO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n-\n-\t/*\n-\t * Include root refs i.e. HEAD and pseudorefs along with the regular\n-\t * refs.\n-\t */\n-\tDO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n-};\n-\n /*\n  * Return an iterator that goes over each reference in `refs` for\n  * which the refname begins with prefix. If trim is non-zero, then\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536473","messageId":"20260220-pks-refs-for-each-unification-v1-3-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 03/17] refs: rename `do_for_each_ref_flags`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:07Z","receivedAt":"2026-02-20T08:24:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The enum `do_for_each_ref_flags` and its individual values don't match\nto our current best practices when it comes to naming things. Rename it\nto `refs_for_each_flag`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            |  2 +-\n refs.c                  | 20 ++++++++++----------\n refs.h                  | 12 ++++++------\n refs/files-backend.c    | 12 ++++++------\n refs/packed-backend.c   |  8 ++++----\n refs/reftable-backend.c | 10 +++++-----\n 6 files changed, 32 insertions(+), 32 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 3917c4ccd9..4bc54ebd9d 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2810,7 +2810,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn for_each_fullref_with_seek(filter, cb, cb_data,\n-\t\t\t\t\t\t  DO_FOR_EACH_INCLUDE_ROOT_REFS);\n+\t\t\t\t\t\t  REFS_FOR_EACH_INCLUDE_ROOT_REFS);\n \t}\n \n \tif (!filter->match_as_path) {\ndiff --git a/refs.c b/refs.c\nindex 600913b99f..0cad3b4759 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1812,7 +1812,7 @@ struct ref_iterator *refs_ref_iterator_begin(\n \t\tconst char *prefix,\n \t\tconst char **exclude_patterns,\n \t\tint trim,\n-\t\tenum do_for_each_ref_flags flags)\n+\t\tenum refs_for_each_flag flags)\n {\n \tstruct ref_iterator *iter;\n \tstruct strvec normalized_exclude_patterns = STRVEC_INIT;\n@@ -1834,14 +1834,14 @@ struct ref_iterator *refs_ref_iterator_begin(\n \t\texclude_patterns = normalized_exclude_patterns.v;\n \t}\n \n-\tif (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {\n+\tif (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) {\n \t\tstatic int ref_paranoia = -1;\n \n \t\tif (ref_paranoia < 0)\n \t\t\tref_paranoia = git_env_bool(\"GIT_REF_PARANOIA\", 1);\n \t\tif (ref_paranoia) {\n-\t\t\tflags |= DO_FOR_EACH_INCLUDE_BROKEN;\n-\t\t\tflags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;\n+\t\t\tflags |= REFS_FOR_EACH_INCLUDE_BROKEN;\n+\t\t\tflags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS;\n \t\t}\n \t}\n \n@@ -1861,7 +1861,7 @@ struct ref_iterator *refs_ref_iterator_begin(\n static int do_for_each_ref(struct ref_store *refs, const char *prefix,\n \t\t\t   const char **exclude_patterns,\n \t\t\t   each_ref_fn fn, int trim,\n-\t\t\t   enum do_for_each_ref_flags flags, void *cb_data)\n+\t\t\t   enum refs_for_each_flag flags, void *cb_data)\n {\n \tstruct ref_iterator *iter;\n \n@@ -1897,7 +1897,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \treturn do_for_each_ref(refs, git_replace_ref_base, NULL, fn,\n \t\t\t       strlen(git_replace_ref_base),\n-\t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n+\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n int refs_for_each_namespaced_ref(struct ref_store *refs,\n@@ -1929,14 +1929,14 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n \t\t\t    each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(refs, prefix, NULL, fn, 0,\n-\t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n+\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,\n \t\t\t\t    void *cb_data)\n {\n \treturn do_for_each_ref(refs, \"\", NULL, fn, 0,\n-\t\t\t       DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);\n+\t\t\t       REFS_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);\n }\n \n static int qsort_strcmp(const void *va, const void *vb)\n@@ -2748,7 +2748,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \n \t\t\tif (!iter)\n \t\t\t\titer = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,\n-\t\t\t\t\t\t\t       DO_FOR_EACH_INCLUDE_BROKEN);\n+\t\t\t\t\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN);\n \t\t\telse if (ref_iterator_seek(iter, dirname.buf,\n \t\t\t\t\t\t   REF_ITERATOR_SEEK_SET_PREFIX) < 0)\n \t\t\t\tgoto cleanup;\n@@ -3288,7 +3288,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n \t * ensure that there are no concurrent writes.\n \t */\n \tret = do_for_each_ref(old_refs, \"\", NULL, migrate_one_ref, 0,\n-\t\t\t      DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,\n+\t\t\t      REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,\n \t\t\t      &data);\n \tif (ret < 0)\n \t\tgoto done;\ndiff --git a/refs.h b/refs.h\nindex 40974c017b..8ac1ef7a8b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -406,7 +406,7 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n  * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),\n  * which feeds it).\n  */\n-enum do_for_each_ref_flags {\n+enum refs_for_each_flag {\n \t/*\n \t * Include broken references in a do_for_each_ref*() iteration, which\n \t * would normally be omitted. This includes both refs that point to\n@@ -416,7 +416,7 @@ enum do_for_each_ref_flags {\n \t * ref; this is not a corruption, but as they have no valid oid, we\n \t * omit them from normal iteration results).\n \t */\n-\tDO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),\n+\tREFS_FOR_EACH_INCLUDE_BROKEN = (1 << 0),\n \n \t/*\n \t * Only include per-worktree refs in a do_for_each_ref*() iteration.\n@@ -424,19 +424,19 @@ enum do_for_each_ref_flags {\n \t * where all reference backends will presumably store their\n \t * per-worktree refs.\n \t */\n-\tDO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),\n+\tREFS_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),\n \n \t/*\n \t * Omit dangling symrefs from output; this only has an effect with\n \t * INCLUDE_BROKEN, since they are otherwise not included at all.\n \t */\n-\tDO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n+\tREFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n \n \t/*\n \t * Include root refs i.e. HEAD and pseudorefs along with the regular\n \t * refs.\n \t */\n-\tDO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n+\tREFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n };\n \n /*\n@@ -1378,7 +1378,7 @@ struct ref_iterator;\n struct ref_iterator *refs_ref_iterator_begin(\n \tstruct ref_store *refs,\n \tconst char *prefix, const char **exclude_patterns,\n-\tint trim, enum do_for_each_ref_flags flags);\n+\tint trim, enum refs_for_each_flag flags);\n \n /*\n  * Advance the iterator to the first or next item and return ITER_OK.\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex b1b13b41f6..6c98e14414 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -439,7 +439,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs,\n \n \t\tdir = get_ref_dir(refs->loose->root);\n \n-\t\tif (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS)\n+\t\tif (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS)\n \t\t\tadd_root_refs(refs, dir);\n \n \t\t/*\n@@ -955,17 +955,17 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n-\t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n+\t\tif (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&\n \t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n \n-\t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n+\t\tif ((iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n \t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n \t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n \t\t\tcontinue;\n \n-\t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n+\t\tif (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&\n \t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n \t\t\t\t\t    iter->repo,\n \t\t\t\t\t    iter->iter0->ref.oid,\n@@ -1012,7 +1012,7 @@ static struct ref_iterator *files_ref_iterator_begin(\n \tstruct ref_iterator *ref_iterator;\n \tunsigned int required_flags = REF_STORE_READ;\n \n-\tif (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))\n+\tif (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))\n \t\trequired_flags |= REF_STORE_ODB;\n \n \trefs = files_downcast(ref_store, required_flags, \"ref_iterator_begin\");\n@@ -1050,7 +1050,7 @@ static struct ref_iterator *files_ref_iterator_begin(\n \t */\n \tpacked_iter = refs_ref_iterator_begin(\n \t\t\trefs->packed_ref_store, prefix, exclude_patterns, 0,\n-\t\t\tDO_FOR_EACH_INCLUDE_BROKEN);\n+\t\t\tREFS_FOR_EACH_INCLUDE_BROKEN);\n \n \toverlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 59b3ecb9d6..5ef4ae32b8 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -982,11 +982,11 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tconst char *refname = iter->base.ref.name;\n \t\tconst char *prefix = iter->prefix;\n \n-\t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n+\t\tif (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&\n \t\t    !is_per_worktree_ref(iter->base.ref.name))\n \t\t\tcontinue;\n \n-\t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n+\t\tif (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&\n \t\t    !ref_resolves_to_object(iter->base.ref.name, iter->repo,\n \t\t\t\t\t    &iter->oid, iter->flags))\n \t\t\tcontinue;\n@@ -1159,7 +1159,7 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \tstruct ref_iterator *ref_iterator;\n \tunsigned int required_flags = REF_STORE_READ;\n \n-\tif (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))\n+\tif (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))\n \t\trequired_flags |= REF_STORE_ODB;\n \trefs = packed_downcast(ref_store, required_flags, \"ref_iterator_begin\");\n \n@@ -1401,7 +1401,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t * of updates is exhausted, leave i set to updates->nr.\n \t */\n \titer = packed_ref_iterator_begin(&refs->base, \"\", NULL,\n-\t\t\t\t\t DO_FOR_EACH_INCLUDE_BROKEN);\n+\t\t\t\t\t REFS_FOR_EACH_INCLUDE_BROKEN);\n \tif ((ok = ref_iterator_advance(iter)) != ITER_OK) {\n \t\tref_iterator_free(iter);\n \t\titer = NULL;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 5611808ad7..34bc074dd3 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -662,7 +662,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t * the root refs are to be included. We emulate the same behaviour here.\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n-\t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n+\t\t    !(iter->flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS &&\n \t\t      is_root_ref(iter->ref.refname))) {\n \t\t\tcontinue;\n \t\t}\n@@ -676,7 +676,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tif (iter->exclude_patterns && should_exclude_current_ref(iter))\n \t\t\tcontinue;\n \n-\t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n+\t\tif (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY &&\n \t\t    parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=\n \t\t\t    REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n@@ -714,12 +714,12 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tflags |= REF_BAD_NAME | REF_ISBROKEN;\n \t\t}\n \n-\t\tif (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS &&\n+\t\tif (iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS &&\n \t\t    flags & REF_ISSYMREF &&\n \t\t    flags & REF_ISBROKEN)\n \t\t\tcontinue;\n \n-\t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n+\t\tif (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) &&\n \t\t    !ref_resolves_to_object(iter->ref.refname, refs->base.repo,\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n@@ -871,7 +871,7 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto\n \tstruct reftable_ref_store *refs;\n \tunsigned int required_flags = REF_STORE_READ;\n \n-\tif (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))\n+\tif (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN))\n \t\trequired_flags |= REF_STORE_ODB;\n \trefs = reftable_be_downcast(ref_store, required_flags, \"ref_iterator_begin\");\n \n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536474","messageId":"20260220-pks-refs-for-each-unification-v1-4-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 04/17] refs: rename `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:08Z","receivedAt":"2026-02-20T08:24:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Similar to the preceding commit, rename `each_ref_fn` to better match\nour current best practices around how we name things.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n pack-bitmap.c   |  2 +-\n pack-bitmap.h   |  2 +-\n ref-filter.c    |  6 +++---\n refs.c          | 36 ++++++++++++++++++------------------\n refs.h          | 40 ++++++++++++++++++++--------------------\n refs/iterator.c |  2 +-\n revision.c      |  8 ++++----\n submodule.c     |  2 +-\n upload-pack.c   |  2 +-\n worktree.c      |  2 +-\n worktree.h      |  2 +-\n 11 files changed, 52 insertions(+), 52 deletions(-)\n\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex 1c93871484..efef7081e6 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -3324,7 +3324,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)\n }\n \n void for_each_preferred_bitmap_tip(struct repository *repo,\n-\t\t\t\t   each_ref_fn cb, void *cb_data)\n+\t\t\t\t   refs_for_each_cb cb, void *cb_data)\n {\n \tstruct string_list_item *item;\n \tconst struct string_list *preferred_tips;\ndiff --git a/pack-bitmap.h b/pack-bitmap.h\nindex d0611d0481..a95e1c2d11 100644\n--- a/pack-bitmap.h\n+++ b/pack-bitmap.h\n@@ -105,7 +105,7 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,\n  * \"pack.preferBitmapTips\" and invoke the callback on each function.\n  */\n void for_each_preferred_bitmap_tip(struct repository *repo,\n-\t\t\t\t   each_ref_fn cb, void *cb_data);\n+\t\t\t\t   refs_for_each_cb cb, void *cb_data);\n \n #define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \\\n \t\"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL\"\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 4bc54ebd9d..049e845a19 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2781,7 +2781,7 @@ static int start_ref_iterator_after(struct ref_iterator *iter, const char *marke\n \treturn ret;\n }\n \n-static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,\n+static int for_each_fullref_with_seek(struct ref_filter *filter, refs_for_each_cb cb,\n \t\t\t\t       void *cb_data, unsigned int flags)\n {\n \tstruct ref_iterator *iter;\n@@ -2804,7 +2804,7 @@ static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb,\n  * pattern match, so the callback still has to match each ref individually.\n  */\n static int for_each_fullref_in_pattern(struct ref_filter *filter,\n-\t\t\t\t       each_ref_fn cb,\n+\t\t\t\t       refs_for_each_cb cb,\n \t\t\t\t       void *cb_data)\n {\n \tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n@@ -3303,7 +3303,7 @@ void filter_is_base(struct repository *r,\n \tfree(bases);\n }\n \n-static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)\n+static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for_each_cb fn, void *cb_data)\n {\n \tconst char *prefix = NULL;\n \tint ret = 0;\ndiff --git a/refs.c b/refs.c\nindex 0cad3b4759..e9ac0a7101 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -445,7 +445,7 @@ char *refs_resolve_refdup(struct ref_store *refs,\n struct for_each_ref_filter {\n \tconst char *pattern;\n \tconst char *prefix;\n-\teach_ref_fn *fn;\n+\trefs_for_each_cb *fn;\n \tvoid *cb_data;\n };\n \n@@ -527,22 +527,22 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n \trefs_for_each_rawref(refs, warn_if_dangling_symref, &data);\n }\n \n-int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn refs_for_each_ref_in(refs, \"refs/tags/\", fn, cb_data);\n }\n \n-int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn refs_for_each_ref_in(refs, \"refs/heads/\", fn, cb_data);\n }\n \n-int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn refs_for_each_ref_in(refs, \"refs/remotes/\", fn, cb_data);\n }\n \n-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tint ret = 0;\n@@ -590,7 +590,7 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n \tstrbuf_release(&normalized_pattern);\n }\n \n-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t      const char *pattern, const char *prefix, void *cb_data)\n {\n \tstruct strbuf real_pattern = STRBUF_INIT;\n@@ -620,7 +620,7 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n \treturn ret;\n }\n \n-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t   const char *pattern, void *cb_data)\n {\n \treturn refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);\n@@ -1788,7 +1788,7 @@ const char *find_descendant_ref(const char *dirname,\n \treturn NULL;\n }\n \n-int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \tstruct object_id oid;\n \tint flag;\n@@ -1860,7 +1860,7 @@ struct ref_iterator *refs_ref_iterator_begin(\n \n static int do_for_each_ref(struct ref_store *refs, const char *prefix,\n \t\t\t   const char **exclude_patterns,\n-\t\t\t   each_ref_fn fn, int trim,\n+\t\t\t   refs_for_each_cb fn, int trim,\n \t\t\t   enum refs_for_each_flag flags, void *cb_data)\n {\n \tstruct ref_iterator *iter;\n@@ -1874,25 +1874,25 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,\n \treturn do_for_each_ref_iterator(iter, fn, cb_data);\n }\n \n-int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn do_for_each_ref(refs, \"\", NULL, fn, 0, 0, cb_data);\n }\n \n int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t each_ref_fn fn, void *cb_data)\n+\t\t\t refs_for_each_cb fn, void *cb_data)\n {\n \treturn do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);\n }\n \n int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n \t\t\t     const char **exclude_patterns,\n-\t\t\t     each_ref_fn fn, void *cb_data)\n+\t\t\t     refs_for_each_cb fn, void *cb_data)\n {\n \treturn do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);\n }\n \n-int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \treturn do_for_each_ref(refs, git_replace_ref_base, NULL, fn,\n@@ -1902,7 +1902,7 @@ int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_d\n \n int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t const char **exclude_patterns,\n-\t\t\t\t each_ref_fn fn, void *cb_data)\n+\t\t\t\t refs_for_each_cb fn, void *cb_data)\n {\n \tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n \tstruct strbuf prefix = STRBUF_INIT;\n@@ -1920,19 +1920,19 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \treturn ret;\n }\n \n-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn refs_for_each_rawref_in(refs, \"\", fn, cb_data);\n }\n \n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t    each_ref_fn fn, void *cb_data)\n+\t\t\t    refs_for_each_cb fn, void *cb_data)\n {\n \treturn do_for_each_ref(refs, prefix, NULL, fn, 0,\n \t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t\t    void *cb_data)\n {\n \treturn do_for_each_ref(refs, \"\", NULL, fn, 0,\n@@ -2001,7 +2001,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,\n \t\t\t\t      const char *namespace,\n \t\t\t\t      const char **patterns,\n \t\t\t\t      const char **exclude_patterns,\n-\t\t\t\t      each_ref_fn fn, void *cb_data)\n+\t\t\t\t      refs_for_each_cb fn, void *cb_data)\n {\n \tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n \tstruct string_list prefixes = STRING_LIST_INIT_DUP;\ndiff --git a/refs.h b/refs.h\nindex 8ac1ef7a8b..e37574009b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -170,7 +170,7 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err);\n  *\n  *   peel_object(r, oid, &peeled);\n  *\n- * with the \"oid\" value given to the each_ref_fn callback, except\n+ * with the \"oid\" value given to the refs_for_each_cb callback, except\n  * that some ref storage may be able to answer the query without\n  * actually loading the object in memory.\n  */\n@@ -329,7 +329,7 @@ int check_tag_ref(struct strbuf *sb, const char *name);\n struct ref_transaction;\n \n /*\n- * Bit values set in the flags argument passed to each_ref_fn() and\n+ * Bit values set in the flags argument passed to refs_for_each_cb() and\n  * stored in ref_iterator::flags. Other bits are for internal use\n  * only:\n  */\n@@ -400,7 +400,7 @@ int reference_get_peeled_oid(struct repository *repo,\n  * argument is only guaranteed to be valid for the duration of a\n  * single callback invocation.\n  */\n-typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n+typedef int refs_for_each_cb(const struct reference *ref, void *cb_data);\n \n /*\n  * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),\n@@ -449,22 +449,22 @@ enum refs_for_each_flag {\n  * stop the iteration. Returned references are sorted.\n  */\n int refs_head_ref(struct ref_store *refs,\n-\t\t  each_ref_fn fn, void *cb_data);\n+\t\t  refs_for_each_cb fn, void *cb_data);\n int refs_head_ref_namespaced(struct ref_store *refs,\n-\t\t\t     each_ref_fn fn, void *cb_data);\n+\t\t\t     refs_for_each_cb fn, void *cb_data);\n \n int refs_for_each_ref(struct ref_store *refs,\n-\t\t      each_ref_fn fn, void *cb_data);\n+\t\t      refs_for_each_cb fn, void *cb_data);\n int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t each_ref_fn fn, void *cb_data);\n+\t\t\t refs_for_each_cb fn, void *cb_data);\n int refs_for_each_tag_ref(struct ref_store *refs,\n-\t\t\t  each_ref_fn fn, void *cb_data);\n+\t\t\t  refs_for_each_cb fn, void *cb_data);\n int refs_for_each_branch_ref(struct ref_store *refs,\n-\t\t\t     each_ref_fn fn, void *cb_data);\n+\t\t\t     refs_for_each_cb fn, void *cb_data);\n int refs_for_each_remote_ref(struct ref_store *refs,\n-\t\t\t     each_ref_fn fn, void *cb_data);\n+\t\t\t     refs_for_each_cb fn, void *cb_data);\n int refs_for_each_replace_ref(struct ref_store *refs,\n-\t\t\t      each_ref_fn fn, void *cb_data);\n+\t\t\t      refs_for_each_cb fn, void *cb_data);\n \n /*\n  * references matching any pattern in \"exclude_patterns\" are omitted from the\n@@ -472,7 +472,7 @@ int refs_for_each_replace_ref(struct ref_store *refs,\n  */\n int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n \t\t\t     const char **exclude_patterns,\n-\t\t\t     each_ref_fn fn, void *cb_data);\n+\t\t\t     refs_for_each_cb fn, void *cb_data);\n \n /**\n  * iterate all refs in \"patterns\" by partitioning patterns into disjoint sets\n@@ -487,13 +487,13 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *refs,\n \t\t\t\t      const char *namespace,\n \t\t\t\t      const char **patterns,\n \t\t\t\t      const char **exclude_patterns,\n-\t\t\t\t      each_ref_fn fn, void *cb_data);\n+\t\t\t\t      refs_for_each_cb fn, void *cb_data);\n \n /* iterates all refs that match the specified glob pattern. */\n-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t   const char *pattern, void *cb_data);\n \n-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t      const char *pattern, const char *prefix, void *cb_data);\n \n /*\n@@ -502,17 +502,17 @@ int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n  */\n int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t const char **exclude_patterns,\n-\t\t\t\t each_ref_fn fn, void *cb_data);\n+\t\t\t\t refs_for_each_cb fn, void *cb_data);\n \n /* can be used to learn about broken ref and symref */\n-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);\n+int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);\n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t    each_ref_fn fn, void *cb_data);\n+\t\t\t    refs_for_each_cb fn, void *cb_data);\n \n /*\n  * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.\n  */\n-int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,\n+int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t\t    void *cb_data);\n \n /*\n@@ -1427,6 +1427,6 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);\n  * iterator style.\n  */\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n-\t\t\t     each_ref_fn fn, void *cb_data);\n+\t\t\t     refs_for_each_cb fn, void *cb_data);\n \n #endif /* REFS_H */\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex d79aa5ec82..d5cacde51b 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -423,7 +423,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,\n }\n \n int do_for_each_ref_iterator(struct ref_iterator *iter,\n-\t\t\t     each_ref_fn fn, void *cb_data)\n+\t\t\t     refs_for_each_cb fn, void *cb_data)\n {\n \tint retval = 0, ok;\n \ndiff --git a/revision.c b/revision.c\nindex 29972c3a19..8c206830d5 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1646,7 +1646,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,\n \n static void handle_refs(struct ref_store *refs,\n \t\t\tstruct rev_info *revs, unsigned flags,\n-\t\t\tint (*for_each)(struct ref_store *, each_ref_fn, void *))\n+\t\t\tint (*for_each)(struct ref_store *, refs_for_each_cb, void *))\n {\n \tstruct all_refs_cb cb;\n \n@@ -2728,7 +2728,7 @@ void revision_opts_finish(struct rev_info *revs)\n \t}\n }\n \n-static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,\n+static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t       void *cb_data, const char *term)\n {\n \tstruct strbuf bisect_refs = STRBUF_INIT;\n@@ -2739,12 +2739,12 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,\n \treturn status;\n }\n \n-static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+static int for_each_bad_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn for_each_bisect_ref(refs, fn, cb_data, term_bad);\n }\n \n-static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n+static int for_each_good_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n {\n \treturn for_each_bisect_ref(refs, fn, cb_data, term_good);\n }\ndiff --git a/submodule.c b/submodule.c\nindex 508938e4da..4f9aaa2c75 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -101,7 +101,7 @@ int is_staging_gitmodules_ok(struct index_state *istate)\n }\n \n static int for_each_remote_ref_submodule(const char *submodule,\n-\t\t\t\t\t each_ref_fn fn, void *cb_data)\n+\t\t\t\t\t refs_for_each_cb fn, void *cb_data)\n {\n \treturn refs_for_each_remote_ref(repo_get_submodule_ref_store(the_repository,\n \t\t\t\t\t\t\t\t     submodule),\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 2d2b70cbf2..7fe397b0d0 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -607,7 +607,7 @@ static int allow_hidden_refs(enum allow_uor allow_uor)\n \treturn !(allow_uor & (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1));\n }\n \n-static void for_each_namespaced_ref_1(each_ref_fn fn,\n+static void for_each_namespaced_ref_1(refs_for_each_cb fn,\n \t\t\t\t      struct upload_pack_data *data)\n {\n \tconst char **excludes = NULL;\ndiff --git a/worktree.c b/worktree.c\nindex 9308389cb6..bf8c54c04d 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -575,7 +575,7 @@ void strbuf_worktree_ref(const struct worktree *wt,\n \tstrbuf_addstr(sb, refname);\n }\n \n-int other_head_refs(each_ref_fn fn, void *cb_data)\n+int other_head_refs(refs_for_each_cb fn, void *cb_data)\n {\n \tstruct worktree **worktrees, **p;\n \tstruct strbuf refname = STRBUF_INIT;\ndiff --git a/worktree.h b/worktree.h\nindex e4bcccdc0a..12484a91a7 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -191,7 +191,7 @@ int is_shared_symref(const struct worktree *wt,\n  * Similar to head_ref() for all HEADs _except_ one from the current\n  * worktree, which is covered by head_ref().\n  */\n-int other_head_refs(each_ref_fn fn, void *cb_data);\n+int other_head_refs(refs_for_each_cb fn, void *cb_data);\n \n int is_worktree_being_rebased(const struct worktree *wt, const char *target);\n int is_worktree_being_bisected(const struct worktree *wt, const char *target);\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536475","messageId":"20260220-pks-refs-for-each-unification-v1-5-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:09Z","receivedAt":"2026-02-20T08:24:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Remove the unused `refs_for_each_include_root_ref()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 7 -------\n refs.h | 6 ------\n 2 files changed, 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex e9ac0a7101..a45cc61211 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1932,13 +1932,6 @@ int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n \t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n-int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,\n-\t\t\t\t    void *cb_data)\n-{\n-\treturn do_for_each_ref(refs, \"\", NULL, fn, 0,\n-\t\t\t       REFS_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);\n-}\n-\n static int qsort_strcmp(const void *va, const void *vb)\n {\n \tconst char *a = *(const char **)va;\ndiff --git a/refs.h b/refs.h\nindex e37574009b..5190e98b2c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -509,12 +509,6 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d\n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n \t\t\t    refs_for_each_cb fn, void *cb_data);\n \n-/*\n- * Iterates over all refs including root refs, i.e. pseudorefs and HEAD.\n- */\n-int refs_for_each_include_root_refs(struct ref_store *refs, refs_for_each_cb fn,\n-\t\t\t\t    void *cb_data);\n-\n /*\n  * Normalizes partial refs to their fully qualified form.\n  * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536476","messageId":"20260220-pks-refs-for-each-unification-v1-6-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 06/17] refs: introduce `refs_for_each_ref_ext`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:10Z","receivedAt":"2026-02-20T08:24:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the refs subsystem we have a proliferation of functions that all\niterate through references. (Almost) all of these functions internally\ncall `do_for_each_ref()` and provide slightly different arguments so\nthat one can control different aspects of its behaviour. This approach\ndoesn't really scale: every time there is a slightly different use case\nfor iterating through refs we create another new function.\n\nThis combinatorial explosion doesn't make a lot of sense: it leads to\nconfusing interfaces and heightens the maintenance burden.\n\nRefactor the code to become more composable by:\n\n  - Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.\n\n  - Introducing an options structure that lets the caller control\n    individual options.\n\nThis gives us a much better foundation to build on going forward.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 78 ++++++++++++++++++++++++++++++++++++++++--------------------------\n refs.h | 29 +++++++++++++++++++++++++\n 2 files changed, 77 insertions(+), 30 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a45cc61211..ec9e466381 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(\n \treturn iter;\n }\n \n-static int do_for_each_ref(struct ref_store *refs, const char *prefix,\n-\t\t\t   const char **exclude_patterns,\n-\t\t\t   refs_for_each_cb fn, int trim,\n-\t\t\t   enum refs_for_each_flag flags, void *cb_data)\n+int refs_for_each_ref_ext(struct ref_store *refs,\n+\t\t\t  refs_for_each_cb cb, void *cb_data,\n+\t\t\t  const struct refs_for_each_ref_options *opts)\n {\n \tstruct ref_iterator *iter;\n \n \tif (!refs)\n \t\treturn 0;\n \n-\titer = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,\n-\t\t\t\t       flags);\n+\titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n+\t\t\t\t       opts->exclude_patterns,\n+\t\t\t\t       opts->trim_prefix, opts->flags);\n \n-\treturn do_for_each_ref_iterator(iter, fn, cb_data);\n+\treturn do_for_each_ref_iterator(iter, cb, cb_data);\n }\n \n-int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n-\treturn do_for_each_ref(refs, \"\", NULL, fn, 0, 0, cb_data);\n+\tstruct refs_for_each_ref_options opts = { 0 };\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t refs_for_each_cb fn, void *cb_data)\n+\t\t\t refs_for_each_cb cb, void *cb_data)\n {\n-\treturn do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = prefix,\n+\t\t.trim_prefix = strlen(prefix),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n \t\t\t     const char **exclude_patterns,\n-\t\t\t     refs_for_each_cb fn, void *cb_data)\n+\t\t\t     refs_for_each_cb cb, void *cb_data)\n {\n-\treturn do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = prefix,\n+\t\t.exclude_patterns = exclude_patterns,\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n-\treturn do_for_each_ref(refs, git_replace_ref_base, NULL, fn,\n-\t\t\t       strlen(git_replace_ref_base),\n-\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = git_replace_ref_base,\n+\t\t.trim_prefix = strlen(git_replace_ref_base),\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t const char **exclude_patterns,\n-\t\t\t\t refs_for_each_cb fn, void *cb_data)\n+\t\t\t\t refs_for_each_cb cb, void *cb_data)\n {\n+\tstruct refs_for_each_ref_options opts = { 0 };\n \tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n \tstruct strbuf prefix = STRBUF_INIT;\n \tint ret;\n \n-\texclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n-\t\t\t\t\t\t\t   get_git_namespace(),\n-\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n-\n+\topts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n+\t\t\t\t\t\t\t\tget_git_namespace(),\n+\t\t\t\t\t\t\t\t&namespaced_exclude_patterns);\n \tstrbuf_addf(&prefix, \"%srefs/\", get_git_namespace());\n-\tret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);\n+\topts.prefix = prefix.buf;\n+\n+\tret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n \n \tstrvec_clear(&namespaced_exclude_patterns);\n \tstrbuf_release(&prefix);\n@@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d\n }\n \n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t    refs_for_each_cb fn, void *cb_data)\n+\t\t\t    refs_for_each_cb cb, void *cb_data)\n {\n-\treturn do_for_each_ref(refs, prefix, NULL, fn, 0,\n-\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = prefix,\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n static int qsort_strcmp(const void *va, const void *vb)\n@@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n \t\t\t\t    struct strbuf *errbuf)\n {\n \tstruct ref_store *old_refs = NULL, *new_refs = NULL;\n+\tstruct refs_for_each_ref_options for_each_ref_opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n \tstruct ref_transaction *transaction = NULL;\n \tstruct strbuf new_gitdir = STRBUF_INIT;\n \tstruct migration_data data = {\n@@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n \tdata.errbuf = errbuf;\n \n \t/*\n-\t * We need to use the internal `do_for_each_ref()` here so that we can\n+\t * We need to use `refs_for_each_ref_ext()` here so that we can\n \t * also include broken refs and symrefs. These would otherwise be\n \t * skipped silently.\n \t *\n@@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n \t * allow for a central lock due to its design. It's thus on the user to\n \t * ensure that there are no concurrent writes.\n \t */\n-\tret = do_for_each_ref(old_refs, \"\", NULL, migrate_one_ref, 0,\n-\t\t\t      REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,\n-\t\t\t      &data);\n+\tret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);\n \tif (ret < 0)\n \t\tgoto done;\n \ndiff --git a/refs.h b/refs.h\nindex 5190e98b2c..bb9c64a51c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,\n int refs_head_ref_namespaced(struct ref_store *refs,\n \t\t\t     refs_for_each_cb fn, void *cb_data);\n \n+\n+struct refs_for_each_ref_options {\n+\t/* Only iterate over references that have this given prefix. */\n+\tconst char *prefix;\n+\n+\t/*\n+\t * Exclude any references that match any of these patterns on a\n+\t * best-effort basis. The caller needs to be prepared for the exclude\n+\t * patterns to be ignored.\n+\t *\n+\t * The array must be terminated with a NULL sentinel value.\n+\t */\n+\tconst char **exclude_patterns;\n+\n+\t/*\n+\t * The number of bytes to trim from the refname. Note that the trimmed\n+\t * bytes must not cause the reference to become empty. As such, this\n+\t * field should typically only be set when one uses a `prefix` ending\n+\t * in a slash.\n+\t */\n+\tsize_t trim_prefix;\n+\n+\t/* Flags that change which refs will be included. */\n+\tenum refs_for_each_flag flags;\n+};\n+\n int refs_for_each_ref(struct ref_store *refs,\n \t\t      refs_for_each_cb fn, void *cb_data);\n+int refs_for_each_ref_ext(struct ref_store *refs,\n+\t\t\t  refs_for_each_cb cb, void *cb_data,\n+\t\t\t  const struct refs_for_each_ref_options *opts);\n int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n \t\t\t refs_for_each_cb fn, void *cb_data);\n int refs_for_each_tag_ref(struct ref_store *refs,\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536477","messageId":"20260220-pks-refs-for-each-unification-v1-7-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:11Z","receivedAt":"2026-02-20T08:24:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `refs_for_each_glob_ref_in()` can be used to iterate\nthrough all refs in a specific prefix with globbing. The logic to handle\nthis is currently hosted by `refs_for_each_glob_ref_in()`, which sets up\na callback function that knows to filter out refs that _don't_ match the\ngiven globbing pattern.\n\nThe way we do this is somewhat inefficient though: even though the\nfunction is expected to only yield refs in the given prefix, we still\nend up iterating through _all_ references, regardless of whether or not\ntheir name matches the given prefix.\n\nExtend `refs_for_each_ref_ext()` so that it can handle patterns and\nadapt `refs_for_each_glob_ref_in()` to use it. This means we continue to\nuse the same callback-based infrastructure to filter individual refs via\nthe globbing pattern, but we can now also use the other functionality of\nthe `_ext()` variant.\n\nMost importantly, this means that we now properly handle the prefix.\nThis results in a performance improvement when using a prefix where a\nsignificant majority of refs exists outside of the prefix. The following\nbenchmark is an extreme case, with 1 million refs that exist outside the\nprefix and a single ref that exists inside it:\n\n    Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)\n      Time (mean ± σ):     115.9 ms ±   0.7 ms    [User: 113.0 ms, System: 2.4 ms]\n      Range (min … max):   114.9 ms … 117.8 ms    25 runs\n\n    Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)\n      Time (mean ± σ):       1.1 ms ±   0.1 ms    [User: 0.3 ms, System: 0.7 ms]\n      Range (min … max):     1.0 ms …   2.3 ms    2092 runs\n\n    Summary\n      git rev-parse --branches=refs/heads/* (rev = HEAD) ran\n      107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 69 ++++++++++++++++++++++++++++++++++++++----------------------------\n refs.h | 10 ++++++++++\n 2 files changed, 50 insertions(+), 29 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ec9e466381..ac34bbe6c1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -590,40 +590,23 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n \tstrbuf_release(&normalized_pattern);\n }\n \n-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,\n+int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,\n \t\t\t      const char *pattern, const char *prefix, void *cb_data)\n {\n-\tstruct strbuf real_pattern = STRBUF_INIT;\n-\tstruct for_each_ref_filter filter;\n-\tint ret;\n-\n-\tif (!prefix && !starts_with(pattern, \"refs/\"))\n-\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n-\telse if (prefix)\n-\t\tstrbuf_addstr(&real_pattern, prefix);\n-\tstrbuf_addstr(&real_pattern, pattern);\n-\n-\tif (!has_glob_specials(pattern)) {\n-\t\t/* Append implied '/' '*' if not present. */\n-\t\tstrbuf_complete(&real_pattern, '/');\n-\t\t/* No need to check for '*', there is none. */\n-\t\tstrbuf_addch(&real_pattern, '*');\n-\t}\n-\n-\tfilter.pattern = real_pattern.buf;\n-\tfilter.prefix = prefix;\n-\tfilter.fn = fn;\n-\tfilter.cb_data = cb_data;\n-\tret = refs_for_each_ref(refs, for_each_filter_refs, &filter);\n-\n-\tstrbuf_release(&real_pattern);\n-\treturn ret;\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.pattern = pattern,\n+\t\t.prefix = prefix,\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n+int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,\n \t\t\t   const char *pattern, void *cb_data)\n {\n-\treturn refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.pattern = pattern,\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n const char *prettify_refname(const char *name)\n@@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n \t\t\t  refs_for_each_cb cb, void *cb_data,\n \t\t\t  const struct refs_for_each_ref_options *opts)\n {\n+\tstruct strbuf real_pattern = STRBUF_INIT;\n+\tstruct for_each_ref_filter filter;\n \tstruct ref_iterator *iter;\n+\tint ret;\n \n \tif (!refs)\n \t\treturn 0;\n \n+\tif (opts->pattern) {\n+\t\tif (!opts->prefix && !starts_with(opts->pattern, \"refs/\"))\n+\t\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n+\t\telse if (opts->prefix)\n+\t\t\tstrbuf_addstr(&real_pattern, opts->prefix);\n+\t\tstrbuf_addstr(&real_pattern, opts->pattern);\n+\n+\t\tif (!has_glob_specials(opts->pattern)) {\n+\t\t\t/* Append implied '/' '*' if not present. */\n+\t\t\tstrbuf_complete(&real_pattern, '/');\n+\t\t\t/* No need to check for '*', there is none. */\n+\t\t\tstrbuf_addch(&real_pattern, '*');\n+\t\t}\n+\n+\t\tfilter.pattern = real_pattern.buf;\n+\t\tfilter.prefix = opts->prefix;\n+\t\tfilter.fn = cb;\n+\t\tfilter.cb_data = cb_data;\n+\n+\t\tcb = for_each_filter_refs;\n+\t\tcb_data = &filter;\n+\t}\n+\n \titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n \t\t\t\t       opts->exclude_patterns,\n \t\t\t\t       opts->trim_prefix, opts->flags);\n \n-\treturn do_for_each_ref_iterator(iter, cb, cb_data);\n+\tret = do_for_each_ref_iterator(iter, cb, cb_data);\n+\tstrbuf_release(&real_pattern);\n+\treturn ret;\n }\n \n int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\ndiff --git a/refs.h b/refs.h\nindex bb9c64a51c..a66dbf3865 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -458,6 +458,16 @@ struct refs_for_each_ref_options {\n \t/* Only iterate over references that have this given prefix. */\n \tconst char *prefix;\n \n+\t/*\n+\t * A globbing pattern that can be used to only yield refs that match.\n+\t * If given, refs will be matched against the pattern with\n+\t * `wildmatch()`.\n+\t *\n+\t * If the pattern doesn't contain any globbing characters then it is\n+\t * treated as if it was ending with \"/\" and \"*\".\n+\t */\n+\tconst char *pattern;\n+\n \t/*\n \t * Exclude any references that match any of these patterns on a\n \t * best-effort basis. The caller needs to be prepared for the exclude\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536478","messageId":"20260220-pks-refs-for-each-unification-v1-8-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:12Z","receivedAt":"2026-02-20T08:24:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `refs_for_each_namespaced_ref()` iterates through all\nreferences that are part of the current ref namespace. This namespace\ncan be configured by setting the `GIT_NAMESPACE` environment variable\nand is then retrieved by calling `get_git_namespace()`.\n\nIf a namespace is configured, then we:\n\n  - Obviously only yield refs that exist in this namespace.\n\n  - Rewrite exclude patterns so that they work for the given namespace,\n    if any namespace is currently configured.\n\nPort this logic to `refs_for_each_ref_ext()` by adding a new `namespace`\nfield to the options structure. This gives callers more flexibility as\nthey can decide by themselves whether they want to use the globally\nconfigured or an arbitrary other namespace.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 47 +++++++++++++++++++++++++++++------------------\n refs.h |  6 ++++++\n 2 files changed, 35 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ac34bbe6c1..99994879d9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1845,9 +1845,13 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n \t\t\t  refs_for_each_cb cb, void *cb_data,\n \t\t\t  const struct refs_for_each_ref_options *opts)\n {\n+\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n+\tstruct strbuf namespaced_prefix = STRBUF_INIT;\n \tstruct strbuf real_pattern = STRBUF_INIT;\n \tstruct for_each_ref_filter filter;\n \tstruct ref_iterator *iter;\n+\tconst char **exclude_patterns;\n+\tconst char *prefix;\n \tint ret;\n \n \tif (!refs)\n@@ -1876,11 +1880,29 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n \t\tcb_data = &filter;\n \t}\n \n-\titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n-\t\t\t\t       opts->exclude_patterns,\n+\tif (opts->namespace) {\n+\t\tstrbuf_addstr(&namespaced_prefix, opts->namespace);\n+\t\tif (opts->prefix)\n+\t\t\tstrbuf_addstr(&namespaced_prefix, opts->prefix);\n+\t\telse\n+\t\t\tstrbuf_addstr(&namespaced_prefix, \"refs/\");\n+\n+\t\tprefix = namespaced_prefix.buf;\n+\t\texclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,\n+\t\t\t\t\t\t\t\t   opts->namespace,\n+\t\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n+\t} else {\n+\t\tprefix = opts->prefix ? opts->prefix : \"\";\n+\t\texclude_patterns = opts->exclude_patterns;\n+\t}\n+\n+\titer = refs_ref_iterator_begin(refs, prefix, exclude_patterns,\n \t\t\t\t       opts->trim_prefix, opts->flags);\n \n \tret = do_for_each_ref_iterator(iter, cb, cb_data);\n+\n+\tstrvec_clear(&namespaced_exclude_patterns);\n+\tstrbuf_release(&namespaced_prefix);\n \tstrbuf_release(&real_pattern);\n \treturn ret;\n }\n@@ -1927,22 +1949,11 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t const char **exclude_patterns,\n \t\t\t\t refs_for_each_cb cb, void *cb_data)\n {\n-\tstruct refs_for_each_ref_options opts = { 0 };\n-\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n-\tstruct strbuf prefix = STRBUF_INIT;\n-\tint ret;\n-\n-\topts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n-\t\t\t\t\t\t\t\tget_git_namespace(),\n-\t\t\t\t\t\t\t\t&namespaced_exclude_patterns);\n-\tstrbuf_addf(&prefix, \"%srefs/\", get_git_namespace());\n-\topts.prefix = prefix.buf;\n-\n-\tret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-\n-\tstrvec_clear(&namespaced_exclude_patterns);\n-\tstrbuf_release(&prefix);\n-\treturn ret;\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.exclude_patterns = exclude_patterns,\n+\t\t.namespace = get_git_namespace(),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\ndiff --git a/refs.h b/refs.h\nindex a66dbf3865..2bde60aa0e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -468,6 +468,12 @@ struct refs_for_each_ref_options {\n \t */\n \tconst char *pattern;\n \n+\t/*\n+\t * If set, only yield refs part of the configured namespace. Exclude\n+\t * patterns will be rewritten to apply to the namespace.\n+\t */\n+\tconst char *namespace;\n+\n \t/*\n \t * Exclude any references that match any of these patterns on a\n \t * best-effort basis. The caller needs to be prepared for the exclude\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536479","messageId":"20260220-pks-refs-for-each-unification-v1-9-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:13Z","receivedAt":"2026-02-20T08:24:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `refs_for_each_fullref_in_prefixes()` can be used to\niterate over all references part of any of the user-provided prefixes.\nIn contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it\nknows to handle the case well where multiple of the passed-in prefixes\nstart with a common prefix by computing longest common prefixes and then\niterating over those.\n\nWhile we could move this logic into `refs_for_each_ref_ext()`, this one\nfeels somewhat special as we perform multiple iterations. But what we\n_can_ do is to generalize how this function works: instead of accepting\nonly a small handful of parameters, we can have it accept the full\noptions structure.\n\nOne obvious exception is that the caller must not provide a prefix via\nthe options. But this case can be easily detected.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ls-refs.c    | 11 +++++++----\n ref-filter.c | 11 +++++++----\n refs.c       | 39 +++++++++++++++------------------------\n refs.h       | 16 +++++-----------\n 4 files changed, 34 insertions(+), 43 deletions(-)\n\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 8641281b86..9759826ca7 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,\n \n int ls_refs(struct repository *r, struct packet_reader *request)\n {\n+\tstruct refs_for_each_ref_options opts = { 0 };\n \tstruct ls_refs_data data;\n \n \tmemset(&data, 0, sizeof(data));\n@@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)\n \tsend_possibly_unborn_head(&data);\n \tif (!data.prefixes.nr)\n \t\tstrvec_push(&data.prefixes, \"\");\n-\trefs_for_each_fullref_in_prefixes(get_main_ref_store(r),\n-\t\t\t\t\t  get_git_namespace(), data.prefixes.v,\n-\t\t\t\t\t  hidden_refs_to_excludes(&data.hidden_refs),\n-\t\t\t\t\t  send_ref, &data);\n+\n+\topts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);\n+\topts.namespace = get_git_namespace();\n+\n+\trefs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,\n+\t\t\t\t      &opts, send_ref, &data);\n \tpacket_fflush(stdout);\n \tstrvec_clear(&data.prefixes);\n \tstrbuf_release(&data.buf);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 049e845a19..7c682e0a33 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\t\t\t       refs_for_each_cb cb,\n \t\t\t\t       void *cb_data)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.exclude_patterns = filter->exclude.v,\n+\t};\n+\n \tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn for_each_fullref_with_seek(filter, cb, cb_data,\n@@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\treturn for_each_fullref_with_seek(filter, cb, cb_data, 0);\n \t}\n \n-\treturn refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t NULL, filter->name_patterns,\n-\t\t\t\t\t\t filter->exclude.v,\n-\t\t\t\t\t\t cb, cb_data);\n+\treturn refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),\n+\t\t\t\t\t     filter->name_patterns, &opts,\n+\t\t\t\t\t     cb, cb_data);\n }\n \n /*\ndiff --git a/refs.c b/refs.c\nindex 99994879d9..20d34faeb5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2029,40 +2029,31 @@ static void find_longest_prefixes(struct string_list *out,\n \tstrbuf_release(&prefix);\n }\n \n-int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,\n-\t\t\t\t      const char *namespace,\n-\t\t\t\t      const char **patterns,\n-\t\t\t\t      const char **exclude_patterns,\n-\t\t\t\t      refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,\n+\t\t\t\t  const char **prefixes,\n+\t\t\t\t  const struct refs_for_each_ref_options *opts,\n+\t\t\t\t  refs_for_each_cb cb, void *cb_data)\n {\n-\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n-\tstruct string_list prefixes = STRING_LIST_INIT_DUP;\n+\tstruct string_list longest_prefixes = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *prefix;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint ret = 0, namespace_len;\n+\tint ret = 0;\n \n-\tfind_longest_prefixes(&prefixes, patterns);\n+\tif (opts->prefix)\n+\t\tBUG(\"refs_for_each_ref_in_prefixes called with specific prefix\");\n \n-\tif (namespace)\n-\t\tstrbuf_addstr(&buf, namespace);\n-\tnamespace_len = buf.len;\n+\tfind_longest_prefixes(&longest_prefixes, prefixes);\n \n-\texclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n-\t\t\t\t\t\t\t   namespace,\n-\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n+\tfor_each_string_list_item(prefix, &longest_prefixes) {\n+\t\tstruct refs_for_each_ref_options prefix_opts = *opts;\n+\t\tprefix_opts.prefix = prefix->string;\n \n-\tfor_each_string_list_item(prefix, &prefixes) {\n-\t\tstrbuf_addstr(&buf, prefix->string);\n-\t\tret = refs_for_each_fullref_in(ref_store, buf.buf,\n-\t\t\t\t\t       exclude_patterns, fn, cb_data);\n+\t\tret = refs_for_each_ref_ext(ref_store, cb, cb_data,\n+\t\t\t\t\t    &prefix_opts);\n \t\tif (ret)\n \t\t\tbreak;\n-\t\tstrbuf_setlen(&buf, namespace_len);\n \t}\n \n-\tstrvec_clear(&namespaced_exclude_patterns);\n-\tstring_list_clear(&prefixes, 0);\n-\tstrbuf_release(&buf);\n+\tstring_list_clear(&longest_prefixes, 0);\n \treturn ret;\n }\n \ndiff --git a/refs.h b/refs.h\nindex 2bde60aa0e..65d1665c72 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -520,19 +520,13 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n \t\t\t     refs_for_each_cb fn, void *cb_data);\n \n /**\n- * iterate all refs in \"patterns\" by partitioning patterns into disjoint sets\n+ * Iterate all refs in \"prefixes\" by partitioning prefixes into disjoint sets\n  * and iterating the longest-common prefix of each set.\n- *\n- * references matching any pattern in \"exclude_patterns\" are omitted from the\n- * result set on a best-effort basis.\n- *\n- * callers should be prepared to ignore references that they did not ask for.\n  */\n-int refs_for_each_fullref_in_prefixes(struct ref_store *refs,\n-\t\t\t\t      const char *namespace,\n-\t\t\t\t      const char **patterns,\n-\t\t\t\t      const char **exclude_patterns,\n-\t\t\t\t      refs_for_each_cb fn, void *cb_data);\n+int refs_for_each_ref_in_prefixes(struct ref_store *refs,\n+\t\t\t\t  const char **prefixes,\n+\t\t\t\t  const struct refs_for_each_ref_options *opts,\n+\t\t\t\t  refs_for_each_cb cb, void *cb_data);\n \n /* iterates all refs that match the specified glob pattern. */\n int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536480","messageId":"20260220-pks-refs-for-each-unification-v1-10-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 10/17] refs: improve verification for-each-ref options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:14Z","receivedAt":"2026-02-20T08:24:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Improve verification of the passed-in for-each-ref options:\n\n  - Require that the `refs` store must be given. It's arguably very\n    surprising that we simply return successfully in case the ref store\n    is a `NULL` pointer.\n\n  - When expected to trim ref prefixes we will `BUG()` in case the\n    refname would become empty or in case we're expected to trim a\n    longer prefix than the refname is long. As such, this case is only\n    guaranteed to _not_ `BUG()` in case the caller also specified a\n    prefix. And furthermore, that prefix must end in a trailing slash,\n    as otherwise it may produce an exact match that could lead us to\n    trim to the empty string.\n\nAn audit shows that there are no callsites that rely on either of these\nbehaviours, so this should not result in a functional change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 13 ++++++++++++-\n 1 file changed, 12 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 20d34faeb5..3b676432b4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1855,7 +1855,18 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n \tint ret;\n \n \tif (!refs)\n-\t\treturn 0;\n+\t\tBUG(\"no refs passed\");\n+\n+\tif (opts->trim_prefix) {\n+\t\tsize_t prefix_len;\n+\n+\t\tif (!opts->prefix)\n+\t\t\tBUG(\"trimming only allowed with a prefix\");\n+\n+\t\tprefix_len = strlen(opts->prefix);\n+\t\tif (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')\n+\t\t\tBUG(\"ref pattern must end in a trailing slash when trimming\");\n+\t}\n \n \tif (opts->pattern) {\n \t\tif (!opts->prefix && !starts_with(opts->pattern, \"refs/\"))\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536481","messageId":"20260220-pks-refs-for-each-unification-v1-11-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 11/17] refs: replace `refs_for_each_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:15Z","receivedAt":"2026-02-20T08:24:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_ref_in()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                  |  8 ++++++--\n builtin/rev-parse.c       | 13 +++++++++----\n pack-bitmap.c             | 13 +++++++------\n refs.c                    | 34 ++++++++++++++++++----------------\n refs.h                    |  2 --\n t/helper/test-ref-store.c |  7 +++++--\n 6 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 2bdad4ee42..296836c154 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n \n static int read_bisect_refs(void)\n {\n-\treturn refs_for_each_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t    \"refs/bisect/\", register_ref, NULL);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/bisect/\",\n+\t\t.trim_prefix = strlen(\"refs/bisect/\"),\n+\t};\n+\treturn refs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t     register_ref, NULL, &opts);\n }\n \n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 9032cc6327..02703f2fb8 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)\n \n static void handle_ref_opt(const char *pattern, const char *prefix)\n {\n-\tif (pattern)\n+\tif (pattern) {\n \t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n \t\t\t\t\t  show_reference, pattern, prefix,\n \t\t\t\t\t  NULL);\n-\telse\n-\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t     prefix, show_reference, NULL);\n+\t} else {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.prefix = prefix,\n+\t\t\t.trim_prefix = strlen(prefix),\n+\t\t};\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      show_reference, NULL, &opts);\n+\t}\n \tclear_ref_exclusions(&ref_excludes);\n }\n \ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex efef7081e6..22419bfb33 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -3326,6 +3326,7 @@ static const struct string_list *bitmap_preferred_tips(struct repository *r)\n void for_each_preferred_bitmap_tip(struct repository *repo,\n \t\t\t\t   refs_for_each_cb cb, void *cb_data)\n {\n+\tstruct refs_for_each_ref_options opts = { 0 };\n \tstruct string_list_item *item;\n \tconst struct string_list *preferred_tips;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -3335,16 +3336,16 @@ void for_each_preferred_bitmap_tip(struct repository *repo,\n \t\treturn;\n \n \tfor_each_string_list_item(item, preferred_tips) {\n-\t\tconst char *pattern = item->string;\n+\t\topts.prefix = item->string;\n \n-\t\tif (!ends_with(pattern, \"/\")) {\n+\t\tif (!ends_with(opts.prefix, \"/\")) {\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addf(&buf, \"%s/\", pattern);\n-\t\t\tpattern = buf.buf;\n+\t\t\tstrbuf_addf(&buf, \"%s/\", opts.prefix);\n+\t\t\topts.prefix = buf.buf;\n \t\t}\n \n-\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n-\t\t\t\t     pattern, cb, cb_data);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(repo),\n+\t\t\t\t      cb, cb_data, &opts);\n \t}\n \n \tstrbuf_release(&buf);\ndiff --git a/refs.c b/refs.c\nindex 3b676432b4..b42a1bfa3f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -527,19 +527,31 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n \trefs_for_each_rawref(refs, warn_if_dangling_symref, &data);\n }\n \n-int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n-\treturn refs_for_each_ref_in(refs, \"refs/tags/\", fn, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/tags/\",\n+\t\t.trim_prefix = strlen(\"refs/tags/\"),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n-\treturn refs_for_each_ref_in(refs, \"refs/heads/\", fn, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/heads/\",\n+\t\t.trim_prefix = strlen(\"refs/heads/\"),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n-\treturn refs_for_each_ref_in(refs, \"refs/remotes/\", fn, cb_data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/remotes/\",\n+\t\t.trim_prefix = strlen(\"refs/remotes/\"),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n@@ -1924,16 +1936,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data\n \treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t refs_for_each_cb cb, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.prefix = prefix,\n-\t\t.trim_prefix = strlen(prefix),\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n \t\t\t     const char **exclude_patterns,\n \t\t\t     refs_for_each_cb cb, void *cb_data)\ndiff --git a/refs.h b/refs.h\nindex 65d1665c72..c9f8d3e6cb 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -500,8 +500,6 @@ int refs_for_each_ref(struct ref_store *refs,\n int refs_for_each_ref_ext(struct ref_store *refs,\n \t\t\t  refs_for_each_cb cb, void *cb_data,\n \t\t\t  const struct refs_for_each_ref_options *opts);\n-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t refs_for_each_cb fn, void *cb_data);\n int refs_for_each_tag_ref(struct ref_store *refs,\n \t\t\t  refs_for_each_cb fn, void *cb_data);\n int refs_for_each_branch_ref(struct ref_store *refs,\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex b1215947c5..a2ef1b6949 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -163,8 +163,11 @@ static int each_ref(const struct reference *ref, void *cb_data UNUSED)\n static int cmd_for_each_ref(struct ref_store *refs, const char **argv)\n {\n \tconst char *prefix = notnull(*argv++, \"prefix\");\n-\n-\treturn refs_for_each_ref_in(refs, prefix, each_ref, NULL);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = prefix,\n+\t\t.trim_prefix = strlen(prefix),\n+\t};\n+\treturn refs_for_each_ref_ext(refs, each_ref, NULL, &opts);\n }\n \n static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536482","messageId":"20260220-pks-refs-for-each-unification-v1-12-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 12/17] refs: replace `refs_for_each_rawref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:16Z","receivedAt":"2026-02-20T08:25:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_rawref()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/describe.c   |  7 +++++--\n builtin/fsck.c       |  7 +++++--\n fetch-pack.c         | 15 +++++++++++----\n refs.c               | 10 ++++------\n refs.h               |  1 -\n refs/files-backend.c |  7 +++++--\n 6 files changed, 30 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex abfe3525a5..bffeed13a3 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -641,6 +641,9 @@ int cmd_describe(int argc,\n \t\t const char *prefix,\n \t\t struct repository *repo UNUSED )\n {\n+\tstruct refs_for_each_ref_options for_each_ref_opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n \tint contains = 0;\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"contains\",   &contains, N_(\"find the tag that comes after the commit\")),\n@@ -738,8 +741,8 @@ int cmd_describe(int argc,\n \t}\n \n \thashmap_init(&names, commit_name_neq, NULL, 0);\n-\trefs_for_each_rawref(get_main_ref_store(the_repository), get_name,\n-\t\t\t     NULL);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      get_name, NULL, &for_each_ref_opts);\n \tif (!hashmap_get_size(&names) && !always)\n \t\tdie(_(\"No names found, cannot describe anything.\"));\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 0512f78a87..24cdb657f5 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -598,6 +598,9 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)\n \n static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n \tstruct worktree **worktrees, **p;\n \tconst char *head_points_at;\n \tstruct object_id head_oid;\n@@ -623,8 +626,8 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)\n \t\treturn;\n \t}\n \n-\trefs_for_each_rawref(get_main_ref_store(the_repository),\n-\t\t\t     snapshot_ref, snap);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      snapshot_ref, snap, &opts);\n \n \tworktrees = get_worktrees();\n \tfor (p = worktrees; *p; p++) {\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 40316c9a34..570caa03fa 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -292,11 +292,14 @@ static int next_flush(int stateless_rpc, int count)\n static void mark_tips(struct fetch_negotiator *negotiator,\n \t\t      const struct oid_array *negotiation_tips)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n \tint i;\n \n \tif (!negotiation_tips) {\n-\t\trefs_for_each_rawref(get_main_ref_store(the_repository),\n-\t\t\t\t     rev_list_insert_ref_oid, negotiator);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      rev_list_insert_ref_oid, negotiator, &opts);\n \t\treturn;\n \t}\n \n@@ -792,8 +795,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,\n \t */\n \ttrace2_region_enter(\"fetch-pack\", \"mark_complete_local_refs\", NULL);\n \tif (!args->deepen) {\n-\t\trefs_for_each_rawref(get_main_ref_store(the_repository),\n-\t\t\t\t     mark_complete_oid, NULL);\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t\t};\n+\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      mark_complete_oid, NULL, &opts);\n \t\tfor_each_cached_alternate(NULL, mark_alternate_complete);\n \t\tif (cutoff)\n \t\t\tmark_recent_complete_commits(args, cutoff);\ndiff --git a/refs.c b/refs.c\nindex b42a1bfa3f..5739804870 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -524,7 +524,10 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n \t\t.indent = indent,\n \t\t.dry_run = dry_run,\n \t};\n-\trefs_for_each_rawref(refs, warn_if_dangling_symref, &data);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n+\trefs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts);\n }\n \n int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n@@ -1969,11 +1972,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n-{\n-\treturn refs_for_each_rawref_in(refs, \"\", fn, cb_data);\n-}\n-\n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n \t\t\t    refs_for_each_cb cb, void *cb_data)\n {\ndiff --git a/refs.h b/refs.h\nindex c9f8d3e6cb..7a0c9c3693 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -542,7 +542,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t refs_for_each_cb fn, void *cb_data);\n \n /* can be used to learn about broken ref and symref */\n-int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data);\n int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n \t\t\t    refs_for_each_cb fn, void *cb_data);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 6c98e14414..ab96760781 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3149,6 +3149,9 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t\t\t\t\t    struct ref_transaction *transaction,\n \t\t\t\t\t    struct strbuf *err)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t};\n \tsize_t i;\n \tint ret = 0;\n \tstruct string_list affected_refnames = STRING_LIST_INIT_NODUP;\n@@ -3173,8 +3176,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t * so here we really only check that none of the references\n \t * that we are creating already exists.\n \t */\n-\tif (refs_for_each_rawref(&refs->base, ref_present,\n-\t\t\t\t &transaction->refnames))\n+\tif (refs_for_each_ref_ext(&refs->base, ref_present,\n+\t\t\t\t  &transaction->refnames, &opts))\n \t\tBUG(\"initial ref transaction called with existing refs\");\n \n \tpacked_transaction = ref_store_transaction_begin(refs->packed_ref_store,\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536483","messageId":"20260220-pks-refs-for-each-unification-v1-13-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 13/17] refs: replace `refs_for_each_rawref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:17Z","receivedAt":"2026-02-20T08:25:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_rawref_in()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c |  8 ++++++--\n refs.c           | 10 ----------\n refs.h           |  4 ----\n 3 files changed, 6 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex ace390c671..0fddaa1773 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -912,6 +912,9 @@ static int mv(int argc, const char **argv, const char *prefix,\n \t\t\t\t\t\told_remote_context.buf);\n \n \tif (refspecs_need_update) {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n+\t\t};\n \t\trename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n \t\t\t\t\t\t\t       0, &err);\n \t\tif (!rename.transaction)\n@@ -923,9 +926,10 @@ static int mv(int argc, const char **argv, const char *prefix,\n \n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"refs/remotes/%s/\", rename.old_name);\n+\t\topts.prefix = buf.buf;\n \n-\t\tresult = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf,\n-\t\t\t\trename_one_ref, &rename);\n+\t\tresult = refs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t\t       rename_one_ref, &rename, &opts);\n \t\tif (result < 0)\n \t\t\tdie(_(\"queueing remote ref renames failed: %s\"), rename.err->buf);\n \ndiff --git a/refs.c b/refs.c\nindex 5739804870..454c9dd017 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1972,16 +1972,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t    refs_for_each_cb cb, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.prefix = prefix,\n-\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n static int qsort_strcmp(const void *va, const void *vb)\n {\n \tconst char *a = *(const char **)va;\ndiff --git a/refs.h b/refs.h\nindex 7a0c9c3693..2915ff081a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -541,10 +541,6 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n \t\t\t\t const char **exclude_patterns,\n \t\t\t\t refs_for_each_cb fn, void *cb_data);\n \n-/* can be used to learn about broken ref and symref */\n-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t    refs_for_each_cb fn, void *cb_data);\n-\n /*\n  * Normalizes partial refs to their fully qualified form.\n  * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536484","messageId":"20260220-pks-refs-for-each-unification-v1-14-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 14/17] refs: replace `refs_for_each_glob_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:18Z","receivedAt":"2026-02-20T08:25:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_glob_ref_in()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c    | 34 ++++++++++++++++++++++++----------\n builtin/rev-parse.c |  9 ++++++---\n refs.c              | 10 ----------\n refs.h              |  3 ---\n revision.c          | 27 ++++++++++++++++++---------\n 5 files changed, 48 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 4cc118fb57..774abb3620 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -422,13 +422,16 @@ static void bisect_status(struct bisect_state *state,\n {\n \tchar *bad_ref = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n \tchar *good_glob = xstrfmt(\"%s-*\", terms->term_good);\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.pattern = good_glob,\n+\t\t.prefix = \"refs/bisect/\",\n+\t};\n \n \tif (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))\n \t\tstate->nr_bad = 1;\n \n-\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr,\n-\t\t\t\t  good_glob, \"refs/bisect/\",\n-\t\t\t\t  (void *) &state->nr_good);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      inc_nr, &state->nr_good, &opts);\n \n \tfree(good_glob);\n \tfree(bad_ref);\n@@ -562,6 +565,9 @@ static int add_bisect_ref(const struct reference *ref, void *cb)\n \n static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/bisect/\",\n+\t};\n \tint res = 0;\n \tstruct add_bisect_ref_data cb = { revs };\n \tchar *good = xstrfmt(\"%s-*\", terms->term_good);\n@@ -581,11 +587,16 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)\n \treset_revision_walk();\n \trepo_init_revisions(the_repository, revs, NULL);\n \tsetup_revisions(0, NULL, revs, NULL);\n-\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t  add_bisect_ref, bad, \"refs/bisect/\", &cb);\n+\n+\topts.pattern = bad;\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      add_bisect_ref, &cb, &opts);\n+\n \tcb.object_flags = UNINTERESTING;\n-\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t  add_bisect_ref, good, \"refs/bisect/\", &cb);\n+\topts.pattern = good;\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      add_bisect_ref, &cb, &opts);\n+\n \tif (prepare_revision_walk(revs))\n \t\tres = error(_(\"revision walk setup failed\"));\n \n@@ -1191,10 +1202,13 @@ static int verify_good(const struct bisect_terms *terms, const char *command)\n \tchar *good_glob = xstrfmt(\"%s-*\", terms->term_good);\n \tint no_checkout = refs_ref_exists(get_main_ref_store(the_repository),\n \t\t\t\t\t  \"BISECT_HEAD\");\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.pattern = good_glob,\n+\t\t.prefix = \"refs/bisect/\",\n+\t};\n \n-\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t  get_first_good, good_glob, \"refs/bisect/\",\n-\t\t\t\t  &good_rev);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      get_first_good, &good_rev, &opts);\n \tfree(good_glob);\n \n \tif (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? \"BISECT_HEAD\" : \"HEAD\", &current_rev))\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 02703f2fb8..206f5bda39 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -614,9 +614,12 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)\n static void handle_ref_opt(const char *pattern, const char *prefix)\n {\n \tif (pattern) {\n-\t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t  show_reference, pattern, prefix,\n-\t\t\t\t\t  NULL);\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.pattern = pattern,\n+\t\t\t.prefix = prefix,\n+\t\t};\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      show_reference, NULL, &opts);\n \t} else {\n \t\tstruct refs_for_each_ref_options opts = {\n \t\t\t.prefix = prefix,\ndiff --git a/refs.c b/refs.c\nindex 454c9dd017..1fa16facb8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -605,16 +605,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n \tstrbuf_release(&normalized_pattern);\n }\n \n-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,\n-\t\t\t      const char *pattern, const char *prefix, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.pattern = pattern,\n-\t\t.prefix = prefix,\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,\n \t\t\t   const char *pattern, void *cb_data)\n {\ndiff --git a/refs.h b/refs.h\nindex 2915ff081a..1d21999ae1 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -530,9 +530,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,\n int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t   const char *pattern, void *cb_data);\n \n-int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,\n-\t\t\t      const char *pattern, const char *prefix, void *cb_data);\n-\n /*\n  * references matching any pattern in \"exclude_patterns\" are omitted from the\n  * result set on a best-effort basis.\ndiff --git a/revision.c b/revision.c\nindex 8c206830d5..da7737ce28 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2827,34 +2827,43 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,\n \t\texclude_hidden_refs(&revs->ref_excludes, optarg);\n \t\treturn argcount;\n \t} else if (skip_prefix(arg, \"--branches=\", &optarg)) {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.prefix = \"refs/heads/\",\n+\t\t\t.pattern = optarg,\n+\t\t};\n \t\tstruct all_refs_cb cb;\n \t\tif (revs->ref_excludes.hidden_refs_configured)\n \t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n \t\t\t\t     \"--exclude-hidden\", \"--branches\");\n \t\tinit_all_refs_cb(&cb, revs, *flags);\n-\t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t  handle_one_ref, optarg,\n-\t\t\t\t\t  \"refs/heads/\", &cb);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      handle_one_ref, &cb, &opts);\n \t\tclear_ref_exclusions(&revs->ref_excludes);\n \t} else if (skip_prefix(arg, \"--tags=\", &optarg)) {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.prefix = \"refs/tags/\",\n+\t\t\t.pattern = optarg,\n+\t\t};\n \t\tstruct all_refs_cb cb;\n \t\tif (revs->ref_excludes.hidden_refs_configured)\n \t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n \t\t\t\t     \"--exclude-hidden\", \"--tags\");\n \t\tinit_all_refs_cb(&cb, revs, *flags);\n-\t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t  handle_one_ref, optarg,\n-\t\t\t\t\t  \"refs/tags/\", &cb);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      handle_one_ref, &cb, &opts);\n \t\tclear_ref_exclusions(&revs->ref_excludes);\n \t} else if (skip_prefix(arg, \"--remotes=\", &optarg)) {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.prefix = \"refs/remotes/\",\n+\t\t\t.pattern = optarg,\n+\t\t};\n \t\tstruct all_refs_cb cb;\n \t\tif (revs->ref_excludes.hidden_refs_configured)\n \t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n \t\t\t\t     \"--exclude-hidden\", \"--remotes\");\n \t\tinit_all_refs_cb(&cb, revs, *flags);\n-\t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t  handle_one_ref, optarg,\n-\t\t\t\t\t  \"refs/remotes/\", &cb);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      handle_one_ref, &cb, &opts);\n \t\tclear_ref_exclusions(&revs->ref_excludes);\n \t} else if (!strcmp(arg, \"--reflog\")) {\n \t\tadd_reflogs_to_pending(revs, *flags);\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536485","messageId":"20260220-pks-refs-for-each-unification-v1-15-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 15/17] refs: replace `refs_for_each_glob_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:19Z","receivedAt":"2026-02-20T08:25:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_glob_ref()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fetch.c | 7 +++++--\n notes.c         | 7 +++++--\n refs.c          | 9 ---------\n refs.h          | 4 ----\n revision.c      | 7 +++++--\n 5 files changed, 15 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex a3bc7e9380..a3323fbfd7 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1542,6 +1542,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)\n \n \tfor (i = 0; i < negotiation_tip.nr; i++) {\n \t\tconst char *s = negotiation_tip.items[i].string;\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.pattern = s,\n+\t\t};\n \t\tint old_nr;\n \t\tif (!has_glob_specials(s)) {\n \t\t\tstruct object_id oid;\n@@ -1553,8 +1556,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)\n \t\t\tcontinue;\n \t\t}\n \t\told_nr = oids->nr;\n-\t\trefs_for_each_glob_ref(get_main_ref_store(the_repository),\n-\t\t\t\t       add_oid, s, oids);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      add_oid, oids, &opts);\n \t\tif (old_nr == oids->nr)\n \t\t\twarning(\"ignoring --negotiation-tip=%s because it does not match any refs\",\n \t\t\t\ts);\ndiff --git a/notes.c b/notes.c\nindex 090c48bbd5..51a7ef9f83 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -952,8 +952,11 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob)\n {\n \tassert(list->strdup_strings);\n \tif (has_glob_specials(glob)) {\n-\t\trefs_for_each_glob_ref(get_main_ref_store(the_repository),\n-\t\t\t\t       string_list_add_one_ref, glob, list);\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.pattern = glob,\n+\t\t};\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      string_list_add_one_ref, list, &opts);\n \t} else {\n \t\tstruct object_id oid;\n \t\tif (repo_get_oid(the_repository, glob, &oid))\ndiff --git a/refs.c b/refs.c\nindex 1fa16facb8..e11ee94013 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -605,15 +605,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n \tstrbuf_release(&normalized_pattern);\n }\n \n-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,\n-\t\t\t   const char *pattern, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.pattern = pattern,\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n const char *prettify_refname(const char *name)\n {\n \tif (skip_prefix(name, \"refs/heads/\", &name) ||\ndiff --git a/refs.h b/refs.h\nindex 1d21999ae1..f408367b01 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -526,10 +526,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,\n \t\t\t\t  const struct refs_for_each_ref_options *opts,\n \t\t\t\t  refs_for_each_cb cb, void *cb_data);\n \n-/* iterates all refs that match the specified glob pattern. */\n-int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n-\t\t\t   const char *pattern, void *cb_data);\n-\n /*\n  * references matching any pattern in \"exclude_patterns\" are omitted from the\n  * result set on a best-effort basis.\ndiff --git a/revision.c b/revision.c\nindex da7737ce28..8a6a871b1c 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2814,10 +2814,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,\n \t\thandle_refs(refs, revs, *flags, refs_for_each_remote_ref);\n \t\tclear_ref_exclusions(&revs->ref_excludes);\n \t} else if ((argcount = parse_long_opt(\"glob\", argv, &optarg))) {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.pattern = optarg,\n+\t\t};\n \t\tstruct all_refs_cb cb;\n \t\tinit_all_refs_cb(&cb, revs, *flags);\n-\t\trefs_for_each_glob_ref(get_main_ref_store(the_repository),\n-\t\t\t\t       handle_one_ref, optarg, &cb);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      handle_one_ref, &cb, &opts);\n \t\tclear_ref_exclusions(&revs->ref_excludes);\n \t\treturn argcount;\n \t} else if ((argcount = parse_long_opt(\"exclude\", argv, &optarg))) {\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536486","messageId":"20260220-pks-refs-for-each-unification-v1-16-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 16/17] refs: replace `refs_for_each_namespaced_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:20Z","receivedAt":"2026-02-20T08:25:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_namespaced_ref()` with the newly\nintroduced `refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http-backend.c |  8 ++++++--\n refs.c         | 11 -----------\n refs.h         |  8 --------\n upload-pack.c  | 11 +++++++----\n 4 files changed, 13 insertions(+), 25 deletions(-)\n\ndiff --git a/http-backend.c b/http-backend.c\nindex 0122146df6..1a171c5c5a 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -565,9 +565,13 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)\n \t\trun_service(argv, 0);\n \n \t} else {\n+\t\tstruct refs_for_each_ref_options opts = {\n+\t\t\t.namespace = get_git_namespace(),\n+\t\t};\n+\n \t\tselect_getanyfile(hdr);\n-\t\trefs_for_each_namespaced_ref(get_main_ref_store(the_repository),\n-\t\t\t\t\t     NULL, show_text_ref, &buf);\n+\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t      show_text_ref, &buf, &opts);\n \t\tsend_strbuf(hdr, \"text/plain\", &buf);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/refs.c b/refs.c\nindex e11ee94013..28142fa967 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1942,17 +1942,6 @@ int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void\n \treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_namespaced_ref(struct ref_store *refs,\n-\t\t\t\t const char **exclude_patterns,\n-\t\t\t\t refs_for_each_cb cb, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.exclude_patterns = exclude_patterns,\n-\t\t.namespace = get_git_namespace(),\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n static int qsort_strcmp(const void *va, const void *vb)\n {\n \tconst char *a = *(const char **)va;\ndiff --git a/refs.h b/refs.h\nindex f408367b01..eae45ce15a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -526,14 +526,6 @@ int refs_for_each_ref_in_prefixes(struct ref_store *refs,\n \t\t\t\t  const struct refs_for_each_ref_options *opts,\n \t\t\t\t  refs_for_each_cb cb, void *cb_data);\n \n-/*\n- * references matching any pattern in \"exclude_patterns\" are omitted from the\n- * result set on a best-effort basis.\n- */\n-int refs_for_each_namespaced_ref(struct ref_store *refs,\n-\t\t\t\t const char **exclude_patterns,\n-\t\t\t\t refs_for_each_cb fn, void *cb_data);\n-\n /*\n  * Normalizes partial refs to their fully qualified form.\n  * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'.\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 7fe397b0d0..d21f0577f9 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -610,7 +610,10 @@ static int allow_hidden_refs(enum allow_uor allow_uor)\n static void for_each_namespaced_ref_1(refs_for_each_cb fn,\n \t\t\t\t      struct upload_pack_data *data)\n {\n-\tconst char **excludes = NULL;\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.namespace = get_git_namespace(),\n+\t};\n+\n \t/*\n \t * If `data->allow_uor` allows fetching hidden refs, we need to\n \t * mark all references (including hidden ones), to check in\n@@ -621,10 +624,10 @@ static void for_each_namespaced_ref_1(refs_for_each_cb fn,\n \t * hidden references.\n \t */\n \tif (allow_hidden_refs(data->allow_uor))\n-\t\texcludes = hidden_refs_to_excludes(&data->hidden_refs);\n+\t\topts.exclude_patterns = hidden_refs_to_excludes(&data->hidden_refs);\n \n-\trefs_for_each_namespaced_ref(get_main_ref_store(the_repository),\n-\t\t\t\t     excludes, fn, data);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      fn, data, &opts);\n }\n \n \n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536487","messageId":"20260220-pks-refs-for-each-unification-v1-17-17170bd99de1@pks.im","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH 17/17] refs: replace `refs_for_each_fullref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T08:24:21Z","receivedAt":"2026-02-20T08:25:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Replace calls to `refs_for_each_fullref_in()` with the newly introduced\n`refs_for_each_ref_ext()` function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                  |  8 +++++---\n builtin/receive-pack.c    |  8 ++++----\n builtin/rev-parse.c       | 15 +++++++--------\n builtin/show-ref.c        | 21 +++++++++++++--------\n refs.c                    | 11 -----------\n refs.h                    |  8 --------\n revision.c                |  4 +++-\n t/helper/test-ref-store.c |  8 +++++---\n 8 files changed, 37 insertions(+), 46 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 296836c154..ef17a442e5 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -1190,13 +1190,15 @@ static int mark_for_removal(const struct reference *ref, void *cb_data)\n \n int bisect_clean_state(void)\n {\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = \"refs/bisect/\",\n+\t};\n \tint result = 0;\n \n \t/* There may be some refs packed during bisection */\n \tstruct string_list refs_for_removal = STRING_LIST_INIT_DUP;\n-\trefs_for_each_fullref_in(get_main_ref_store(the_repository),\n-\t\t\t\t \"refs/bisect/\", NULL, mark_for_removal,\n-\t\t\t\t &refs_for_removal);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      mark_for_removal, &refs_for_removal, &opts);\n \tstring_list_append(&refs_for_removal, \"BISECT_HEAD\");\n \tstring_list_append(&refs_for_removal, \"BISECT_EXPECTED_REV\");\n \tresult = refs_delete_refs(get_main_ref_store(the_repository),\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 4c0112b4bc..8c5ad5b81e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -343,9 +343,9 @@ static void show_one_alternate_ref(const struct object_id *oid,\n \n static void write_head_info(void)\n {\n+\tstruct refs_for_each_ref_options opts = { 0 };\n \tstatic struct oidset seen = OIDSET_INIT;\n \tstruct strvec excludes_vector = STRVEC_INIT;\n-\tconst char **exclude_patterns;\n \n \t/*\n \t * We need access to the reference names both with and without their\n@@ -353,12 +353,12 @@ static void write_head_info(void)\n \t * thus have to adapt exclude patterns to carry the namespace prefix\n \t * ourselves.\n \t */\n-\texclude_patterns = get_namespaced_exclude_patterns(\n+\topts.exclude_patterns = get_namespaced_exclude_patterns(\n \t\thidden_refs_to_excludes(&hidden_refs),\n \t\tget_git_namespace(), &excludes_vector);\n \n-\trefs_for_each_fullref_in(get_main_ref_store(the_repository), \"\",\n-\t\t\t\t exclude_patterns, show_ref_cb, &seen);\n+\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t      show_ref_cb, &seen, &opts);\n \todb_for_each_alternate_ref(the_repository->objects,\n \t\t\t\t   show_one_alternate_ref, &seen);\n \ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 206f5bda39..6c73631733 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -939,14 +939,13 @@ int cmd_rev_parse(int argc,\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--bisect\")) {\n-\t\t\t\trefs_for_each_fullref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t \"refs/bisect/bad\",\n-\t\t\t\t\t\t\t NULL, show_reference,\n-\t\t\t\t\t\t\t NULL);\n-\t\t\t\trefs_for_each_fullref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t \"refs/bisect/good\",\n-\t\t\t\t\t\t\t NULL, anti_reference,\n-\t\t\t\t\t\t\t NULL);\n+\t\t\t\tstruct refs_for_each_ref_options opts = { 0 };\n+\t\t\t\topts.prefix = \"refs/bisect/bad\";\n+\t\t\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t\t\t      show_reference, NULL, &opts);\n+\t\t\t\topts.prefix = \"refs/bisect/good\";\n+\t\t\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t\t\t      anti_reference, NULL, &opts);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (opt_with_value(arg, \"--branches\", &arg)) {\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4d4984e4e0..5d31acea7c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -215,14 +215,19 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n \t\trefs_head_ref(get_main_ref_store(the_repository), show_ref,\n \t\t\t      &show_ref_data);\n \tif (opts->branches_only || opts->tags_only) {\n-\t\tif (opts->branches_only)\n-\t\t\trefs_for_each_fullref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t \"refs/heads/\", NULL,\n-\t\t\t\t\t\t show_ref, &show_ref_data);\n-\t\tif (opts->tags_only)\n-\t\t\trefs_for_each_fullref_in(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t \"refs/tags/\", NULL, show_ref,\n-\t\t\t\t\t\t &show_ref_data);\n+\t\tstruct refs_for_each_ref_options for_each_ref_opts = { 0 };\n+\n+\t\tif (opts->branches_only) {\n+\t\t\tfor_each_ref_opts.prefix = \"refs/heads/\";\n+\t\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t\t      show_ref, &show_ref_data, &for_each_ref_opts);\n+\t\t}\n+\n+\t\tif (opts->tags_only) {\n+\t\t\tfor_each_ref_opts.prefix = \"refs/tags/\";\n+\t\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n+\t\t\t\t\t      show_ref, &show_ref_data, &for_each_ref_opts);\n+\t\t}\n \t} else {\n \t\trefs_for_each_ref(get_main_ref_store(the_repository),\n \t\t\t\t  show_ref, &show_ref_data);\ndiff --git a/refs.c b/refs.c\nindex 28142fa967..781fe2ed82 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1920,17 +1920,6 @@ int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data\n \treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n }\n \n-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t     const char **exclude_patterns,\n-\t\t\t     refs_for_each_cb cb, void *cb_data)\n-{\n-\tstruct refs_for_each_ref_options opts = {\n-\t\t.prefix = prefix,\n-\t\t.exclude_patterns = exclude_patterns,\n-\t};\n-\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n-}\n-\n int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n {\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\ndiff --git a/refs.h b/refs.h\nindex eae45ce15a..d7332a01f9 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -509,14 +509,6 @@ int refs_for_each_remote_ref(struct ref_store *refs,\n int refs_for_each_replace_ref(struct ref_store *refs,\n \t\t\t      refs_for_each_cb fn, void *cb_data);\n \n-/*\n- * references matching any pattern in \"exclude_patterns\" are omitted from the\n- * result set on a best-effort basis.\n- */\n-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n-\t\t\t     const char **exclude_patterns,\n-\t\t\t     refs_for_each_cb fn, void *cb_data);\n-\n /**\n  * Iterate all refs in \"prefixes\" by partitioning prefixes into disjoint sets\n  * and iterating the longest-common prefix of each set.\ndiff --git a/revision.c b/revision.c\nindex 8a6a871b1c..76c170e92d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2731,10 +2731,12 @@ void revision_opts_finish(struct rev_info *revs)\n static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn,\n \t\t\t       void *cb_data, const char *term)\n {\n+\tstruct refs_for_each_ref_options opts = { 0 };\n \tstruct strbuf bisect_refs = STRBUF_INIT;\n \tint status;\n \tstrbuf_addf(&bisect_refs, \"refs/bisect/%s\", term);\n-\tstatus = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data);\n+\topts.prefix = bisect_refs.buf;\n+\tstatus = refs_for_each_ref_ext(refs, fn, cb_data, &opts);\n \tstrbuf_release(&bisect_refs);\n \treturn status;\n }\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a2ef1b6949..74edf2029a 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -173,10 +173,12 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv)\n static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv)\n {\n \tconst char *prefix = notnull(*argv++, \"prefix\");\n-\tconst char **exclude_patterns = argv;\n+\tstruct refs_for_each_ref_options opts = {\n+\t\t.prefix = prefix,\n+\t\t.exclude_patterns = argv,\n+\t};\n \n-\treturn refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref,\n-\t\t\t\t\tNULL);\n+\treturn refs_for_each_ref_ext(refs, each_ref, NULL, &opts);\n }\n \n static int cmd_resolve_ref(struct ref_store *refs, const char **argv)\n\n-- \n2.53.0.414.gf7e9f6c205.dirty\n\n"},{"id":"536506","messageId":"aZg3krJEqj3Vs76p@ugly.lan","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-5-17170bd99de1@pks.im","subject":"Re: [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2026-02-20T10:29:38Z","receivedAt":"2026-02-20T10:29:40Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Fri, Feb 20, 2026 at 09:24:09AM +0100, Patrick Steinhardt wrote:\n>Remove the unused `refs_for_each_include_root_ref()` function.\n>\nat first sight it would seem sensible to move this to the start of the \nseries to reduce churn.\n"},{"id":"536507","messageId":"aZhN934uXU_eGydk@pks.im","threadId":"65028","inReplyTo":"aZg3krJEqj3Vs76p@ugly.lan","subject":"Re: [PATCH 05/17] refs: remove unused `refs_for_each_include_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-20T12:05:11Z","receivedAt":"2026-02-20T12:05:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 20, 2026 at 11:29:38AM +0100, Oswald Buddenhagen wrote:\n> On Fri, Feb 20, 2026 at 09:24:09AM +0100, Patrick Steinhardt wrote:\n> > Remove the unused `refs_for_each_include_root_ref()` function.\n> > \n> at first sight it would seem sensible to move this to the start of the\n> series to reduce churn.\n\nRight, that makes sense indeed. I've queued that change locally and will\nsend it out with the next version, thanks!\n\nPatrick\n"},{"id":"536704","messageId":"CAOLa=ZRLeTmGQJF3Wo0SHaQDgy+0G4L6=zciEMtwMPoYqpPM1A@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-1-17170bd99de1@pks.im","subject":"Re: [PATCH 01/17] refs: move `refs_head_ref_namespaced()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T08:05:27Z","receivedAt":"2026-02-23T08:05:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `refs_head_ref_namespaced()` is somewhat special when\n> compared to most of the other functions that take a callback function:\n> while `refs_for_each_*()` functions yield multiple refs, we only yield\n\nPerhaps swap s/we/refs_head_ref_namespaced() will/\n\n> at most the HEAD ref of the current function. As such, the function is\n\nShould this be s/function/namespace?\n\n> related to `refs_head_ref()` and not to the for-each functions.\n>\n> Move the function to be located next to `refs_head_ref()` to clarify.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.h | 5 +++--\n>  1 file changed, 3 insertions(+), 2 deletions(-)\n>\n> diff --git a/refs.h b/refs.h\n> index f16b1b697b..62e8ef61e7 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -413,6 +413,9 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n>   */\n>  int refs_head_ref(struct ref_store *refs,\n>  \t\t  each_ref_fn fn, void *cb_data);\n> +int refs_head_ref_namespaced(struct ref_store *refs,\n> +\t\t\t     each_ref_fn fn, void *cb_data);\n> +\n>  int refs_for_each_ref(struct ref_store *refs,\n>  \t\t      each_ref_fn fn, void *cb_data);\n>  int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n> @@ -456,8 +459,6 @@ int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,\n>  int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\n>  \t\t\t      const char *pattern, const char *prefix, void *cb_data);\n>\n> -int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);\n> -\n>  /*\n>   * references matching any pattern in \"exclude_patterns\" are omitted from the\n>   * result set on a best-effort basis.\n>\n> --\n> 2.53.0.414.gf7e9f6c205.dirty\n"},{"id":"536705","messageId":"CAOLa=ZQjTM+zdhRNELxAk8p9E4WJYUbrKQ7npRkQEjnvyAF2RA@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-4-17170bd99de1@pks.im","subject":"Re: [PATCH 04/17] refs: rename `each_ref_fn`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T08:07:51Z","receivedAt":"2026-02-23T08:07:52Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Similar to the preceding commit, rename `each_ref_fn` to better match\n> our current best practices around how we name things.\n>\n\nThis and the previous commit are renames and make sense to me.\n\n[snip]\n"},{"id":"536706","messageId":"CAOLa=ZQN3FfZAQpRGOdd_VDWKodYGw8jpKbt2R08FbZLN0+RPw@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-6-17170bd99de1@pks.im","subject":"Re: [PATCH 06/17] refs: introduce `refs_for_each_ref_ext`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T08:14:00Z","receivedAt":"2026-02-23T08:14:02Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In the refs subsystem we have a proliferation of functions that all\n> iterate through references. (Almost) all of these functions internally\n> call `do_for_each_ref()` and provide slightly different arguments so\n> that one can control different aspects of its behaviour. This approach\n> doesn't really scale: every time there is a slightly different use case\n> for iterating through refs we create another new function.\n>\n> This combinatorial explosion doesn't make a lot of sense: it leads to\n> confusing interfaces and heightens the maintenance burden.\n>\n> Refactor the code to become more composable by:\n>\n>   - Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.\n>\n>   - Introducing an options structure that lets the caller control\n>     individual options.\n>\n> This gives us a much better foundation to build on going forward.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.c | 78 ++++++++++++++++++++++++++++++++++++++++--------------------------\n>  refs.h | 29 +++++++++++++++++++++++++\n>  2 files changed, 77 insertions(+), 30 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index a45cc61211..ec9e466381 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(\n>  \treturn iter;\n>  }\n>\n> -static int do_for_each_ref(struct ref_store *refs, const char *prefix,\n> -\t\t\t   const char **exclude_patterns,\n> -\t\t\t   refs_for_each_cb fn, int trim,\n> -\t\t\t   enum refs_for_each_flag flags, void *cb_data)\n> +int refs_for_each_ref_ext(struct ref_store *refs,\n> +\t\t\t  refs_for_each_cb cb, void *cb_data,\n> +\t\t\t  const struct refs_for_each_ref_options *opts)\n\nSo instead of passing prefix, exclude_patterns and trim, we pass in a\nstruct which contains the options. Ok.\n\nWe also rename fn to cb, since that is more legible.\n\n>  {\n>  \tstruct ref_iterator *iter;\n>\n>  \tif (!refs)\n>  \t\treturn 0;\n>\n> -\titer = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,\n> -\t\t\t\t       flags);\n> +\titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n> +\t\t\t\t       opts->exclude_patterns,\n> +\t\t\t\t       opts->trim_prefix, opts->flags);\n>\n> -\treturn do_for_each_ref_iterator(iter, fn, cb_data);\n> +\treturn do_for_each_ref_iterator(iter, cb, cb_data);\n>  }\n>\n> -int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n> +int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n>  {\n> -\treturn do_for_each_ref(refs, \"\", NULL, fn, 0, 0, cb_data);\n> +\tstruct refs_for_each_ref_options opts = { 0 };\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n> -\t\t\t refs_for_each_cb fn, void *cb_data)\n> +\t\t\t refs_for_each_cb cb, void *cb_data)\n>  {\n> -\treturn do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.prefix = prefix,\n> +\t\t.trim_prefix = strlen(prefix),\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,\n>  \t\t\t     const char **exclude_patterns,\n> -\t\t\t     refs_for_each_cb fn, void *cb_data)\n> +\t\t\t     refs_for_each_cb cb, void *cb_data)\n>  {\n> -\treturn do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.prefix = prefix,\n> +\t\t.exclude_patterns = exclude_patterns,\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n> -int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n> +int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n>  {\n>  \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n> -\treturn do_for_each_ref(refs, git_replace_ref_base, NULL, fn,\n> -\t\t\t       strlen(git_replace_ref_base),\n> -\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.prefix = git_replace_ref_base,\n> +\t\t.trim_prefix = strlen(git_replace_ref_base),\n> +\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  int refs_for_each_namespaced_ref(struct ref_store *refs,\n>  \t\t\t\t const char **exclude_patterns,\n> -\t\t\t\t refs_for_each_cb fn, void *cb_data)\n> +\t\t\t\t refs_for_each_cb cb, void *cb_data)\n>  {\n> +\tstruct refs_for_each_ref_options opts = { 0 };\n>  \tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n>  \tstruct strbuf prefix = STRBUF_INIT;\n>  \tint ret;\n>\n> -\texclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n> -\t\t\t\t\t\t\t   get_git_namespace(),\n> -\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n> -\n> +\topts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n> +\t\t\t\t\t\t\t\tget_git_namespace(),\n> +\t\t\t\t\t\t\t\t&namespaced_exclude_patterns);\n>  \tstrbuf_addf(&prefix, \"%srefs/\", get_git_namespace());\n> -\tret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);\n> +\topts.prefix = prefix.buf;\n> +\n> +\tret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>\n>  \tstrvec_clear(&namespaced_exclude_patterns);\n>  \tstrbuf_release(&prefix);\n> @@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d\n>  }\n>\n>  int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,\n> -\t\t\t    refs_for_each_cb fn, void *cb_data)\n> +\t\t\t    refs_for_each_cb cb, void *cb_data)\n>  {\n> -\treturn do_for_each_ref(refs, prefix, NULL, fn, 0,\n> -\t\t\t       REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.prefix = prefix,\n> +\t\t.flags = REFS_FOR_EACH_INCLUDE_BROKEN,\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  static int qsort_strcmp(const void *va, const void *vb)\n> @@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n>  \t\t\t\t    struct strbuf *errbuf)\n>  {\n>  \tstruct ref_store *old_refs = NULL, *new_refs = NULL;\n> +\tstruct refs_for_each_ref_options for_each_ref_opts = {\n> +\t\t.flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,\n> +\t};\n>  \tstruct ref_transaction *transaction = NULL;\n>  \tstruct strbuf new_gitdir = STRBUF_INIT;\n>  \tstruct migration_data data = {\n> @@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n>  \tdata.errbuf = errbuf;\n>\n>  \t/*\n> -\t * We need to use the internal `do_for_each_ref()` here so that we can\n> +\t * We need to use `refs_for_each_ref_ext()` here so that we can\n>  \t * also include broken refs and symrefs. These would otherwise be\n>  \t * skipped silently.\n>  \t *\n> @@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n>  \t * allow for a central lock due to its design. It's thus on the user to\n>  \t * ensure that there are no concurrent writes.\n>  \t */\n> -\tret = do_for_each_ref(old_refs, \"\", NULL, migrate_one_ref, 0,\n> -\t\t\t      REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,\n> -\t\t\t      &data);\n> +\tret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);\n>  \tif (ret < 0)\n>  \t\tgoto done;\n>\n\nThen we modify all the callees to use the new options struct instead of\npassing in the arguments individually. Seems good.\n\n> diff --git a/refs.h b/refs.h\n> index 5190e98b2c..bb9c64a51c 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,\n>  int refs_head_ref_namespaced(struct ref_store *refs,\n>  \t\t\t     refs_for_each_cb fn, void *cb_data);\n>\n> +\n> +struct refs_for_each_ref_options {\n> +\t/* Only iterate over references that have this given prefix. */\n> +\tconst char *prefix;\n> +\n> +\t/*\n> +\t * Exclude any references that match any of these patterns on a\n> +\t * best-effort basis. The caller needs to be prepared for the exclude\n> +\t * patterns to be ignored.\n> +\t *\n> +\t * The array must be terminated with a NULL sentinel value.\n> +\t */\n> +\tconst char **exclude_patterns;\n> +\n> +\t/*\n> +\t * The number of bytes to trim from the refname. Note that the trimmed\n> +\t * bytes must not cause the reference to become empty. As such, this\n> +\t * field should typically only be set when one uses a `prefix` ending\n> +\t * in a slash.\n> +\t */\n> +\tsize_t trim_prefix;\n> +\n> +\t/* Flags that change which refs will be included. */\n> +\tenum refs_for_each_flag flags;\n> +};\n> +\n>  int refs_for_each_ref(struct ref_store *refs,\n>  \t\t      refs_for_each_cb fn, void *cb_data);\n> +int refs_for_each_ref_ext(struct ref_store *refs,\n> +\t\t\t  refs_for_each_cb cb, void *cb_data,\n> +\t\t\t  const struct refs_for_each_ref_options *opts);\n>  int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,\n>  \t\t\t refs_for_each_cb fn, void *cb_data);\n>  int refs_for_each_tag_ref(struct ref_store *refs,\n>\n> --\n> 2.53.0.414.gf7e9f6c205.dirty\n"},{"id":"536708","messageId":"CAOLa=ZRogQCpsD5eXOQrgt_DvgsXNfagbaxQm2eL+NwfUpw9OQ@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-7-17170bd99de1@pks.im","subject":"Re: [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T08:27:15Z","receivedAt":"2026-02-23T08:27:17Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `refs_for_each_glob_ref_in()` can be used to iterate\n> through all refs in a specific prefix with globbing. The logic to handle\n> this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up\n> a callback function that knows to filter out refs that _don't_ match the\n> given globbing pattern.\n>\n> The way we do this is somewhat inefficient though: even though the\n> function is expected to only yield refs in the given prefix, we still\n> end up iterating through _all_ references, regardless of whether or not\n> their name matches the given prefix.\n>\n\nSo currently instead of relying on the backends to do the prefix\nmatching, the function uses its own callback to do the prefix matching.\n\n> Extend `refs_for_each_ref_ext()` so that it can handle patterns and\n> adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to\n> use the same callback-based infrastructure to filter individual refs via\n> the globbing pattern, but we can now also use the other functionality of\n> the `_ext()` variant.\n>\n\nSo this change, ensures we don't do the filtering for prefix match\nourselves and allows the backend to do it.\n\n> Most importantly, this means that we now properly handle the prefix.\n> This results in a performance improvement when using a prefix where a\n> significant majority of refs exists outside of the prefix. The following\n> benchmark is an extreme case, with 1 million refs that exist outside the\n> prefix and a single ref that exists inside it:\n>\n>     Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)\n>       Time (mean ± σ):     115.9 ms ±   0.7 ms    [User: 113.0 ms, System: 2.4 ms]\n>       Range (min … max):   114.9 ms … 117.8 ms    25 runs\n>\n>     Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)\n>       Time (mean ± σ):       1.1 ms ±   0.1 ms    [User: 0.3 ms, System: 0.7 ms]\n>       Range (min … max):     1.0 ms …   2.3 ms    2092 runs\n>\n>     Summary\n>       git rev-parse --branches=refs/heads/* (rev = HEAD) ran\n>       107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)\n>\n\nNice. That's a really neat bump in speed.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.c | 69 ++++++++++++++++++++++++++++++++++++++----------------------------\n>  refs.h | 10 ++++++++++\n>  2 files changed, 50 insertions(+), 29 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index ec9e466381..ac34bbe6c1 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -590,40 +590,23 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n>  \tstrbuf_release(&normalized_pattern);\n>  }\n>\n> -int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,\n> +int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,\n>  \t\t\t      const char *pattern, const char *prefix, void *cb_data)\n>  {\n> -\tstruct strbuf real_pattern = STRBUF_INIT;\n> -\tstruct for_each_ref_filter filter;\n> -\tint ret;\n> -\n> -\tif (!prefix && !starts_with(pattern, \"refs/\"))\n> -\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n> -\telse if (prefix)\n> -\t\tstrbuf_addstr(&real_pattern, prefix);\n> -\tstrbuf_addstr(&real_pattern, pattern);\n> -\n> -\tif (!has_glob_specials(pattern)) {\n> -\t\t/* Append implied '/' '*' if not present. */\n> -\t\tstrbuf_complete(&real_pattern, '/');\n> -\t\t/* No need to check for '*', there is none. */\n> -\t\tstrbuf_addch(&real_pattern, '*');\n> -\t}\n> -\n> -\tfilter.pattern = real_pattern.buf;\n> -\tfilter.prefix = prefix;\n> -\tfilter.fn = fn;\n> -\tfilter.cb_data = cb_data;\n> -\tret = refs_for_each_ref(refs, for_each_filter_refs, &filter);\n> -\n> -\tstrbuf_release(&real_pattern);\n> -\treturn ret;\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.pattern = pattern,\n> +\t\t.prefix = prefix,\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n> -int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,\n> +int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,\n>  \t\t\t   const char *pattern, void *cb_data)\n>  {\n> -\treturn refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.pattern = pattern,\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  const char *prettify_refname(const char *name)\n> @@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n>  \t\t\t  refs_for_each_cb cb, void *cb_data,\n>  \t\t\t  const struct refs_for_each_ref_options *opts)\n>  {\n> +\tstruct strbuf real_pattern = STRBUF_INIT;\n> +\tstruct for_each_ref_filter filter;\n>  \tstruct ref_iterator *iter;\n> +\tint ret;\n>\n>  \tif (!refs)\n>  \t\treturn 0;\n>\n> +\tif (opts->pattern) {\n> +\t\tif (!opts->prefix && !starts_with(opts->pattern, \"refs/\"))\n> +\t\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n> +\t\telse if (opts->prefix)\n> +\t\t\tstrbuf_addstr(&real_pattern, opts->prefix);\n> +\t\tstrbuf_addstr(&real_pattern, opts->pattern);\n> +\n> +\t\tif (!has_glob_specials(opts->pattern)) {\n> +\t\t\t/* Append implied '/' '*' if not present. */\n> +\t\t\tstrbuf_complete(&real_pattern, '/');\n> +\t\t\t/* No need to check for '*', there is none. */\n> +\t\t\tstrbuf_addch(&real_pattern, '*');\n> +\t\t}\n> +\n> +\t\tfilter.pattern = real_pattern.buf;\n> +\t\tfilter.prefix = opts->prefix;\n\nCan't we now remove this option and cleanup `for_each_filter_refs()` to\nremove prefix trimming?\n\n> +\t\tfilter.fn = cb;\n> +\t\tfilter.cb_data = cb_data;\n> +\n> +\t\tcb = for_each_filter_refs;\n> +\t\tcb_data = &filter;\n> +\t}\n> +\n>  \titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n>  \t\t\t\t       opts->exclude_patterns,\n>  \t\t\t\t       opts->trim_prefix, opts->flags);\n>\n> -\treturn do_for_each_ref_iterator(iter, cb, cb_data);\n> +\tret = do_for_each_ref_iterator(iter, cb, cb_data);\n> +\tstrbuf_release(&real_pattern);\n> +\treturn ret;\n>  }\n>\n>  int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)\n> diff --git a/refs.h b/refs.h\n> index bb9c64a51c..a66dbf3865 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -458,6 +458,16 @@ struct refs_for_each_ref_options {\n>  \t/* Only iterate over references that have this given prefix. */\n>  \tconst char *prefix;\n>\n> +\t/*\n> +\t * A globbing pattern that can be used to only yield refs that match.\n> +\t * If given, refs will be matched against the pattern with\n> +\t * `wildmatch()`.\n> +\t *\n> +\t * If the pattern doesn't contain any globbing characters then it is\n> +\t * treated as if it was ending with \"/\" and \"*\".\n> +\t */\n> +\tconst char *pattern;\n> +\n>  \t/*\n>  \t * Exclude any references that match any of these patterns on a\n>  \t * best-effort basis. The caller needs to be prepared for the exclude\n>\n> --\n> 2.53.0.414.gf7e9f6c205.dirty\n"},{"id":"536715","messageId":"CAOLa=ZQjZ-YLedF=Cqn=Tb8-rhX8=+Lnd2VKv0fn_ryrO563_g@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-8-17170bd99de1@pks.im","subject":"Re: [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T09:02:56Z","receivedAt":"2026-02-23T09:02:57Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `refs_for_each_namespaced_ref()` iterates through all\n> references that are part of the current ref namespace. This namespace\n> can be configured by setting the `GIT_NAMESPACE` environment variable\n> and is then retrieved by calling `get_git_namespace()`.\n>\n> If a namespace is configured, then we:\n>\n>   - Obviously only yield refs that exist in this namespace.\n>\n>   - Rewrite exclude patterns so that they work for the given namespace,\n>     if any namespace is currently configured.\n>\n> Port this logic to `refs_for_each_ref_ext()` by adding a new `namespace`\n> field to the options structure. This gives callers more flexibility as\n> they can decide by themselves whether they want to use the globally\n> configured or an arbitrary other namespace.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.c | 47 +++++++++++++++++++++++++++++------------------\n>  refs.h |  6 ++++++\n>  2 files changed, 35 insertions(+), 18 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index ac34bbe6c1..99994879d9 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1845,9 +1845,13 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n>  \t\t\t  refs_for_each_cb cb, void *cb_data,\n>  \t\t\t  const struct refs_for_each_ref_options *opts)\n>  {\n> +\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n> +\tstruct strbuf namespaced_prefix = STRBUF_INIT;\n>  \tstruct strbuf real_pattern = STRBUF_INIT;\n>  \tstruct for_each_ref_filter filter;\n>  \tstruct ref_iterator *iter;\n> +\tconst char **exclude_patterns;\n> +\tconst char *prefix;\n>  \tint ret;\n>\n>  \tif (!refs)\n> @@ -1876,11 +1880,29 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n>  \t\tcb_data = &filter;\n>  \t}\n>\n> -\titer = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : \"\",\n> -\t\t\t\t       opts->exclude_patterns,\n> +\tif (opts->namespace) {\n> +\t\tstrbuf_addstr(&namespaced_prefix, opts->namespace);\n> +\t\tif (opts->prefix)\n> +\t\t\tstrbuf_addstr(&namespaced_prefix, opts->prefix);\n> +\t\telse\n> +\t\t\tstrbuf_addstr(&namespaced_prefix, \"refs/\");\n> +\n\nSo if the namespace is 'foo', we'll have the namespace folder as\n'refs/namespace/foo', and a prefix of 'refs/heads/' would mean that the\n'namespaced_prefix' is now 'refs/namespace/foo/refs/heads'. Looks good.\n\n> +\t\tprefix = namespaced_prefix.buf;\n> +\t\texclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,\n> +\t\t\t\t\t\t\t\t   opts->namespace,\n> +\t\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n> +\t} else {\n> +\t\tprefix = opts->prefix ? opts->prefix : \"\";\n> +\t\texclude_patterns = opts->exclude_patterns;\n> +\t}\n> +\n> +\titer = refs_ref_iterator_begin(refs, prefix, exclude_patterns,\n>  \t\t\t\t       opts->trim_prefix, opts->flags);\n>\n>  \tret = do_for_each_ref_iterator(iter, cb, cb_data);\n> +\n> +\tstrvec_clear(&namespaced_exclude_patterns);\n> +\tstrbuf_release(&namespaced_prefix);\n>  \tstrbuf_release(&real_pattern);\n>  \treturn ret;\n>  }\n> @@ -1927,22 +1949,11 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,\n>  \t\t\t\t const char **exclude_patterns,\n>  \t\t\t\t refs_for_each_cb cb, void *cb_data)\n>  {\n> -\tstruct refs_for_each_ref_options opts = { 0 };\n> -\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n> -\tstruct strbuf prefix = STRBUF_INIT;\n> -\tint ret;\n> -\n> -\topts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n> -\t\t\t\t\t\t\t\tget_git_namespace(),\n> -\t\t\t\t\t\t\t\t&namespaced_exclude_patterns);\n> -\tstrbuf_addf(&prefix, \"%srefs/\", get_git_namespace());\n> -\topts.prefix = prefix.buf;\n> -\n> -\tret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n> -\n> -\tstrvec_clear(&namespaced_exclude_patterns);\n> -\tstrbuf_release(&prefix);\n> -\treturn ret;\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.exclude_patterns = exclude_patterns,\n> +\t\t.namespace = get_git_namespace(),\n> +\t};\n> +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n>  }\n>\n>  int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)\n> diff --git a/refs.h b/refs.h\n> index a66dbf3865..2bde60aa0e 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -468,6 +468,12 @@ struct refs_for_each_ref_options {\n>  \t */\n>  \tconst char *pattern;\n>\n> +\t/*\n> +\t * If set, only yield refs part of the configured namespace. Exclude\n> +\t * patterns will be rewritten to apply to the namespace.\n> +\t */\n> +\tconst char *namespace;\n\nNit: should we also mention how prefix is appended to namespace?\n\n> +\n>  \t/*\n>  \t * Exclude any references that match any of these patterns on a\n>  \t * best-effort basis. The caller needs to be prepared for the exclude\n>\n> --\n> 2.53.0.414.gf7e9f6c205.dirty\n"},{"id":"536717","messageId":"CAOLa=ZSKF1WoE_2q5oEjU8MOh=_0s--cPwf0R8xnQQF8=9dWDw@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-9-17170bd99de1@pks.im","subject":"Re: [PATCH 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T09:06:38Z","receivedAt":"2026-02-23T09:06:41Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `refs_for_each_fullref_in_prefixes()` can be used to\n> iterate over all references part of any of the user-provided prefixes.\n> In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it\n> knows to handle the case well where multiple of the passed-in prefixes\n> start with a common prefix by computing longest common prefixes and then\n> iterating over those.\n>\n> While we could move this logic into `refs_for_each_ref_ext()`, this one\n> feels somewhat special as we perform multiple iterations. But what we\n> _can_ do is to generalize how this function works: instead of accepting\n> only a small handful of parameters, we can have it accept the full\n> options structure.\n>\n> One obvious exception is that the caller must not provide a prefix via\n> the options. But this case can be easily detected.\n>\n> Refactor the code accordingly.\n>\n\nI've attempted the refactoring that this series is doing a couple of\ntimes, I always got stuck up on how to integrate this function with the\nrest. This is a novel approach.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  ls-refs.c    | 11 +++++++----\n>  ref-filter.c | 11 +++++++----\n>  refs.c       | 39 +++++++++++++++------------------------\n>  refs.h       | 16 +++++-----------\n>  4 files changed, 34 insertions(+), 43 deletions(-)\n>\n> diff --git a/ls-refs.c b/ls-refs.c\n> index 8641281b86..9759826ca7 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,\n>\n>  int ls_refs(struct repository *r, struct packet_reader *request)\n>  {\n> +\tstruct refs_for_each_ref_options opts = { 0 };\n>  \tstruct ls_refs_data data;\n>\n>  \tmemset(&data, 0, sizeof(data));\n> @@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)\n>  \tsend_possibly_unborn_head(&data);\n>  \tif (!data.prefixes.nr)\n>  \t\tstrvec_push(&data.prefixes, \"\");\n> -\trefs_for_each_fullref_in_prefixes(get_main_ref_store(r),\n> -\t\t\t\t\t  get_git_namespace(), data.prefixes.v,\n> -\t\t\t\t\t  hidden_refs_to_excludes(&data.hidden_refs),\n> -\t\t\t\t\t  send_ref, &data);\n> +\n> +\topts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);\n> +\topts.namespace = get_git_namespace();\n> +\n> +\trefs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,\n> +\t\t\t\t      &opts, send_ref, &data);\n>  \tpacket_fflush(stdout);\n>  \tstrvec_clear(&data.prefixes);\n>  \tstrbuf_release(&data.buf);\n> diff --git a/ref-filter.c b/ref-filter.c\n> index 049e845a19..7c682e0a33 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n>  \t\t\t\t       refs_for_each_cb cb,\n>  \t\t\t\t       void *cb_data)\n>  {\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.exclude_patterns = filter->exclude.v,\n> +\t};\n> +\n>  \tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n>  \t\t/* In this case, we want to print all refs including root refs. */\n>  \t\treturn for_each_fullref_with_seek(filter, cb, cb_data,\n> @@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n>  \t\treturn for_each_fullref_with_seek(filter, cb, cb_data, 0);\n>  \t}\n>\n> -\treturn refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),\n> -\t\t\t\t\t\t NULL, filter->name_patterns,\n> -\t\t\t\t\t\t filter->exclude.v,\n> -\t\t\t\t\t\t cb, cb_data);\n> +\treturn refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),\n> +\t\t\t\t\t     filter->name_patterns, &opts,\n> +\t\t\t\t\t     cb, cb_data);\n>  }\n>\n>  /*\n> diff --git a/refs.c b/refs.c\n> index 99994879d9..20d34faeb5 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2029,40 +2029,31 @@ static void find_longest_prefixes(struct string_list *out,\n>  \tstrbuf_release(&prefix);\n>  }\n>\n> -int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,\n> -\t\t\t\t      const char *namespace,\n> -\t\t\t\t      const char **patterns,\n> -\t\t\t\t      const char **exclude_patterns,\n> -\t\t\t\t      refs_for_each_cb fn, void *cb_data)\n> +int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,\n> +\t\t\t\t  const char **prefixes,\n> +\t\t\t\t  const struct refs_for_each_ref_options *opts,\n> +\t\t\t\t  refs_for_each_cb cb, void *cb_data)\n>  {\n> -\tstruct strvec namespaced_exclude_patterns = STRVEC_INIT;\n> -\tstruct string_list prefixes = STRING_LIST_INIT_DUP;\n> +\tstruct string_list longest_prefixes = STRING_LIST_INIT_DUP;\n>  \tstruct string_list_item *prefix;\n> -\tstruct strbuf buf = STRBUF_INIT;\n> -\tint ret = 0, namespace_len;\n> +\tint ret = 0;\n>\n> -\tfind_longest_prefixes(&prefixes, patterns);\n> +\tif (opts->prefix)\n> +\t\tBUG(\"refs_for_each_ref_in_prefixes called with specific prefix\");\n>\n> -\tif (namespace)\n> -\t\tstrbuf_addstr(&buf, namespace);\n> -\tnamespace_len = buf.len;\n> +\tfind_longest_prefixes(&longest_prefixes, prefixes);\n>\n> -\texclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,\n> -\t\t\t\t\t\t\t   namespace,\n> -\t\t\t\t\t\t\t   &namespaced_exclude_patterns);\n> +\tfor_each_string_list_item(prefix, &longest_prefixes) {\n> +\t\tstruct refs_for_each_ref_options prefix_opts = *opts;\n> +\t\tprefix_opts.prefix = prefix->string;\n>\n> -\tfor_each_string_list_item(prefix, &prefixes) {\n> -\t\tstrbuf_addstr(&buf, prefix->string);\n> -\t\tret = refs_for_each_fullref_in(ref_store, buf.buf,\n> -\t\t\t\t\t       exclude_patterns, fn, cb_data);\n> +\t\tret = refs_for_each_ref_ext(ref_store, cb, cb_data,\n> +\t\t\t\t\t    &prefix_opts);\n\nOkay so it still calls `refs_for_each_ref_ext()` on the longest prefix.\nMakes sense.\n\n[snip]\n"},{"id":"536718","messageId":"CAOLa=ZQa5riqB37bVL0_2KZbXS3V2L=iHqpAwMYom1bjKiNsRg@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-10-17170bd99de1@pks.im","subject":"Re: [PATCH 10/17] refs: improve verification for-each-ref options","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T09:09:15Z","receivedAt":"2026-02-23T09:09:18Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Improve verification of the passed-in for-each-ref options:\n>\n>   - Require that the `refs` store must be given. It's arguably very\n>     surprising that we simply return successfully in case the ref store\n>     is a `NULL` pointer.\n>\n>   - When expected to trim ref prefixes we will `BUG()` in case the\n>     refname would become empty or in case we're expected to trim a\n>     longer prefix than the refname is long. As such, this case is only\n>     guaranteed to _not_ `BUG()` in case the caller also specified a\n>     prefix. And furthermore, that prefix must end in a trailing slash,\n>     as otherwise it may produce an exact match that could lead us to\n>     trim to the empty string.\n>\n> An audit shows that there are no callsites that rely on either of these\n> behaviours, so this should not result in a functional change.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.c | 13 ++++++++++++-\n>  1 file changed, 12 insertions(+), 1 deletion(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 20d34faeb5..3b676432b4 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1855,7 +1855,18 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n>  \tint ret;\n>\n>  \tif (!refs)\n> -\t\treturn 0;\n> +\t\tBUG(\"no refs passed\");\n> +\n\nNit: s/refs/ref store/, mostly from a readability point, but since this\nis a BUG(), I think its okay to leave as is.\n\n> +\tif (opts->trim_prefix) {\n> +\t\tsize_t prefix_len;\n> +\n> +\t\tif (!opts->prefix)\n> +\t\t\tBUG(\"trimming only allowed with a prefix\");\n> +\n> +\t\tprefix_len = strlen(opts->prefix);\n> +\t\tif (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')\n> +\t\t\tBUG(\"ref pattern must end in a trailing slash when trimming\");\n> +\t}\n>\n>  \tif (opts->pattern) {\n>  \t\tif (!opts->prefix && !starts_with(opts->pattern, \"refs/\"))\n>\n> --\n> 2.53.0.414.gf7e9f6c205.dirty\n"},{"id":"536719","messageId":"CAOLa=ZRXbQEyw5-RvZrXTYMnRQr47=JuiUP3Ps4LGQPspo8gPQ@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-11-17170bd99de1@pks.im","subject":"Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T09:11:52Z","receivedAt":"2026-02-23T09:11:55Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Replace calls to `refs_for_each_ref_in()` with the newly introduced\n> `refs_for_each_ref_ext()` function.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  bisect.c                  |  8 ++++++--\n>  builtin/rev-parse.c       | 13 +++++++++----\n>  pack-bitmap.c             | 13 +++++++------\n>  refs.c                    | 34 ++++++++++++++++++----------------\n>  refs.h                    |  2 --\n>  t/helper/test-ref-store.c |  7 +++++--\n>  6 files changed, 45 insertions(+), 32 deletions(-)\n>\n> diff --git a/bisect.c b/bisect.c\n> index 2bdad4ee42..296836c154 100644\n> --- a/bisect.c\n> +++ b/bisect.c\n> @@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n>\n>  static int read_bisect_refs(void)\n>  {\n> -\treturn refs_for_each_ref_in(get_main_ref_store(the_repository),\n> -\t\t\t\t    \"refs/bisect/\", register_ref, NULL);\n> +\tstruct refs_for_each_ref_options opts = {\n> +\t\t.prefix = \"refs/bisect/\",\n> +\t\t.trim_prefix = strlen(\"refs/bisect/\"),\n> +\t};\n> +\treturn refs_for_each_ref_ext(get_main_ref_store(the_repository),\n> +\t\t\t\t     register_ref, NULL, &opts);\n>  }\n>\n>  static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index 9032cc6327..02703f2fb8 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)\n>\n>  static void handle_ref_opt(const char *pattern, const char *prefix)\n>  {\n> -\tif (pattern)\n> +\tif (pattern) {\n>  \t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n>  \t\t\t\t\t  show_reference, pattern, prefix,\n>  \t\t\t\t\t  NULL);\n> -\telse\n> -\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n> -\t\t\t\t     prefix, show_reference, NULL);\n> +\t} else {\n> +\t\tstruct refs_for_each_ref_options opts = {\n> +\t\t\t.prefix = prefix,\n> +\t\t\t.trim_prefix = strlen(prefix),\n\nTangent: I wonder if it makes sense to make `trim_prefix` a bool and\nthen internally trim strlen(prefix). Is there a usecase where\n`.trim_prefix != strlen(prefix)`?\n\n> +\t\t};\n> +\t\trefs_for_each_ref_ext(get_main_ref_store(the_repository),\n> +\t\t\t\t      show_reference, NULL, &opts);\n> +\t}\n>  \tclear_ref_exclusions(&ref_excludes);\n>  }\n>\n\n[snip]\n"},{"id":"536721","messageId":"CAOLa=ZT6a8wdMgznVr7+ou3mhYKDT_AC3M0s4FCZ-Orjxf+6eQ@mail.gmail.com","threadId":"65028","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"Re: [PATCH 00/17] refs: unify `refs_for_each_*()` functions","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T09:14:20Z","receivedAt":"2026-02-23T09:14:22Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> we currently have 14 different `refs_for_each_*()` functions, with each\n> of them doing slightly different things. This makes for a confusing API\n> surface, and because the API is not built for extension we have to add a\n> new function every now and then to handle another esoteric edge case\n> that will ultimately only have at most a handful of callers.\n>\n> This design isn't really sensible in my opinion, and this patch series\n> aims to fix that. Instead of having a dozen different functions, it\n> introduces a new `refs_for_each_ref_ext()` function that simply takes an\n> options structure as input. From thereon, callers can mix and match the\n> parameters that they care about.\n>\n> The patch series is structured like this:\n>\n>   - Patches 1 to 5 introduce some preliminary cleanups.\n>\n>   - Patches 6 to 9 introduce `refs_for_each_ref_ext()` and move\n>     more functionality into it. This also fixes a performance bug that\n>     we have in one of the implementations.\n>\n>   - Patch 10 adds some more verification for options that would have\n>     caught the bugs in ps/for-each-ref-in-fixes.\n>\n>   - The remaining patches drop 7 out of 14 functions and replace them\n>     with `refs_for_each_ref_ext()`. It results in a bit of churn, so\n>     while I think this churn is worth it, I consider these patches to be\n>     optional.\n>\n> The patch series is built on top of 73fd77805f (The 5th batch,\n> 2026-02-17) with ps/for-each-ref-in-fixes at 6375a00ef1 (bisect:\n> simplify string_list memory handling, 2026-02-19) merged into it.\n>\n> Thanks!\n>\n> Patrick\n>\n\nI'm really happy with the patches, I have some small nits/questions, but\nit looks good otherwise.\n\nThanks,\nKarthik\n\n[snip]\n"},{"id":"536735","messageId":"aZwwIUaoN4bmmXXR@pks.im","threadId":"65028","inReplyTo":"CAOLa=ZRLeTmGQJF3Wo0SHaQDgy+0G4L6=zciEMtwMPoYqpPM1A@mail.gmail.com","subject":"Re: [PATCH 01/17] refs: move `refs_head_ref_namespaced()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T10:46:57Z","receivedAt":"2026-02-23T10:47:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Feb 23, 2026 at 03:05:27AM -0500, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The function `refs_head_ref_namespaced()` is somewhat special when\n> > compared to most of the other functions that take a callback function:\n> > while `refs_for_each_*()` functions yield multiple refs, we only yield\n> \n> Perhaps swap s/we/refs_head_ref_namespaced() will/\n> \n> > at most the HEAD ref of the current function. As such, the function is\n> \n> Should this be s/function/namespace?\n\nYup. Will adapt both, thanks!\n\nPatrick\n"},{"id":"536736","messageId":"aZwwYynerY8Yauqc@pks.im","threadId":"65028","inReplyTo":"CAOLa=ZRogQCpsD5eXOQrgt_DvgsXNfagbaxQm2eL+NwfUpw9OQ@mail.gmail.com","subject":"Re: [PATCH 07/17] refs: speed up `refs_for_each_glob_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T10:48:03Z","receivedAt":"2026-02-23T10:48:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Feb 23, 2026 at 12:27:15AM -0800, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/refs.c b/refs.c\n> > index ec9e466381..ac34bbe6c1 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> > @@ -1862,16 +1845,44 @@ int refs_for_each_ref_ext(struct ref_store *refs,\n> >  \t\t\t  refs_for_each_cb cb, void *cb_data,\n> >  \t\t\t  const struct refs_for_each_ref_options *opts)\n> >  {\n> > +\tstruct strbuf real_pattern = STRBUF_INIT;\n> > +\tstruct for_each_ref_filter filter;\n> >  \tstruct ref_iterator *iter;\n> > +\tint ret;\n> >\n> >  \tif (!refs)\n> >  \t\treturn 0;\n> >\n> > +\tif (opts->pattern) {\n> > +\t\tif (!opts->prefix && !starts_with(opts->pattern, \"refs/\"))\n> > +\t\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n> > +\t\telse if (opts->prefix)\n> > +\t\t\tstrbuf_addstr(&real_pattern, opts->prefix);\n> > +\t\tstrbuf_addstr(&real_pattern, opts->pattern);\n> > +\n> > +\t\tif (!has_glob_specials(opts->pattern)) {\n> > +\t\t\t/* Append implied '/' '*' if not present. */\n> > +\t\t\tstrbuf_complete(&real_pattern, '/');\n> > +\t\t\t/* No need to check for '*', there is none. */\n> > +\t\t\tstrbuf_addch(&real_pattern, '*');\n> > +\t\t}\n> > +\n> > +\t\tfilter.pattern = real_pattern.buf;\n> > +\t\tfilter.prefix = opts->prefix;\n> \n> Can't we now remove this option and cleanup `for_each_filter_refs()` to\n> remove prefix trimming?\n\nNo, unfortunately not. This is because the glob pattern is expected to\nmatch on the full refname, so if we were to strip the refname before we\npass it to the `filter` callback then we wouldn't be able to do the call\nto wildmatch anymore.\n\nBut the stripping part is still a bit funky after my refactoring, as we\nunconditionall strip the prefix right now. This is the expected\nbehaviour, but it is somewhat surprising I guess. I'll rework this part\na bit, thanks!\n\nPatrick\n"},{"id":"536737","messageId":"aZwwaVr3sObAgley@pks.im","threadId":"65028","inReplyTo":"CAOLa=ZQjZ-YLedF=Cqn=Tb8-rhX8=+Lnd2VKv0fn_ryrO563_g@mail.gmail.com","subject":"Re: [PATCH 08/17] refs: generalize `refs_for_each_namespaced_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T10:48:09Z","receivedAt":"2026-02-23T10:48:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Feb 23, 2026 at 01:02:56AM -0800, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/refs.h b/refs.h\n> > index a66dbf3865..2bde60aa0e 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -468,6 +468,12 @@ struct refs_for_each_ref_options {\n> >  \t */\n> >  \tconst char *pattern;\n> >\n> > +\t/*\n> > +\t * If set, only yield refs part of the configured namespace. Exclude\n> > +\t * patterns will be rewritten to apply to the namespace.\n> > +\t */\n> > +\tconst char *namespace;\n> \n> Nit: should we also mention how prefix is appended to namespace?\n\nYeah, makes sense, will do.\n\nPatrick\n"},{"id":"536738","messageId":"aZwwchZjnSMTiN0m@pks.im","threadId":"65028","inReplyTo":"CAOLa=ZRXbQEyw5-RvZrXTYMnRQr47=JuiUP3Ps4LGQPspo8gPQ@mail.gmail.com","subject":"Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T10:48:18Z","receivedAt":"2026-02-23T10:48:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Feb 23, 2026 at 04:11:52AM -0500, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> > index 9032cc6327..02703f2fb8 100644\n> > --- a/builtin/rev-parse.c\n> > +++ b/builtin/rev-parse.c\n> > @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)\n> >\n> >  static void handle_ref_opt(const char *pattern, const char *prefix)\n> >  {\n> > -\tif (pattern)\n> > +\tif (pattern) {\n> >  \t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n> >  \t\t\t\t\t  show_reference, pattern, prefix,\n> >  \t\t\t\t\t  NULL);\n> > -\telse\n> > -\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n> > -\t\t\t\t     prefix, show_reference, NULL);\n> > +\t} else {\n> > +\t\tstruct refs_for_each_ref_options opts = {\n> > +\t\t\t.prefix = prefix,\n> > +\t\t\t.trim_prefix = strlen(prefix),\n> \n> Tangent: I wonder if it makes sense to make `trim_prefix` a bool and\n> then internally trim strlen(prefix). Is there a usecase where\n> `.trim_prefix != strlen(prefix)`?\n\nI don't think there is right now, and I cannot think about any myself.\nHow about we leave this as a #leftoverbit though?\n\nPatrick\n"},{"id":"536789","messageId":"CAOLa=ZTduJUmcBeViAg43DL6LcpaSLf6eNHTpriaZ9swo7PoAA@mail.gmail.com","threadId":"65028","inReplyTo":"aZwwchZjnSMTiN0m@pks.im","subject":"Re: [PATCH 11/17] refs: replace `refs_for_each_ref_in()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-23T13:35:19Z","receivedAt":"2026-02-23T13:35:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Feb 23, 2026 at 04:11:52AM -0500, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> > diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n>> > index 9032cc6327..02703f2fb8 100644\n>> > --- a/builtin/rev-parse.c\n>> > +++ b/builtin/rev-parse.c\n>> > @@ -613,13 +613,18 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)\n>> >\n>> >  static void handle_ref_opt(const char *pattern, const char *prefix)\n>> >  {\n>> > -\tif (pattern)\n>> > +\tif (pattern) {\n>> >  \t\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n>> >  \t\t\t\t\t  show_reference, pattern, prefix,\n>> >  \t\t\t\t\t  NULL);\n>> > -\telse\n>> > -\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n>> > -\t\t\t\t     prefix, show_reference, NULL);\n>> > +\t} else {\n>> > +\t\tstruct refs_for_each_ref_options opts = {\n>> > +\t\t\t.prefix = prefix,\n>> > +\t\t\t.trim_prefix = strlen(prefix),\n>>\n>> Tangent: I wonder if it makes sense to make `trim_prefix` a bool and\n>> then internally trim strlen(prefix). Is there a usecase where\n>> `.trim_prefix != strlen(prefix)`?\n>\n> I don't think there is right now, and I cannot think about any myself.\n> How about we leave this as a #leftoverbit though?\n>\n> Patrick\n\nSure!\n"}]}