Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Release process

A cerebro release is a git tag and a Codeberg release page. Nothing is published to crates.io: all four crates are publish = false, they depend on each other by path, and cargo package cannot resolve a path dependency from a registry. That is the whole story in ADR-0012.

What a release looks like

main  ──▶  just release  ──▶  release PR  ──▶  merge  ──▶  CI publishes
           bump + changelog                  reviewed     tag + release page
           + lockfile

One version covers the whole workspace. All four crates inherit [workspace.package] version, so one tag names one release: cerebro-v0.3.0.

Cutting one

just release-status   # what Cargo.toml says vs. what the commits propose
just release          # branch, changelog section, version bump, one commit

just release needs git-cliff (cargo install git-cliff --locked) and cargo. It writes nothing to the forge and prints the two commands that follow:

git push -u codeberg release/cerebro-v0.3.0
fj pr create 'chore(release): cerebro-v0.3.0' --base main

One commit goes on that branch, carrying three files: CHANGELOG.md with the new section, Cargo.toml with the version, and Cargo.lock re-resolved to match. All four crates inherit [workspace.package] version, so the lock moves with the manifest or it quietly disagrees with every release tag until someone’s build rewrites it.

Read the changelog section in that diff. It is generated from commit subjects, and a subject that needs rewording is cheaper to fix in the release PR than in a published release page.

Once the PR merges, CI publishes on its own. The push to main leaves the manifest ahead of the last tag, so scripts/release.sh publish posts one release with that version’s changelog section as the body, and Codeberg creates the tag pointing at the commit CI built. On any other push to main it prints “already released” and exits 0.

To see what CI would do without doing it:

just release-preview

Version numbers

Commit types decide the bump, since the last cerebro-v* tag:

Commits since the last tagBumpExample
fix:patch0.2.2 → 0.2.3
feat:minor0.2.2 → 0.3.0
feat!: or a BREAKING CHANGE: footermajor0.2.2 → 1.0.0
build:, ci:, chore:, style:, test: onlynone0.2.2 → nothing to release

Commit subjects follow conventional commits because they are the release notes. The commit-msg hook runs cog verify, which enforces the format.

Releasing by hand

If CI is unavailable, the same API call works from a laptop. It is the exact request publish makes:

version=$(bash scripts/release.sh version)
notes=$(bash scripts/release.sh notes "$version")

Do not git push a tag by hand. The forge cuts it when the release is created, which keeps the tag, the release page, and the commit CI tested in agreement.

Tests

bash scripts/release-test.sh

Scratch git repositories, a stubbed curl, and assertions on every decision: which line bump rewrites, what next proposes for each commit type, whether notes stops at the next section, and the publish branches (no-op, publish once, stand down after the tag exists, refuse without notes or a token). It runs in CI’s test step and inside just test, so a broken release script cannot merge quietly.