refs: generalize `refs_for_each_fullref_in_prefixes()`

The function `refs_for_each_fullref_in_prefixes()` can be used to iterate over all references part of any of the user-provided prefixes. In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it knows to handle the case well where multiple of the passed-in prefixes start with a common prefix by computing longest common prefixes and then iterating over those. While we could move this logic into `refs_for_each_ref_ext()`, this one feels somewhat special as we perform multiple iterations. But what we _can_ do is to generalize how this function works: instead of accepting only a small handful of parameters, we can have it accept the full options structure. One obvious exception is that the caller must not provide a prefix via the options. But this case can be easily detected. Refactor the code accordingly. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Feb 23, 2026 at 12:59 UTC f503bb7dc96ee92623ade8d60eed401ecfddae0f
4 files changed +34 -43
ls-refs.c
+7 -4
@@ -160,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value,
160
161 int ls_refs(struct repository *r, struct packet_reader *request)
162 {
163 + struct refs_for_each_ref_options opts = { 0 };
164 struct ls_refs_data data;
165
166 memset(&data, 0, sizeof(data));
@@ -201,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request)
202 send_possibly_unborn_head(&data);
203 if (!data.prefixes.nr)
204 strvec_push(&data.prefixes, "");
204 - refs_for_each_fullref_in_prefixes(get_main_ref_store(r),
205 - get_git_namespace(), data.prefixes.v,
206 - hidden_refs_to_excludes(&data.hidden_refs),
207 - send_ref, &data);
205 +
206 + opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs);
207 + opts.namespace = get_git_namespace();
208 +
209 + refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v,
210 + &opts, send_ref, &data);
211 packet_fflush(stdout);
212 strvec_clear(&data.prefixes);
213 strbuf_release(&data.buf);
ref-filter.c
+7 -4
@@ -2807,6 +2807,10 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
2807 refs_for_each_cb cb,
2808 void *cb_data)
2809 {
2810 + struct refs_for_each_ref_options opts = {
2811 + .exclude_patterns = filter->exclude.v,
2812 + };
2813 +
2814 if (filter->kind & FILTER_REFS_ROOT_REFS) {
2815 /* In this case, we want to print all refs including root refs. */
2816 return for_each_fullref_with_seek(filter, cb, cb_data,
@@ -2836,10 +2840,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
2840 return for_each_fullref_with_seek(filter, cb, cb_data, 0);
2841 }
2842
2839 - return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository),
2840 - NULL, filter->name_patterns,
2841 - filter->exclude.v,
2842 - cb, cb_data);
2843 + return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository),
2844 + filter->name_patterns, &opts,
2845 + cb, cb_data);
2846 }
2847
2848 /*
refs.c
+15 -24
@@ -2039,40 +2039,31 @@ static void find_longest_prefixes(struct string_list *out,
2039 strbuf_release(&prefix);
2040 }
2041
2042 -int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
2043 - const char *namespace,
2044 - const char **patterns,
2045 - const char **exclude_patterns,
2046 - refs_for_each_cb fn, void *cb_data)
2042 +int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
2043 + const char **prefixes,
2044 + const struct refs_for_each_ref_options *opts,
2045 + refs_for_each_cb cb, void *cb_data)
2046 {
2048 - struct strvec namespaced_exclude_patterns = STRVEC_INIT;
2049 - struct string_list prefixes = STRING_LIST_INIT_DUP;
2047 + struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
2048 struct string_list_item *prefix;
2051 - struct strbuf buf = STRBUF_INIT;
2052 - int ret = 0, namespace_len;
2049 + int ret = 0;
2050
2054 - find_longest_prefixes(&prefixes, patterns);
2051 + if (opts->prefix)
2052 + BUG("refs_for_each_ref_in_prefixes called with specific prefix");
2053
2056 - if (namespace)
2057 - strbuf_addstr(&buf, namespace);
2058 - namespace_len = buf.len;
2054 + find_longest_prefixes(&longest_prefixes, prefixes);
2055
2060 - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
2061 - namespace,
2062 - &namespaced_exclude_patterns);
2056 + for_each_string_list_item(prefix, &longest_prefixes) {
2057 + struct refs_for_each_ref_options prefix_opts = *opts;
2058 + prefix_opts.prefix = prefix->string;
2059
2064 - for_each_string_list_item(prefix, &prefixes) {
2065 - strbuf_addstr(&buf, prefix->string);
2066 - ret = refs_for_each_fullref_in(ref_store, buf.buf,
2067 - exclude_patterns, fn, cb_data);
2060 + ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
2061 + &prefix_opts);
2062 if (ret)
2063 break;
2070 - strbuf_setlen(&buf, namespace_len);
2064 }
2065
2073 - strvec_clear(&namespaced_exclude_patterns);
2074 - string_list_clear(&prefixes, 0);
2075 - strbuf_release(&buf);
2066 + string_list_clear(&longest_prefixes, 0);
2067 return ret;
2068 }
2069
refs.h
+5 -11
@@ -521,19 +521,13 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
521 refs_for_each_cb fn, void *cb_data);
522
523 /**
524 - * iterate all refs in "patterns" by partitioning patterns into disjoint sets
524 + * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets
525 * and iterating the longest-common prefix of each set.
526 - *
527 - * references matching any pattern in "exclude_patterns" are omitted from the
528 - * result set on a best-effort basis.
529 - *
530 - * callers should be prepared to ignore references that they did not ask for.
526 */
532 -int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
533 - const char *namespace,
534 - const char **patterns,
535 - const char **exclude_patterns,
536 - refs_for_each_cb fn, void *cb_data);
527 +int refs_for_each_ref_in_prefixes(struct ref_store *refs,
528 + const char **prefixes,
529 + const struct refs_for_each_ref_options *opts,
530 + refs_for_each_cb cb, void *cb_data);
531
532 /* iterates all refs that match the specified glob pattern. */
533 int refs_for_each_glob_ref(struct ref_store *refs, refs_for_each_cb fn,