Skip to content

Mirror sync: negotiate from the default branch and recent tips, not only changed refs' old tips - #51

Merged
piob-io merged 2 commits into
mainfrom
devin/1788629698-sync-negotiation-tips
Sep 5, 2026
Merged

Mirror sync: negotiate from the default branch and recent tips, not only changed refs' old tips#51
piob-io merged 2 commits into
mainfrom
devin/1788629698-sync-negotiation-tips

Conversation

@piob-io

@piob-io piob-io commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The post-step mirror sync fetches only the refs that ls-remote shows changed, and restricts negotiation with --negotiation-tip (so git does not walk all local refs on a cold disk). Until now the only tips offered were the old local tips of the changed refs; main/master was guessed by name and added only when every changed ref was new locally.

That is the right common ancestor for a fast-forward push, but not for a rebased / force-pushed branch (or a batch of one stale branch + one new branch): the old tip's ancestry ends at the old fork point, so the server has to ship everything on the base branch since then. On a large monorepo with long-lived stacked branches that is hundreds of MB to GBs, the fetch blows the 120 s sync budget, and the disk is released uncommitted. In production this shows up as a strictly bimodal sync-fetch distribution (tens of thousands of fetches < 5 s, almost nothing in between, then a cluster at the budget) with 1–5 changed refs per timed-out job.

Change: add base tips in front of the old tips (old tips are kept — they are still the best common for fast-forwards), and stop guessing the default branch's name:

negotiationTips = dedupe([
  local(mirror HEAD symref target),   // the default branch, always
  ...recentBranchTips(mirror),        // 32 newest refs/heads by committerdate; only when a commit-graph exists
  ...oldTipsOfChangedRefs
]).slice(0, MAX_NEGOTIATION_TIPS /* 1000 */)
  • diffMirrorRefs(lsRemote, localRefs, hints?: {defaultBranchRef, recentTips}) — the cap now lives here, and base tips are ordered first so a >1000-ref batch trims old tips rather than the default branch.
  • mirrorHeadRef() = git symbolic-ref -q HEAD (the upstream default branch as of clone --mirror). It is deliberately not refreshed from upstream each sync: asking a remote for HEAD alone (ls-remote origin HEAD, remote set-head -a) sends no ref-prefix in protocol v2 (verified with GIT_TRACE_PACKET on git 2.34), so it pulls the complete advertisement — the very thing the ls-remote-first sync avoids.
  • recentBranchTips() = for-each-ref --sort=-committerdate --count=32 refs/heads, gated on hasCommitGraph() (the same gate used for fetch.writeCommitGraph) because the sort parses every branch tip's commit and is only cheap with the graph. Without a graph the behaviour is today's plus the default branch tip.
  • With fetch.negotiationAlgorithm=skipping the extra tips cost ~33 haves in the first round; the server ACKs the current base immediately and the pack shrinks to the genuinely new commits. Worst case (no base tip is an ancestor of the new ref) is identical to today.

Unchanged: which refs are fetched, the sync budget, purge/gc/commit-graph handling, refs/pull.

Tests

  • Unit: rewritten-branch case ([defaultTip, oldFeatureTip]), default branch develop + recent tips ordering/dedupe/invalid-oid filtering, default branch ref missing from the mirror, no hints → no tips, 1200-ref batch keeps the default tip under the 1000 cap.
  • Real git (__test__/mirror-sync-negotiation-git.test.ts): source repo with default branch trunk, feature forked early, mirror cloned, then feature rebased onto 40 newer trunk commits upstream. Control (fetch --negotiation-tip=<old feature tip> only, i.e. current behaviour) receives >120 objects; syncMirrorFromRemote offers trunk's tip (via the HEAD symref), gets it ACKed, and receives < 40 objects (just the rewritten commit).

dist/index.js rebuilt.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Link to Devin session: https://app.devin.ai/sessions/a3b42ef15226438faf3f9cdde9662918
Open in Devin Desktop: https://app.devin.ai/desktop/session/a3b42ef15226438faf3f9cdde9662918?variant=devin
Requested by: @piob-io

…nly changed refs' old tips

The sync fetch offered only the old local tips of the changed refs as
--negotiation-tip. For a rebased/force-pushed branch (or a batch of a
stale branch plus a new one) the old tip's ancestry stops at the old
fork point, so the server had to send every commit on the base branch
since then - up to the entire recent history of a large repository -
and the fetch blew the sync budget.

Always offer the mirror's HEAD branch, main/master and (when a
commit-graph makes it cheap) the 32 most recently committed branch tips
in front of the old tips, deduplicated and still capped at 1000.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

… name guessing

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@piob-io
piob-io merged commit f505656 into main Sep 5, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant