fix(push): stop the pack walk at remote haves and shallow boundaries - #183
Merged
Merged
Conversation
`js_push_remote` packed everything reachable from HEAD before talking to the receiver at all. On a shallow history that walk runs off the end: the tip's parents were never fetched, so it aborted with "Missing commit object" — the failure reported for a Cloudflare Artifacts repo imported with `depth: 1`, where the *remote* is shallow and any clone of it is too. Real git pushes to such a remote because the boundary commit is already on the server and never needs to be sent. Discover the receiver's refs first and use them as the exclusion set, as the native `push_http` path already does. That both fixes the shallow case and stops re-sending history the remote advertises. Teach the collectors about shallow boundaries as well, so a walk stops at a commit whose parents were never fetched instead of failing, the way git's grafted walk does. The boundary is only known if it was recorded, so the JS clone and fetch paths now write `.git/shallow` from the server's shallow/unshallow lines, and fetch reports its current boundary back. `sync_remote_have_ids` moves to `bit_lib` as `advertised_have_ids` so both push paths share one implementation. Verified with the reproduction from the issue: before, three of its four cases failed; after, all four push and the shallow remote's branch advances. Added as `tools/shallow-push.test.mjs`, which serves a bare repo and a depth-1 copy of it over `git http-backend`. Fixes #182 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDVUJHu38YtVAr6sKZtRDB
This was referenced Sep 20, 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.
Fixes #182.
The bug
js_push_remotebuilt the packfile before contacting the receiver, collecting everything reachable fromHEAD. On a shallow history that walk runs off the end of what was fetched — the tip's parents are not on disk — and aborts withMissing commit object.That is exactly the shape of a repo imported with
depth: 1(Cloudflare Artifacts): the remote is shallow, so every clone of it is shallow too, whether or not the client asked for a depth. Real git pushes to such a remote fine, because the boundary commit is already on the server and never needs to be sent.Reproducing the issue's script against the current build, the failure is actually broader than reported — three of the four cases fail:
The fix
Discover before collecting.
js_push_remotenow fetches the receiver's ref advertisement first and uses it as the exclusion set, which is what the nativepush_httppath has always done. Everything reachable from an advertised ref is already on the other side, so the walk stops there — it never reaches the absent parents, and it stops re-sending history the remote already has.Teach the collectors about shallow boundaries.
collect_reachable_objects*take an optionalshallow~set; a commit in it contributes its own tree but ends the walk, the way git's grafted walk treats.git/shallow. This covers the cases the ref advertisement does not bound, such as a force-push or a branch the remote has never seen.Record the boundary. That set only means something if it was written down, so the JS clone and fetch paths now switch to
fetch_pack_with_http_resultand persist the server'sshallow/unshallowlines via@repo.apply_shallow_updates; fetch also reports its current boundary back to the server. The native push paths pass their boundary through too.sync_remote_have_idsmoves intobit_libasadvertised_have_idsso both push paths share one implementation.Verification
All four cases from the issue now pass, and the shallow remote's branch really advances:
Tests added:
tools/shallow-push.test.mjs— the end-to-end guard, serving a bare repo and a--depth 1copy of it overgit http-backendand pushing to both from a bit clone. It fails on the pre-fix build withMissing commit objectand passes after. Registered inTaskfile.pkl/Test.pkl; it skips itself wheregit http-backendis unavailable.pack_collect_test.mbtcovering a tip whose parent object is absent, the shallow-boundary graft, andadvertised_have_ids.Suites run:
moon test(1481 native + 2 wasm, all pass),moon test --target native(1483/1483),moon test --target js(1084/1086 — the two failures are the pre-existingssh-keygenenvironment failures, identical on the base commit),node tools/check-layers.mjs, and the JS bundle guardrails intools/js-build.test.mjs.🤖 Generated with Claude Code
https://claude.ai/code/session_01VDVUJHu38YtVAr6sKZtRDB
Generated by Claude Code