--ignore-case in ListRefs negates the performance benefit of Gitaly-side ref search

Background

Rails currently implements ref search (Branches/Tags API + UI) by fetching all refs from Gitaly and filtering in Ruby. The cost scales with the total number of refs in the repository, not with the number of matches: for a repository with 10,000+ tags, every search request makes Gitaly build, serialize, and stream all 10,000 tags so Rails can keep ~20 of them. A customer has reported ~20 s Tags API responses because of this.

We want to move the search down to ListRefs so the filtering happens inside Gitaly and only matching refs are returned (epic &11057).

Constraint

Ref search in GitLab has always been case-insensitive, and we need to keep it that way (context).

Problem

ListRefs supports case-insensitive matching only through git's --ignore-case flag. That flag is expensive. It prevents git from using its fast ref iteration path. In our benchmarks, the slowdown from --ignore-case outweighs the gain from transferring fewer refs — so overall, searching via ListRefs ends up slower than the current fetch-everything approach:

Legacy (fetch all + Ruby filter) ListRefs + --ignore-case
Branches, 12k refs (benchmark) 620 ms 691 ms
Tags, 2.6k refs (benchmark) 44 ms 45.5 ms

We also tried encoding case-insensitivity as character-class globs (test → [Tt][Ee][Ss][Tt]): fast, but it cannot express Unicode case rules (Turkish İ/ı, German ß → SS), so it was shipped and reverted.

Questions

  1. Is there a way to perform case-insensitive matching in ListRefs without the --ignore-case penalty — for example, a casefolded comparison that keeps git's optimized ref iteration?
  2. If not feasible in git as-is, can gitaly filter (outside of git)?, e.g. iterating refs with the fast path and matching case-insensitively in Go before streaming?
  3. If neither is viable, what approach would you recommend for making ref search scale with the number of matches instead of the repository size?

Impact

  • Blocks Gitaly-side tag search (gitlab#372051)
  • Branch search currently falls back to the legacy fetch-all finder for the same reason
Edited by Emma Park