fix(shallow): honour the shallow boundary across clone, gc, fsck and rev-list - #190
Merged
Merged
Conversation
…rev-list A shallow clone records which commits have no parents on disk. bit got that record wrong and then walked past it in four places, the worst of which deleted history without saying so. clone --depth wrote the *wanted ref tips* into .git/shallow rather than the boundary the server reported. Those coincide only at depth 1, so any deeper clone claimed its tip was parentless. `repack -ad` believed it, packed the tip alone and dropped the pack holding the rest: a depth-4 clone lost three commits and exited 0. Record the response's shallow lines instead. The native HTTP clone never wrote the file at all, because prepare_clone_with_http used the fetch variant that discards shallow lines. The repository then reported itself complete, and `fetch --unshallow` — gated on that file existing — short-circuited as up to date, so such a clone could never be completed. Carry the lines through PreparedClone and apply them in both the sync and async writers. gc, prune and repack walked from the ref tips with no boundary and aborted with "Missing commit object"; #183 added the `shallow~` parameter for this and left the call sites unwired. fsck followed the same absent parents and reported a well-formed shallow clone as corrupt. rev-list emitted the boundary's parent, an id with no object behind it, so it counted one commit more than the repository holds while `log` counted correctly. Adds t9021, which covers all six against real git over both transports, and a unit test pinning the fsck graft. Verified by building the pre-fix tree: 9 of its 13 assertions fail there and all pass after. Also runs t902x in the test-subdir task. The filter is a substring match, so `t900` alone had been silently skipping t9020 as well. Fixes #184 Fixes #185 Fixes #186 Fixes #187 Fixes #188 Fixes #189 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDVUJHu38YtVAr6sKZtRDB
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 #184, #185, #186, #187, #188, #189.
A shallow clone records which commits have no parents on disk. bit got that record wrong and then walked past it in four places — the worst of which deleted history without saying so.
None of this is covered by the git-compat suite:
t5537-fetch-shallow.shand friends run upstream git's porcelain against bit's four shimmed plumbing commands, sobit clone --depth,bit gcandbit rev-listnever execute. Reproducing any of it also needs--no-git-fallback; without it bit delegates these commands to the real git binary and every difference disappears.What was wrong
clone --depthrecorded the wanted ref tips (upload_pack_process.mbt) instead of the boundary the server reported. Those coincide only at depth 1, so any deeper clone claimed its tip was parentless:repack -adbelieved it, packed the tip alone and dropped the pack holding the rest — exit 0, no warning, objects unreadable by git afterwards:The native HTTP clone never wrote the file at all, because
prepare_clone_with_httpused thefetch_pack_with_httpvariant that discards the shallow lines. The repository then reported itself complete, andfetch --unshallow— gated on that file existing — short-circuited as already up to date, so such a clone could never be completed (git 6 commits, bit 3). #183 closed the same gap on the JS path; the native one still had it.gc,pruneandrepackwalked from the ref tips with no boundary and aborted withMissing commit object. #183 added theshallow~parameter for exactly this and left the call sites unwired.fsckfollowed the same absent parents and reported a well-formed shallow clone as corrupt.rev-listemitted the boundary's parent — an id with no object behind it — so it counted one more commit than the repository holds whilelogcounted correctly.The fix
clone_process_to_fstakesfetch_pack_process_resultand hands the response's lines to@repo.apply_shallow_updates.PreparedClonecarriesshallow/unshallow; both the sync and async HTTP writers apply them. Newwrite_shallow_boundaries_asynccovers the async writer, which has noAsyncRepoFileSystemto merge against — a fresh clone has nothing to merge.gc_git_dir,repack_git_dir,prune_git_dirand the reflog walk pass@repo.read_shallow_boundaries.fsck_connectivity_checktakesshallow~and stops following parents at a boundary commit; the CLI passes the repository's.rev-listwalk stops at a boundary commit, aslogalready did.Verification
A differential harness ran git × bit × {full clone, shallow clone} over both
file://and smart HTTP, reporting only what breaks on the shallow clone while matching on the full one — so unimplemented-command noise is excluded. 17 shallow-specific divergences before, 0 after (matched 14 → 60).t/t9021-shallow-clone-integrity.shcovers all six against real git over both transports. I built the pre-fix tree to confirm it is a real guard: 9 of its 13 assertions fail there, all 13 pass after. A unit test pins the fsck graft directly.Two flaws in my own test were caught and fixed during that check, both of which had made it pass against broken code:
git logwas tautological, sincegit logis itself bounded by.git/shallow; it now compares the commit objects on disk against what a boundary-honouring walk shows.&&chain withkill … || true, which swallowed every assertion before it. Tear-down moved to anEXITtrap.About the
t/suite — this test does not run in CIWorth stating plainly, since the new test's placement suggests otherwise: no CI job runs anything under
t/. The workflow's jobs arecheck, the JS/WASM and native test runs, thecmd-native-testshards,distributed-test,nix-build,git-compatandjs-build; none of them invokes thetest-subdirtask.t9021is therefore reached only bypkf run test-subdirorbash t/run-tests.shlocally, as is the existingt9020.Within that local task, the filter is a substring match, so
t900alone skippedt9011–t9021. This PR adds at902pass so the task at least coverst9020and the new test.t9011–t9018remain uncovered by any filter, and two of those tests are red today — see #191, wherefixtures/workspace_flow/bootstrap.shuses a baregit initand the tests hardcodemain. Wiring the wholet/tree into CI wants that fixed first, so it is left out of this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01VDVUJHu38YtVAr6sKZtRDB