diff --git a/README.md b/README.md index 7eeb0dd..f83c934 100644 --- a/README.md +++ b/README.md @@ -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 `X.Y.Z` tag below the version, so a chart release of the same repository, `-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-` again on every push; - a **release** never replaces one: a version that already exists fails the job. diff --git a/scripts/github-release.sh b/scripts/github-release.sh index 5d685ff..6b49133 100755 --- a/scripts/github-release.sh +++ b/scripts/github-release.sh @@ -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- 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 X.Y.Z tag below the version, +# where 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 (-X.Y.Z) of the same repository. # # Environment: # VERSION tag and name of the release (required) @@ -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) @@ -70,7 +76,34 @@ if [ "$release_exists" = true ] || [ "$tag_exists" = true ]; then fi fi +# previous_release : the highest 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 diff --git a/tests/github-release.bats b/tests/github-release.bats index 800be9f..7778224 100644 --- a/tests/github-release.bats +++ b/tests/github-release.bats @@ -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 @@ -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 @@ -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" +}