diff options
| author | user <user@clank> | 2026-08-28 15:44:01 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-28 15:44:01 +0200 |
| commit | 79df05ec1a3c38751956e8f6b122333ed3737efc (patch) | |
| tree | d7ec5b8eea2bc86d4c138dfa1dcfa84e82daaad8 /contrib | |
| parent | VERSION: 0.1.1, the bump v0.1.1 should have carried (diff) | |
| download | slosh-79df05ec1a3c38751956e8f6b122333ed3737efc.tar.gz slosh-79df05ec1a3c38751956e8f6b122333ed3737efc.tar.xz slosh-79df05ec1a3c38751956e8f6b122333ed3737efc.zip | |
release: one command to bump, commit and tag
contrib/release X.Y.Z moves ./VERSION, commits and cuts the annotated tag
together, so the floor and the tag cannot disagree by construction. It
refuses a dirty tree, a detached HEAD, a malformed or non-increasing
version, and an existing tag; notes passed as the second argument land in
the tag message. It prints the push and `make release` commands instead of
running them, because publishing should be deliberate.
contrib/release --check verifies the invariant (VERSION equals the newest
tag) on its own: `make release` runs it before building anything, and the
new pre-push hook runs the same comparison against the commit a v* tag
points at, so a tag made by hand is caught before it escapes the machine.
Diffstat (limited to 'contrib')
| -rwxr-xr-x | contrib/release | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/contrib/release b/contrib/release new file mode 100755 index 0000000..55fb1cf --- /dev/null +++ b/contrib/release @@ -0,0 +1,79 @@ +#!/bin/sh +# release -- cut a release: bump ./VERSION, commit, tag. One command, so the +# two steps that must agree cannot be done separately. +# +# contrib/release 0.1.2 # bump + commit + tag v0.1.2 +# contrib/release 0.1.2 "What changed." # ...with release notes in the tag +# contrib/release --check # VERSION agrees with the newest tag? +# +# The mistake this exists to prevent: tagging vX.Y.Z while ./VERSION still +# says the previous number. VERSION is the floor contrib/version falls back to +# when there is no git -- a cgit snapshot, a Homebrew source build -- so a +# stale floor means those builds report a version that was already released. +# It happened for v0.1.1. Bumping and tagging as one step removes the step +# that was forgotten; the pre-push hook and `make release` both run --check, +# so a tag made by hand is caught before it escapes. +# +# After tagging, the script prints the push and `make release` commands rather +# than running them: pushing a tag is publishing, and publishing should be the +# thing you do on purpose. +set -eu + +root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +cd "$root" + +fail() { printf 'release: %s\n' "$*" >&2; exit 1; } + +# The invariant, checkable on its own: after every release, ./VERSION equals +# the newest tag. It holds between releases too, because this script is the +# only thing that moves either. +if [ "${1:-}" = "--check" ]; then + newest="$(git tag -l 'v[0-9]*' | sort -V | tail -1)" + if [ -n "$newest" ] && [ "v$(cat VERSION)" != "$newest" ]; then + fail "VERSION says $(cat VERSION) but the newest tag is $newest -- + the floor was not bumped when that tag was cut. Bring the file up by + hand (echo ${newest#v} > VERSION, commit), and use contrib/release for + the next one." + fi + exit 0 +fi + +[ $# -ge 1 ] || fail "usage: contrib/release X.Y.Z [notes] | contrib/release --check" +v="${1#v}" # accept 0.1.2 and v0.1.2; the tag gets the v either way +notes="${2:-}" + +# The shape first, so nothing below has to wonder. +printf '%s' "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$' \ + || fail "'$v' does not look like X.Y.Z (or X.Y.Z-rcN)" + +# A release is cut from committed work on a branch, not from whatever happens +# to be lying around in the tree or a detached HEAD. +git symbolic-ref -q HEAD >/dev/null || fail "detached HEAD -- check out a branch" +git diff --quiet && git diff --cached --quiet \ + || fail "the tree is not clean -- commit or stash first" + +git rev-parse -q --verify "refs/tags/v$v" >/dev/null \ + && fail "v$v already exists" + +# Strictly newer than everything already out there: the floor, and the newest +# tag. sort -V, because 0.1.10 comes after 0.1.9 in versions and before it in +# the alphabet. +floor="$(cat VERSION)" +newest="$( { git tag -l 'v[0-9]*' | sed 's/^v//'; echo "$floor"; } | sort -V | tail -1)" +[ "$(printf '%s\n%s\n' "$newest" "$v" | sort -V | tail -1)" = "$v" ] && [ "$v" != "$newest" ] \ + || fail "$v is not newer than $newest" + +# The cut itself: floor, commit, tag, in that order, so the tagged commit is +# the one whose VERSION file already says so. +printf '%s\n' "$v" > VERSION +git add VERSION +git commit -q -m "release: v$v" -- VERSION +if [ -n "$notes" ]; then + git tag -a "v$v" -m "slosh $v" -m "$notes" +else + git tag -a "v$v" -m "slosh $v" +fi + +echo "release: v$v -- VERSION bumped, committed, tagged. To publish:" +echo " git push origin master v$v" +echo " make release REF=v$v # the tarballs and zips, into dist/" |
