Investigate Gitaly server-side exclude support for unprotected branches pagination
Summary
The current unprotectedBranches GraphQL field (introduced in !228758 (closed)) uses client-side filtering to exclude protected branches from Gitaly's FindLocalBranches results. While this resolves the immediate performance problem of loading all branches into memory on every request, it has an inherent correctness limitation.
Problem
The current approach fetches sorted batches from Gitaly (3x page size), filters out protected branches, and loops for additional batches if filtering removed too many results. However, if limit protected branches are not found within MAX_GITALY_BATCHES * FETCH_MULTIPLIER * limit branches, the field returns has_next_page: false when there could be more unprotected branches.
With the current multipliers:
- At the default
limit=100, breaking pagination would require ~3,000 consecutive individually-protected branches (wildcard protections are not excluded) - At
limit=10, it would take ~300
This risk is considered negligible in practice, but the correctness gap exists.
Proposed Solution
Gitaly's git for-each-ref supports the --exclude flag that allows excluding multiple references:
git for-each-ref refs/heads --exclude=refs/heads/master --exclude=refs/heads/mainOnce Gitaly supports this option in its RPC, we can:
- Fetch protected branches with exact branch names (without wildcards)
- Make a call to the Gitaly RPC with an
excludeparameter containing a list of these branches - Gitaly will return the desired paginated list, avoiding post-processing on the Ruby side
This would eliminate the correctness issue entirely and further improve performance.
Context
- Current implementation: !228758 (closed)
- Related issue: #591103
- Original discussion about Gitaly approach: !228758 (comment 3193954016)
- Reviewer request to log this issue: !228758 (comment 3353122468)
- Trade-off analysis: !228758 (comment 3350166430)