From 59d469339da36fec9bc995e48f8fb503d14b7b8c Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Tue, 16 Apr 2024 03:34:00 +0200 Subject: [PATCH 1/5] Automated changelogs [WIP] --- .github/pull_request_template.md | 3 + .github/workflows/check-changelog.yml | 21 +++++++ .github/workflows/regular-release.yml | 42 +++++++++++++ changes/unreleased/README.md | 18 ++++++ changes/unreleased/major/.gitkeep | 0 changes/unreleased/medium/.gitkeep | 0 changes/unreleased/minor/.gitkeep | 0 default.nix | 16 +++++ scripts/check-changelog.sh | 56 ++++++++++++++++++ scripts/release.sh | 5 +- scripts/version.sh | 85 +++++++++++++++++++++++++++ 11 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/check-changelog.yml create mode 100644 .github/workflows/regular-release.yml create mode 100644 changes/unreleased/README.md create mode 100644 changes/unreleased/major/.gitkeep create mode 100644 changes/unreleased/medium/.gitkeep create mode 100644 changes/unreleased/minor/.gitkeep create mode 100755 scripts/check-changelog.sh create mode 100755 scripts/version.sh diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..27ff15e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ + + +- [x] This change is user-facing diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml new file mode 100644 index 0000000..04446c3 --- /dev/null +++ b/.github/workflows/check-changelog.yml @@ -0,0 +1,21 @@ +name: Changelog +on: + pull_request: + branches: + - main + # Edited such that we can detect changes to the description + types: [opened, synchronize, reopened, edited] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: check changelog + run: scripts/check-changelog.sh . ${{ github.event.pull_request.number }} + env: + GH_TOKEN: ${{ github.token }} + diff --git a/.github/workflows/regular-release.yml b/.github/workflows/regular-release.yml new file mode 100644 index 0000000..7a0b718 --- /dev/null +++ b/.github/workflows/regular-release.yml @@ -0,0 +1,42 @@ +name: Regular Version +on: + workflow_dispatch: # Allows triggering manually + schedule: + - cron: '47 14 * * 2' # runs every Tuesday at 14:47 UTC (chosen somewhat randomly) + +jobs: + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # This fetches the entire Git history. + # This is needed so we can determine the commits (and therefore PRs) + # where the changelogs have been added + depth: 0 + + - uses: cachix/install-nix-action@v26 + + - name: Increment version and assemble changelog + id: version + run: | + version=$(./scripts/version.sh .) + echo "version=$version" >> "$GITHUB_OUTPUTS" + env: + GH_TOKEN: ${{ github.token }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + # To trigger CI for automated PRs, we use a separate machine account + # See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#workarounds-to-trigger-further-workflow-runs + # and https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork + token: ${{ secrets.MACHINE_USER_PAT }} + path: repo + push-to-fork: infinixbot/nixpkgs-check-by-name + committer: infinixbot + author: infinixbot + commit-message: "Version ${{ github.steps.version.outputs.version }}" + branch: version + title: "Version ${{ github.steps.version.outputs.version }}" + body: "Automated version update. Merging this PR will trigger a release." diff --git a/changes/unreleased/README.md b/changes/unreleased/README.md new file mode 100644 index 0000000..62705c9 --- /dev/null +++ b/changes/unreleased/README.md @@ -0,0 +1,18 @@ +# Changelogs + +To add a changelog, add a Markdown file to a subdirectory depending on the effort required to update to +that version: + +- [Major](./major): A large effort. This will cause a version bump from e.g. 0.1.2 to 1.0.0 +- [Medium](./medium): Some effort. This will cause a version bump from e.g. 0.1.2 to 1.2.0 +- [Minor](./minor): Little/no effort. This will cause a version bump from e.g. 0.1.2 to 0.1.3 + +Therefore, the versions use [EffVer](https://jacobtomlinson.dev/effver/). + +The Markdown file must have the `.md` file ending, and be of the form + +```markdown +# Some descriptive title of the change + +Optionally more information +``` diff --git a/changes/unreleased/major/.gitkeep b/changes/unreleased/major/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/changes/unreleased/medium/.gitkeep b/changes/unreleased/medium/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/changes/unreleased/minor/.gitkeep b/changes/unreleased/minor/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/default.nix b/default.nix index a684206..bc07968 100644 --- a/default.nix +++ b/default.nix @@ -112,6 +112,22 @@ let ''; }; + # Run regularly by CI and turned into a PR + autoVersion = + pkgs.writeShellApplication { + name = "auto-version"; + runtimeInputs = with pkgs; [ + coreutils + git + github-cli + jq + cargo + toml-cli + cargo-edit + ]; + text = builtins.readFile ./scripts/version.sh; + }; + # Tests the tool on the pinned Nixpkgs tree, this is a good sanity check nixpkgsCheck = pkgs.runCommand "test-nixpkgs-check-by-name" { nativeBuildInputs = [ diff --git a/scripts/check-changelog.sh b/scripts/check-changelog.sh new file mode 100755 index 0000000..3ee8a14 --- /dev/null +++ b/scripts/check-changelog.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -euo pipefail +shopt -s nullglob + +root=$1 +prNumber=$2 + +# The PR template has this, selected by default +userFacingString="- [x] This change is user-facing" +nonUserFacingString="- [ ] This change is user-facing" + +# Run this first to validate files +for file in "$root"/changes/unreleased/*/*; do + if [[ "$(basename "$file")" == ".gitkeep" ]]; then + continue + fi + if [[ ! "$file" == *.md ]]; then + echo "File $file: Must be a markdown file with file ending .md" + exit 1 + fi + if [[ "$(sed -n '/^#/=' "$file")" != "1" ]]; then + echo "File $file: The first line must start with #, while all others must not start with #" + exit 1 + fi +done + +body=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/NixOS/nixpkgs-check-by-name/pulls/"$prNumber" \ + | jq -r '.body') + +if grep -F -- "$userFacingString" <<< "$body" >/dev/null ; then + echo "User-facing change, changelog necessary" +elif grep -F -- "$nonUserFacingString" <<< "$body" >/dev/null; then + echo "Not a user-facing change, no changelog necessary" + exit 0 +else + echo "Depending on whether this PR has a user-facing change, add one of these lines to the PR description:" + printf "%s\n" "$userFacingString" + printf "%s\n" "$nonUserFacingString" + exit 1 +fi + +# This checks whether the most recent commit changed any files in changes/unreleased +# This works well for PR's CI because there it runs on the merge commit, +# where HEAD^ is the first parent commit, which is the base branch. +if [[ -z "$(git -C "$root" log HEAD^..HEAD --name-only "$root"/changes/unreleased)" ]]; then + echo "If this PR contains a user-facing change, add a changelog in ./changes/unreleased" + echo "Otherwise, check the checkbox:" + printf "%s\n" "$nonUserFacingString" + exit 1 +else + echo "A changelog exists" +fi diff --git a/scripts/release.sh b/scripts/release.sh index b512e07..c61ab7d 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -46,7 +46,10 @@ To import it: ```bash gzip -cd '"$artifactName"' | nix-store --import | tail -1 ``` -' + +## Changes + +'"$(tail -1 "$root"/changes/released/"$version".md)" echo "Creating draft release" if ! release=$(gh api \ diff --git a/scripts/version.sh b/scripts/version.sh new file mode 100755 index 0000000..53e55c8 --- /dev/null +++ b/scripts/version.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +set -euo pipefail +shopt -s nullglob + +root=$1 + +[[ "$(toml get --raw Cargo.toml package.version)" =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]] +splitVersion=( "${BASH_REMATCH[@]:1}" ) + +majorChanges=( "$root"/changes/unreleased/major/*.md ) +mediumChanges=( "$root"/changes/unreleased/medium/*.md ) +minorChanges=( "$root"/changes/unreleased/minor/*.md ) + +if (( ${#majorChanges[@]} > 0 )); then + # If we didn't have `|| true` this would exit the program due to `set -e`, + # because (( ... )) returns the incremental value, which is treated as the exit code.. + (( splitVersion[0]++ )) || true + splitVersion[1]=0 + splitVersion[2]=0 +elif (( ${#mediumChanges[@]} > 0 )); then + (( splitVersion[1]++ )) || true + splitVersion[2]=0 +elif (( ${#minorChanges[@]} > 0 )); then + (( splitVersion[2]++ )) || true +else + echo >&2 "No changes" + exit 0 +fi + +next=${splitVersion[0]}.${splitVersion[1]}.${splitVersion[2]} +releaseFile=$root/changes/released/${next}.md +mkdir -p "$(dirname "$releaseFile")" + +echo "# Version $next ($(date --iso-8601 --utc))" > "$releaseFile" +echo "" >> "$releaseFile" + +# shellcheck disable=SC2016 +for file in "${majorChanges[@]}" "${mediumChanges[@]}" "${minorChanges[@]}"; do + commit=$(git log -1 --format=%H -- "$file") + if ! gh api graphql \ + -f sha="$commit" \ + -f query=' + query ($sha: String) { + repository(owner: "NixOS", name: "nixpkgs-check-by-name") { + commit: object(expression: $sha) { + ... on Commit { + associatedPullRequests(first: 100) { + nodes { + merged + baseRefName + baseRepository { nameWithOwner } + number + author { login } + } + } + } + } + } + }' | \ + jq --exit-status -r --arg file "$file" ' + .data.repository.commit.associatedPullRequests?.nodes?[]? + | select( + # We need to make sure to get the right PR, there can be many + .merged and + .baseRepository.nameWithOwner == "NixOS/nixpkgs-check-by-name" and + .baseRefName == "main") + | "\(.number) \(.author.login) \($ARGS.named.file)"'; then + echo >&2 "Couldn't get PR for file $file" + exit 1 + fi +done | \ +sort -n | \ +while read -r number author file; do + # Replace the first line `# ` by `- <title> by @author in #number` + # All other non-empty lines are indented with 2 spaces to make the markdown formatting work + sed "$file" >> "$releaseFile" \ + -e "1s|#[[:space:]]\(.*\)|- \1 by [@$author](https://github.com/$author) in [#$number](https://github.com/NixOS/nixpkgs-check-by-name/pull/$number)|" \ + -e '2,$s/^\(.\)/ \1/' + + rm "$file" +done + +cargo set-version "$next" +echo "$next" From bab8abead2840c6122a6da0a5ed6c495a3a83b35 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger <silvan.mosberger@tweag.io> Date: Thu, 18 Apr 2024 03:11:23 +0200 Subject: [PATCH 2/5] Generate draft release notes on PRs --- .github/workflows/main.yml | 38 +++++++++++++++++++++++++++ .github/workflows/regular-release.yml | 37 +++++--------------------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36d9200..bd34208 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,6 +3,9 @@ on: pull_request: branches: - main + push: + branches: + - main jobs: build: @@ -15,6 +18,41 @@ jobs: - name: build run: nix-build -A ci + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # This fetches the entire Git history. + # This is needed so we can determine the commits (and therefore PRs) + # where the changelogs have been added + depth: 0 + + - uses: cachix/install-nix-action@v26 + + - name: Increment version and assemble changelog + run: | + nix-build -A autoVersion + version=$(result/bin/auto-version .) + echo "version=$version" >> "$GITHUB_ENV" + env: + GH_TOKEN: ${{ github.token }} + + - name: Outputting draft release notes + if: ${{ env.version && github.event_name != 'push' }} + run: cat changes/released/${{ env.version }}.md > "$GITHUB_STEP_SUMMARY" + + - name: Update release branch + if: ${{ env.version && github.event_name == 'push' }} + run: | + git config user.name infinixbot + git config user.email infinixbot@infinisil.com + git commit --all --message 'Version ${{ env.version }} + + Automated release' + git push origin HEAD:release + + test-update: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/regular-release.yml b/.github/workflows/regular-release.yml index 7a0b718..cf68973 100644 --- a/.github/workflows/regular-release.yml +++ b/.github/workflows/regular-release.yml @@ -8,35 +8,12 @@ jobs: version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - # This fetches the entire Git history. - # This is needed so we can determine the commits (and therefore PRs) - # where the changelogs have been added - depth: 0 - - - uses: cachix/install-nix-action@v26 - - - name: Increment version and assemble changelog - id: version + - name: Create Pull Request run: | - version=$(./scripts/version.sh .) - echo "version=$version" >> "$GITHUB_OUTPUTS" + gh pr create \ + --repo ${{ github.repository }} \ + --fill \ + --head release env: - GH_TOKEN: ${{ github.token }} - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 - with: - # To trigger CI for automated PRs, we use a separate machine account - # See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#workarounds-to-trigger-further-workflow-runs - # and https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork - token: ${{ secrets.MACHINE_USER_PAT }} - path: repo - push-to-fork: infinixbot/nixpkgs-check-by-name - committer: infinixbot <infinixbot@infinisil.com> - author: infinixbot <infinixbot@infinisil.com> - commit-message: "Version ${{ github.steps.version.outputs.version }}" - branch: version - title: "Version ${{ github.steps.version.outputs.version }}" - body: "Automated version update. Merging this PR will trigger a release." + # Needed so that CI triggers + GH_TOKEN: ${{ secrets.MACHINE_USER_PAT }} From d1e6ce2d69b704a86868591ab6aee69dbc0cdb1a Mon Sep 17 00:00:00 2001 From: Silvan Mosberger <silvan.mosberger@tweag.io> Date: Thu, 18 Apr 2024 03:13:42 +0200 Subject: [PATCH 3/5] Add test changelog --- .github/workflows/main.yml | 18 +++++++++--------- changes/unreleased/minor/add-changelog.md | 3 +++ scripts/version.sh | 11 ++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 changes/unreleased/minor/add-changelog.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bd34208..419b729 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,14 +26,20 @@ jobs: # This fetches the entire Git history. # This is needed so we can determine the commits (and therefore PRs) # where the changelogs have been added - depth: 0 + fetch-depth: 0 - uses: cachix/install-nix-action@v26 - name: Increment version and assemble changelog run: | nix-build -A autoVersion - version=$(result/bin/auto-version .) + version=$(result/bin/auto-version . ${{ github.event.pull_request.number || '' }}) + git config user.name ${{ github.actor }} + git config user.email ${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com + git commit --all --message "Version $version + + Automated release" + echo "version=$version" >> "$GITHUB_ENV" env: GH_TOKEN: ${{ github.token }} @@ -44,13 +50,7 @@ jobs: - name: Update release branch if: ${{ env.version && github.event_name == 'push' }} - run: | - git config user.name infinixbot - git config user.email infinixbot@infinisil.com - git commit --all --message 'Version ${{ env.version }} - - Automated release' - git push origin HEAD:release + run: git push origin HEAD:release test-update: diff --git a/changes/unreleased/minor/add-changelog.md b/changes/unreleased/minor/add-changelog.md new file mode 100644 index 0000000..f2c7667 --- /dev/null +++ b/changes/unreleased/minor/add-changelog.md @@ -0,0 +1,3 @@ +# Add changelog + +This is just a test to show that changelogs are actually used! diff --git a/scripts/version.sh b/scripts/version.sh index 53e55c8..58606a3 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -4,8 +4,9 @@ set -euo pipefail shopt -s nullglob root=$1 +currentPrNumber=${2:-} -[[ "$(toml get --raw Cargo.toml package.version)" =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]] +[[ "$(toml get --raw "$root"/Cargo.toml package.version)" =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]] splitVersion=( "${BASH_REMATCH[@]:1}" ) majorChanges=( "$root"/changes/unreleased/major/*.md ) @@ -37,7 +38,7 @@ echo "" >> "$releaseFile" # shellcheck disable=SC2016 for file in "${majorChanges[@]}" "${mediumChanges[@]}" "${minorChanges[@]}"; do - commit=$(git log -1 --format=%H -- "$file") + commit=$(git -C "$root" log -1 --format=%H -- "$file") if ! gh api graphql \ -f sha="$commit" \ -f query=' @@ -58,11 +59,11 @@ for file in "${majorChanges[@]}" "${mediumChanges[@]}" "${minorChanges[@]}"; do } } }' | \ - jq --exit-status -r --arg file "$file" ' + jq --exit-status -r ${currentPrNumber:+--argjson currentPrNumber "$currentPrNumber"} --arg file "$file" ' .data.repository.commit.associatedPullRequests?.nodes?[]? | select( # We need to make sure to get the right PR, there can be many - .merged and + (.merged or .number == $ARGS.named.currentPrNumber) and .baseRepository.nameWithOwner == "NixOS/nixpkgs-check-by-name" and .baseRefName == "main") | "\(.number) \(.author.login) \($ARGS.named.file)"'; then @@ -81,5 +82,5 @@ while read -r number author file; do rm "$file" done -cargo set-version "$next" +cargo set-version --manifest-path "$root"/Cargo.toml "$next" echo "$next" From f4bb32214a961f707dfb45f260b3d9bec5831900 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger <silvan.mosberger@tweag.io> Date: Thu, 18 Apr 2024 04:23:58 +0200 Subject: [PATCH 4/5] Test release pushing --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 419b729..32abadb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,8 +49,11 @@ jobs: run: cat changes/released/${{ env.version }}.md > "$GITHUB_STEP_SUMMARY" - name: Update release branch - if: ${{ env.version && github.event_name == 'push' }} - run: git push origin HEAD:release + #if: ${{ env.version && github.event_name == 'push' }} + # This continuously updates the release branch to contain the latest release notes, + # so that one can just merge the release branch into main to do a release. + # A PR to do that is opened regularly with another workflow + run: git push origin HEAD:refs/heads/release -f test-update: From a0d1ade0c50e150b8963ec9ab15d385d07c99415 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger <silvan.mosberger@tweag.io> Date: Thu, 18 Apr 2024 04:41:27 +0200 Subject: [PATCH 5/5] Various improvements and testing --- .github/workflows/check-changelog.yml | 6 ++++++ .github/workflows/main.yml | 13 +++++++++++-- .github/workflows/regular-release.yml | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml index 04446c3..ca89f84 100644 --- a/.github/workflows/check-changelog.yml +++ b/.github/workflows/check-changelog.yml @@ -6,12 +6,18 @@ on: # Edited such that we can detect changes to the description types: [opened, synchronize, reopened, edited] +permissions: + pull-requests: read + jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: + # We need to fetch the parents of the HEAD commit (which is a merge), + # because we need to compare the PR against the base branch + # to check whether it added a changelog fetch-depth: 2 - name: check changelog diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32abadb..d073911 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,9 @@ jobs: # This is needed so we can determine the commits (and therefore PRs) # where the changelogs have been added fetch-depth: 0 + # Needed so we can push to the fork later, it's stored in the git config otherwise and + # would override the URL basic authentication + persist-credentials: false - uses: cachix/install-nix-action@v26 @@ -36,7 +39,8 @@ jobs: version=$(result/bin/auto-version . ${{ github.event.pull_request.number || '' }}) git config user.name ${{ github.actor }} git config user.email ${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com - git commit --all --message "Version $version + git add --all + git commit --message "Version $version Automated release" @@ -45,15 +49,20 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Outputting draft release notes + # If we have a new version at all (it's not an empty string) + # And it's not a push event (so it's a PR), if: ${{ env.version && github.event_name != 'push' }} + # we just output the draft changelog into the step summary run: cat changes/released/${{ env.version }}.md > "$GITHUB_STEP_SUMMARY" - name: Update release branch + # But if this is a push te the main branch, #if: ${{ env.version && github.event_name == 'push' }} + # we push to the release branch. # This continuously updates the release branch to contain the latest release notes, # so that one can just merge the release branch into main to do a release. # A PR to do that is opened regularly with another workflow - run: git push origin HEAD:refs/heads/release -f + run: git push https://${{ secrets.MACHINE_USER_PAT }}@github.com/infinixbot/nixpkgs-check-by-name.git HEAD:refs/heads/release -f test-update: diff --git a/.github/workflows/regular-release.yml b/.github/workflows/regular-release.yml index cf68973..8eb603b 100644 --- a/.github/workflows/regular-release.yml +++ b/.github/workflows/regular-release.yml @@ -3,17 +3,27 @@ on: workflow_dispatch: # Allows triggering manually schedule: - cron: '47 14 * * 2' # runs every Tuesday at 14:47 UTC (chosen somewhat randomly) + #pull_request: + # branches: + # main jobs: version: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + with: + repository: infinixbot/nixpkgs-check-by-name + ref: release + - name: Create Pull Request run: | + subject=$(git log -1 --format=%s HEAD) gh pr create \ --repo ${{ github.repository }} \ - --fill \ - --head release + --title "$(head -1 <<< "$subject")" \ + --body "$(tail -1 <<< "$subject")" \ + --head infinixbot:release env: # Needed so that CI triggers GH_TOKEN: ${{ secrets.MACHINE_USER_PAT }}