#!/bin/sh set -eu fail() { printf '%s\n' "release: $*" >&2; exit 1; } [ "$#" -eq 1 ] || fail "usage: $0 vMAJOR.MINOR.PATCH" version=$1 printf '%s\n' "$version" | grep -E -x 'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)' >/dev/null || fail "invalid stable release version" script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) cd "$script_dir/.." export GOWORK=off candidate=$(git rev-parse 'HEAD^{commit}') check_candidate() { [ "$(git branch --show-current)" = main ] || fail "release branch must be main" [ -z "$(git status --porcelain)" ] || fail "worktree and index must be clean" [ "$(git rev-parse 'HEAD^{commit}')" = "$candidate" ] || fail "candidate changed during validation" git cat-file -e "$candidate:docs/releases/$version.md" || fail "release note must be committed" } check_publication() { # Query failure is not proof that the tag is unused. remote_tag=$(git ls-remote --tags origin "refs/tags/$version" "refs/tags/$version^{}") || fail "cannot determine remote tag availability" [ -z "$remote_tag" ] || fail "remote tag already exists: $version" if git show-ref --verify --quiet "refs/tags/$version"; then fail "local tag already exists: $version" fi git fetch --no-tags origin refs/heads/main || fail "cannot fetch authoritative main" remote_main=$(git rev-parse 'FETCH_HEAD^{commit}') git merge-base --is-ancestor "$remote_main" "$candidate" || fail "candidate does not contain authoritative main" } check_candidate check_publication "$script_dir/check-release-source.sh" "$version" check_candidate check_publication if ! git -c push.followTags=false push origin "$candidate:refs/heads/main"; then # Do not publish a tag after a failed branch push, even if the branch arrived. git ls-remote origin refs/heads/main >&2 || fail "branch publication status unknown; inspect origin before retrying" fail "branch push failed; inspect the reported remote state before retrying" fi git -c tag.gpgSign=false tag "$version" "$candidate" [ "$(git cat-file -t "$version")" = commit ] || fail "tag is not lightweight" [ "$(git rev-parse "$version^{commit}")" = "$candidate" ] || fail "tag target changed" if ! git -c push.followTags=false push origin "refs/tags/$version:refs/tags/$version"; then # A connection can fail after the server accepted the tag. Preserve it locally # until authoritative state proves whether publication succeeded. remote_tag=$(git ls-remote --tags origin "refs/tags/$version" "refs/tags/$version^{}") || fail "tag publication status unknown; preserve local tag and inspect origin" expected=$(printf '%s\t%s' "$candidate" "refs/tags/$version") [ "$remote_tag" = "$expected" ] || fail "tag push failed; preserve local tag and inspect origin before retrying" fi printf 'Released %s at %s\n' "$version" "$candidate"