7d12ada45d
v0.1.0-rc3 no longer exists anywhere -- not local, not gh, not origin, not gitea. Verified here independently before changing anything; the releases are untouched (rc1, rc2, and there never was an rc3 release). THREE MEASUREMENTS OF ONE FACT IN ONE SESSION, and only the first was careless: 1. present on all three remotes and locally (from aa4e901's commit message) 2. on gh alone (git ls-remote, this branch) 3. nowhere at all (git ls-remote, now) Reading 1 was asserted rather than asked, and correcting it was right. Readings 2 and 3 were each taken correctly, by two different people, and they disagree -- because the tag was deleted in between. So the lesson this section reached for was too weak. It is not "ask rather than assert"; it is that A REMOTE-SHAPED FACT HAS A SHELF LIFE EVEN AFTER YOU ASK. A verified answer decays, and prose cannot distinguish a stale line from a live one, so a reader following RELEASING.md at 2am has no way to tell which they are holding. WHAT CHANGES. The present-tense inventory goes, everywhere I put it: RELEASING.md's "It is on gh only; origin and gitea carry rc1 and rc2", the same clause's "the mirrors did not agree", tooling.md's "rc3 is on gh alone", and "pushed to gh" in both tooling.md and the script header -- that last one named a specific remote too, and we do not actually know which remotes the push reached. rc3 survives as a plainly historical example: it was pushed while every crate still said 0.1.0-rc2, dist refused, no release was built. Nothing about where it is now. WHAT STAYS. The instruction to ask -- `git tag -l`, `git ls-remote --tags <remote>` -- because that is the part that ages well: it tells the reader to look rather than to believe. And the record that aa4e901's claim was wrong when checked, which is durable precisely because its subject is a fixed text. Note which half of the earlier correction survived and which did not: "the commit message was wrong" is permanent; "the tag is on gh alone" was true and is now false. The keeper is the one whose subject cannot change. Both files now say explicitly that they record no inventory, so the next editor knows the absence is deliberate rather than an omission to helpfully fill in. The principle never decayed and is untouched: a wrong tag is left dead and the next version cut; a published tag is not moved. No version bumped, no tag cut, no tag deleted by me. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
72 lines
3.1 KiB
Markdown
72 lines
3.1 KiB
Markdown
# Releasing
|
|
|
|
A checklist, not an explanation. The reasoning is `context/code/tooling.md` §4;
|
|
read it if a step surprises you, not before.
|
|
|
|
Its own file rather than a `README.md` section because `README.md` §Installation
|
|
addresses someone *installing* improvise and this addresses the one person
|
|
*publishing* it — folding them together lengthens the user-facing document for
|
|
no user benefit.
|
|
|
|
## The one rule
|
|
|
|
**Bump the version and commit it BEFORE you tag.** `dist` matches the tag
|
|
against the root package's version and refuses when they disagree — and by the
|
|
time CI tells you, the tag is already published and a published tag is not
|
|
moved. Every step below exists to put that check before the irreversible one.
|
|
|
|
## Cutting a release
|
|
|
|
1. **Bump the version.** `Cargo.toml`'s `[package]` version is the one `dist`
|
|
announces (`dist-workspace.toml` says `members = ["cargo:."]`). Keep the
|
|
crates under `crates/` in step with it; `gp-tree` sits at its own version
|
|
deliberately, and the preflight lists such crates as a note rather than an
|
|
error.
|
|
|
|
2. **Commit the bump**, and let `Cargo.lock` be part of that commit.
|
|
|
|
3. **Run the preflight**, with the tag you intend to push:
|
|
|
|
```sh
|
|
scripts/release-preflight.sh vX.Y.Z
|
|
```
|
|
|
|
It refuses a dirty tree, refuses a tag that does not match the version, runs
|
|
`dist plan` as a second opinion when `dist` is on `PATH`, and prints the tag
|
|
commands only once it has passed. It cuts nothing itself.
|
|
|
|
4. **Tag and push**, using the commands it printed:
|
|
|
|
```sh
|
|
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
git push gh vX.Y.Z
|
|
```
|
|
|
|
Push the tag to **`gh`** — `.github/workflows/release.yml` triggers on a tag
|
|
push there, matching `**[0-9]+.[0-9]+.[0-9]+*`. `origin` and `gitea` are
|
|
mirrors and build nothing.
|
|
|
|
5. **Watch the run.** A green release takes about three minutes and publishes
|
|
tarballs for all four targets with their `.sha256` files, `sha256.sum`,
|
|
`dist-manifest.json`, `source.tar.gz` and `improvise-installer.sh`.
|
|
|
|
The failure signature is unmistakable once you know it: **`plan` fails in
|
|
about twenty seconds and the four jobs behind it skip.** That is the tag not
|
|
matching the version — see below. A run that gets past `plan` is building.
|
|
|
|
## When a tag has already been pushed against the wrong version
|
|
|
|
**Leave it dead and cut the next one.** Do not move or re-push the tag: it is
|
|
already public, and a published tag that changes meaning is worse than one that
|
|
never built. Bump to the next version, commit, run the preflight, and tag that.
|
|
|
|
The worked example is `v0.1.0-rc3`: it was pushed while every crate still said
|
|
`0.1.0-rc2`, `dist` refused, and the four jobs behind `plan` skipped. No release
|
|
was ever built from it.
|
|
|
|
**Ask which tags exist; do not trust a document that tells you.** `git tag -l`,
|
|
and `git ls-remote --tags <remote>` for each remote you push to. Which tags are
|
|
where is a fact with a shelf life — it changed twice while this file was being
|
|
written — so this file deliberately records no inventory of it. Cleaning up a
|
|
dead tag is a separate decision and is not a step in cutting a release.
|