refs: speed up `refs_for_each_glob_ref_in()`

The function `refs_for_each_glob_ref_in()` can be used to iterate through all refs in a specific prefix with globbing. The logic to handle this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up a callback function that knows to filter out refs that _don't_ match the given globbing pattern. The way we do this is somewhat inefficient though: even though the function is expected to only yield refs in the given prefix, we still end up iterating through _all_ references, regardless of whether or not their name matches the given prefix. Extend `refs_for_each_ref_ext()` so that it can handle patterns and adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to use the same callback-based infrastructure to filter individual refs via the globbing pattern, but we can now also use the other functionality of the `_ext()` variant. Most importantly, this means that we now properly handle the prefix. This results in a performance improvement when using a prefix where a significant majority of refs exists outside of the prefix. The following benchmark is an extreme case, with 1 million refs that exist outside the prefix and a single ref that exists inside it: Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~) Time (mean ± σ): 115.9 ms ± 0.7 ms [User: 113.0 ms, System: 2.4 ms] Range (min … max): 114.9 ms … 117.8 ms 25 runs Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD) Time (mean ± σ): 1.1 ms ± 0.1 ms [User: 0.3 ms, System: 0.7 ms] Range (min … max): 1.0 ms … 2.3 ms 2092 runs Summary git rev-parse --branches=refs/heads/* (rev = HEAD) ran 107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~) Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Feb 23, 2026 at 12:59 UTC daf01b1366ca644d45374451560aeeb4fc8a7765
2 files changed +64 -33
refs.c
+54 -33
@@ -444,7 +444,7 @@ char *refs_resolve_refdup(struct ref_store *refs,
444 /* The argument to for_each_filter_refs */
445 struct for_each_ref_filter {
446 const char *pattern;
447 - const char *prefix;
447 + size_t trim_prefix;
448 refs_for_each_cb *fn;
449 void *cb_data;
450 };
@@ -475,9 +475,11 @@ static int for_each_filter_refs(const struct reference *ref, void *data)
475
476 if (wildmatch(filter->pattern, ref->name, 0))
477 return 0;
478 - if (filter->prefix) {
478 + if (filter->trim_prefix) {
479 struct reference skipped = *ref;
480 - skip_prefix(skipped.name, filter->prefix, &skipped.name);
480 + if (strlen(skipped.name) <= filter->trim_prefix)
481 + BUG("attempt to trim too many characters");
482 + skipped.name += filter->trim_prefix;
483 return filter->fn(&skipped, filter->cb_data);
484 } else {
485 return filter->fn(ref, filter->cb_data);
@@ -590,40 +592,24 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
592 strbuf_release(&normalized_pattern);
593 }
594
593 -int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb fn,
595 +int refs_for_each_glob_ref_in(struct ref_store *refs, refs_for_each_cb cb,
596 const char *pattern, const char *prefix, void *cb_data)
597 {
596 - struct strbuf real_pattern = STRBUF_INIT;
597 - struct for_each_ref_filter filter;
598 - int ret;
599 -
600 - if (!prefix && !starts_with(pattern, "refs/"))
601 - strbuf_addstr(&real_pattern, "refs/");
602 - else if (prefix)
603 - strbuf_addstr(&real_pattern, prefix);
604 - strbuf_addstr(&real_pattern, pattern);
605 -
606 - if (!has_glob_specials(pattern)) {
607 - /* Append implied '/' '*' if not present. */
608 - strbuf_complete(&real_pattern, '/');
609 - /* No need to check for '*', there is none. */
610 - strbuf_addch(&real_pattern, '*');
611 - }
612 -
613 - filter.pattern = real_pattern.buf;
614 - filter.prefix = prefix;
615 - filter.fn = fn;
616 - filter.cb_data = cb_data;
617 - ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
618 -
619 - strbuf_release(&real_pattern);
620 - return ret;
598 + struct refs_for_each_ref_options opts = {
599 + .pattern = pattern,
600 + .prefix = prefix,
601 + .trim_prefix = prefix ? strlen(prefix) : 0,
602 + };
603 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
604 }
605
623 -int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,
606 +int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb cb,
607 const char *pattern, void *cb_data)
608 {
626 - return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
609 + struct refs_for_each_ref_options opts = {
610 + .pattern = pattern,
611 + };
612 + return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
613 }
614
615 const char *prettify_refname(const char *name)
@@ -1862,16 +1848,51 @@ int refs_for_each_ref_ext(struct ref_store *refs,
1848 refs_for_each_cb cb, void *cb_data,
1849 const struct refs_for_each_ref_options *opts)
1850 {
1851 + struct strbuf real_pattern = STRBUF_INIT;
1852 + struct for_each_ref_filter filter;
1853 struct ref_iterator *iter;
1854 + size_t trim_prefix = opts->trim_prefix;
1855 + int ret;
1856
1857 if (!refs)
1858 return 0;
1859
1860 + if (opts->pattern) {
1861 + if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
1862 + strbuf_addstr(&real_pattern, "refs/");
1863 + else if (opts->prefix)
1864 + strbuf_addstr(&real_pattern, opts->prefix);
1865 + strbuf_addstr(&real_pattern, opts->pattern);
1866 +
1867 + if (!has_glob_specials(opts->pattern)) {
1868 + /* Append implied '/' '*' if not present. */
1869 + strbuf_complete(&real_pattern, '/');
1870 + /* No need to check for '*', there is none. */
1871 + strbuf_addch(&real_pattern, '*');
1872 + }
1873 +
1874 + filter.pattern = real_pattern.buf;
1875 + filter.trim_prefix = opts->trim_prefix;
1876 + filter.fn = cb;
1877 + filter.cb_data = cb_data;
1878 +
1879 + /*
1880 + * We need to trim the prefix in the callback function as the
1881 + * pattern is expected to match on the full refname.
1882 + */
1883 + trim_prefix = 0;
1884 +
1885 + cb = for_each_filter_refs;
1886 + cb_data = &filter;
1887 + }
1888 +
1889 iter = refs_ref_iterator_begin(refs, opts->prefix ? opts->prefix : "",
1890 opts->exclude_patterns,
1872 - opts->trim_prefix, opts->flags);
1891 + trim_prefix, opts->flags);
1892
1874 - return do_for_each_ref_iterator(iter, cb, cb_data);
1893 + ret = do_for_each_ref_iterator(iter, cb, cb_data);
1894 + strbuf_release(&real_pattern);
1895 + return ret;
1896 }
1897
1898 int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
refs.h
+10
@@ -458,6 +458,16 @@ struct refs_for_each_ref_options {
458 /* Only iterate over references that have this given prefix. */
459 const char *prefix;
460
461 + /*
462 + * A globbing pattern that can be used to only yield refs that match.
463 + * If given, refs will be matched against the pattern with
464 + * `wildmatch()`.
465 + *
466 + * If the pattern doesn't contain any globbing characters then it is
467 + * treated as if it was ending with "/" and "*".
468 + */
469 + const char *pattern;
470 +
471 /*
472 * Exclude any references that match any of these patterns on a
473 * best-effort basis. The caller needs to be prepared for the exclude