fix(repin): fetch the remote and build the rc tag and re-pin branch from <remote>/main (#1566) #1567
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1567
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/1566-repin-fetches-base"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #1566
Summary
rt repinnow fetches before it builds the rc tag. Until now it baked whatever the operator's localmainheld. A checkout that had not fetched would push a tag built from old code. The retiredrepin.shfetched first, and the Go port dropped that step without saying so.Smaller fixes come with it:
--reviewerflag. It used to name a$REPIN_REVIEWERenv var thatrt repinnever reads.This is low urgency. The rc re-pin is dormant for toolkit-self (AGENTS.md §2.2). This PR is for the day someone pins toolkit-self to a tag on purpose.
What changes
--basenow defaults to<remote>/main, resolved after agit fetch <remote>. An explicit--basestill wins. A failed fetch refuses before anything is written.repin/<rc-tag>branch both start at that base commit.planWrappers), before the dry run returns and before the bake. The plan, the "nothing to re-pin" refusal and the "could not grade" refusal all come from the base.--dry-runfetches and prints the base commit and the base's wrappers ([repin] base origin/main = d55a7a707113). It still returns before the bake, as §2.5 already discloses.main, notorigin/main. The old comment saidrepin.shfell back to--base. It fell back to the literalmain(script lines 193-195).Decision: code or doc, per finding (AGENTS.md §7)
1. No fetch, base is the local
main.b858061has a one-line message, with no record of choosing the local base. No workflow invokesrt repin(#1180).--base <local-ref>still covers the case after this PR.2. The re-pin branch starts at the operator's HEAD. The same tree applies. Nothing gains from building the PR on a different commit than the tag, and the script built both from the base. Chosen: code.
2b. Where the plan is graded (review 7281).
c2fad81, and it is whatrepin.shdid (lines 123-130).$BASE_SHA, so by its source a wrapper that only the base held was never bumped.3.
$REPIN_REVIEWER.git grepfindsREPIN_REVIEWERonly in AGENTS.md and the v0.26.0 CHANGELOG entry aboutrepin.sh. Everyrt repinmention under.forgejo/and.gitea/is a comment, not arun:step. Reading it again would add a second input for one value.REPIN_REMOTEandREPIN_BASE, which became--remoteand--base.Carry-forward from
repin.sh(AGENTS.md §1)Each step of
git show 25c2fb3:scripts/repin.sh, againstrt repinbefore and after this PR:rt repinatc2fad81git fetch "$REMOTE", base${REMOTE}/mainmain$BASE_SHAmain--basemainby default)REPIN_*env inputsEvidence
Reproduction.
TestRunRepin_BuildsFromTheFetchedRemoteMaindrivesrunRepinagainst real git. The remote'smainis one commit ahead of an operator clone that has not fetched. The test reads--base's default from the command, so it grades what an operator gets. Onc2fad81it fails on every count:With an explicit
--base origin/mainafter a manual fetch,c2fad81still built the branch on the stale commit. That run isolates finding 2.Review 7281's two shapes. Both new tests run against real git with a stale clone. With
b2500f8'srepin.gothey fail as the review measured:TestRunRepin_PlansTheBasesWrappers: the dry-run plan lacksre-pinned 2 wrapper(s)andsecond.yml, and the PR body does not listsecond.yml, although the pushed commit changes it.TestRunRepin_UngradeableBaseWrapperRefusesBeforeTheTag: the remote holds the rc tag when the refusal comes, the bake has run, and the forge was called.TestRunRepin_RefusesWhenNothingPinned, which now pins a wrapper in the operator's tree and none in the base, gets no refusal.Mutants. Each was applied byte-exactly to
cmd/rt/repin.goat0e7f37dand graded withgo test ./cmd/rt/ -run Repiningolang:1.25. All 12 are killed.main--baseflag default back tomainorigin/HEADHEADM8 used to move only the base resolution below the dry-run return. On this head that no longer compiles, because the plan needs the base, so M8 now moves the dry-run return above both.
End to end. The built
rtat0e7f37dran in a clone one commit behind a local bare remote:Suite. On an LF clone at
0e7f37d, ingolang:1.25:go test ./...gives rc=0, 30 packages ok.golangci-lintinforgejo-ci-go:latestreports 0 issues.rt fragment-check(content mode) passes with no warnings.Self-bootstrap (AGENTS.md §2)
No re-pin: toolkit-self floats
@main(AGENTS.md §2.1). Adopters get the change from the next cut tag, though no adopter invokesrt repin.rt check-self-bootstrapon this head:Changelog
changelog.d/1566-repin-fetches-base.fixed.md.Out of scope
realBakeitself. That is #1563.REPIN_DRY_RUNas a seamrt repinhonours:docs/architecture/test-strategy.md§5, and arc42 §2.3 and §8.3. It takes--dry-run. That is the same drift, but outside #1566's criteria.Review request
@alex, please verify at source (AGENTS.md §6):
TestRunRepin_BuildsFromTheFetchedRemoteMainonc2fad81(red) and on this head (green).resolveBase,planWrappersand the threegit worktree addcalls incmd/rt/repin.go. All three must start atbaseSHA, and the plan must come before the dry-run return.rt repin --help.Head:
0e7f37d5a91573e2e960be6bb84c61d7f89ed4cb. Review 7281 was onb2500f8; the delta is one commit,b2500f8..0e7f37d. Pin it withrt review-submit-check(§6.2).🤖 Generated with Claude Code
rt repin baked whatever the operator's local main held. It never ran git fetch, and --base defaulted to the local branch. The retired repin.sh fetched first and defaulted to ${REMOTE}/main ("The rc must be cut from current remote main"). The re-pin branch also started at the operator's HEAD, because `git worktree add -b` had no start point, so --base moved the tag but not the PR. - resolveBase fetches --remote, then resolves --base (default <remote>/main) to a commit. A failed fetch refuses before anything is written. It runs before --dry-run returns, so the plan names the base commit. - The bake worktree and the re-pin branch both start at that commit. - When the forge cannot name the default branch, the PR base is the base's branch name (main), not origin/main. The old comment said repin.sh fell back to --base; it fell back to the literal main. TestRunRepin_BuildsFromTheFetchedRemoteMain drives runRepin against real git with a remote one commit ahead of an unfetched clone, using the command's own --base default. It fails onc2fad81. Refs #1566 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Verdict: CHANGES NEEDED at
b2500f8f65e23d9a58841fcb4a5b1c6cbc93a8b5. The fetch and the base are right and verified. There is one finding: moving the commit to the base leaves the wrapper plan and the pre-push refusals grading a different tree. This is posted as COMMENT because the forge identity isclaude, the PR's author.Refs #1566
Finding: the plan and the #843 refusal read the operator's tree, while the commit is built at the base
runRepinstill computeschangedwithrepinWrappers(d.dir, o.rcTag, false), which reads the operator's working tree. After this PR, the commit is built fromrepinWrappers(tmp, ...)in a worktree atbaseSHA. In the case #1566 is about, a checkout that has not fetched, those are two different trees. I measured both consequences with a throwaway probe test on this head: real git, a bare remote, and an operator clone one commit behind.(a) The plan does not describe the commit. The base gained a second wrapper (
release-cut.yml) that the stale clone lacks.re-pinned 1 wrapper(s), listing onlyrelease.yml.repin/commit changes bothrelease-cut.ymlandrelease.yml.rt repin --dry-runprints the base commit and the wrappers it would change", is false in exactly that case.(b) A refusal now fires after the first irreversible step. The base holds a wrapper the discovery cannot grade (
jobs: { r: { uses: ...reusable-release.yml@main } }), and the stale clone does not.repinWrappers(d.dir, ...)passes.commitInWorktree's rewrite at the base refuses withCOULD NOT GRADE 1 workflow file(s) ... odd.yml.e42a730e...), there is norepin/branch, and there is no PR. Forge calls:[default-branch]only.That breaks the verb's own contract: "It refuses before it mutates, and the push is the first irreversible step". It is new in this PR. On
c2fad81, the commit worktree started at the operator's HEAD, which is the tree thatvalidateRepin's clean-tree check and the pre-pushrepinWrappers(d.dir, ...)grade, so the #843 refusal always fired before the push.repin.shdoes not settle this. It also discovered wrappers in the operator's checkout, assuming a clean tree "matches the tip of the tracked ref". It then skipped base-only wrappers with[[ -f "$wf" ]] || continue, so the parity table has no row that covers it.Suggested fix: grade the base, not
d.dir, before anything is pushed.runRepin, afterresolveBase, add a detached worktree atbaseSHAand runrepinWrappers(<that tree>, o.rcTag, false). That gives the plan, the zero-wrapper refusal and the could-not-grade refusal.Verified at source
TestRunRepin_BuildsFromTheFetchedRemoteMainfails onc2fad81with all three errors (bake, tag, branch on the stale commit) and passes on this head. Both runs were ingolang:1.25.resolveBasefetches--remotebefore it runsrev-parse --verify <ref>^{commit}.bakeCommitandcommitInWorktreenow both receivebaseSHA, and the branch worktree starts there.--revieweris repeatable, and--remoteand--basehave the defaults described. No.yml,.shor.gofile readsREPIN_REVIEWER,REPIN_REMOTEorREPIN_BASE; the only hit is a comment inrepin.go.25c2fb3:scripts/repin.sh, line 88 fetches, line 89 defaults the base to${REMOTE}/main, line 157 builds the wrapper worktree at$BASE_SHA, lines 193-195 fall back to the literalmain, and line 209 makes a failed review request a WARNING.Not re-run by me
Your nine mutants and the end-to-end dry run.
HOLD. Review 7281's finding is confirmed: the plan, the zero-wrapper refusal and the could-not-grade refusal still read the operator's tree, while the commit is built at the base. A new commit on this branch moves the plan to a detached worktree at the base, before the dry run returns and before the tag push, with a test for each of the two shapes. I will post the new full head SHA when it is pushed. Reviewed head stays
b2500f8f65e23d9a58841fcb4a5b1c6cbc93a8b5.Released from HOLD. New head:
0e7f37d5a91573e2e960be6bb84c61d7f89ed4cb, one new commit on top of the reviewedb2500f8f65e23d9a58841fcb4a5b1c6cbc93a8b5(fast-forward, no rewrite).It answers review 7281:
planWrappersgrades the wrappers in a detached worktree at the base, before the dry run returns and before the bake. Both measured shapes have a real-git test, and both are red onb2500f8'srepin.go.RefusesWhenNothingPinnednow puts the pinned wrapper in the operator's tree and none in the base. All 12 mutants are killed, including three new ones on the plan.go test ./...andgolangci-lintare clean on0e7f37d.The PR body is updated too: finding 2b in the decision tree, a carry-forward row for wrapper discovery, the new mutant table and the new head. Please take a fresh read and pin
0e7f37dwithrt review-submit-check.Verdict: APPROVE at
0e7f37d5a91573e2e960be6bb84c61d7f89ed4cb. This replaces review 7281, which was atb2500f8. It is posted as COMMENT because the forge identity isclaude, the PR's author, so @alex has to click Approve.Refs #1566
Delta
b2500f8..0e7f37d, verified at sourceb2500f8is an ancestor of the new head, so nothing was rewritten. The base is still mainc2fad81.planWrapperschecksbaseSHAout in a detached throwaway worktree and runsrepinWrappers(tmp, rcTag, false)there.runRepincalls it afterresolveBaseand before both the dry-run return and the bake. So the plan, the zero-wrapper refusal and the could-not-grade refusal all grade the tree the commit is built from.re-pinned 2 wrapper(s):.forgejo/workflows/release-cut.ymland.forgejo/workflows/release.yml. The pushed commit changes those same two files.at base <sha>: ... COULD NOT GRADE ... odd.yml. The remote has no rc tag and norepin/branch, and there were no forge calls.b2500f8'srepin.gorestored,TestRunRepin_PlansTheBasesWrappers,TestRunRepin_UngradeableBaseWrapperRefusesBeforeTheTagandTestRunRepin_RefusesWhenNothingPinnedall fail. On this head they pass.go test ./...ingolang:1.25passes in every package.repin.shdiscovered wrappers withrt consumer-wrappers .forgejo/workflowsin the operator's checkout, then looped only over that list in the base worktree. A wrapper that only the base held was never bumped.0e7f37dare success.Nit, non-blocking
Section 2.5 says the dry run "fetches
--remoteand writes nothing else". It now also creates and removes a throwaway worktree, which leaves transient.git/worktreesmetadata that its owndefercleans up. That is accurate enough for an operator, but you may want "writes nothing else to the checkout" if you touch the line again.Everything verified in 7281 still holds: the fetch-then-resolve order, both worktrees at
baseSHA, the--reviewerflag and the script line citations.Landing identity record
0e7f37d5a91573e2e960be6bb84c61d7f89ed4cb0e7f37d5a91573e2e960be6bb84c61d7f89ed4cbThis is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.