Batch ref-update commands to stay under receive-pack cap#94
Merged
Conversation
A sync of a repo with many refs sent every ref-update command in a single
receive-pack request, which entire-server rejects past 25,000 commands
("too many ref-update commands: 55006 (limit 25000)"). The relay/materialize
strategies (replicate, incremental, materialized) had no command-count
batching.
Batch inside the gitproto push primitives so every strategy benefits:
- PushPack / PushObjects send the pack with the first batch and the remaining
refs as ref-only follow-ups. The pack carries every object for the whole
push, and receive-pack commits the entire received pack (entire-server via
CommitQuarantinedFanout, canonical git via tmp_objdir_migrate — neither
prunes objects unreachable from the pushed tips), so later batches only move
ref pointers.
- PushCommands chunks all commands under maxRefUpdatesPerPush (20_000, with
headroom under the server's 25_000 cap).
Works against both entire-server and canonical git/GitHub (GitHub's per-push
branch/tag limit is opt-in, default unlimited).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 5d4b8f06380e
GitHub returns 500 Internal Server Error when a single push updates ~10k refs at once but accepts 5k — well below entire-server's 25k cap. Lower the default batch to 5_000 so mirroring a many-ref repo works against GitHub out of the box, and add GITSYNC_MAX_REF_UPDATES_PER_PUSH to raise it for targets known to tolerate larger pushes (entire-server, up to 25k) and cut round trips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 7f01a5ec2f19
Plumb a per-target ref-update batch size through the Pusher (MaxRefUpdates), the syncer config, the unstable Options, and the replicate/sync/plan/bootstrap commands as --target-max-ref-updates. Zero keeps the env-or-default limit (GITSYNC_MAX_REF_UPDATES_PER_PUSH or 5000); a positive value overrides it — raise it for entire-server targets (up to 25k), lower it for stricter providers. No global mutable state: the value rides on the Pusher. Also stop spewing a bare "target:" sideband line per ref-only follow-up batch: those carry no useful progress, so push them with progress suppressed and, when verbose, emit one concise "pushed ref-update batch N/M (K refs)" line per batch instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: d908aa3f2758
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 3ab7999b3055
nodo
approved these changes
Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Syncing a repo with many refs fails with a server-side rejection:
entire-server caps a single receive-pack request at 25,000 ref-update commands (
server/githttp.maxRefUpdateCommands). git-sync's relay/materialize strategies (replicate,incremental,materialized) sent every ref-update command in one request, so a repo with 55k refs exceeded the cap in a single push. Only thebootstrapstrategy batched, and only by pack size — never by command count.Fix
Batch inside the gitproto push primitives (
PushPack,PushCommands,PushObjects) so every strategy benefits from one place:PushPack/PushObjectssend the pack with the first batch, then the remaining refs as ref-only follow-up batches. The pack carries every object for the whole push, and receive-pack commits the entire received pack — verified at both ends:CommitQuarantinedFanout("we commit even when some refs failed connectivity… the pack may carry objects shared with refs that did pass")tmp_objdir_migratemigrates the whole quarantine, no reachability pruningSo later ref-only batches resolve against objects already on the server.
PushCommandschunks all commands under the per-push limit.A shared
splitFirstBatch/chunkRefUpdatespair keeps the splitting logic in one place.Batch size: default 5,000, configurable
Tested end-to-end mirroring a real 55,008-ref repo between two GitHub repos. Findings:
GitHub's receive-pack returns 500 Internal Server Error when a single push updates ~10k refs at once (the pack is accepted; it fails applying the ref updates) — far below entire-server's 25k cap. So the default is 5,000, with two override paths:
--target-max-ref-updates NCLI flag (onreplicate,sync,plan,bootstrap).GITSYNC_MAX_REF_UPDATES_PER_PUSHenv var.Precedence: flag/
Pusher.MaxRefUpdates> env > default. Invalid/non-positive → default. The value rides on thePusher(no global mutable state) and is plumbed flag → Options → syncer config → Pusher. Raise it for entire-server targets (up to 25k) to cut round trips; lower it for stricter providers.Progress output
Ref-only follow-up batches carry no useful sideband progress, so a large push used to print a bare
target:line per batch. They now push with progress suppressed; when--verbose, each batch emits one concise line instead:target: pushed ref-update batch N/M (K refs).GitHub compatibility
refs/entire/checkpoints/*to GitHub can trip GitHub secret-scanning push protection; exclude that namespace (--exclude-ref-prefix refs/entire/) or disable push protection on the target. Not a git-sync concern.Tests
TestChunkRefUpdates,TestEffectiveMaxRefUpdates— splitting + limit resolutionTestResolveMaxRefUpdatesPerPush— env override + fallbackTestPushCommandsBatchesOverCap,TestPushPackBatchesOverCap— splitting with an explicit limit; pack rides with first batch, remainder ref-onlyTestPushPackUsesDefaultLimitWhenZero— 0 → defaultTestPushCommandsVerboseLogsBatches— per-batch verbose log; quiet for a single batchgo build,go vet, and the full test suite pass. Verified live: 55,008-ref GitHub→GitHub mirror succeeds at the 5k default.🤖 Generated with Claude Code