Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ jobs:

## GitHub release (`github-release.yml`)

Publishes a GitHub release of a version, tagging the commit the run is for, with notes generated from the merged pull requests. It pairs with `semver-version.yml`:
Publishes a GitHub release of a version, tagging the commit the run is for, with notes generated from the pull requests merged since the previous release of the same series (the highest `<prefix>X.Y.Z` tag below the version, so a chart release of the same repository, `<chart>-X.Y.Z`, is not taken as the start). It pairs with `semver-version.yml`:

- a **pre-release** replaces the release and the tag of the same name, so a pull request publishes its `X.Y.Z-PRE-<number>` again on every push;
- a **release** never replaces one: a version that already exists fails the job.
Expand Down
35 changes: 34 additions & 1 deletion scripts/github-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
#
# A pre-release replaces the release and the tag of the same name, if there are any: a pull request
# publishes the same X.Y.Z-PRE-<number> on every push. A release refuses to replace one: a version,
# once released, does not change. The release notes are generated from the merged pull requests.
# once released, does not change. The release notes are generated from the pull requests merged
# since the previous release of the same series: the highest <prefix>X.Y.Z tag below the version,
# where <prefix> is whatever precedes the version number (v in v1.2.3, nothing in 1.2.3). Without
# it, GitHub would start from its latest release, which can be one of another series, such as a
# chart release (<chart>-X.Y.Z) of the same repository.
#
# Environment:
# VERSION tag and name of the release (required)
Expand All @@ -12,6 +16,8 @@
# TITLE title of the release (default: VERSION)
# FILES newline- or space-separated globs of the files to attach, relative to the current
# directory; a glob that matches nothing fails
# NOTES_START_TAG tag the release notes start from (default: the previous release, see above;
# none when there is no previous one)
# REPOSITORY owner/repo (default: $GITHUB_REPOSITORY)
# GH gh binary (default: gh)

Expand Down Expand Up @@ -70,7 +76,34 @@ if [ "$release_exists" = true ] || [ "$tag_exists" = true ]; then
fi
fi

# previous_release <version>: the highest <prefix>X.Y.Z tag of the repository below the X.Y.Z of the
# version, or nothing
previous_release() {
local version=$1 prefix number
prefix="${version%%[0-9]*}"
number="${version#"$prefix"}"
number="${number%%[-+]*}"
"$GH" api --paginate "repos/$REPOSITORY/tags" --jq '.[].name' \
| while IFS= read -r tag; do
case "$tag" in "$prefix"*) ;; *) continue ;; esac
candidate="${tag#"$prefix"}"
[[ "$candidate" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue
# keep the candidates below the version
if [ "$(printf '%s\n%s\n' "$candidate" "$number" | sort -t. -k1,1n -k2,2n -k3,3n | head -n 1)" = "$candidate" ] \
&& [ "$candidate" != "$number" ]; then
echo "$candidate"
fi
done \
| sort -t. -k1,1n -k2,2n -k3,3n | tail -n 1 | sed "s/^/$prefix/"
}

NOTES_START_TAG="${NOTES_START_TAG-$(previous_release "$VERSION")}"

args=(release create "$VERSION" --repo "$REPOSITORY" --target "$TARGET" --title "$TITLE" --generate-notes)
if [ -n "$NOTES_START_TAG" ]; then
echo "Release notes start from $NOTES_START_TAG"
args+=(--notes-start-tag "$NOTES_START_TAG")
fi
if [ "$PRERELEASE" = true ]; then
args+=(--prerelease)
fi
Expand Down
36 changes: 35 additions & 1 deletion tests/github-release.bats
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bats
# Tests for scripts/github-release.sh, with a fake gh. $EXISTING_RELEASE and $EXISTING_TAG name the
# release and the tag the fake repository already has.
# release and the tag the fake repository already has, and $TAGS lists all its tags.

load helpers

Expand All @@ -11,6 +11,7 @@ setup() {
fake_command gh
cat > "$BATS_TEST_TMPDIR/gh.sh" <<'EOF'
case "$1 $2" in
"api --paginate") printf '%s\n' ${TAGS:-} ;;
"release view") [ "$3" = "${EXISTING_RELEASE:-}" ] || exit 1 ;;
"api repos/owner/repo/git/ref/tags/"*) [ "${2##*/}" = "${EXISTING_TAG:-}" ] || exit 1 ;;
esac
Expand Down Expand Up @@ -79,3 +80,36 @@ EOF
PRERELEASE=yes run "$SCRIPT"
[ "$status" -eq 1 ]
}

@test "the notes start from the previous release of the same series, not from another series" {
TAGS="1.0.0 0.9.0 1.0.1-PRE-45 decentralized-iam-2.1.30 1.1.0" VERSION=1.0.1-PRE-45 PRERELEASE=true run "$SCRIPT"
[ "$status" -eq 0 ]
[[ "$output" == *"Release notes start from 1.0.0"* ]]
grep -q "release create 1.0.1-PRE-45 .* --notes-start-tag 1.0.0" "$BATS_TEST_TMPDIR/gh.log"
}

@test "a release takes the previous release as start, numerically" {
TAGS="11.9.0 11.39.0 11.38.1-PRE-3 11.4.0" VERSION=11.39.1 run "$SCRIPT"
[ "$status" -eq 0 ]
grep -q -- "--notes-start-tag 11.39.0$" "$BATS_TEST_TMPDIR/gh.log"
}

@test "a prefixed version only looks at the tags with its prefix" {
TAGS="v1.1.2 v1 1.5.0 v1.2.0" VERSION=v1.2.1 run "$SCRIPT"
[ "$status" -eq 0 ]
grep -q -- "--notes-start-tag v1.2.0$" "$BATS_TEST_TMPDIR/gh.log"
}

@test "without a previous release the notes have no start tag" {
TAGS="decentralized-iam-2.1.30 1.0.1-PRE-45" VERSION=1.0.0 run "$SCRIPT"
[ "$status" -eq 0 ]
! grep -q -- "--notes-start-tag" "$BATS_TEST_TMPDIR/gh.log"
}

@test "NOTES_START_TAG overrides the previous release, and empty disables it" {
TAGS="1.0.0" NOTES_START_TAG=0.5.0 VERSION=1.0.1 run "$SCRIPT"
grep -q -- "--notes-start-tag 0.5.0$" "$BATS_TEST_TMPDIR/gh.log"
: > "$BATS_TEST_TMPDIR/gh.log"
TAGS="1.0.0" NOTES_START_TAG="" VERSION=1.0.1 run "$SCRIPT"
! grep -q -- "--notes-start-tag" "$BATS_TEST_TMPDIR/gh.log"
}
Loading