fix(repo): pin a --repo worktree to the fetched revision, refuse an unknown base - #83
Merged
Merged
Conversation
…nknown base PR #73 added remote validation and a fetch, and recorded "pinning the worktree to freshness.remoteTip explicitly" as a follow-up. That follow-up turns out to be load-bearing, not cosmetic. `git fetch` advances remote refs and FETCH_HEAD. It does NOT move the mirror's checked-out HEAD — refreshMirror says so in its own contract ("never checks out, resets, merges, pulls or cleans"). And worktreeAddArgs built: ["-C", repoRoot, "worktree", "add", "-b", branch, dir] with no start point, so `worktree add` branched off whatever the mirror already had. code.ts read co.freshness.remoteTip only to print it. The reachable shape was: mirror HEAD A origin/main A-B-C FETCH_HEAD C printed "(fetched) @ C" worktree cut A so a run could report a fresh base and then start days behind it. The summary this project shipped — "--repo fetches instead of branching off a stale copy" — was true of the fetch and false of the worktree. Two changes. 1. worktreeAddArgs and createWorktree take an optional startRevision, appended last so git reads it as the start point. Omitted, behaviour is unchanged, so a plain --worktree run still branches from the user's own checkout. 2. code.ts pins to co.freshness.remoteTip, and REFUSES when the base cannot be named. Previously an unfetchable mirror printed a warning and proceeded. Proceeding is the dangerous half: it starts work on an unknown base while having just printed a reassuring line. A run that cannot establish its base now exits 1 and says why. Tests: three argv tests plus a real-git canary that asserts the resulting checkout rather than the arguments. It builds a remote at A, clones it, moves the remote to C, fetches, asserts the mirror's HEAD is still A (the defect itself), cuts the worktree, and requires `git rev-parse HEAD` inside it to equal C. Mutation-checked, and this is the evidence the bug was real: reverting worktreeAddArgs to drop the start point fails the canary with "the worktree must start at the fetched revision" — the worktree lands on A. Restored, 19/19. Gates at this commit: npm run typecheck exit 0 npm test 1090 pass / 0 fail
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
--repocould report a freshly fetched base and then start work days behind it.PR #73 added remote validation and a fetch, and recorded "pinning the worktree to
freshness.remoteTipexplicitly" as a follow-up. That follow-up is load-bearing.git fetchadvances remote refs andFETCH_HEAD. It does not move the mirror's checked-out HEAD —refreshMirrorsays so in its own contract: "never checks out, resets, merges, pulls or cleans."And
worktreeAddArgsbuilt:code.tsreadco.freshness.remoteTiponly to print it.The reachable shape:
The summary this project shipped — "
--repofetches instead of branching off a stale copy" — was true of the fetch and false of the worktree.Contract
1. The start point is explicit.
worktreeAddArgsandcreateWorktreetake an optionalstartRevision, appended last so git reads it as the start point. Omitted, behaviour is unchanged — a plain--worktreerun still branches from the user's own checkout, which is the intended base there.2. An unnameable base is refused, not warned about. Previously an unfetchable mirror printed a warning and carried on. Proceeding is the dangerous half: it starts work on an unknown base immediately after printing a reassuring line. Now:
Exit 1. No worktree, no run.
Tests
Three argv tests, plus a real-git canary that asserts the resulting checkout rather than the arguments — because arguments were never the thing in doubt.
It builds a remote at
A, clones it, advances the remote toC, fetches, then:FETCH_HEAD == CHEADis stillA— this is the defect itself, pinned as a fixturegit rev-parse HEADinside the worktree to equalC, and to not equalAMutation-checked, and this is the evidence the bug was real: reverting
worktreeAddArgsto drop the start point fails the canary withthe worktree must start at the fetched revision— the worktree lands onA. Restored: 19/19.npm run typechecknpm testWhy refuse rather than warn
Fail-open was defensible while the base was merely unverified. It is not defensible once we know the worktree silently starts from a stale commit — the failure is invisible, the output actively reassuring, and the resulting branch is what a user then opens a PR from.
If an override is wanted later it should be an explicit flag with its own name, not the absence of a check.
Scope
src/core/worktree.ts,src/commands/code.ts,test/worktree.test.ts. No change to a plain--worktreerun, and no change to the fetch/validation contract from #73.Credit
Found in review of merged
mainat37fff3c, not by this project's own tests — which is worth recording, because #73's tests asserted the argv and the freshness report, and both were correct. Nothing asserted the commit the worktree actually landed on. That gap is what the new canary closes.