feat(release-candidate): release a commit that is not the trunk tip - #45
Merged
Conversation
`main` moves while a release is being prepared, and until now the pipeline had no way to ship anything but its tip: propose is guarded to the default branch, the candidate PR merges there, and cut creates the release branch at that merge commit. `base_commit` names the commit to release instead. Propose resolves it (any commit-ish), refuses anything the default branch cannot reach so a release still ships code that went through trunk, creates `release-vX.Y.Z` at that commit itself, and opens the candidate PR against that branch — it cannot target the default branch, because merging it there would take the commits the release is deliberately leaving out. Checking the branch out first also means the bump and the changelog entry are drafted over what actually ships. Cut then has nothing to do, and settle is untouched: the release branch tip it wants is the merge of that PR. The default branch is left alone, so its version file keeps the old version and the next candidate syncs the changelog entry back from the tag, the way it already does after any release. Re-dispatching after a failed attempt reuses a release branch still sitting on exactly the requested commit; anything else is refused. The trunk-first flow is unchanged when `base_commit` is empty: the PR's base falls back to the checked-out branch and the guard keeps every rule it had. Tested in actions-test.yml by extracting the shipped steps from action.yml and running them against a throwaway repository shaped like a settled release line, with a fake `gh`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
mainmoves while a release is being prepared. Until now the pipeline couldonly ship its tip:
proposeis guarded to the default branch, the candidate PRmerges there, and
cutcreates the release branch at that merge commit.This adds a
base_commitinput torelease-candidate,proposeonly.What it does
gh workflow run release-candidate.yml --ref main -f version=1.2.3 -f base_commit=<sha>origin/main~4) and refusesanything the default branch cannot reach, so a release still ships code that
went through trunk. Note this rules out
v*tags, which sit off trunk oncesettlement commits the dated changelog on the release branch.
release-v1.2.3at that commit itself and checks it out, so the bumpand the changelog entry are drafted over what actually ships, not over the
trunk tip.
branch: merging it there would take the commits the release is deliberately
leaving out.
cutthen has nothing to do, and does not even fire (the template'spull_requesttrigger filters on the default branch). It also short-circuitswith a notice if a consumer's workflow has no such filter.
release-settleis unchanged, as asked: the release-branch tip it wants is themerge of that candidate PR.
Consequences, documented
version; it gains no changelog entry until the next candidate, which syncs it
back from the tag the way it already does after any release.
v*tag, so this releases anearlier commit on the current line. It is not a backport flow.
exactly the requested commit; any other mismatch is refused.
release branchruleset, so it needs itsapproving review and a squash merge.
Unchanged when
base_commitis emptybase:oncreate-pull-requestfalls back to the checked-out branch when theinput is empty (source),
and every existing guard rule is kept in order.
Testing
New
release-candidatejob inactions-test.yml. It extracts the shippedsteps from
action.ymlrather than copying them, and runs them against athrowaway repository shaped like a settled release line (tag off trunk, an
unreachable side commit), with a fake
ghfor the default-branch lookup and theref creation. It covers: the trunk path unchanged; SHA and relative-ref
resolution; refusal of off-trunk commits, release tags and nonsense; the
pre-existing default-branch and version rules; the branch actually cut at the
commit with the notes stopping there; and re-dispatch reuse.
Verified locally that the job fails when the ancestry guard is removed, and when
a step under test is renamed.
🤖 Generated with Claude Code