feat(repo-server): add opt-in optimized ls-remote mode for large repositories
Summary
Add an opt-in repo-server mode to optimize Git revision resolution for large repositories by using native Git with protocol v2 server-side ref narrowing where possible.
Example setting:
reposerver.git.lsremote.optimized.enabled: "false"The optimization is disabled by default, so existing behavior does not change unless an operator enables it.
Related discussion: https://github.com/argoproj/argo-cd/pull/26429#discussion_r3542404499
Motivation
In large monorepos, git ls-remote can advertise hundreds of thousands of refs. In our repository, the advertised ref set is roughly 800k entries, including many pull-request refs that are not used by normal Argo CD Applications.
Argo CD currently resolves revisions through go-git by listing remote refs. For large repositories, this can be slow and resource-intensive even when Applications only target branches, tags, semantic tag ranges, HEAD, or commit SHAs.
Using native Git with protocol v2 and branch/tag narrowing is much faster for this case:
git -c protocol.version=2 ls-remote --heads --tags <repository>In our environment, this is around 100 times faster because protocol v2 servers do not need to advertise unrelated refs such as pull-request refs.
Proposal
Introduce a disabled-by-default repo-server feature flag for optimized revision resolution:
reposerver.git.lsremote.optimized.enabled: "true"The equivalent repo-server flag is:
argocd-repo-server --git-ls-remote-optimizedWhen enabled, Argo CD should resolve branches, tags, semantic tag ranges, and HEAD through one shared optimized cache entry.
For each cache refresh, one cache-lock owner should:
Retrieve branches and tags using:
git -c protocol.version=2 ls-remote --heads --tags <repository>Resolve
HEADwith a targeted protocol v2 dry-run fetch from a temporary bare repository:git -c protocol.version=2 fetch --dry-run --porcelain --no-tags --depth=1 --filter=tree:0 <repository> HEAD
The targeted fetch uses a bounded set of HEAD-related ref prefixes. --dry-run prevents Git from updating local refs, but it still negotiates and transfers a pack. --depth=1 limits fetched history, while --filter=tree:0 omits trees and blobs when the server supports partial clone filtering. If filtering is unsupported, Git may transfer the complete tip tree.
Both results should be stored together in the same cache entry. Concurrent requests should share this cache fill, producing one branch/tag query and one HEAD query per refresh instead of additional HEAD queries for every caller. Cache hits should not make remote requests.
Full commit SHAs should continue to resolve without a network request.
Existing behavior should remain the default. If either optimized native Git query fails, Argo CD should use the current go-git resolver as the final fallback.
Unsupported fully qualified namespaces, such as refs/pull/123/head, refs/merge-requests/123/head, and other custom refs outside refs/heads/ and refs/tags/, should continue to use go-git directly to preserve compatibility. Short names that cannot be resolved from the optimized branch/tag snapshot should also fall back to the existing resolver.
Source: argoproj/argo-cd