From 19a39b477626606e7b285c24e104c064d7d665e2 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 9 Dec 2022 11:48:59 +0000 Subject: [PATCH 01/11] feat: Add `release.sh` script and detect breaking changes This commit introduces three new scripts: - `release.sh` To be run by a user on their local machine to preview and create a new release (tag + push) - `check_commit_metadata.sh` For e.g. detecting breaking changes - `genereate_release_notes.sh` To display the generated release notes, used for previews and in `publish_release.sh` The `release.sh` script can be run without arguments, and it will automatically determine if we're to do a patch or minor release. A minor release can be forced via `--minor` flag. Breaking changes can be annotated either via commit/merge title prefix (`feat!:`, `feat(api)!:`), or by adding the `release/breaking` label to the PR that was merged (on GitHub). Related #5233 This work will be followed up by changes to move the tag creation from `release.sh` to CI workflows, the `release.sh` will then become a lightweight wrapper to run the workflow. --- scripts/check_commit_metadata.sh | 108 ++++++++++++++++++++++++++ scripts/generate_release_notes.sh | 121 ++++++++++++++++++++++++++++++ scripts/lib.sh | 9 +++ scripts/publish_release.sh | 19 ++--- scripts/release.sh | 112 +++++++++++++++++++++++++++ 5 files changed, 356 insertions(+), 13 deletions(-) create mode 100755 scripts/check_commit_metadata.sh create mode 100755 scripts/generate_release_notes.sh create mode 100755 scripts/release.sh diff --git a/scripts/check_commit_metadata.sh b/scripts/check_commit_metadata.sh new file mode 100755 index 0000000000000..756daeec05aa7 --- /dev/null +++ b/scripts/check_commit_metadata.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +# Usage: source ./check_commit_tags.sh +# Usage: ./check_commit_tags.sh +# +# Example: ./check_commit_tags.sh v0.13.1..971e3678 +# +# When sourced, this script will populate the COMMIT_METADATA_* variables +# with the commit metadata for each commit in the revision range. +# +# Because this script does some expensive lookups via the GitHub API, it's +# results will be cached in the environment and restored if this script is +# sourced a second time with the same arguments. + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +range=${1:-} + +if [[ -z $range ]]; then + error "No revision range specified" +fi + +# Check dependencies. +dependencies gh + +COMMIT_METADATA_BREAKING=0 +declare -A COMMIT_METADATA_TITLE COMMIT_METADATA_CATEGORY + +main() { + # Match a commit prefix pattern, e.g. feat: or feat(site):. + prefix_pattern="^([a-z]+)(\([a-z]*\))?:" + + # If a commit contains this title prefix or the source PR contains the + # label, patch releases will not be allowed. + # This regex matches both `feat!:` and `feat(site)!:`. + breaking_title="^[a-z]+(\([a-z]*\))?!:" + breaking_label=release/breaking + breaking_category=breaking + + mapfile -t commits < <(git log --no-merges --pretty=format:"%h %s" "$range") + + for commit in "${commits[@]}"; do + mapfile -d ' ' -t parts <<<"$commit" + commit_sha=${parts[0]} + commit_prefix=${parts[1]} + + # Store the commit title for later use. + title=${parts[*]:1} + title=${title%$'\n'} + COMMIT_METADATA_TITLE[$commit_sha]=$title + + # First, check the title for breaking changes. This avoids doing a + # GH API request if there's a match. + if [[ $commit_prefix =~ $breaking_title ]]; then + COMMIT_METADATA_CATEGORY[$commit_sha]=$breaking_category + COMMIT_METADATA_BREAKING=1 + continue + fi + + # Get the labels for the PR associated with this commit. + mapfile -t labels < <(gh api -H "Accept: application/vnd.github+json" "/repos/coder/coder/commits/${commit_sha}/pulls" -q '.[].labels[].name') + + if [[ " ${labels[*]} " = *" ${breaking_label} "* ]]; then + COMMIT_METADATA_CATEGORY[$commit_sha]=$breaking_category + COMMIT_METADATA_BREAKING=1 + continue + fi + + if [[ $commit_prefix =~ $prefix_pattern ]]; then + commit_prefix=${BASH_REMATCH[1]} + fi + case $commit_prefix in + feat | fix) + COMMIT_METADATA_CATEGORY[$commit_sha]=$commit_prefix + ;; + *) + COMMIT_METADATA_CATEGORY[$commit_sha]=other + ;; + esac + done +} + +declare_print_commit_metadata() { + declare -p COMMIT_METADATA_BREAKING COMMIT_METADATA_TITLE COMMIT_METADATA_CATEGORY +} + +export_commit_metadata() { + _COMMIT_METADATA_CACHE="${range}:$(declare_print_commit_metadata)" + export _COMMIT_METADATA_CACHE COMMIT_METADATA_BREAKING COMMIT_METADATA_TITLE COMMIT_METADATA_CATEGORY +} + +# _COMMIT_METADATA_CACHE is used to cache the results of this script in +# the environment because bash arrays are not passed on to subscripts. +if [[ ${_COMMIT_METADATA_CACHE:-} == "${range}:"* ]]; then + eval "${_COMMIT_METADATA_CACHE#*:}" +else + main +fi + +export_commit_metadata + +# Make it easier to debug this script by printing the associative array +# when it's not sourced. +if ! issourced; then + declare_print_commit_metadata +fi diff --git a/scripts/generate_release_notes.sh b/scripts/generate_release_notes.sh new file mode 100755 index 0000000000000..d9b7e5d582a5a --- /dev/null +++ b/scripts/generate_release_notes.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +# Usage: ./generate_release_notes.sh --old-version --new-version --ref +# +# Example: ./generate_release_notes.sh --old-version v0.13.0 --new-version v0.13.1 --ref 1e6b244c +# +# This script generates release notes for the given version. It will generate +# release notes for all commits between the old version and the new version. +# +# Ref must be set to the commit that the new version will be tagget at. This +# is used to determine the commits that are included in the release. If the +# commit is already tagged, ref can be set to the tag name. + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +old_version= +new_version= +ref= + +args="$(getopt -o '' -l old-version:,new-version:,ref: -- "$@")" +eval set -- "$args" +while true; do + case "$1" in + --old-version) + old_version="$2" + shift 2 + ;; + --new-version) + new_version="$2" + shift 2 + ;; + --ref) + ref="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + error "Unrecognized option: $1" + ;; + esac +done + +# Check dependencies. +dependencies gh sort + +if [[ -z $old_version ]]; then + error "No old version specified" +fi +if [[ -z $new_version ]]; then + error "No new version specified" +fi +if [[ -z $ref ]]; then + error "No ref specified" +fi + +# shellcheck source=scripts/check_commit_metadata.sh +source "$SCRIPT_DIR/check_commit_metadata.sh" "${old_version}..${ref}" + +# Sort commits by title prefix, then by date, only return sha at the end. +mapfile -t commits < <(git log --no-merges --pretty=format:"%ct %h %s" "${old_version}..${ref}" | sort -k3,3 -k1,1n | cut -d' ' -f2) + +breaking_changelog= +feat_changelog= +fix_changelog= +other_changelog= + +for commit in "${commits[@]}"; do + line="- $commit ${COMMIT_METADATA_TITLE[$commit]}\n" + + case "${COMMIT_METADATA_CATEGORY[$commit]}" in + breaking) + breaking_changelog+="$line" + ;; + feat) + feat_changelog+="$line" + ;; + fix) + fix_changelog+="$line" + ;; + *) + other_changelog+="$line" + ;; + esac +done + +changelog="$( + if ((${#breaking_changelog} > 0)); then + echo -e "### BREAKING CHANGES\n" + echo -e "$breaking_changelog" + fi + if ((${#feat_changelog} > 0)); then + echo -e "### Features\n" + echo -e "$feat_changelog" + fi + if ((${#fix_changelog} > 0)); then + echo -e "### Bug fixes\n" + echo -e "$fix_changelog" + fi + if ((${#other_changelog} > 0)); then + echo -e "### Other changes\n" + echo -e "$other_changelog" + fi +)" + +image_tag="$(execrelative ./image_tag.sh --version "$new_version")" + +echo -e "## Changelog: + +$changelog + +https://github.com/coder/coder/compare/${old_version}...${new_version} + +## Container image: + +- \`docker pull $image_tag\` +" diff --git a/scripts/lib.sh b/scripts/lib.sh index 67922db3dd9d9..081b43310a431 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -123,6 +123,9 @@ log() { # error prints an error message and returns an error exit code. error() { log "ERROR: $*" + if issourced; then + return 1 + fi exit 1 } @@ -131,6 +134,12 @@ isdarwin() { [[ "${OSTYPE:-darwin}" == *darwin* ]] } +# issourced returns true if the script that sourced this script is being +# sourced by another. +issourced() { + [[ "${BASH_SOURCE[1]}" != "$0" ]] +} + # We don't need to check dependencies more than once per script, but some # scripts call other scripts that also `source lib.sh`, so we set an environment # variable after successfully checking dependencies once. diff --git a/scripts/publish_release.sh b/scripts/publish_release.sh index 80725cca9a931..7420b309eca1a 100755 --- a/scripts/publish_release.sh +++ b/scripts/publish_release.sh @@ -101,23 +101,16 @@ old_tag="$(git describe --abbrev=0 HEAD^1)" # For dry-run builds we want to use the SHA instead of the tag, because the new # tag probably doesn't exist. -changelog_range="$old_tag..$new_tag" +new_ref="$new_tag" if [[ "$dry_run" == 1 ]]; then - changelog_range="$old_tag..$(git rev-parse --short HEAD)" + new_ref="$(git rev-parse --short HEAD)" fi -# Craft the release notes. -changelog="$(git log --no-merges --pretty=format:"- %h %s" "$changelog_range")" -image_tag="$(execrelative ./image_tag.sh --version "$version")" -release_notes=" -## Changelog - -$changelog +# shellcheck source=scripts/check_commit_metadata.sh +source "$SCRIPT_DIR/check_commit_metadata.sh" "$old_tag..$new_ref" -## Container Image -- \`docker pull $image_tag\` - -" +# Craft the release notes. +release_notes="$(execrelative ./generate_release_notes.sh --old-version "$old_tag" --new-version "$new_tag" --ref "$new_ref")" release_notes_file="$(mktemp)" echo "$release_notes" >"$release_notes_file" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000000000..1aa46f4ea3f93 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# This script should be called to create a new release. +# +# When run, this script will display the new version number and optionally a +# preview of the release notes. The new version will be selected automatically +# based on if the release contains breaking changes or not. If the release +# contains breaking changes, a new minor version will be created. Otherwise, a +# new patch version will be created. +# +# Set --ref if you need to specify a specific commit that the new version will +# be tagged at, otherwise the latest commit will be used. +# +# Set --minor to force a minor version bump, even when there are no breaking +# changes. +# +# To mark a release as containing breaking changes, the commit title should +# either contain a known prefix with an exclamation mark ("feat!:", +# "feat(api)!:") or the PR that was merged can be tagged with the +# "release/breaking" label. +# +# Usage: ./release.sh [--ref ] [--minor] + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +cdroot + +ref= +minor=0 + +args="$(getopt -o n -l ref:,minor -- "$@")" +eval set -- "$args" +while true; do + case "$1" in + --ref) + ref="$2" + shift 2 + ;; + --minor) + minor=1 + shift + ;; + --) + shift + break + ;; + *) + error "Unrecognized option: $1" + ;; + esac +done + +# Check dependencies. +dependencies gh sort + +# Make sure the repository is up-to-date before generating release notes. +log "Fetching main and tags from origin..." +git fetch --quiet --tags origin main + +# Resolve to the latest ref on origin/main unless otherwise specified. +ref=$(git rev-parse --short "${ref:-origin/main}") + +# Make sure that we're running the latest release script. +if [[ -n $(git diff --name-status origin/main -- ./scripts/release.sh) ]]; then + error "Release script is out-of-date. Please check out the latest version and try again." +fi + +# Check the current version tag from GitHub (by number) using the API to +# ensure no local tags are considered. +mapfile -t versions < <(gh api -H "Accept: application/vnd.github+json" /repos/coder/coder/git/refs/tags -q '.[].ref | split("/") | .[2]' | grep '^v' | sort -r -V) +old_version=${versions[0]} + +log "Checking commit metadata for changes since $old_version..." +# shellcheck source=scripts/check_commit_metadata.sh +source "$SCRIPT_DIR/check_commit_metadata.sh" "$old_version..$ref" + +mapfile -d . -t version_parts <<<"$old_version" +if [[ $minor == 1 ]] || [[ $COMMIT_METADATA_BREAKING == 1 ]]; then + if [[ $COMMIT_METADATA_BREAKING == 1 ]]; then + log "Breaking change detected, incrementing minor version..." + else + log "Forcing minor version bump..." + fi + version_parts[1]=$((version_parts[1] + 1)) + version_parts[2]=0 +else + log "No breaking changes detected, incrementing patch version..." + version_parts[2]=$((version_parts[2] + 1)) +fi +new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + +log "Old version: ${old_version}" +log "New version: ${new_version}" + +release_notes="$(execrelative ./generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" + +echo +read -p "Preview release notes? (y/n) " -n 1 -r show_reply +echo +if [[ $show_reply =~ ^[Yy]$ ]]; then + echo -e "$release_notes\n" +fi + +read -p "Create release? (y/n) " -n 1 -r create +echo +if [[ $create =~ ^[Yy]$ ]]; then + log "Tagging commit $ref as $new_version..." + echo git tag -a "$new_version" -m "$new_version" "$ref" + log "Pushing tag to origin..." + echo git push -u origin "$new_version" +fi From 7d372f95336c0a61523063dac82ea3b2be25ba5b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 12:27:32 +0200 Subject: [PATCH 02/11] Update scripts/check_commit_metadata.sh Co-authored-by: Marcin Tojek --- scripts/check_commit_metadata.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_commit_metadata.sh b/scripts/check_commit_metadata.sh index 756daeec05aa7..7329c81bcc9e4 100755 --- a/scripts/check_commit_metadata.sh +++ b/scripts/check_commit_metadata.sh @@ -8,7 +8,7 @@ # When sourced, this script will populate the COMMIT_METADATA_* variables # with the commit metadata for each commit in the revision range. # -# Because this script does some expensive lookups via the GitHub API, it's +# Because this script does some expensive lookups via the GitHub API, its # results will be cached in the environment and restored if this script is # sourced a second time with the same arguments. From 0f5883f6d36e7df5c4fcb50467857b78d9f7dbe7 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 11:52:56 +0000 Subject: [PATCH 03/11] chore: Move release scripts to scripts/release folder --- .github/workflows/release.yaml | 2 +- scripts/release.sh | 6 +++--- scripts/{ => release}/check_commit_metadata.sh | 2 +- scripts/{ => release}/generate_release_notes.sh | 6 +++--- scripts/{publish_release.sh => release/publish.sh} | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) rename scripts/{ => release}/check_commit_metadata.sh (98%) rename scripts/{ => release}/generate_release_notes.sh (92%) rename scripts/{publish_release.sh => release/publish.sh} (93%) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e4f36e82e30f4..8442dc8c69ec2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -157,7 +157,7 @@ jobs: - name: Publish release run: | - ./scripts/publish_release.sh \ + ./scripts/release/publish.sh \ ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ ./build/*_installer.exe \ ./build/*.zip \ diff --git a/scripts/release.sh b/scripts/release.sh index 1aa46f4ea3f93..60abfb0c2d673 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -72,8 +72,8 @@ mapfile -t versions < <(gh api -H "Accept: application/vnd.github+json" /repos/c old_version=${versions[0]} log "Checking commit metadata for changes since $old_version..." -# shellcheck source=scripts/check_commit_metadata.sh -source "$SCRIPT_DIR/check_commit_metadata.sh" "$old_version..$ref" +# shellcheck source=scripts/release/check_commit_metadata.sh +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version..$ref" mapfile -d . -t version_parts <<<"$old_version" if [[ $minor == 1 ]] || [[ $COMMIT_METADATA_BREAKING == 1 ]]; then @@ -93,7 +93,7 @@ new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" log "Old version: ${old_version}" log "New version: ${new_version}" -release_notes="$(execrelative ./generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" +release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" echo read -p "Preview release notes? (y/n) " -n 1 -r show_reply diff --git a/scripts/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh similarity index 98% rename from scripts/check_commit_metadata.sh rename to scripts/release/check_commit_metadata.sh index 7329c81bcc9e4..2b4595551b6f1 100755 --- a/scripts/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -14,7 +14,7 @@ set -euo pipefail # shellcheck source=scripts/lib.sh -source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" range=${1:-} diff --git a/scripts/generate_release_notes.sh b/scripts/release/generate_release_notes.sh similarity index 92% rename from scripts/generate_release_notes.sh rename to scripts/release/generate_release_notes.sh index d9b7e5d582a5a..b5a7b552726cc 100755 --- a/scripts/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -13,7 +13,7 @@ set -euo pipefail # shellcheck source=scripts/lib.sh -source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" old_version= new_version= @@ -58,8 +58,8 @@ if [[ -z $ref ]]; then error "No ref specified" fi -# shellcheck source=scripts/check_commit_metadata.sh -source "$SCRIPT_DIR/check_commit_metadata.sh" "${old_version}..${ref}" +# shellcheck source=scripts/release/check_commit_metadata.sh +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "${old_version}..${ref}" # Sort commits by title prefix, then by date, only return sha at the end. mapfile -t commits < <(git log --no-merges --pretty=format:"%ct %h %s" "${old_version}..${ref}" | sort -k3,3 -k1,1n | cut -d' ' -f2) diff --git a/scripts/publish_release.sh b/scripts/release/publish.sh similarity index 93% rename from scripts/publish_release.sh rename to scripts/release/publish.sh index 7420b309eca1a..018e32fe378ed 100755 --- a/scripts/publish_release.sh +++ b/scripts/release/publish.sh @@ -7,7 +7,7 @@ # pipeline to do the final publish step. If you want to create a release use: # git tag -a -m "$ver" "$ver" && git push origin "$ver" # -# Usage: ./publish_release.sh [--version 1.2.3] [--dry-run] path/to/asset1 path/to/asset2 ... +# Usage: ./publish.sh [--version 1.2.3] [--dry-run] path/to/asset1 path/to/asset2 ... # # The supplied images must already be pushed to the registry or this will fail. # Also, the source images cannot be in a different registry than the target @@ -27,7 +27,7 @@ set -euo pipefail # shellcheck source=scripts/lib.sh -source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" if [[ "${CI:-}" == "" ]]; then error "This script must be run in CI" @@ -106,8 +106,8 @@ if [[ "$dry_run" == 1 ]]; then new_ref="$(git rev-parse --short HEAD)" fi -# shellcheck source=scripts/check_commit_metadata.sh -source "$SCRIPT_DIR/check_commit_metadata.sh" "$old_tag..$new_ref" +# shellcheck source=scripts/release/check_commit_metadata.sh +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_tag..$new_ref" # Craft the release notes. release_notes="$(execrelative ./generate_release_notes.sh --old-version "$old_tag" --new-version "$new_tag" --ref "$new_ref")" From a5468e27748337dec3df5b95297d560110590b05 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 12:09:40 +0000 Subject: [PATCH 04/11] fix: Explicitly format compare URL like GitHub would --- scripts/release/generate_release_notes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/generate_release_notes.sh b/scripts/release/generate_release_notes.sh index b5a7b552726cc..d228f9b6afd21 100755 --- a/scripts/release/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -113,7 +113,7 @@ echo -e "## Changelog: $changelog -https://github.com/coder/coder/compare/${old_version}...${new_version} +[\`${old_version}...${new_version}\`](https://github.com/coder/coder/compare/${old_version}...${new_version}) ## Container image: From 318be9aca5bdbc88c3729b187b3096118a239fa2 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 13:13:49 +0000 Subject: [PATCH 05/11] fix: Remove no-op tagging --- scripts/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 60abfb0c2d673..481135a015988 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -106,7 +106,7 @@ read -p "Create release? (y/n) " -n 1 -r create echo if [[ $create =~ ^[Yy]$ ]]; then log "Tagging commit $ref as $new_version..." - echo git tag -a "$new_version" -m "$new_version" "$ref" + git tag -a "$new_version" -m "$new_version" "$ref" log "Pushing tag to origin..." - echo git push -u origin "$new_version" + git push -u origin "$new_version" fi From 8e89727efdeb530939246296f541b6d356d2264a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 13:32:08 +0000 Subject: [PATCH 06/11] Remove extra : --- scripts/release/generate_release_notes.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/generate_release_notes.sh b/scripts/release/generate_release_notes.sh index d228f9b6afd21..ccdd1f00fc13d 100755 --- a/scripts/release/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -109,13 +109,13 @@ changelog="$( image_tag="$(execrelative ./image_tag.sh --version "$new_version")" -echo -e "## Changelog: +echo -e "## Changelog $changelog [\`${old_version}...${new_version}\`](https://github.com/coder/coder/compare/${old_version}...${new_version}) -## Container image: +## Container image - \`docker pull $image_tag\` " From 1cf1c842f2c4dfa3c5067f0af2142195d4c9efc1 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 13:35:07 +0000 Subject: [PATCH 07/11] Add compare to comparison link --- scripts/release/generate_release_notes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/generate_release_notes.sh b/scripts/release/generate_release_notes.sh index ccdd1f00fc13d..c06e4ca93651a 100755 --- a/scripts/release/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -113,7 +113,7 @@ echo -e "## Changelog $changelog -[\`${old_version}...${new_version}\`](https://github.com/coder/coder/compare/${old_version}...${new_version}) +Compare: [\`${old_version}...${new_version}\`](https://github.com/coder/coder/compare/${old_version}...${new_version}) ## Container image From 3e6c295ffc8aee368e5070c42ef7490d344e0adf Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 12 Dec 2022 15:29:57 +0000 Subject: [PATCH 08/11] Remove issourced check from error --- scripts/lib.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/lib.sh b/scripts/lib.sh index 081b43310a431..ea00c7b7cbdf3 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -123,9 +123,6 @@ log() { # error prints an error message and returns an error exit code. error() { log "ERROR: $*" - if issourced; then - return 1 - fi exit 1 } From c871cb7744212652fb0c16c3ca5b7b46546f02fe Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 15 Dec 2022 10:28:27 +0000 Subject: [PATCH 09/11] Use gh pr list to reduce API requests --- scripts/release.sh | 2 +- scripts/release/check_commit_metadata.sh | 82 ++++++++++++++++------- scripts/release/generate_release_notes.sh | 2 +- scripts/release/publish.sh | 2 +- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 481135a015988..83c1ea01fde4d 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -73,7 +73,7 @@ old_version=${versions[0]} log "Checking commit metadata for changes since $old_version..." # shellcheck source=scripts/release/check_commit_metadata.sh -source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version..$ref" +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" mapfile -d . -t version_parts <<<"$old_version" if [[ $minor == 1 ]] || [[ $COMMIT_METADATA_BREAKING == 1 ]]; then diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index 2b4595551b6f1..220227625c3e3 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Usage: source ./check_commit_tags.sh -# Usage: ./check_commit_tags.sh +# Usage: source ./check_commit_metadata.sh +# Usage: ./check_commit_metadata.sh # -# Example: ./check_commit_tags.sh v0.13.1..971e3678 +# Example: ./check_commit_metadata.sh v0.13.1..971e3678 # # When sourced, this script will populate the COMMIT_METADATA_* variables # with the commit metadata for each commit in the revision range. @@ -16,11 +16,17 @@ set -euo pipefail # shellcheck source=scripts/lib.sh source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" -range=${1:-} +from_ref=${1:-} +to_ref=${2:-} -if [[ -z $range ]]; then - error "No revision range specified" +if [[ -z $from_ref ]]; then + error "No from_ref specified" fi +if [[ -z $from_ref ]]; then + error "No to_ref specified" +fi + +range="$from_ref..$to_ref" # Check dependencies. dependencies gh @@ -39,31 +45,59 @@ main() { breaking_label=release/breaking breaking_category=breaking - mapfile -t commits < <(git log --no-merges --pretty=format:"%h %s" "$range") + # Get abbreviated and full commit hashes and titles for each commit. + mapfile -t commits < <(git log --no-merges --pretty=format:"%h %H %s" "$range") + + # If this is a tag, use rev-list to find the commit it points to. + from_commit=$(git rev-list -n 1 "$from_ref") + # Get the committer date of the commit so that we can list PRs merged. + from_commit_date=$(git show --no-patch --date=short --format=%cd "$from_commit") + + # Get the labels for all PRs merged since the last release, this is + # inexact based on date, so a few PRs part of the previous release may + # be included. + # + # Example output: + # + # 27386d49d08455b6f8fbf2c18f38244d03fda892 label:security + # d9f2aaf3b430d8b6f3d5f24032ed6357adaab1f1 + # fd54512858c906e66f04b0744d8715c2e0de97e6 label:stale label:enhancement + mapfile -t pr_labels_raw < <( + gh pr list \ + --base main \ + --state merged \ + --limit 10000 \ + --search "merged:>=$from_commit_date" \ + --json mergeCommit,labels \ + --jq '.[] | .mergeCommit.oid + " " + (["label:" + .labels[].name] | join(" "))' + ) + declare -A labels + for entry in "${pr_labels_raw[@]}"; do + commit_sha_long=${entry%% *} + all_labels=${entry#* } + labels[$commit_sha_long]=$all_labels + done for commit in "${commits[@]}"; do mapfile -d ' ' -t parts <<<"$commit" - commit_sha=${parts[0]} - commit_prefix=${parts[1]} + commit_sha_short=${parts[0]} + commit_sha_long=${parts[1]} + commit_prefix=${parts[2]} + + # Safety-check, guarantee all commits had their metadata fetched. + if [[ ! -v labels[$commit_sha_long] ]]; then + error "Metadata missing for commit $commit_sha_short" + fi # Store the commit title for later use. - title=${parts[*]:1} + title=${parts[*]:2} title=${title%$'\n'} - COMMIT_METADATA_TITLE[$commit_sha]=$title + COMMIT_METADATA_TITLE[$commit_sha_short]=$title # First, check the title for breaking changes. This avoids doing a # GH API request if there's a match. - if [[ $commit_prefix =~ $breaking_title ]]; then - COMMIT_METADATA_CATEGORY[$commit_sha]=$breaking_category - COMMIT_METADATA_BREAKING=1 - continue - fi - - # Get the labels for the PR associated with this commit. - mapfile -t labels < <(gh api -H "Accept: application/vnd.github+json" "/repos/coder/coder/commits/${commit_sha}/pulls" -q '.[].labels[].name') - - if [[ " ${labels[*]} " = *" ${breaking_label} "* ]]; then - COMMIT_METADATA_CATEGORY[$commit_sha]=$breaking_category + if [[ $commit_prefix =~ $breaking_title ]] || [[ ${labels[$commit_sha_long]} = *"label:$breaking_label"* ]]; then + COMMIT_METADATA_CATEGORY[$commit_sha_short]=$breaking_category COMMIT_METADATA_BREAKING=1 continue fi @@ -73,10 +107,10 @@ main() { fi case $commit_prefix in feat | fix) - COMMIT_METADATA_CATEGORY[$commit_sha]=$commit_prefix + COMMIT_METADATA_CATEGORY[$commit_sha_short]=$commit_prefix ;; *) - COMMIT_METADATA_CATEGORY[$commit_sha]=other + COMMIT_METADATA_CATEGORY[$commit_sha_short]=other ;; esac done diff --git a/scripts/release/generate_release_notes.sh b/scripts/release/generate_release_notes.sh index c06e4ca93651a..a966c3f768d1e 100755 --- a/scripts/release/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -59,7 +59,7 @@ if [[ -z $ref ]]; then fi # shellcheck source=scripts/release/check_commit_metadata.sh -source "$SCRIPT_DIR/release/check_commit_metadata.sh" "${old_version}..${ref}" +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "${old_version}" "${ref}" # Sort commits by title prefix, then by date, only return sha at the end. mapfile -t commits < <(git log --no-merges --pretty=format:"%ct %h %s" "${old_version}..${ref}" | sort -k3,3 -k1,1n | cut -d' ' -f2) diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index 018e32fe378ed..1ae4a7ba8ecb1 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -107,7 +107,7 @@ if [[ "$dry_run" == 1 ]]; then fi # shellcheck source=scripts/release/check_commit_metadata.sh -source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_tag..$new_ref" +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_tag" "$new_ref" # Craft the release notes. release_notes="$(execrelative ./generate_release_notes.sh --old-version "$old_tag" --new-version "$new_tag" --ref "$new_ref")" From 10f15fc5191727c7db239246b4752a5fa181c1de Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 15 Dec 2022 10:31:22 +0000 Subject: [PATCH 10/11] Fix usage comment --- scripts/release/check_commit_metadata.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index 220227625c3e3..28909a435954d 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Usage: source ./check_commit_metadata.sh -# Usage: ./check_commit_metadata.sh +# Usage: source ./check_commit_metadata.sh +# Usage: ./check_commit_metadata.sh # -# Example: ./check_commit_metadata.sh v0.13.1..971e3678 +# Example: ./check_commit_metadata.sh v0.13.1 971e3678 # # When sourced, this script will populate the COMMIT_METADATA_* variables # with the commit metadata for each commit in the revision range. From 906a32485e3e0dfba80b38161c7e9358f468e3a9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 15 Dec 2022 10:32:06 +0000 Subject: [PATCH 11/11] Fix value check --- scripts/release/check_commit_metadata.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index 28909a435954d..94704e5b3f6e7 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -22,7 +22,7 @@ to_ref=${2:-} if [[ -z $from_ref ]]; then error "No from_ref specified" fi -if [[ -z $from_ref ]]; then +if [[ -z $to_ref ]]; then error "No to_ref specified" fi