refs: introduce `refs_for_each_ref_ext`

In the refs subsystem we have a proliferation of functions that all iterate through references. (Almost) all of these functions internally call `do_for_each_ref()` and provide slightly different arguments so that one can control different aspects of its behaviour. This approach doesn't really scale: every time there is a slightly different use case for iterating through refs we create another new function. This combinatorial explosion doesn't make a lot of sense: it leads to confusing interfaces and heightens the maintenance burden. Refactor the code to become more composable by: - Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`. - Introducing an options structure that lets the caller control individual options. This gives us a much better foundation to build on going forward. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Feb 23, 2026 at 12:59 UTC aefcc9b367016581743e57adf667ee4d56691bb1
2 files changed +77 -30
refs.c
+48 -30
@@ -1858,62 +1858,76 @@ struct ref_iterator *refs_ref_iterator_begin(
1858 return iter;
1859 }
1860
1861 -static int do_for_each_ref(struct ref_store *refs, const char *prefix,
1862 - const char **exclude_patterns,
1863 - refs_for_each_cb fn, int trim,
1864 - enum refs_for_each_flag flags, void *cb_data)
1861 +int refs_for_each_ref_ext(struct ref_store *refs,
1862 + refs_for_each_cb cb, void *cb_data,
1863 + const struct refs_for_each_ref_options *opts)
1864 {
1865 struct ref_iterator *iter;
1866
1867 if (!refs)
1868 return 0;
1869
1871 - iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
1872 - flags);
1870 + iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
1871 + opts->exclude_patterns,
1872 + opts->trim_prefix, opts->flags);
1873
1874 - return do_for_each_ref_iterator(iter, fn, cb_data);
1874 + return do_for_each_ref_iterator(iter, cb, cb_data);
1875 }
1876
1877 -int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
1877 +int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
1878 {
1879 - return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
1879 + struct refs_for_each_ref_options opts = { 0 };
1880 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1881 }
1882
1883 int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
1883 - refs_for_each_cb fn, void *cb_data)
1884 + refs_for_each_cb cb, void *cb_data)
1885 {
1885 - return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
1886 + struct refs_for_each_ref_options opts = {
1887 + .prefix = prefix,
1888 + .trim_prefix = strlen(prefix),
1889 + };
1890 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1891 }
1892
1893 int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
1894 const char **exclude_patterns,
1890 - refs_for_each_cb fn, void *cb_data)
1895 + refs_for_each_cb cb, void *cb_data)
1896 {
1892 - return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
1897 + struct refs_for_each_ref_options opts = {
1898 + .prefix = prefix,
1899 + .exclude_patterns = exclude_patterns,
1900 + };
1901 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1902 }
1903
1895 -int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
1904 +int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
1905 {
1906 const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
1898 - return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
1899 - strlen(git_replace_ref_base),
1900 - REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
1907 + struct refs_for_each_ref_options opts = {
1908 + .prefix = git_replace_ref_base,
1909 + .trim_prefix = strlen(git_replace_ref_base),
1910 + .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
1911 + };
1912 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1913 }
1914
1915 int refs_for_each_namespaced_ref(struct ref_store *refs,
1916 const char **exclude_patterns,
1905 - refs_for_each_cb fn, void *cb_data)
1917 + refs_for_each_cb cb, void *cb_data)
1918 {
1919 + struct refs_for_each_ref_options opts = { 0 };
1920 struct strvec namespaced_exclude_patterns = STRVEC_INIT;
1921 struct strbuf prefix = STRBUF_INIT;
1922 int ret;
1923
1911 - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1912 - get_git_namespace(),
1913 - &namespaced_exclude_patterns);
1914 -
1924 + opts.exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1925 + get_git_namespace(),
1926 + &namespaced_exclude_patterns);
1927 strbuf_addf(&prefix, "%srefs/", get_git_namespace());
1916 - ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
1928 + opts.prefix = prefix.buf;
1929 +
1930 + ret = refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1931
1932 strvec_clear(&namespaced_exclude_patterns);
1933 strbuf_release(&prefix);
@@ -1926,10 +1940,13 @@ int refs_for_each_rawref(struct ref_store *refs, refs_for_each_cb fn, void *cb_d
1940 }
1941
1942 int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
1929 - refs_for_each_cb fn, void *cb_data)
1943 + refs_for_each_cb cb, void *cb_data)
1944 {
1931 - return do_for_each_ref(refs, prefix, NULL, fn, 0,
1932 - REFS_FOR_EACH_INCLUDE_BROKEN, cb_data);
1945 + struct refs_for_each_ref_options opts = {
1946 + .prefix = prefix,
1947 + .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
1948 + };
1949 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
1950 }
1951
1952 static int qsort_strcmp(const void *va, const void *vb)
@@ -3187,6 +3204,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
3204 struct strbuf *errbuf)
3205 {
3206 struct ref_store *old_refs = NULL, *new_refs = NULL;
3207 + struct refs_for_each_ref_options for_each_ref_opts = {
3208 + .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
3209 + };
3210 struct ref_transaction *transaction = NULL;
3211 struct strbuf new_gitdir = STRBUF_INIT;
3212 struct migration_data data = {
@@ -3270,7 +3290,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
3290 data.errbuf = errbuf;
3291
3292 /*
3273 - * We need to use the internal `do_for_each_ref()` here so that we can
3293 + * We need to use `refs_for_each_ref_ext()` here so that we can
3294 * also include broken refs and symrefs. These would otherwise be
3295 * skipped silently.
3296 *
@@ -3280,9 +3300,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
3300 * allow for a central lock due to its design. It's thus on the user to
3301 * ensure that there are no concurrent writes.
3302 */
3283 - ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
3284 - REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
3285 - &data);
3303 + ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
3304 if (ret < 0)
3305 goto done;
3306
refs.h
+29
@@ -453,8 +453,37 @@ int refs_head_ref(struct ref_store *refs,
453 int refs_head_ref_namespaced(struct ref_store *refs,
454 refs_for_each_cb fn, void *cb_data);
455
456 +
457 +struct refs_for_each_ref_options {
458 + /* Only iterate over references that have this given prefix. */
459 + const char *prefix;
460 +
461 + /*
462 + * Exclude any references that match any of these patterns on a
463 + * best-effort basis. The caller needs to be prepared for the exclude
464 + * patterns to be ignored.
465 + *
466 + * The array must be terminated with a NULL sentinel value.
467 + */
468 + const char **exclude_patterns;
469 +
470 + /*
471 + * The number of bytes to trim from the refname. Note that the trimmed
472 + * bytes must not cause the reference to become empty. As such, this
473 + * field should typically only be set when one uses a `prefix` ending
474 + * in a slash.
475 + */
476 + size_t trim_prefix;
477 +
478 + /* Flags that change which refs will be included. */
479 + enum refs_for_each_flag flags;
480 +};
481 +
482 int refs_for_each_ref(struct ref_store *refs,
483 refs_for_each_cb fn, void *cb_data);
484 +int refs_for_each_ref_ext(struct ref_store *refs,
485 + refs_for_each_cb cb, void *cb_data,
486 + const struct refs_for_each_ref_options *opts);
487 int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
488 refs_for_each_cb fn, void *cb_data);
489 int refs_for_each_tag_ref(struct ref_store *refs,