Files
fish-shell/build_tools/release.sh
2025-10-08 10:51:47 +02:00

254 lines
6.0 KiB
Bash
Executable File

#!/bin/sh
{
set -ex
version=$1
repository_owner=fish-shell
remote=origin
if [ -n "$2" ]; then
set -u
repository_owner=$2
remote=$3
set +u
[ $# -eq 3 ]
fi
[ -n "$version" ]
for tool in \
bundle \
gh \
jq \
ruby \
timeout \
; do
if ! command -v "$tool" >/dev/null; then
echo >&2 "$0: missing command: $1"
exit 1
fi
done
repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
fish_site_repo=git@github.com:$repository_owner/fish-site
for path in . "$fish_site"
do
if ! git -C "$path" diff HEAD --quiet ||
git ls-files --others --exclude-standard | grep .; then
echo >&2 "$0: index and worktree must be clean"
exit 1
fi
done
(
cd "$fish_site"
[ "$(git rev-parse HEAD)" = \
"$(git ls-remote "$fish_site_repo" refs/heads/master |
awk '{print $1}')" ]
)
if git tag | grep -qxF "$version"; then
echo >&2 "$0: tag $version already exists"
exit 1
fi
integration_branch=$(
git for-each-ref --points-at=HEAD 'refs/heads/Integration_*' \
--format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
git merge-base --is-ancestor $remote/master HEAD
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
changelog_title="fish $version (released $(date +'%B %d, %Y'))"
sed -i \
-e "1c$changelog_title" \
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
CHANGELOG.rst
CommitVersion() {
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline
git add CHANGELOG.rst Cargo.toml Cargo.lock
git commit -m "$2
Created by ./build_tools/release.sh $version"
}
CommitVersion "$version" "Release $version"
# N.B. this is not GPG-signed.
git tag --annotate --message="Release $version" $version
git push $remote $version
TIMEOUT=
gh() {
command ${TIMEOUT:+timeout $TIMEOUT} \
gh --repo "$repository_owner/fish-shell" "$@"
}
gh workflow run release.yml --ref="$version" \
--raw-field="version=$version"
run_id=
while [ -z "$run_id" ] && sleep 5
do
run_id=$(gh run list \
--json=databaseId --jq=.[].databaseId \
--workflow=release.yml --limit=1 \
--commit="$(git rev-parse "$version^{commit}")")
done
# Update fishshell.com
tag_oid=$(git rev-parse "$version")
tmpdir=$(mktemp -d)
# TODO This works on draft releases only if "gh" is configured to
# have write access to the fish-shell repository. Unless we are fine
# publishing the release at this point, we should at least fail if
# "gh" doesn't have write access.
while ! \
gh release download "$version" --dir="$tmpdir" \
--pattern="fish-$version.tar.xz"
do
TIMEOUT=30 gh run watch "$run_id" ||:
sleep 5
done
actual_tag_oid=$(git ls-remote "$remote" |
awk '$2 == "refs/tags/'"$version"'" { print $1 }')
[ "$tag_oid" = "$actual_tag_oid" ]
( cd "$tmpdir" && tar xf fish-$version.tar.xz )
CopyDocs() {
rm -rf "$fish_site/site/docs/$1"
cp -r "$tmpdir/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
git -C $fish_site add "site/docs/$1"
}
minor_version=${version%.*}
CopyDocs "$minor_version"
latest_release=$(
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
's/-/~/g' | LC_ALL=C sort --version-sort)
printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
CopyDocs current
fi
rm -rf "$tmpdir"
(
cd "$fish_site"
make
git add -u
git add docs
if git ls-files --others --exclude-standard | grep .; then
exit 1
fi
git commit --message="$(printf %s "\
| Release $version (docs)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
)
gh_api_repo() {
path=$1
shift
command gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/$repository_owner/fish-shell/$path" \
"$@"
}
# Approve macos-codesign
# TODO what if current user can't approve?
gh_pending_deployments() {
gh_api_repo "actions/runs/$run_id/pending_deployments" "$@"
}
while {
environment_id=$(gh_pending_deployments | jq .[].environment.id)
[ -z "$environment_id" ]
}
do
sleep 5
done
echo '
{
"environment_ids": ['"$environment_id"'],
"state": "approved",
"comment": "Approved via ./build_tools/release.sh"
}
' |
gh_pending_deployments --method POST --input=-
# Await completion.
gh run watch "$run_id"
while {
! draft=$(gh release view "$version" --json=isDraft --jq=.isDraft) \
|| [ "$draft" = true ]
}
do
sleep 20
done
(
cd "$fish_site"
make new-release
git add -u
git add docs
if git ls-files --others --exclude-standard | grep .; then
exit 1
fi
git commit --message="$(printf %s "\
| Release $version (release list update)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
# This takes care to support remote names that are different from
# fish-shell remote name. Also, support detached HEAD state.
git push "$fish_site_repo" HEAD:master
)
if [ -n "$integration_branch" ]; then {
git push $remote "$version^{commit}":refs/heads/$integration_branch
} else {
changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
=========================
EOF
)
printf %s\\n "$changelog" >CHANGELOG.rst
CommitVersion ${version}-snapshot "start new cycle"
git push $remote HEAD:master
} fi
milestone_number=$(
gh_api_repo milestones?state=open |
jq '.[] | select(.title == "fish '"$version"'") | .number'
)
gh_api_repo milestones/$milestone_number --method PATCH \
--raw-field state=closed
next_patch_version=$(
echo "$version" | awk -F. '
NF == 3 && $3 ~ /[0-9]+/ {
printf "%s.%s.%s", $1, $2, $3+1
}
'
)
if [ -n "$next_patch_version" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_patch_version"
fi
exit
}