57 lines
2.7 KiB
Bash
Executable File
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"
|