ci: detect breaking-change commits in PRs #1
Workflow file for this run
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
| name: PR Breaking Change Check | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled, unlabeled] | |
| branches: | |
| - main | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| detect-breaking: | |
| name: Detect Breaking Commits | |
| runs-on: | |
| group: databricks-protected-runner-group | |
| labels: linux-ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Find breaking commits | |
| id: scan | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| # Only commits that touch the packages tracked by .release-it.json | |
| # are reported, since those are the ones that influence the next | |
| # released version. A breaking change in docs-only or tooling-only | |
| # commits doesn't bump the published packages, so flagging them | |
| # would be noise. | |
| run: | | |
| set -euo pipefail | |
| PATHS=(packages/appkit packages/appkit-ui packages/shared) | |
| # Conventional Commits breaking-change markers: | |
| # 1. `type!:` or `type(scope)!:` in the subject line | |
| # 2. `BREAKING CHANGE:` or `BREAKING-CHANGE:` footer line | |
| PATTERN='^(feat|fix|chore|refactor|perf|build|ci|docs|style|test|revert)(\([^)]+\))?!:|^BREAKING[ -]CHANGE:' | |
| breaking="" | |
| while IFS= read -r sha; do | |
| [ -z "$sha" ] && continue | |
| msg=$(git log -1 --format=%B "$sha") | |
| if printf '%s\n' "$msg" | grep -Eq "$PATTERN"; then | |
| subject=$(git log -1 --format=%s "$sha") | |
| breaking+="- \`${sha:0:7}\` ${subject}"$'\n' | |
| fi | |
| done < <(git rev-list "$BASE_SHA".."$HEAD_SHA" -- "${PATHS[@]}") | |
| if [ -n "$breaking" ]; then | |
| { | |
| echo "found=true" | |
| echo "list<<COMMITS_EOF" | |
| printf '%s' "$breaking" | |
| echo "COMMITS_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Breaking commits found:" | |
| printf '%s' "$breaking" | |
| else | |
| echo "found=false" >> "$GITHUB_OUTPUT" | |
| echo "No breaking commits found." | |
| fi | |
| - name: Upsert sticky PR comment | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| env: | |
| FOUND: ${{ steps.scan.outputs.found }} | |
| BREAKING_LIST: ${{ steps.scan.outputs.list }} | |
| ALLOWED: ${{ contains(github.event.pull_request.labels.*.name, 'allow-breaking-change') }} | |
| with: | |
| script: | | |
| const marker = '<!-- pr-breaking-change-check -->'; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const found = process.env.FOUND === 'true'; | |
| const allowed = process.env.ALLOWED === 'true'; | |
| const list = process.env.BREAKING_LIST || ''; | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { owner, repo, issue_number, per_page: 100 }, | |
| ); | |
| const existing = comments.find((c) => c.body && c.body.includes(marker)); | |
| if (!found) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| }); | |
| } | |
| return; | |
| } | |
| const status = allowed | |
| ? '> This PR has the `allow-breaking-change` label, so this check will pass. Make sure the next release is intentionally bumped to a major version.' | |
| : '> Add the **`allow-breaking-change`** label to this PR if the breaking change is intentional, or rewrite the offending commits to remove the `!` / `BREAKING CHANGE:` footer.'; | |
| const body = [ | |
| marker, | |
| '### Breaking change detected', | |
| '', | |
| 'The following commits in this PR contain Conventional Commits breaking-change markers (`type!:` or `BREAKING CHANGE:` footer) and touch packages tracked by `.release-it.json`:', | |
| '', | |
| list.trim(), | |
| '', | |
| 'Merging this PR will force a **major** version bump on the next release (`bumpStrict: true` in `.release-it.json`).', | |
| '', | |
| status, | |
| ].join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number, | |
| body, | |
| }); | |
| } | |
| - name: Fail unless explicitly allowed | |
| if: steps.scan.outputs.found == 'true' && !contains(github.event.pull_request.labels.*.name, 'allow-breaking-change') | |
| run: | | |
| echo "::error::Breaking-change commits detected in tracked packages. Add the 'allow-breaking-change' label to bypass, or rewrite the offending commits." | |
| exit 1 |