Skip to content

Commit 9a3b90f

Browse files
wu-shengclaude
andcommitted
Name the vote-passed versions explicitly; never take them positionally
`vote-passed <old_version>` published the version resolved from the tag and deleted the version passed as the argument. Two versions that do opposite things, and only one of them positional - the deleted one. Reading `vote-passed 9.7.0` as "release 9.7.0" is the natural interpretation, and it would have promoted 9.7.0 into dist/release and then immediately svn rm'd it. Take them by name, or ask for them: vote-passed --release 9.7.0 --old_version 9.6.0 vote-passed --release 9.7.0 --no-cleanup vote-passed # asks for both Anything not named is prompted for, each prompt saying what will happen to that version. Positional arguments are now an error pointing at the named form, so existing muscle memory fails loudly rather than quietly meaning something else. Equal versions are refused, both are validated as x.y.z, and the release version must already be tagged. RELEASE_VERSION and OLD_VERSION still work, with the flags winning over them. The defaults behind those prompts were wrong too. The release version came from the highest version tag, but releases are not monotonic: a 9.6.1 patch cut from the 9.6.0 line after 9.7.0 has shipped is newer in time but lower in version, so the suggestion would have been the already-released 9.7.0. Order by tag creation date instead - maven-release-plugin writes annotated tags, so that timestamp belongs to the tag and survives fetches. The old version now comes from dist/release, the only place that knows what this release actually replaces, with the version being released filtered out so re-running after a partial failure cannot offer to delete it. Testing the flags uncovered a bug that predates them and reached every command. error() wrote to stdout, and resolve_version is always called as `version=$(resolve_version ...)`, so its message was captured into the variable instead of shown: releasing an untagged version aborted, correctly, but printed nothing about why. info/warn/error now write to stderr. The email templates use plain echo, so they stay on stdout and remain pipeable. The old version was also unvalidated, so a typo like `--old_version 9.6` would have been handed to svn rm as a path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent afede1b commit 9a3b90f

2 files changed

Lines changed: 158 additions & 20 deletions

File tree

docs/en/contribution/release-java-agent.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ The release script `tools/releasing/release.sh` automates the full release workf
4747

4848
# (send vote email to dev@skywalking.apache.org, wait 72h for vote to pass)
4949

50-
# Step 2: Promote, push Docker images, generate announce email, and clean up
51-
./tools/releasing/release.sh vote-passed [old_version_to_remove]
50+
# Step 2: Promote, publish the GitHub Release, generate announce email, and clean up
51+
./tools/releasing/release.sh vote-passed
5252
```
5353

5454
Run `./tools/releasing/release.sh` without arguments to see all available commands, including individual steps if you need to run them separately.
@@ -107,6 +107,26 @@ merged and `release/x.y.z` deleted, and `main` has already moved on to the next
107107
the highest `vx.y.z` tag in the repository, and can be overridden with a positional
108108
argument (`./release.sh docker 9.7.0`) or `RELEASE_VERSION=9.7.0`.
109109

110+
`vote-passed` never takes the versions positionally — they do opposite things, and swapping
111+
them would delete the release that was just promoted. Name them, or be asked:
112+
113+
```shell
114+
./tools/releasing/release.sh vote-passed --release 9.7.0 --old_version 9.6.0
115+
./tools/releasing/release.sh vote-passed --release 9.7.0 --no-cleanup
116+
./tools/releasing/release.sh vote-passed # asks for both
117+
```
118+
119+
- **Release version** — the one being published. Defaults to the most recently *created*
120+
`vx.y.z` tag, not the highest one: a `9.6.1` patch cut from the `9.6.0` line after `9.7.0`
121+
has shipped is newer in time but lower in version.
122+
- **Old version** — removed from `dist/release`, which ASF policy keeps to just the current
123+
release. Defaults to what is published there now, excluding the version being released.
124+
Answer `none` to skip.
125+
126+
`RELEASE_VERSION` and `OLD_VERSION` are honoured too; the flags win over them. Both versions
127+
are validated as `x.y.z`, the release version must already be tagged, and the two being equal
128+
is refused.
129+
110130
After the vote passes, run `vote-passed` which executes:
111131
1. **promote** — move packages from `dist/dev` to `dist/release` in Apache SVN (prompts for SVN credentials), then release the Nexus staging repository at https://repository.apache.org and update the website download page
112132
2. **github-release** — publish the GitHub Release for the tag, using `changes/changes-x.y.z.md` as its notes

tools/releasing/release.sh

Lines changed: 136 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
# ./release.sh prepare-vote Run prepare + stage + upload, then generate vote email
2828
# ./release.sh email [vote|announce] Generate email content
2929
# ./release.sh promote Move from dist/dev to dist/release in SVN
30-
# ./release.sh docker Build and push Docker images
31-
# ./release.sh vote-passed Run promote + docker, then generate announce email
30+
# ./release.sh github-release Publish the GitHub Release (pushes Docker images via CI)
31+
# ./release.sh docker Push Docker images locally (fallback)
32+
# ./release.sh vote-passed [--release x.y.z] [--old_version x.y.z]
3233
# ./release.sh cleanup <old_version> Remove old release from dist/release
3334

3435
set -euo pipefail
@@ -42,9 +43,45 @@ GREEN='\033[0;32m'
4243
YELLOW='\033[1;33m'
4344
NC='\033[0m' # No Color
4445

45-
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
46-
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
47-
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
46+
# Diagnostics go to stderr. resolve_version and the detect_* helpers are called
47+
# inside $( ), which captures stdout - an error printed there would be swallowed
48+
# into the variable instead of reaching the release manager.
49+
info() { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
50+
warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
51+
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
52+
53+
# ============================================================
54+
# detect_release_version — best guess at the release in flight
55+
# ============================================================
56+
# Ordered by when the tag was made, not by version number. Releases are not
57+
# monotonic: a 9.6.1 patch cut from the 9.6.0 line after 9.7.0 has shipped is
58+
# newer in time but lower in version, and sorting by version would pick the
59+
# already-released 9.7.0. maven-release-plugin writes annotated tags, so
60+
# creatordate is the tag's own timestamp and is stable across fetches.
61+
#
62+
# This is only ever a default offered to the release manager, never the final
63+
# word - vote-passed asks them to confirm it.
64+
detect_release_version() {
65+
git for-each-ref --sort=-creatordate --format='%(refname:short)' \
66+
'refs/tags/v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null | head -1 | sed 's/^v//'
67+
}
68+
69+
# ============================================================
70+
# detect_old_version — the release currently published in dist/release
71+
# ============================================================
72+
# ASF policy keeps only the current release in dist/release; older ones are
73+
# served from archive.apache.org. Whatever is there now is therefore what this
74+
# release replaces. Excludes the version being released, so re-running after a
75+
# partial failure - when promote has already copied it in - does not offer to
76+
# delete the release itself. Best effort: no network, no default.
77+
detect_old_version() {
78+
local exclude="${1:-}"
79+
svn ls "https://dist.apache.org/repos/dist/release/skywalking/java-agent/" 2>/dev/null \
80+
| sed 's#/$##' \
81+
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \
82+
| grep -vx "$exclude" \
83+
| sort -V | tail -1
84+
}
4885

4986
# ============================================================
5087
# resolve_version — identify the release from its tag
@@ -60,8 +97,8 @@ error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
6097
# That would aim SVN moves and Docker pushes at the wrong version. Tags are
6198
# branch-independent and outlive the release branch, so select from the tag list.
6299
#
63-
# Order of precedence: explicit argument, then $RELEASE_VERSION, then the highest
64-
# vX.Y.Z tag in the repository.
100+
# Order of precedence: explicit argument, then $RELEASE_VERSION, then the most
101+
# recently created vX.Y.Z tag (see detect_release_version for why not the highest).
65102
resolve_version() {
66103
local explicit="${1:-}"
67104
[ -z "$explicit" ] && explicit="${RELEASE_VERSION:-}"
@@ -71,11 +108,11 @@ resolve_version() {
71108
version="${explicit#v}"
72109
else
73110
local latest
74-
latest=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1)
111+
latest=$(detect_release_version)
75112
if [ -z "$latest" ]; then
76113
error "No vX.Y.Z release tag found. Pass the version explicitly, e.g. '$0 <command> 9.7.0'."
77114
fi
78-
version="${latest#v}"
115+
version="$latest"
79116
fi
80117

81118
case "$version" in
@@ -710,15 +747,92 @@ cmd_prepare_vote() {
710747
# vote-passed — run all steps after the vote passes
711748
# ============================================================
712749
cmd_vote_passed() {
713-
local old_version="${1:-}"
714-
715750
cd "$PROJECT_ROOT"
716751

717-
# Resolved from the release tag, so this works after the release branch has
718-
# been merged and deleted. Show it before touching SVN or Docker Hub, both of
719-
# which are public and awkward to undo.
720-
local version
721-
version=$(resolve_version "")
752+
# Two versions, and they do opposite things: one is published, the other is
753+
# deleted. Never take them positionally - `vote-passed 9.7.0` while releasing
754+
# 9.7.0 reads as "release this" but would svn rm what promote just copied in.
755+
# Name them, or be asked for them. Detection only supplies defaults.
756+
local version="${RELEASE_VERSION:-}"
757+
local old_version="${OLD_VERSION:-}"
758+
local old_version_given=0
759+
[ -n "$old_version" ] && old_version_given=1
760+
761+
while [ "$#" -gt 0 ]; do
762+
case "$1" in
763+
--release|--release-version)
764+
[ -n "${2:-}" ] || error "$1 needs a version, e.g. --release 9.7.0"
765+
version="$2"; shift 2 ;;
766+
--old_version|--old-version)
767+
[ -n "${2:-}" ] || error "$1 needs a version, e.g. --old_version 9.6.0 (or --no-cleanup)"
768+
old_version="$2"; old_version_given=1; shift 2 ;;
769+
--no-cleanup)
770+
old_version=""; old_version_given=1; shift ;;
771+
-h|--help)
772+
echo "Usage: $0 vote-passed [--release x.y.z] [--old_version x.y.z | --no-cleanup]"
773+
echo ""
774+
echo " --release the version being published; must already be tagged"
775+
echo " --old_version the version removed from dist/release"
776+
echo " --no-cleanup leave dist/release alone"
777+
echo ""
778+
echo "Anything not given is asked for. RELEASE_VERSION and OLD_VERSION are"
779+
echo "honoured as well, and the flags win over them."
780+
return 0 ;;
781+
-*)
782+
error "Unknown option: $1
783+
Usage: $0 vote-passed [--release x.y.z] [--old_version x.y.z | --no-cleanup]" ;;
784+
*)
785+
error "'$0 vote-passed' does not take positional arguments - it is too easy to
786+
confuse the version being released with the one being deleted. Name them:
787+
$0 vote-passed --release <new> --old_version <old>
788+
or run it with no arguments and answer the prompts." ;;
789+
esac
790+
done
791+
792+
info "Publishing a release. Two versions are needed."
793+
echo ""
794+
795+
if [ -z "$version" ]; then
796+
local suggested
797+
suggested=$(detect_release_version)
798+
echo " The version being released. It must already be tagged and voted on."
799+
if [ -n "$suggested" ]; then
800+
echo " Most recently tagged: ${suggested} (tag v${suggested})"
801+
fi
802+
read -rp " Release version${suggested:+ [$suggested]}: " version
803+
version="${version:-$suggested}"
804+
fi
805+
[ -z "$version" ] && error "No release version given."
806+
version=$(resolve_version "$version")
807+
echo ""
808+
809+
if [ "$old_version_given" -eq 0 ]; then
810+
local current
811+
current=$(detect_old_version "$version")
812+
echo " The version to remove from dist/release. ASF policy keeps only the"
813+
echo " current release there; older ones are served from archive.apache.org."
814+
if [ -n "$current" ]; then
815+
echo " Currently published in dist/release: ${current}"
816+
else
817+
echo " Could not read dist/release, so there is no suggestion."
818+
fi
819+
read -rp " Old version to remove${current:+ [$current]} (or 'none' to skip): " old_version
820+
old_version="${old_version:-$current}"
821+
fi
822+
# 'none' is the explicit opt out; blank accepts the suggestion above.
823+
[ "$old_version" = "none" ] && old_version=""
824+
if [ -n "$old_version" ]; then
825+
case "$old_version" in
826+
[0-9]*.[0-9]*.[0-9]*) ;;
827+
*) error "Old version must look like x.y.z, or 'none' / --no-cleanup to skip. Got: ${old_version}" ;;
828+
esac
829+
fi
830+
831+
# The mistake this whole prompt exists to prevent.
832+
if [ -n "$old_version" ] && [ "$old_version" = "$version" ]; then
833+
error "Old version and release version are both ${version}; that would delete the release being published."
834+
fi
835+
echo ""
722836

723837
info "Publishing release ${version}:"
724838
echo " Release tag : v${version}"
@@ -781,7 +895,8 @@ main() {
781895
echo "Quick start (two-step release):"
782896
echo " $0 prepare-vote 9.7.0 [9.8.0] # before vote (next version auto-calculated if omitted)"
783897
echo " (wait for 72h vote to pass)"
784-
echo " $0 vote-passed [old_version] # after vote"
898+
echo " $0 vote-passed # after vote (asks for the versions)"
899+
echo " $0 vote-passed --release 9.7.0 --old_version 9.6.0"
785900
echo ""
786901
echo "Every command after 'prepare' identifies the release by its tag (vX.Y.Z), not by the"
787902
echo "checked-out branch, so they still work once release/x.y.z has been merged and deleted."
@@ -799,7 +914,10 @@ main() {
799914
echo " the Docker images, via publish-docker.yaml"
800915
echo " docker [ver] Push Docker images from this machine (fallback"
801916
echo " for when the workflow fails)"
802-
echo " vote-passed [old_ver] Run promote + github-release + announce [+ cleanup]"
917+
echo " vote-passed [--release x.y.z] [--old_version x.y.z | --no-cleanup]"
918+
echo " Run promote + github-release + announce [+ cleanup]."
919+
echo " Anything not named is asked for. Never positional:"
920+
echo " the two versions do opposite things."
803921
echo " cleanup <old_version> Remove old release from dist/release"
804922
;;
805923
esac

0 commit comments

Comments
 (0)