[Update] Run the trunk update and the discards on the bundled Git (#385) - #407
Conversation
|
@coderabbitai review |
|
Warning Review limit reachedNext included review available in 38 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (21)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
3a754a7 to
e346d37
Compare
e346d37 to
e30e17e
Compare
e30e17e to
785a93f
Compare
|
Windows walkthrough, 2026-09-09, Windows 11 VM with no Git installed, Buildkite artifact of #411 ( "Update to latest trunk" on a fresh partial clone: "Already up to date with trunk", |
785a93f to
4d7e1ae
Compare
4d7e1ae to
d12b061
Compare
d12b061 to
a6f1bdf
Compare
a6f1bdf to
0586aaa
Compare
The fetch-and-reset behind "Update to latest trunk" and the two discards were the last writes in trunk-update.js still on isomorphic-git. They now run on the primitives in git-write.cjs: a fetch of one branch from the checkout's own origin (#359), update-ref guarded by the old tip, the forced checkout, a reset of the stale index entries by pathspec, and a clean of what is untracked and not ignored. Signatures, return shapes, the stage and worktreeReset tags and the IPC payloads are unchanged. fetchDepth is gone: with the legacy gate below this, every site the update can reach is a partial clone whose own config keeps the fetch partial, or a full clone adopted from disk. The fetch suite no longer needs a loopback smart-HTTP server; its origin is a repository on disk over file://, cloned by cloneSite, and it asserts that the update leaves no shallow boundary and the promisor config in place. streamGit in git-run.cjs is the one runner for the commands that report progress; cloneSite and checkoutBranch are wrappers over it, and the fetch is its third caller. Git's own fetch lines reach the terminal as printed. The fetch and checkout children are tracked for the quit sweep, in the update and in both discards. Docs: AGENTS.md, the review standard and TESTING.md no longer say the update runs on isomorphic-git or that a suite starts a server; the guide says the update fetches from the site's origin. The comment and the standard sentence claiming ensureAutocrlf writes core.autocrlf are corrected: it is an in-memory view and writes nothing. Part of #364 and #385. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J6koA9mvjSExVaKJp4nvSx
The fixture behind the discard tests now pins core.autocrlf=false the way the clone does, so the forced checkout the discards run through writes LF on Windows too; the CRLF-view tests, which are about a repository with no such value, ask for one explicitly. The update refuses a site with no origin remote before parking anything, with the app's own sentence and a code, the shape of the other two blocks; a read that fails leaves the fetch to report its reason. The ref write is guarded with trunk's own value rather than with HEAD's, since the module never assumed the checkout is on trunk, and a test updates from a detached HEAD. The fetch allows only https, http and file as transports: the URL now comes from the site's own config, and Git's ext transport would run a command named there. A test pins that the fetch stayed partial (the blob nothing checked out is still on the server). Prose that the deletion of fetchDepth and of the last isomorphic-git require left false is corrected: the isLegacySite comment, the trunk-update.js header, patch-apply.js's header, AGENTS.md and the review standard, which no longer say any write runs on isomorphic-git. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J6koA9mvjSExVaKJp4nvSx
0586aaa to
cb40851
Compare
Why
src/trunk-update.jsheld the last writes onisomorphic-gitoutside patch apply: the fetch-and-reset behind Update to latest trunk and the two discards. Its reads already ran on the bundled Git (#402); the writes did not, which is whyfetchDepthhad to guess per site whether a fetch would re-shallow a partial clone, why the fetch suite carried a hand-rolled smart-HTTP server (isomorphic-githas nofile://), and why the update fetched a URL fixed in the app instead of the checkout's own remote (#359). With #405 below this, no site the update can reach is shallow, so all three go. Stacked on #405.What changes
src/git-run.cjs:streamGit(args, { cwd, onProgress, onStderr, onChild, spawn }), the one runner for the commands that report progress.cloneSiteandcheckoutBranchare wrappers over it now (the same 35-line spawn-and-read block used to live in both); the fetch is its third caller. Rejects with the lastfatal:line, not the first line of a screen of progress.src/git-write.cjs:fetchBranch(dir, remote, branch)(fetch --progress --no-tags -- <remote> <branch>, thenFETCH_HEAD; no--depth, no--filter: the site's own promisor config keeps the fetch partial; onlyhttps,httpandfileallowed as transports, since the URL is the site's own config),unstagePaths(reset -qby NUL pathspec on stdin, literal),cleanUntracked(clean -fd: ignored and excluded files stay, a nested repository stays).src/git-read.cjs:isAncestor(merge-base --is-ancestor, exit 0/1) andremoteUrl(one config read).src/trunk-update.js:updateToLatestTrunk({ dir, onLog, onChild }):urlis gone; it fetchesorigin trunk, compares the lockfile blob between the two tips as before, unstages the stale index entries,update-ref refs/heads/trunk <new> <trunk's own oid>(a second writer fails loudly instead of being overwritten), forced checkout. Git's own fetch lines reach the terminal as printed.discardChanges/discardToBase: forced checkout of the current branch plusclean -fd, the rewind still guarded by ancestry.fetchDepthdeleted.ensureAutocrlf/createCrlfCompatibleFsstay exported forpatch-apply.jsandsites:adduntil the patch flow moves; nothing here calls them.src/main.js:noOriginBlock, the shape oflegacySiteBlockandmidSwitchBlock: a site with nooriginremote is refused with the app's sentence andcode: 'no-origin'before the ticket is parked. The update, and both discards, passonChild: trackGitChild(sitePath)so a quit mid-fetch or mid-checkout finds the child.WORDPRESS_GIT_URLkeeps its one consumer, the clone. ThelegacySiteBlockcomment no longer claimsensureAutocrlfwritescore.autocrlf(it is an in-memory view forisomorphic-git; it never wrote the repository).trunk-update-fetch.integration.test.cjsloses the loopback server and clones its site withcloneSitefrom afile://origin: the same eight cases, the two wire-leveldeepenassertions replaced by "no.git/shallow, promisor intact, full history walkable", plus fetch-from-the-checkout's-origin (E2E: cover the flows the git-native refactor will move #359), both children handed out, and a held ref lock taggedstage: 'checkout'with the tree untouched. Argv tests for the three primitives andstreamGit, integration tests forunstagePaths/cleanUntracked/fetchBranch/isAncestor, two more discard cases (ignored and excluded files survive; an unknown base does not rewind). New journeytrunk-update.spec.js:makeSite({ origin: true })clones the site beside itself, the test moves that origin ahead, More → Update to latest trunk, and asserts the file, the branch, the substrate, no shallow boundary, and the store's new snapshot with no incomplete marker.src/requiresisomorphic-gitany more (it stays inpackage.jsonand the fixtures until Phase 4: isomorphic-git leaves, and the issues parked on this decision are re-asked #386); TESTING.md no longer says a suite starts a server;docs/guide/trunk-updates.mdsays the update fetches from the site's ownoriginand what a site without one gets.Diff size. About 1,370 changed lines in two commits (the move, then the self-review answers), over the ~800 the process asks to declare: 460 of them are the fetch suite (the server goes, the
file://fixture comes), and the runner extraction is net negative ingit-clone.cjs/git-write.cjs. Splitting the primitives from their only caller would have left a PR with no behaviour to test.How to test this
Platforms: macOS and Windows.
Starting state: the Buildkite artifact for the current head installed; one site created by this build (or by #403's), on trunk, built.
remote: Counting objects…/Receiving objects…lines stream in the terminal, thenResetting to latest trunk (…), thenNow on trunk as of …; install skipped unlesspackage-lock.jsonmoved; the green summary afterwards. In the site folder:git rev-parse --is-shallow-repositoryprintsfalse,git config remote.origin.promisorprintstrue,git log --oneline | wc -lis the full history.Already up to date.and the confirmation, no install, no build.node_modulesintact, the site returns to the ticket branch after the update.git addfrom a terminal). Expected: both gone, ignored files intact, still on the ticket branch, parked work intact.Receiving objectsis on screen. Expected: nogit fetchleft inps/ Task Manager; reopening the site shows no half state (the fetch touched no file).git status --porcelainclean afterwards,git config --local core.autocrlfstillfalse.originis a fork. Expected: the update fetches from the fork (From <fork url>in the terminal). A site added from disk with noorigin(git remote remove origin): the terminal says it has no origin remote and nothing is parked or fetched.What must not have happened: a
.git/shallowin any site; a fetch of tags (git tagprints nothing);node_modulesorbuild/removed by a discard; a site left on trunk with the ticket still linked after a failed update (the existing log line says where the work went).Risks and limitations
originis refused before anything moves, with the sentence and how to add one; anoriginwith notrunk, or one reached overssh(a transport the fetch does not allow, since it would spawn a hostssh), fails at the fetch with Git's own reason, taggedstage: 'fetch', nothing moved. The app never makes such a site; one adopted from disk can be. Before this PR the same site would have fetched the fixed URL and silently updated toWordPress/wordpress-developwhatever its origin was.clean -fdis what the old discard did by hand, minus one thing: the old code deleted only pathsstatusMatrixlisted, which honoured.gitignoreand.git/info/excludethe same way. A nested repository inside the site (which nothing in the app creates) is left alone; the old code would have deleted its files.windowsArgslike every other worktree write, for sites adopted from a host Git; a site the app cloned carriescore.autocrlf=falseand the prefix is a no-op.ensureAutocrlf/createCrlfCompatibleFsand their six tests stay until the patch flow moves; so doesisomorphic-gitin the fixtures oftrunk-update.integration.test.cjsand the journeys helper (Phase 4: isomorphic-git leaves, and the issues parked on this decision are re-asked #386).Related
Part of #364 and #385; closes the #359 requirement that the update read the checkout's remote. Stacked on #405 (merge that first). Next: patch flows, then #386.
Design decisions and alternatives considered
update-ref+ forcedcheckout, notreset --hardormerge --ff-only.reset --hardhas no--progressand would hide the tag point the caller relies on (worktreeResetflips exactly when files start being overwritten);merge --ff-onlyrefuses a trunk that has local commits, which the app never makes, so the refusal would only ever be noise. The forced ref write matches what isomorphic-git did, withexpected(trunk's own value) added so a concurrent writer is a loud failure.resetby pathspec, notrm --cached.rm --cachedrefuses a path whose staged content matches neither HEAD nor the worktree; the stale entries patch generation leaves are exactly that shape.\r; the checkout's are mapped to the samephase loaded/totalline as before so the update's log reads the way it did.streamGitextracted now. The fetch would have been the third copy of the spawn-and-read block; two was already one too many, and the third is where a divergence between them (one flushing the reader, one not) becomes a bug nobody can see in a diff.fetchDepthreplacement. The decision behind [Add] Refuse writes on sites the old engine made, and say so on the card (#385) #405 (no old-engine sites are written) is what makes the fetch unconditional; keeping a depth branch for a case the gate excludes would be a second copy of that rule.Review outcome (required — see AGENTS.md)
/self-reviewagainst the parent branch, judgement pass run with fresh context (the diff and the review standard only).npm run lintclean, unit suite green on both Node runtimes before and after (1161, then 1164), journeys 16/16. 6 [fix here] · 4 [follow-up]; the six and two of the four are in the second commit.Applied
core.autocrlf, and the discards now run through the same forced checkout as a switch, which on Windows would have written CRLF into six byte-for-byte assertions. Pinned like the two sibling fixtures; the CRLF-view tests ask for the unset shape explicitly.originused to update silently from the fixed URL and would now have failed with Git's rawfatal:.noOriginBlockin the handler refuses before the ticket is parked, with the app's sentence andcode: 'no-origin'; a read that fails leaves the fetch to speak. Test inipc-wiring.isomorphic-git". It writes hunks withfsby hand, and after the first commit nothing undersrc/requiresisomorphic-gitat all. Both say so now, andpatch-apply.js's header no longer claims the app never shells out to Git.isLegacySite's comment justified "never shallow" with the deletedfetchDepth;trunk-update.js's header miscountedensureAutocrlf's callers.update-refguard compared against HEAD's oid, not trunk's; on a detached HEAD or a stale registry it would have failed atstage: 'checkout'with nothing moved. It compares trunk's own value now, with a test that updates from a detached HEAD..git/config, soext::would run a command.fetchBranchpinsprotocol.allow=neverand allowshttps,httpandfile;sshstays off because it would spawn a hostssh.--filter(the promisor config keeps the fetch partial) now has an assertion: the blob nothing checked out is still on the server after the update.Follow-up, not here
isomorphic-gitis a production dependency nothing insrc/imports, andensureAutocrlfis a no-op at its three call sites. Both go with Phase 4: isomorphic-git leaves, and the issues parked on this decision are re-asked #386, which also retires the fixtures.clean -fdexits 1 when one untracked file cannot be removed (a file held open on Windows), where the old per-file loop carried on. Exposure isnode_modulesandbuild/free (both ignored); left strict and stated here.Rules the standard does not cover (left for a later edit of the instructions file): when a function or constant is deleted, grep for its name in comments elsewhere (three findings were prose two files away that the deletion made false); a design claim a comment states as the reason for a choice needs a test that fails when it stops being true.
Checked and sound: the forced checkout plus
clean -fdreproduces the old discard (staged-new files, untracked, ignored), with.git/info/excludeentries now surviving where they did not;stage/worktreeResetat every exit;FETCH_HEADcorrect for one explicit refspec;windowsArgsoncheckout,reset,cleanand not onfetch;streamGita faithful extraction withgit-progress.cjsstill its one consumer;trackGitChildsafe across the fetch then checkout sequence; no re-shallow or unshallow reachable.Screenshots: none; the terminal now shows Git's own fetch lines where it showed
fetch 1234/5678before. One will be added from the Buildkite artifact.🤖 Generated with Claude Code
https://claude.ai/code/session_01J6koA9mvjSExVaKJp4nvSx