{"thread":{"id":"65055","subject":"[PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()`","startedAt":"2026-02-23T11:59:44Z","lastAt":"2026-02-24T13:14:12Z","messageCount":19,"participants":["Patrick Steinhardt","Karthik Nayak"],"isPatch":true,"patchVersion":2,"patchTotal":17},"messages":[{"id":"536749","messageId":"20260223-pks-refs-for-each-unification-v2-1-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 01/17] refs: remove unused `refs_for_each_include_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:35Z","receivedAt":"2026-02-23T11:59:44Z","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 600913b99f..466398494f 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       DO_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-}\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 f16b1b697b..1fdb809343 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -471,12 +471,6 @@ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn 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 \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-\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.536.g309c995771.dirty\n\n"},{"id":"536750","messageId":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im","subject":"[PATCH v2 00/17] refs: unify `refs_for_each_*()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:34Z","receivedAt":"2026-02-23T11:59:44Z","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\nChanges in v2:\n  - Move the removal of `refs_for_each_include_root_ref()` to the\n    beginning of the series to avoid some unnecessary churn.\n  - Some commit message improvements.\n  - Make the converted version of `refs_for_each_glob_ref_in()` fit into\n    the new calling conventions a bit better. The function was still\n    stripping the prefix unconditionally for example, which I've now\n    changed.\n  - Link to v1: https://lore.kernel.org/r/20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (17):\n      refs: remove unused `refs_for_each_include_root_ref()`\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: 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          |  37 +++++--\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       |  38 ++++---\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                    | 271 ++++++++++++++++++++++------------------------\n refs.h                    | 199 +++++++++++++++++-----------------\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                |  49 ++++++---\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, 457 insertions(+), 361 deletions(-)\n\nRange-diff versus v1:\n\n -:  ---------- >  1:  97473a19a8 refs: remove unused `refs_for_each_include_root_ref()`\n 1:  312fde9bc7 !  2:  625d8bde9d refs: move `refs_head_ref_namespaced()`\n    @@ Commit message\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    -    at most the HEAD ref of the current function. As such, the function is\n    -    related to `refs_head_ref()` and not to the for-each functions.\n    +    while `refs_for_each_*()` functions yield multiple refs,\n    +    `refs_heasd_ref_namespaced()` will only yield at most the HEAD ref of\n    +    the current namespace. As such, the function is related to\n    +    `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 2:  fd0fa20a37 =  3:  2f5a6e7d27 refs: move `do_for_each_ref_flags` further up\n 3:  f11af1c8e7 !  4:  a817b11091 refs: rename `do_for_each_ref_flags`\n    @@ refs.c: 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, 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     @@ refs.c: enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n      \n 4:  37be4c5f59 !  5:  f06b4fc5e4 refs: rename `each_ref_fn`\n    @@ refs.c: int refs_for_each_namespaced_ref(struct ref_store *refs,\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     @@ refs.c: 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    @@ refs.h: int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,\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    +  * Normalizes partial refs to their fully qualified form.\n     @@ refs.h: void ref_iterator_free(struct ref_iterator *ref_iterator);\n       * iterator style.\n       */\n 5:  d70867c5f6 <  -:  ---------- refs: remove unused `refs_for_each_include_root_ref()`\n 6:  b22f654698 =  6:  58620a64dd refs: introduce `refs_for_each_ref_ext`\n 7:  0a050b61f7 !  7:  2ca0ddb23a refs: speed up `refs_for_each_glob_ref_in()`\n    @@ Commit message\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## refs.c ##\n    +@@ refs.c: char *refs_resolve_refdup(struct ref_store *refs,\n    + /* The argument to for_each_filter_refs */\n    + struct for_each_ref_filter {\n    + \tconst char *pattern;\n    +-\tconst char *prefix;\n    ++\tsize_t trim_prefix;\n    + \trefs_for_each_cb *fn;\n    + \tvoid *cb_data;\n    + };\n    +@@ refs.c: static int for_each_filter_refs(const struct reference *ref, void *data)\n    + \n    + \tif (wildmatch(filter->pattern, ref->name, 0))\n    + \t\treturn 0;\n    +-\tif (filter->prefix) {\n    ++\tif (filter->trim_prefix) {\n    + \t\tstruct reference skipped = *ref;\n    +-\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n    ++\t\tif (strlen(skipped.name) <= filter->trim_prefix)\n    ++\t\t\tBUG(\"attempt to trim too many characters\");\n    ++\t\tskipped.name += filter->trim_prefix;\n    + \t\treturn filter->fn(&skipped, filter->cb_data);\n    + \t} else {\n    + \t\treturn filter->fn(ref, filter->cb_data);\n     @@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefix,\n      \tstrbuf_release(&normalized_pattern);\n      }\n    @@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefi\n     +\tstruct refs_for_each_ref_options opts = {\n     +\t\t.pattern = pattern,\n     +\t\t.prefix = prefix,\n    ++\t\t.trim_prefix = prefix ? strlen(prefix) : 0,\n     +\t};\n     +\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n      }\n    @@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,\n     +\tstruct strbuf real_pattern = STRBUF_INIT;\n     +\tstruct for_each_ref_filter filter;\n      \tstruct ref_iterator *iter;\n    ++\tsize_t trim_prefix = opts->trim_prefix;\n     +\tint ret;\n      \n      \tif (!refs)\n    @@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,\n     +\t\t}\n     +\n     +\t\tfilter.pattern = real_pattern.buf;\n    -+\t\tfilter.prefix = opts->prefix;\n    ++\t\tfilter.trim_prefix = opts->trim_prefix;\n     +\t\tfilter.fn = cb;\n     +\t\tfilter.cb_data = cb_data;\n     +\n    ++\t\t/*\n    ++\t\t * We need to trim the prefix in the callback function as the\n    ++\t\t * pattern is expected to match on the full refname.\n    ++\t\t */\n    ++\t\ttrim_prefix = 0;\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    +-\t\t\t\t       opts->trim_prefix, opts->flags);\n    ++\t\t\t\t       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 8:  b15d334f14 !  8:  01a640a61d refs: generalize `refs_for_each_namespaced_ref()`\n    @@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,\n      \tstruct strbuf real_pattern = STRBUF_INIT;\n      \tstruct for_each_ref_filter filter;\n      \tstruct ref_iterator *iter;\n    + \tsize_t trim_prefix = opts->trim_prefix;\n     +\tconst char **exclude_patterns;\n     +\tconst char *prefix;\n      \tint ret;\n    @@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,\n     +\t}\n     +\n     +\titer = refs_ref_iterator_begin(refs, prefix, exclude_patterns,\n    - \t\t\t\t       opts->trim_prefix, opts->flags);\n    + \t\t\t\t       trim_prefix, opts->flags);\n      \n      \tret = do_for_each_ref_iterator(iter, cb, cb_data);\n     +\n    @@ refs.h: struct refs_for_each_ref_options {\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 * patterns will be rewritten to apply to the namespace, and the prefix\n    ++\t * will be considered relative to the namespace.\n     +\t */\n     +\tconst char *namespace;\n     +\n 9:  2e63b1ab88 =  9:  241030d7ad refs: generalize `refs_for_each_fullref_in_prefixes()`\n10:  b408e5c1f0 ! 10:  548aae78f0 refs: improve verification for-each-ref options\n    @@ refs.c: int refs_for_each_ref_ext(struct ref_store *refs,\n      \n      \tif (!refs)\n     -\t\treturn 0;\n    -+\t\tBUG(\"no refs passed\");\n    ++\t\tBUG(\"no ref store passed\");\n     +\n     +\tif (opts->trim_prefix) {\n     +\t\tsize_t prefix_len;\n11:  5c9401df32 = 11:  b0a5c835be refs: replace `refs_for_each_ref_in()`\n12:  39a5f1ef21 = 12:  59f5632719 refs: replace `refs_for_each_rawref()`\n13:  5568ee95d0 = 13:  8b99f6e38c refs: replace `refs_for_each_rawref_in()`\n14:  d73e6362ae ! 14:  ed75a64569 refs: replace `refs_for_each_glob_ref_in()`\n    @@ builtin/bisect.c: static void bisect_status(struct bisect_state *state,\n     +\tstruct refs_for_each_ref_options opts = {\n     +\t\t.pattern = good_glob,\n     +\t\t.prefix = \"refs/bisect/\",\n    ++\t\t.trim_prefix = strlen(\"refs/bisect/\"),\n     +\t};\n      \n      \tif (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))\n    @@ builtin/bisect.c: static int add_bisect_ref(const struct reference *ref, void *c\n      {\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      \tint res = 0;\n      \tstruct add_bisect_ref_data cb = { revs };\n    @@ builtin/bisect.c: static int verify_good(const struct bisect_terms *terms, const\n     +\tstruct refs_for_each_ref_options opts = {\n     +\t\t.pattern = good_glob,\n     +\t\t.prefix = \"refs/bisect/\",\n    ++\t\t.trim_prefix = strlen(\"refs/bisect/\"),\n     +\t};\n      \n     -\trefs_for_each_glob_ref_in(get_main_ref_store(the_repository),\n    @@ builtin/rev-parse.c: static int opt_with_value(const char *arg, const char *opt,\n     +\t\tstruct refs_for_each_ref_options opts = {\n     +\t\t\t.pattern = pattern,\n     +\t\t\t.prefix = prefix,\n    ++\t\t\t.trim_prefix = prefix ? strlen(prefix) : 0,\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    @@ refs.c: void normalize_glob_ref(struct string_list_item *item, const char *prefi\n     -\tstruct refs_for_each_ref_options opts = {\n     -\t\t.pattern = pattern,\n     -\t\t.prefix = prefix,\n    +-\t\t.trim_prefix = prefix ? strlen(prefix) : 0,\n     -\t};\n     -\treturn refs_for_each_ref_ext(refs, cb, cb_data, &opts);\n     -}\n    @@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,\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.trim_prefix = strlen(\"refs/heads/\"),\n     +\t\t\t.pattern = optarg,\n     +\t\t};\n      \t\tstruct all_refs_cb cb;\n    @@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,\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.trim_prefix = strlen(\"refs/tags/\"),\n     +\t\t\t.pattern = optarg,\n     +\t\t};\n      \t\tstruct all_refs_cb cb;\n    @@ revision.c: static int handle_revision_pseudo_opt(struct rev_info *revs,\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.trim_prefix = strlen(\"refs/remotes/\"),\n     +\t\t\t.pattern = optarg,\n     +\t\t};\n      \t\tstruct all_refs_cb cb;\n15:  c957d80f2d = 15:  6256a04ecf refs: replace `refs_for_each_glob_ref()`\n16:  229d69d91c = 16:  ef356544bf refs: replace `refs_for_each_namespaced_ref()`\n17:  4e0fe9f805 = 17:  4616cdc618 refs: replace `refs_for_each_fullref_in()`\n\n---\nbase-commit: dbbe43524e0814c1f93325795ed6aa26eb6e587e\nchange-id: 20260220-pks-refs-for-each-unification-7572c694cfc0\n\n"},{"id":"536751","messageId":"20260223-pks-refs-for-each-unification-v2-2-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 02/17] refs: move `refs_head_ref_namespaced()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:36Z","receivedAt":"2026-02-23T11:59:47Z","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,\n`refs_heasd_ref_namespaced()` will only yield at most the HEAD ref of\nthe current namespace. As such, the function is related to\n`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 1fdb809343..718212a5d7 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.536.g309c995771.dirty\n\n"},{"id":"536752","messageId":"20260223-pks-refs-for-each-unification-v2-3-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 03/17] refs: move `do_for_each_ref_flags` further up","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:37Z","receivedAt":"2026-02-23T11:59:50Z","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 718212a5d7..6fd7a706b5 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@@ -1326,43 +1363,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.536.g309c995771.dirty\n\n"},{"id":"536753","messageId":"20260223-pks-refs-for-each-unification-v2-4-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 04/17] refs: rename `do_for_each_ref_flags`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:38Z","receivedAt":"2026-02-23T11:59:52Z","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                  | 18 +++++++++---------\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, 31 insertions(+), 31 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 466398494f..52a680797a 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,7 +1929,7 @@ 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 static int qsort_strcmp(const void *va, const void *vb)\n@@ -2741,7 +2741,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@@ -3281,7 +3281,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 6fd7a706b5..2ae4a6e75b 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@@ -1372,7 +1372,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.536.g309c995771.dirty\n\n"},{"id":"536754","messageId":"20260223-pks-refs-for-each-unification-v2-5-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 05/17] refs: rename `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:39Z","receivedAt":"2026-02-23T11:59:55Z","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          | 34 +++++++++++++++++-----------------\n refs.h          | 38 +++++++++++++++++++-------------------\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, 50 insertions(+), 50 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 52a680797a..a45cc61211 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,13 +1920,13 @@ 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@@ -1994,7 +1994,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 2ae4a6e75b..5190e98b2c 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,12 +502,12 @@ 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  * Normalizes partial refs to their fully qualified form.\n@@ -1421,6 +1421,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.536.g309c995771.dirty\n\n"},{"id":"536755","messageId":"20260223-pks-refs-for-each-unification-v2-6-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 06/17] refs: introduce `refs_for_each_ref_ext`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:40Z","receivedAt":"2026-02-23T11:59:58Z","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.536.g309c995771.dirty\n\n"},{"id":"536756","messageId":"20260223-pks-refs-for-each-unification-v2-7-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 07/17] refs: speed up `refs_for_each_glob_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:41Z","receivedAt":"2026-02-23T12:00:03Z","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 | 87 +++++++++++++++++++++++++++++++++++++++++-------------------------\n refs.h | 10 ++++++++\n 2 files changed, 64 insertions(+), 33 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ec9e466381..e4402d787f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -444,7 +444,7 @@ char *refs_resolve_refdup(struct ref_store *refs,\n /* The argument to for_each_filter_refs */\n struct for_each_ref_filter {\n \tconst char *pattern;\n-\tconst char *prefix;\n+\tsize_t trim_prefix;\n \trefs_for_each_cb *fn;\n \tvoid *cb_data;\n };\n@@ -475,9 +475,11 @@ static int for_each_filter_refs(const struct reference *ref, void *data)\n \n \tif (wildmatch(filter->pattern, ref->name, 0))\n \t\treturn 0;\n-\tif (filter->prefix) {\n+\tif (filter->trim_prefix) {\n \t\tstruct reference skipped = *ref;\n-\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n+\t\tif (strlen(skipped.name) <= filter->trim_prefix)\n+\t\t\tBUG(\"attempt to trim too many characters\");\n+\t\tskipped.name += filter->trim_prefix;\n \t\treturn filter->fn(&skipped, filter->cb_data);\n \t} else {\n \t\treturn filter->fn(ref, filter->cb_data);\n@@ -590,40 +592,24 @@ 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\t.trim_prefix = prefix ? strlen(prefix) : 0,\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 +1848,51 @@ 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+\tsize_t trim_prefix = opts->trim_prefix;\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.trim_prefix = opts->trim_prefix;\n+\t\tfilter.fn = cb;\n+\t\tfilter.cb_data = cb_data;\n+\n+\t\t/*\n+\t\t * We need to trim the prefix in the callback function as the\n+\t\t * pattern is expected to match on the full refname.\n+\t\t */\n+\t\ttrim_prefix = 0;\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+\t\t\t\t       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.536.g309c995771.dirty\n\n"},{"id":"536757","messageId":"20260223-pks-refs-for-each-unification-v2-8-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 08/17] refs: generalize `refs_for_each_namespaced_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:42Z","receivedAt":"2026-02-23T12:00:05Z","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 |  7 +++++++\n 2 files changed, 36 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex e4402d787f..0d0f0edbfb 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1848,10 +1848,14 @@ 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 \tsize_t trim_prefix = opts->trim_prefix;\n+\tconst char **exclude_patterns;\n+\tconst char *prefix;\n \tint ret;\n \n \tif (!refs)\n@@ -1886,11 +1890,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       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@@ -1937,22 +1959,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..5a5fb4e1e4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -468,6 +468,13 @@ 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, and the prefix\n+\t * will be considered relative 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.536.g309c995771.dirty\n\n"},{"id":"536758","messageId":"20260223-pks-refs-for-each-unification-v2-9-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 09/17] refs: generalize `refs_for_each_fullref_in_prefixes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:43Z","receivedAt":"2026-02-23T12:00:07Z","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 0d0f0edbfb..0aa3b68dd9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2039,40 +2039,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 5a5fb4e1e4..faed63aa81 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -521,19 +521,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.536.g309c995771.dirty\n\n"},{"id":"536759","messageId":"20260223-pks-refs-for-each-unification-v2-10-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 10/17] refs: improve verification for-each-ref options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:44Z","receivedAt":"2026-02-23T12:00:10Z","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 0aa3b68dd9..a57eafd6de 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1859,7 +1859,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 ref store 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.536.g309c995771.dirty\n\n"},{"id":"536760","messageId":"20260223-pks-refs-for-each-unification-v2-11-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 11/17] refs: replace `refs_for_each_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:45Z","receivedAt":"2026-02-23T12:00:12Z","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 a57eafd6de..7b1ef769c0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -529,19 +529,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@@ -1934,16 +1946,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 faed63aa81..7a3bc9e5b7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -501,8 +501,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.536.g309c995771.dirty\n\n"},{"id":"536761","messageId":"20260223-pks-refs-for-each-unification-v2-12-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 12/17] refs: replace `refs_for_each_rawref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:46Z","receivedAt":"2026-02-23T12:00:15Z","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 7b1ef769c0..791654a0f6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -526,7 +526,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@@ -1979,11 +1982,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 7a3bc9e5b7..01dc3c2fd4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -543,7 +543,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.536.g309c995771.dirty\n\n"},{"id":"536762","messageId":"20260223-pks-refs-for-each-unification-v2-13-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 13/17] refs: replace `refs_for_each_rawref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:47Z","receivedAt":"2026-02-23T12:00:18Z","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 791654a0f6..172d4cf941 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1982,16 +1982,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 01dc3c2fd4..673d4ccce5 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -542,10 +542,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.536.g309c995771.dirty\n\n"},{"id":"536764","messageId":"20260223-pks-refs-for-each-unification-v2-14-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 14/17] refs: replace `refs_for_each_glob_ref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:48Z","receivedAt":"2026-02-23T12:00:21Z","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    | 37 +++++++++++++++++++++++++++----------\n builtin/rev-parse.c | 10 +++++++---\n refs.c              | 11 -----------\n refs.h              |  3 ---\n revision.c          | 30 +++++++++++++++++++++---------\n 5 files changed, 55 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 4cc118fb57..4520e585d0 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -422,13 +422,17 @@ 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\t.trim_prefix = strlen(\"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 +566,10 @@ 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\t.trim_prefix = strlen(\"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 +589,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 +1204,14 @@ 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\t.trim_prefix = strlen(\"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..61a3f0fdb9 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -614,9 +614,13 @@ 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\t.trim_prefix = prefix ? strlen(prefix) : 0,\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 172d4cf941..b4ef4ffff0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -607,17 +607,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\t.trim_prefix = prefix ? strlen(prefix) : 0,\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 673d4ccce5..3fa2c11c1f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -531,9 +531,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..074a75b859 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2827,34 +2827,46 @@ 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.trim_prefix = strlen(\"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.trim_prefix = strlen(\"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.trim_prefix = strlen(\"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.536.g309c995771.dirty\n\n"},{"id":"536763","messageId":"20260223-pks-refs-for-each-unification-v2-15-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 15/17] refs: replace `refs_for_each_glob_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:49Z","receivedAt":"2026-02-23T12:00:23Z","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 b4ef4ffff0..ca7fc7289b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -607,15 +607,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 3fa2c11c1f..b63775fa35 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -527,10 +527,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 074a75b859..4ddb3370c6 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.536.g309c995771.dirty\n\n"},{"id":"536765","messageId":"20260223-pks-refs-for-each-unification-v2-16-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 16/17] refs: replace `refs_for_each_namespaced_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:50Z","receivedAt":"2026-02-23T12:00:26Z","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 ca7fc7289b..35a4925ac4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1951,17 +1951,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 b63775fa35..1b468c4ffb 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -527,14 +527,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.536.g309c995771.dirty\n\n"},{"id":"536766","messageId":"20260223-pks-refs-for-each-unification-v2-17-515d48c8087b@pks.im","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"[PATCH v2 17/17] refs: replace `refs_for_each_fullref_in()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-23T11:59:51Z","receivedAt":"2026-02-23T12:00:29Z","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 61a3f0fdb9..01a62800e8 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -940,14 +940,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 35a4925ac4..af51a648d5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1929,17 +1929,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 1b468c4ffb..9b5d57a9b7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -510,14 +510,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 4ddb3370c6..0136ef64f5 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.536.g309c995771.dirty\n\n"},{"id":"536969","messageId":"CAOLa=ZTz-9JKHC3H8tSwP=bDvX2mNA-erjr5Ni6Tiq4cm4WmLg@mail.gmail.com","threadId":"65055","inReplyTo":"20260223-pks-refs-for-each-unification-v2-0-515d48c8087b@pks.im","subject":"Re: [PATCH v2 00/17] refs: unify `refs_for_each_*()` functions","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-24T13:14:10Z","receivedAt":"2026-02-24T13:14:12Z","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> Changes in v2:\n>   - Move the removal of `refs_for_each_include_root_ref()` to the\n>     beginning of the series to avoid some unnecessary churn.\n>   - Some commit message improvements.\n>   - Make the converted version of `refs_for_each_glob_ref_in()` fit into\n>     the new calling conventions a bit better. The function was still\n>     stripping the prefix unconditionally for example, which I've now\n>     changed.\n>   - Link to v1: https://lore.kernel.org/r/20260220-pks-refs-for-each-unification-v1-0-17170bd99de1@pks.im\n>\n\nThe range-diff looks good. I'm happy with this version! :)\n\n[snip]\n"}]}