Files
notarius/scripts/release.sh

57 lines
2.7 KiB
Bash
Executable File

#!/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"