Skip to content

ci: detect breaking-change commits in PRs #1

ci: detect breaking-change commits in PRs

ci: detect breaking-change commits in PRs #1

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