diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 5b2e63bc..8a4d3a8d 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -1,48 +1,71 @@ -# This workflow cannot post sticky comments on PRs from forked repositories. -# Instead, it outputs the message it would have posted as a workflow notice. -# See https://github.com/protocol/.github/issues/254 for details. - name: Release Checker on: [ workflow_call ] jobs: - releaser: + prepare: runs-on: ubuntu-latest env: - TAG_EXISTS: "true" - VERSION: "" # the version number read from version.json - COMPARETO: "" # the version number to compare this version to - GORELEASE: "" - GOCOMPAT: "" - GOMODDIFF: "" - RELEASE_BRANCH_NOTE: "" + GITHUB_TOKEN: ${{ github.token }} + outputs: + version: ${{ steps.version.outputs.this }} + tag_exists: ${{ steps.tag_exists.outputs.this }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: "1.19.x" - - name: Determine version - if: hashFiles('version.json') - run: echo "VERSION=$(jq -r .version version.json)" >> $GITHUB_ENV - - name: Check if the tag already exists - # Check if a git tag for the version (as read from version.json) exists - # If that is the case, we don't need to run the rest of the workflow. - if: env.VERSION != '' + - id: version + name: Retrieve new version from version.json run: | - git fetch origin --tags - status=0 - git rev-list $VERSION &> /dev/null || status=$? - if [[ $status != 0 ]]; then - echo "TAG_EXISTS=false" >> $GITHUB_ENV - fi - - name: Install semver (node command line tool) - if: env.TAG_EXISTS == 'false' - run: npm install -g "https://github.com/npm/node-semver#e79ac3a450e8bb504e78b8159e3efc7089569" # v7.3.5 + # https://docs.github.com/en/rest/reference/repos#get-repository-content + version="$(gh api -X GET 'repos/${{ github.event.pull_request.head.repo.full_name }}/contents/version.json' -f ref='${{ github.event.pull_request.head.sha }}' --jq '.content' | base64 -d | jq -r '.version // ""')" + status=$? && ([[ $status == 0 ]] || exit $status) + echo "version=$version" + echo "::set-output name=this::$version" + - id: tag_exists + name: Check if version tag already exists + if: steps.version.outputs.this != '' + run: | + # https://docs.github.com/en/rest/reference/git#get-a-reference + gh api '/repos/${{ github.repository }}/git/ref/tags/${{ steps.version.outputs.this }}' && tag_exists='true' || tag_exists='false' + + echo "tag_exists=$tag_exists" + echo "::set-output name=this::$tag_exists" + check: + needs: [prepare] + if: needs.prepare.outputs.tag_exists != 'true' + runs-on: ubuntu-latest + env: + COMMENT: "Suggested version: `${{ needs.prepare.outputs.version }}`" + GITHUB_TOKEN: ${{ github.token }} + steps: - name: Check version - if: env.TAG_EXISTS == 'false' - run: semver ${{ env.VERSION }} # fails if the version is not a valid semver version (e.g. v0.1 would fail) - - name: Determine version number to compare to - if: env.TAG_EXISTS == 'false' + run: | + npm install -g 'https://github.com/npm/node-semver#e79ac3a450e8bb504e78b8159e3efc7089569' # v7.3.5 + + semver '${{ needs.prepare.outputs.version }}' # fails if the version is not a valid semver version (e.g. v0.1 would fail) + - id: release + name: Create draft GitHub Release (if it doesn't exist yet) + run: | + # using GraphQL because https://docs.github.com/en/rest/reference/releases#list-releases does not return draft releases + # https://docs.github.com/en/graphql/reference/objects#release + release="$(gh api graphql -f query='query { repository(owner: "${{ github.event.repository.owner.login }}", name: "${{ github.event.repository.name }}") { release(tagName: "${{ needs.prepare.outputs.version }}") { url } } }' --jq '.data.repository.release.url // ""')" + status=$? && ([[ $status == 0 ]] || exit $status) + + if [[ -z "$release" ]]; then + # https://docs.github.com/en/rest/reference/releases#create-a-release + # creating a draft release does not create a tag, only publishing does + release="$(gh api '/repos/${{ github.repository }}/releases' -F 'draft=true' -f 'tag_name=${{ needs.prepare.outputs.version }}' -F 'generate_release_notes=true' --jq '.html_url')" + status=$? && ([[ $status == 0 ]] || exit $status) + fi + + echo "COMMENT<> $GITHUB_ENV + - uses: actions/setup-go@v2 + with: + go-version: "1.17.x" + - id: prev_version + name: Determine version number to compare to # We need to determine the version number we want to compare to, # taking into account that this might be a (patch) release on a release branch. # Example: @@ -50,95 +73,94 @@ jobs: # When trying to cut a release v0.2.1, we need to base our comparisons on v0.2.0. # When trying to cut a release v0.3.1 or v0.4.0, we need to base our comparisons on v0.3.0. run: | - git fetch origin --tags go install github.com/marten-seemann/semver-highest@fcdc98f8820ff0e6613c1bee071c096febd98dbf - v=$(semver-highest -target ${{ env.VERSION }} -versions $(git tag | paste -sd , -)) - status=$? - if [[ $status != 0 ]]; then - echo $v - exit $status + + versions="$(gh api --paginate '/repos/${{ github.repository }}/tags' --jq 'map(.name)' | jq -nr '[inputs] | add | join(",")')" + status=$? && ([[ $status == 0 ]] || exit $status) + + prev_version="$(semver-highest -target '${{ needs.prepare.outputs.version }}' -versions "$versions")" + + echo "prev_version=$prev_version" + echo "::set-output name=this::$prev_version" + + if [[ -z "$prev_version" ]]; then + output="This is the first release of this module." + else + output="Comparing to: [\`$prev_version\`](${{ github.event.pull_request.base.repo.html_url }}/releases/tag/$prev_version) ([diff](${{ github.event.pull_request.base.repo.html_url }}/compare/$prev_version..${{ github.event.pull_request.head.label }}))" fi - echo "COMPARETO=$v" >> $GITHUB_ENV - - name: Post output - if: env.TAG_EXISTS == 'false' && env.COMPARETO == '' - uses: marocchino/sticky-pull-request-comment@82e7a0d3c51217201b3fedc4ddde6632e969a477 # v2.1.1 - with: - header: release-check - recreate: true - message: | - Suggested version: `${{ env.VERSION }}` - This is the first release of this module. - - name: run git diff on go.mod file(s) - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' + echo "COMMENT<> $GITHUB_ENV + - uses: actions/checkout@v2 + if: steps.prev_version.outputs.this != '' + - if: steps.prev_version.outputs.this != '' + run: git fetch origin --tags + - name: Run git diff on go.mod file(s) + if: steps.prev_version.outputs.this != '' run: | # First get the diff for the go.mod file in the root directory... - output=$(git diff ${{ env.COMPARETO }}..HEAD -- './go.mod') + output="$(git diff ${{ steps.prev_version.outputs.this }}..HEAD -- './go.mod')" + # ... then get the diff for all go.mod files in subdirectories. # Note that this command also finds go.mod files more than one level deep in the directory structure. - output+=$(git diff ${{ env.COMPARETO }}..HEAD -- '*/go.mod') - if [[ -z "$output" ]]; then - output="(empty)" - fi - printf "GOMODDIFF<> $GITHUB_ENV + output+="$(git diff ${{ steps.prev_version.outputs.this }}..HEAD -- '*/go.mod')" + + echo "COMMENT<> $GITHUB_ENV - name: Run gorelease - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' + if: steps.prev_version.outputs.this != '' # see https://github.com/golang/exp/commits/master/cmd/gorelease run: | go install golang.org/x/exp/cmd/gorelease@b4e88ed8e8aab63a9aa9a52276782ebbc547adef - output=$((gorelease -base ${{ env.COMPARETO }}) 2>&1 || true) - printf "GORELEASE<> $GITHUB_ENV - - name: Check Compatibility - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' + + output="$((gorelease -base ${{ steps.prev_version.outputs.this }}) 2>&1 || true)" + + echo "COMMENT<> $GITHUB_ENV + - name: Check compatibility + if: steps.prev_version.outputs.this != '' run: | go install github.com/smola/gocompat/cmd/gocompat@8498b97a44792a3a6063c47014726baa63e2e669 # v0.3.0 - output=$(gocompat compare --go1compat --git-refs="${{ env.COMPARETO }}..HEAD" ./... || true) - if [[ -z "$output" ]]; then - output="(empty)" - fi - printf "GOCOMPAT<> $GITHUB_ENV - - run: | - echo "RELEASE_BRANCH_NOTE<> $GITHUB_ENV + - name: Add a note for PRs targeting release branches + if: github.base_ref != github.event.repository.default_branch + run: | + echo "COMMENT<> $GITHUB_ENV - if: github.base_ref != github.event.repository.default_branch - - run: | - echo 'MESSAGE<> $GITHUB_ENV - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' - name: Post message on PR uses: marocchino/sticky-pull-request-comment@82e7a0d3c51217201b3fedc4ddde6632e969a477 # v2.1.1 - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' && github.event.pull_request.head.repo.full_name == github.repository with: header: release-check recreate: true - message: ${{ env.MESSAGE }} - - name: Set a notice message on run - run: | - message="${MESSAGE//'%'/'%25'}" - message="${message//$'\n'/'%0A'}" - message="${message//$'\r'/'%0D'}" - echo "::notice ::$message" - if: env.TAG_EXISTS == 'false' && env.COMPARETO != '' && github.event.pull_request.head.repo.full_name != github.repository + message: ${{ env.COMMENT }} diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml index 7c27f737..06d2d7f5 100644 --- a/.github/workflows/releaser.yml +++ b/.github/workflows/releaser.yml @@ -5,38 +5,40 @@ jobs: releaser: runs-on: ubuntu-latest env: - VERSION: "" - CREATETAG: "false" - DEFAULT_BRANCH: "" + GITHUB_TOKEN: ${{ github.token }} steps: - - uses: actions/checkout@v3 - - name: Determine version - run: echo "VERSION=$(jq -r .version version.json)" >> $GITHUB_ENV - - name: Determine branch - run: echo "DEFAULT_BRANCH=refs/heads/${{ github.event.repository.default_branch }}" >> $GITHUB_ENV - - name: Create a release, if we're on the default branch - run: echo "CREATETAG=true" >> $GITHUB_ENV - if: env.DEFAULT_BRANCH == github.ref - - name: Determine if this commit is a merged PR (if we're not on a default branch) - if: env.DEFAULT_BRANCH != github.ref - id: getmergedpr - uses: actions-ecosystem/action-get-merged-pull-request@59afe90821bb0b555082ce8ff1e36b03f91553d9 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Check if the "release" label was set on the PR - if: steps.getmergedpr.outputs.number != '' && env.DEFAULT_BRANCH != github.ref + - id: version + name: Retrieve new version from version.json run: | - while IFS= read -r label; do - if [[ "$label" == "release" ]]; then - echo "CREATETAG=true" >> $GITHUB_ENV - break - fi - done <<< "${{ steps.getmergedpr.outputs.labels }}" - - name: Create release - if: env.CREATETAG == 'true' + # https://docs.github.com/en/rest/reference/repos#get-repository-content + version="$(gh api -X GET '/repos/${{ github.repository }}/contents/version.json' -f ref='${{ github.ref }}' --jq '.content' | base64 -d | jq -r '.version // ""')" + status=$? && ([[ $status == 0 ]] || exit $status) + + echo "version=$version" + echo "::set-output name=this::$version" + - id: label_exists + name: Check if the related PR is merged and has release label + if: github.ref_name != github.event.repository.default_branch + run: | + # https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests + label_exists="$(gh api -X GET '/search/issues' -f 'q=repository:${{ github.repository }} is:pr is:merged ${{ github.sha }}' -F 'per_page=1' --jq '.items[0].labels // [] | map(select(.name == "release")) | map("true") | .[0] // "false"')" + status=$? && ([[ $status == 0 ]] || exit $status) + + echo "label_exists=$label_exists" + echo "::set-output name=this::$label_exists" + - if: github.ref_name == github.event.repository.default_branch || steps.label_exists.outputs.this == 'true' + name: Publish GitHub Release and/or lightweight git tag run: | - git fetch origin --tags - if ! $(git rev-list ${{ env.VERSION}}.. &> /dev/null); then - git tag ${{ env.VERSION }} - git push --tags + # using GraphQL because https://docs.github.com/en/rest/reference/releases#list-releases does not return draft releases + # https://docs.github.com/en/graphql/reference/objects#release + release="$(gh api graphql -f query='query { repository(owner: "${{ github.event.repository.owner.name }}", name: "${{ github.event.repository.name }}") { release(tagName: "${{ steps.version.outputs.this }}") { databaseId } } }' --jq '.data.repository.release.databaseId // ""')" + status=$? && ([[ $status == 0 ]] || exit $status) + + echo "release=$release" + if [[ -z "$release" ]]; then + # https://docs.github.com/en/rest/reference/git#create-a-reference + gh api '/repos/${{ github.repository }}/git/refs' -f 'ref=refs/tags/${{ steps.version.outputs.this }}' -f 'sha=${{ github.sha }}' + else + # https://docs.github.com/en/rest/reference/releases#update-a-release + gh api -X PATCH "/repos/${{ github.repository }}/releases/$release" -f 'target_commitish=${{ github.sha }}' -F 'draft=false' fi diff --git a/VERSIONING.md b/VERSIONING.md index c4ccb3cf..b1f229fc 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -30,9 +30,15 @@ Every Go repository contains a `version.json` file in the root directory: This version file defines the currently released version. When cutting a new release, open a Pull Request that bumps the version number and have it review by your team mates. -The [release check workflow](.github/workflows/release-check.yml) will comment on the PR and post useful information (the output of `gorelease`, `gocompat` and a diff of the `go.mod` files(s)). +The [release check workflow](.github/workflows/release-check.yml) will comment on the PR and post useful information (the output of `gorelease`, `gocompat` and a diff of the `go.mod` files(s)). It will also post a link to a draft GitHub Release. -As soon as the PR is merged into the default branch, the [releaser workflow](.github/workflows/releaser.yml) is run. This workflow cuts a new release on CI and pushes the tag. +As soon as the PR is merged into the default branch, the [releaser workflow](.github/workflows/releaser.yml) is run. This workflow cuts a new release on CI, publishes the GitHub Release and the tag. + +### Modifying GitHub Release + +All modification you make to the draft GitHub Release created by the release check workflow will be preserved. You can change its' name and body to describe the release in more detail. + +If you do not wish for a GitHub Release to be published after a merge, you can delete the draft. If you do so, only a tag will be published after a merge. ### Using a Release Branch diff --git a/templates/.github/workflows/release-check.yml b/templates/.github/workflows/release-check.yml index f1b5f7bb..73009b4d 100644 --- a/templates/.github/workflows/release-check.yml +++ b/templates/.github/workflows/release-check.yml @@ -1,7 +1,7 @@ name: Release Checker on: - pull_request: - paths: [ 'version.json' ] + pull_request_target: + paths-ignore: [ '!version.json' ] jobs: release-check: