#!/usr/bin/env bash set -euo pipefail usage() { echo "Usage: $0 VERSION" >&2 exit 2 } die() { echo "error: $*" >&2 exit 1 } [ "$#" -eq 1 ] || usage VERSION="$1" case "$VERSION" in v*) TAG="$VERSION" VERSION="${VERSION#v}" ;; *) TAG="v$VERSION" ;; esac echo "Preparing release $VERSION..." # Validate version format. case "$VERSION" in ''|*[!0-9.]*) die "invalid version: $VERSION" ;; esac # Make sure we're in a Git repository. git rev-parse --git-dir >/dev/null 2>&1 || die "not a Git repository" # Make sure the working tree is clean. if [ -n "$(git status --porcelain)" ]; then echo "error: working tree is not clean." >&2 echo >&2 git status --short >&2 echo >&2 echo "Please commit, stash, or discard these changes first." >&2 exit 1 fi # Make sure the tag doesn't already exist. if git rev-parse "$TAG" >/dev/null 2>&1; then die "tag $TAG already exists" fi # Find the latest tag so the release notes can be written from the commits # included since the previous release. LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null) || die "no previous tag found" echo "Updating Cargo.toml to $VERSION..." manifest_tmp="Cargo.toml.tmp.$$" if ! awk -v version="$VERSION" ' /^\[package\]$/ { in_package = 1; print; next } /^\[/ { in_package = 0 } in_package && /^version[[:space:]]*=/ { print "version = \"" version "\"" found = 1 next } { print } END { if (!found) exit 1 } ' Cargo.toml > "$manifest_tmp"; then rm -f "$manifest_tmp" die "could not find the package version in Cargo.toml" fi mv "$manifest_tmp" Cargo.toml cargo update --workspace echo echo "Commits since $LAST_TAG:" git log --oneline "$LAST_TAG..HEAD" echo echo "Edit CHANGELOG.md now, then return here to continue." if ! [ -r /dev/tty ]; then die "cannot pause for changelog editing without a terminal" fi printf "Press Enter when the changelog is ready... " > /dev/tty IFS= read -r _ < /dev/tty || true echo echo "Review the release changes:" git diff -- Cargo.toml Cargo.lock CHANGELOG.md echo printf "Create the release commit? [y/N] " > /dev/tty IFS= read -r CONFIRMATION < /dev/tty || CONFIRMATION="" case "$CONFIRMATION" in y|Y|yes|YES|Yes) ;; *) echo "Release cancelled before creating the release commit." exit 1 ;; esac echo "Creating release commit..." git add Cargo.toml Cargo.lock CHANGELOG.md git commit -m "chore(release): prepare $VERSION" echo echo "Release commit ready for review:" git show --stat --oneline --decorate HEAD echo echo "Review CHANGELOG.md and the release commit before continuing." if ! [ -r /dev/tty ]; then die "cannot prompt for release confirmation without a terminal" fi printf "Create tag $TAG and push the release? [y/N] " > /dev/tty IFS= read -r CONFIRMATION < /dev/tty || CONFIRMATION="" case "$CONFIRMATION" in y|Y|yes|YES|Yes) ;; *) echo "Release cancelled before tagging and pushing." exit 1 ;; esac echo "Creating tag $TAG..." git tag -a "$TAG" -m "Release $VERSION" echo "Pushing release..." git push origin HEAD git push origin "$TAG" echo echo "Release $VERSION created successfully."