-
-
Notifications
You must be signed in to change notification settings - Fork 279
ci: Add action to validate changelog diffs after merging #7525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
d82cda1
Add action to validate changelog diffs after merging
Mrtenz 959c828
Fix getting PR number for pull_request trigger
Mrtenz 1bcd40c
Fix logic
Mrtenz 057789c
Fix logic again
Mrtenz 245dcb3
Use `context` instead of `github`
Mrtenz e98e8b0
Add missing `GH_TOKEN`
Mrtenz cb7cb98
Fix command
Mrtenz f20516b
Clean up logic and fix issues
Mrtenz 1bcbfc9
Only check unreleased section for main and PR changelog
Mrtenz 04224ca
Check if file exists on target ref and fix diff algorithm
Mrtenz 970d7ce
Remove unused `HEAD_REF` environment variable
Mrtenz 458da79
Remove unused `REPOSITORY` environment variable
Mrtenz d5d43b0
Remove unused `EVENT_NAME` environment variable
Mrtenz f343126
Fix script help text
Mrtenz 336365a
Remove unnecessary dependency
Mrtenz ef5a189
Remove unnecessary `startsWith` check
Mrtenz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| name: Check merge queue changelogs | ||
| description: Check if the changelog was incorrectly merged in a merge queue | ||
| pull request. | ||
|
|
||
| inputs: | ||
| github-token: | ||
| description: The GitHub token to use for authentication. | ||
| required: false | ||
| default: ${{ github.token }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Get pull request number | ||
| id: pr-number | ||
| uses: actions/github-script@v8 | ||
| env: | ||
| HEAD_REF: ${{ github.event.pull_request.head.ref || github.event.merge_group.head_ref }} | ||
| with: | ||
| github-token: ${{ inputs.github-token }} | ||
| script: | | ||
| const { HEAD_REF } = process.env; | ||
|
|
||
| if (context.eventName === 'pull_request') { | ||
| const prNumber = context.payload.pull_request.number; | ||
| return core.setOutput('pr-number', prNumber); | ||
| } | ||
|
|
||
| const match = HEAD_REF.match(/\/pr-([0-9]+)-/u); | ||
| if (!match) { | ||
| return core.setFailed(`Could not extract pull request number from head ref: "${HEAD_REF}".`); | ||
| } | ||
|
|
||
| const number = parseInt(match[1], 10); | ||
| core.setOutput('pr-number', number); | ||
|
|
||
| - name: Get pull request branch | ||
| id: pr-branch | ||
| shell: bash | ||
| env: | ||
| REPOSITORY: ${{ github.repository }} | ||
| PR_NUMBER: ${{ steps.pr-number.outputs.pr-number }} | ||
| GH_TOKEN: ${{ inputs.github-token }} | ||
| run: | | ||
| BRANCH=$(gh api "/repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq=.head.ref) | ||
| echo "pr-branch=$BRANCH" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Check changelog changes | ||
| id: changelog-check | ||
| shell: bash | ||
| env: | ||
| BASE_REF: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }} | ||
| PR_BRANCH: ${{ steps.pr-branch.outputs.pr-branch }} | ||
| ACTION_PATH: ${{ github.action_path }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| # Strip invalid prefix from `BASE_REF` | ||
| # It comes prefixed with `refs/heads/`, but the branch is not checked out in this context | ||
| # We need to express it as a remote branch | ||
| PREFIXED_REF_REGEX='refs/heads/(.+)' | ||
| if [[ "$BASE_REF" =~ $PREFIXED_REF_REGEX ]]; then | ||
| BASE_REF="${BASH_REMATCH[1]}" | ||
| fi | ||
|
|
||
| TARGET_REF=$(git merge-base "origin/$BASE_REF" "origin/$PR_BRANCH") | ||
| git fetch origin "$TARGET_REF" | ||
|
|
||
| UPDATED_CHANGELOGS=$(git diff --name-only "$TARGET_REF" "origin/$PR_BRANCH" | grep -E 'CHANGELOG\.md$' || true) | ||
| if [ -n "$UPDATED_CHANGELOGS" ]; then | ||
| for FILE in $UPDATED_CHANGELOGS; do | ||
| if [ ! -f "$FILE" ]; then | ||
| echo "Changelog file \"$FILE\" was deleted in this PR. Skipping." | ||
| continue | ||
| fi | ||
|
|
||
| if ! git cat-file -e "$TARGET_REF":"$FILE" 2>/dev/null; then | ||
| echo "Changelog file \"$FILE\" is new in this PR. Skipping." | ||
| continue | ||
| fi | ||
|
|
||
| echo "Checking changelog file: $FILE" | ||
| git show "$TARGET_REF":"$FILE" > /tmp/base-changelog.md | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| git show origin/"$PR_BRANCH":"$FILE" > /tmp/pr-changelog.md | ||
|
|
||
| node "${ACTION_PATH}/check-changelog-diff.cjs" \ | ||
| /tmp/base-changelog.md \ | ||
| /tmp/pr-changelog.md \ | ||
| "$FILE" | ||
| done | ||
| else | ||
| echo "No CHANGELOG.md files were modified in this PR." | ||
| fi | ||
101 changes: 101 additions & 0 deletions
101
.github/actions/check-merge-queue-changelogs/check-changelog-diff.cjs
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a plain JS file so we can just call |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // This script checks that any new changelog entries added in a PR | ||
| // remain in the [Unreleased] section after the PR is merged. | ||
|
|
||
| const fs = require('fs'); | ||
|
|
||
| if (process.argv.length < 5) { | ||
| console.error( | ||
| 'Usage: node check-changelog-diff.cjs <base-file> <pr-file> <merged-file>', | ||
| ); | ||
|
|
||
| // eslint-disable-next-line n/no-process-exit | ||
| process.exit(1); | ||
| } | ||
|
|
||
| /* eslint-disable n/no-sync */ | ||
| // The type of these is inferred as `Buffer` when using "utf-8" directly instead | ||
|
Gudahtt marked this conversation as resolved.
|
||
| // of an options object. Even though it's a plain JavaScript file, it's nice to | ||
| // keep the types correct. | ||
| const baseContent = fs.readFileSync(process.argv[2], { | ||
| encoding: 'utf-8', | ||
| }); | ||
|
|
||
| const prContent = fs.readFileSync(process.argv[3], { | ||
| encoding: 'utf-8', | ||
| }); | ||
|
|
||
| const mergedContent = fs.readFileSync(process.argv[4], { | ||
| encoding: 'utf-8', | ||
| }); | ||
| /* eslint-enable n/no-sync */ | ||
|
|
||
| /** | ||
| * Extract the "[Unreleased]" section from the changelog content. | ||
| * | ||
| * This doesn't actually parse the Markdown, it just looks for the section | ||
| * header and collects lines until the next section header. | ||
| * | ||
| * @param {string} content - The changelog content. | ||
| * @returns {Set<string>} The lines in the "[Unreleased]" section as a | ||
| * {@link Set}. | ||
| */ | ||
| function getUnreleasedSection(content) { | ||
| const lines = content.split('\n'); | ||
|
|
||
| let inUnreleased = false; | ||
| const sectionLines = new Set(); | ||
|
|
||
| for (const line of lines) { | ||
| // Find unreleased header. | ||
| if (line.trim().match(/^##\s+\[Unreleased\]/u)) { | ||
| inUnreleased = true; | ||
| continue; | ||
| } | ||
|
|
||
| // Stop if we hit the next version header (## [x.x.x]). | ||
| if (inUnreleased && line.trim().match(/^##\s+\[/u)) { | ||
| break; | ||
| } | ||
|
|
||
| // If inside the unreleased header, add lines to the set. | ||
| if (inUnreleased) { | ||
| sectionLines.add(line.trim()); | ||
| } | ||
| } | ||
|
|
||
| return sectionLines; | ||
| } | ||
|
|
||
| /** | ||
| * Get the lines that were added in the PR content compared to the base content. | ||
| * | ||
| * @param {Set<string>} oldLines - The base changelog content. | ||
| * @param {Set<string>} newLines - The PR changelog content. | ||
| * @returns {string[]} The added lines as an array of strings. | ||
| */ | ||
| function getAddedLines(oldLines, newLines) { | ||
| return Array.from(newLines).filter( | ||
| (line) => line.length > 0 && !oldLines.has(line) && !line.startsWith('#'), | ||
| ); | ||
| } | ||
|
|
||
| const mergedUnreleased = getUnreleasedSection(mergedContent); | ||
| const addedLines = getAddedLines( | ||
| getUnreleasedSection(baseContent), | ||
| getUnreleasedSection(prContent), | ||
| ); | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| const missingLines = []; | ||
| for (const line of addedLines) { | ||
| if (!mergedUnreleased.has(line)) { | ||
| missingLines.push(line); | ||
| } | ||
| } | ||
|
|
||
| if (missingLines.length > 0) { | ||
| console.error( | ||
| `The following lines added in the PR are missing from the "Unreleased" section after merge:\n\n ${missingLines.join('\n ')}\n\nPlease update your pull request and ensure that new changelog entries remain in the "Unreleased" section.`, | ||
| ); | ||
|
|
||
| process.exitCode = 1; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.