Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 300
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d82cda1959c8281bcd40c057789c245dcb3e98e8b0cb7cb98f20516b1bcbfc904224ca970d7ce458da79d5d43b0f343126336365aef5a189File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| 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.
Uh oh!There was an error while loading. Please reload this page. | ||
| 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 | ||
MemberAuthor 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 |
| 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.
Uh oh!There was an error while loading. Please reload this page. | ||
| // 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.
Uh oh!There was an error while loading. Please reload this page. | ||
| 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; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.