Skip to content

Script the release bump and tag - #1942

Merged
DanGould merged 4 commits into
payjoin:masterfrom
DanGould:release-chain-head
Oct 4, 2026
Merged

DanGould merged 4 commits into
payjoin:masterfrom
DanGould:release-chain-head

Conversation

@DanGould

@DanGould DanGould commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Four changes to the part of the release process that happens before the tag. Nothing from the tag onward changes.

bump.sh. contrib/release/bump.sh CRATE X.Y.Z does the bump commit's mechanical work: sets the version, rewrites every workspace member's requirement on the crate, renames ## Unreleased to ## X.Y.Z and opens a fresh one, regenerates both lock files, runs check-invariants. Leaves the tree uncommitted for review. Tested on mailroom 0.1.2 to 0.1.3 with the lock step skipped: the manifest, test-utils' requirement and the changelog heading all moved, and check-invariants flagged the stale lock files as it should.

tag.sh. contrib/release/tag.sh creates the signed release tags for HEAD from the manifest versions: one per release crate, and with --bindings one per language package. It runs check-invariants first, refuses a dirty tree or a HEAD that is not on origin/master, writes the one-line message Release <tag>, and prints the push command. --dry-run shows what it would do. Tested against the 1.2.0 bump branch: it reports the crates that are not bumped, offers payjoin-1.2.0, and refuses a crate whose current version is already tagged elsewhere.

Changelog lines land with the PR. Each release crate's CHANGELOG.md gets an ## Unreleased section. A PR that changes what a user sees adds its line there; the bump PR renames the heading. A new workflow fails a PR that touches a release crate's src/ without touching its CHANGELOG.md unless it carries the changelog: none label. Documented in CONTRIBUTING.md and the PR template. The label needs creating in the repo settings before the check can be skipped.

Minor release checklist rewritten to match: changelog from Unreleased, tag from tag.sh, no changelog copy in the tag message, and the bindings and dependent crates listed after the payjoin release instead of implied.

Follow-ups tracked in #1766: payjoin-test-utils out of the publish chain, shared major.minor for payjoin-ffi and its packages, semver-checks on every PR.

Reviewer: @benalleng

Written with Claude Code; I reviewed and ran the gates (nix fmt -- --ci, codespell, a dry run of the script).

@DanGould
DanGould requested a review from benalleng October 2, 2026 08:40
@coveralls

coveralls commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 37031682383

Coverage remained the same at 87.494%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 17848
Covered Lines: 15616
Line Coverage: 87.49%
Coverage Strength: 319.83 hits per line

💛 - Coveralls

@DanGould
DanGould force-pushed the release-chain-head branch from defc5c9 to 6cd76a8 Compare October 2, 2026 15:35
benalleng
benalleng previously approved these changes Oct 2, 2026

@benalleng benalleng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTACK 6cd76a8

I like the ##Unreleased addition, nice feature. Hopefully it keeps Change-logs fresher in our minds.

@DanGould
DanGould force-pushed the release-chain-head branch 2 times, most recently from c85cc32 to b82cb1d Compare October 2, 2026 16:03
Release tags were created by hand from the issue checklist, one per
crate and one per language package, with the changelog pasted into the
tag message. The script reads each version from the manifest that
owns it, skips anything already tagged, runs check-invariants on the
crates it is about to tag, and creates signed annotated tags named
<crate>-<version>. The message is the one line "Release <tag>"; the
GitHub release body already comes from CHANGELOG.md.

It refuses a tree with uncommitted changes or a HEAD that is not on
master, which is what verify-tag-hygiene would reject after the push.
Master is fetched from github.com/payjoin/rust-payjoin by URL at tag
time, because a maintainer's origin is often a fork and a local
remote-tracking ref can be stale; verify-tag-hygiene takes that commit
through RELEASE_MASTER and still defaults to origin/master in CI. It
prints the push command, naming whichever remote points at
payjoin/rust-payjoin, and never pushes.

With no names it sweeps the release crates and reports the ones that
are not bumped. With --bindings it also covers each payjoin-ffi package that ships a
contrib/release-version.sh. That script prints the version the
package's next tag names, so adding a language means adding one script
rather than editing tag.sh, and a package without one is never
tagged. The python tag carries +payjoin-<version> like the others;
pyproject.toml cannot hold it, so its script appends the payjoin
version tag.sh passes in.
Changelog sections were written at release time, in the bump PR. Any
merge to master under an open bump PR then meant editing that PR and
restacking everything above it, and the 1.1.0 bindings release was
lost to that churn.

Each release crate's CHANGELOG.md now keeps an Unreleased section at
the top. A pull request that changes what a user of the crate sees
adds its line there in the same commit. The bump PR renames the
heading and nothing else, so it no longer has to change when something
lands beneath it.

A workflow fails a pull request that touches a release crate's src/
without touching its CHANGELOG.md, unless the PR carries the
"changelog: none" label for changes with no user-visible effect. The
rule is documented in CONTRIBUTING.md. Only triage access can apply a
label, so an outside contributor says in the PR description that the
change has no user-visible effect and a maintainer labels it.
The checklist still asked for a changelog compiled in the issue body
and pasted into the tag message, and it stopped at the crate without
saying what happens to the bindings or the dependent crates.

The changelog is now the crate's Unreleased section, the tag is made
by contrib/release/tag.sh, fixes merge to master with their own lines
instead of being cherry-picked into the bump branch, and a payjoin
release is followed by the bindings bump (Dart pins the release tag
commit, never a branch commit) and by the dependent crates on their
own schedule.
The bump commit was assembled by hand: edit the crate's version, find
every workspace member that requires it and edit those too, retitle the
changelog section, run update-lock-files.sh, and hope check-invariants
agrees. The script does those steps in that order and stops at the
first one that fails, leaving the tree uncommitted so the changelog
section can be read before the commit. --no-lock skips the lock file
regeneration for a quick look at the edits.

The minor release checklist now calls it instead of listing the steps.
@DanGould
DanGould force-pushed the release-chain-head branch from b82cb1d to c6d23fa Compare October 2, 2026 16:06

@benalleng benalleng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReACK c6d23fa

@DanGould
DanGould merged commit 73a7bf9 into payjoin:master Oct 4, 2026
58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants