Skip to content

Commit 2bb563a

Browse files
Merge pull request #819 from XeroAPI/conventional-commit-breaking-check
Switch to using conventional commit for breaking change flag detection
2 parents 2d2cec8 + febb29f commit 2bb563a

5 files changed

Lines changed: 127 additions & 115 deletions

File tree

.github/workflows/api-diff.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,32 @@ concurrency:
1212
cancel-in-progress: true
1313

1414
jobs:
15-
test-branch-logic:
15+
test-conventional-commit-logic:
1616
runs-on: ubuntu-latest
1717
permissions:
1818
contents: read
1919
steps:
2020
- name: Checkout code
2121
uses: actions/checkout@v4
22-
23-
- name: Run branch logic unit tests
22+
23+
- name: Run conventional commit logic unit tests
2424
run: ./scripts/api-diff/api-diff.test.sh
25-
25+
2626
api-diff:
2727
runs-on: ubuntu-latest
28-
needs: test-branch-logic
28+
needs: test-conventional-commit-logic
2929
permissions:
3030
contents: read
3131
pull-requests: write
32-
32+
3333
steps:
3434
- name: Checkout code
3535
uses: actions/checkout@v4
3636
with:
3737
fetch-depth: 0
38-
38+
3939
- name: Make script executable
4040
run: chmod +x scripts/api-diff/api-diff.sh
41-
41+
4242
- name: Run API diff check
43-
run: ./scripts/api-diff/api-diff.sh
43+
run: ./scripts/api-diff/api-diff.sh

README.md

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
An OpenAPI (Swagger) specification for the Xero API with OAuth 2.0 security schema.
33

44
## Description
5-
This repository holds the official Xero [OpenAPI](https://www.openapis.org/) descriptions.
5+
This repository holds the official Xero [OpenAPI](https://www.openapis.org/) descriptions.
66

77
OpenAPI spec 3.0
88
In Release (used for one or more SDKs)
@@ -19,43 +19,33 @@ In Release (used for one or more SDKs)
1919

2020
## Contribution guide
2121

22-
[Conventional commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) format should be used when contributing to this repo.
22+
Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for all commits.
2323

24-
**Context:**
24+
Why this matters:
2525

26-
Release notes are generated using [semantic-release](https://github.com/semantic-release/semantic-release). In brief this package analyses all the commits in the repo to determine the next version number, generate the release notes and publish the package, hence the commit message is important for the release to happen successfully. Please read more on the conventional commit [documentation](https://www.conventionalcommits.org/en/v1.0.0/#summary) before landing your commits, each commit message will determine the release notes.
26+
- We use [semantic-release](https://github.com/semantic-release/semantic-release) to calculate versions automatically.
27+
- Consistent commit formatting keeps versioning predictable and release notes accurate.
2728

28-
**Example:**
29+
Versioning rules:
2930

30-
If your commit message is - ``feat: added a new parameter in the get_account method`` - It will do a **minor version update**.
31+
- `feat:` -> minor version bump
32+
- `fix:` -> patch version bump
33+
- `!` in the header or `BREAKING CHANGE:` footer -> major version bump
3134

32-
Following release notes will be generated -
35+
Examples:
3336

37+
```text
38+
feat(accounting): add includeArchived query parameter to GET /Accounts
3439
```
35-
Release notes
3640

37-
Feature -
38-
- added a new parameter in the get_account method
41+
```text
42+
fix(files): correct nullable schema for File.ContentLength
3943
```
4044

41-
If your commit message is - ``fix: fixed the null issue with get_invoice method`` - It will do a **patch version update**.
42-
43-
Following release notes will be generated -
44-
45-
```
46-
Release notes
47-
48-
Fix -
49-
- fixed the null issue with get_invoice method
50-
```
51-
52-
53-
if your commit message is as below it will do a **major version update** (Breaking change)
45+
```text
46+
feat(payroll-nz)!: remove deprecated EarningsRateID field from Payslip response
5447
55-
```
56-
chore!: drop support for Node 6
57-
58-
BREAKING CHANGE: use JavaScript features not available in Node 6
48+
BREAKING CHANGE: clients must use EarningsRateIdentifier instead of EarningsRateID.
5949
```
6050

6151

@@ -64,7 +54,7 @@ We are using [OpenAPI generator](https://github.com/OpenAPITools/openapi-generat
6454

6555
## Preview
6656
### Online
67-
There are lots of tools available for viewing and editing OpenAPI descriptions in a nicely formatted way. A popular tool is SwaggerHub - a version of which is [hosted here](https://app.swaggerhub.com/home).
57+
There are lots of tools available for viewing and editing OpenAPI descriptions in a nicely formatted way. A popular tool is SwaggerHub - a version of which is [hosted here](https://app.swaggerhub.com/home).
6858

6959
Once you sign up or login, you can create a new API under your account and import a Xero API spec.
7060

@@ -99,10 +89,13 @@ This repository includes automated API diff checking using [oasdiff](https://git
9989
./scripts/api-diff/api-diff.sh xero_accounting.yaml
10090
```
10191

102-
### Branch Naming Convention
103-
Branches containing `breaking` anywhere in the name will allow breaking changes without failing the build. All other branches will fail if breaking changes are detected.
92+
### Breaking Change Enforcement
93+
By default, API diff checks fail when breaking changes are detected.
94+
95+
Breaking changes are allowed only when commit messages include a Conventional Commits breaking marker:
10496

105-
**Examples:** `breaking-api-v2`, `feature-breaking-change`, `api-breaking-update`
97+
- `!` in the commit header, for example `feat!: remove deprecated endpoint`
98+
- `BREAKING CHANGE:` in the commit body/footer
10699

107100
### Full Documentation
108101
For detailed usage, configuration options, environment variables, and integration details, see [scripts/api-diff/README.md](scripts/api-diff/README.md).

scripts/api-diff/README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,39 @@ Main script that compares OpenAPI specifications against the master branch.
2727
- `BASE_BRANCH` - Branch to compare against (default: `origin/master`)
2828

2929
### `api-diff.test.sh`
30-
Unit tests for the branch logic pattern matching used in GitHub Actions.
30+
Unit tests for conventional commit breaking marker detection used in GitHub Actions.
3131

3232
**Usage:**
3333
```bash
3434
./scripts/api-diff/api-diff.test.sh
3535
```
3636

3737
Tests validate that:
38-
- Branches containing `breaking` anywhere in the name are correctly identified
39-
- Other branches are handled with breaking change enforcement
38+
- Commits with `!` in the conventional commit header are correctly identified
39+
- Commits with `BREAKING CHANGE:` footer are correctly identified
40+
- Other commits are handled with breaking change enforcement
4041

4142
## Integration
4243

4344
These scripts are integrated into the GitHub Actions workflow at `.github/workflows/api-diff.yml`:
44-
- **test-branch-logic** job - Runs unit tests
45+
- **test-conventional-commit-logic** job - Runs unit tests
4546
- **api-diff** job - Runs API diff checks with conditional breaking change enforcement
4647

47-
### Branch Naming Convention
48-
The GitHub Actions workflow automatically adjusts its behavior based on branch names:
48+
### Conventional Commit Breaking Markers
49+
The API diff script automatically adjusts behavior based on commit messages:
4950

5051
**Allow Breaking Changes:**
51-
- Any branch containing `breaking` in the name
52-
- Examples: `breaking-api-v2`, `feature-breaking-change`, `api-breaking-update`
52+
- Commit header with `!`, for example: `feat!: remove deprecated endpoint`
53+
- Commit header with scope and `!`, for example: `feat(api)!: remove deprecated endpoint`
54+
- Commit body/footer containing `BREAKING CHANGE: ...`
5355
- The `--fail-on-breaking` flag is NOT passed to the script
5456

5557
**Fail on Breaking Changes:**
56-
- All other branches (main, master, develop, feature branches, etc.)
58+
- Commits without these conventional commit breaking markers
5759
- The `--fail-on-breaking` flag IS passed to the script
5860
- Build will fail if breaking changes are detected
5961

60-
This allows developers to explicitly signal when they're working on breaking changes by including `breaking` in their branch name.
62+
This keeps enforcement aligned with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) and semantic-release expectations.
6163

6264
## Known Limitations
6365

scripts/api-diff/api-diff.sh

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,40 @@ FAIL_ON_BREAKING=false
2626
TARGET_FILE=""
2727
DRY_RUN=false
2828

29+
detect_breaking_commit_marker() {
30+
local commits="$1"
31+
32+
# Conventional Commits breaking indicators:
33+
# 1) An exclamation mark in the type/scope header, e.g. feat!: ... or feat(api)!: ...
34+
# 2) A BREAKING CHANGE footer in the commit body
35+
if echo "$commits" | grep -Eiq '^[[:space:]]*[a-z]+(\([^)]+\))?!:'; then
36+
return 0
37+
fi
38+
39+
if echo "$commits" | grep -Eiq 'BREAKING[ -]CHANGE:'; then
40+
return 0
41+
fi
42+
43+
return 1
44+
}
45+
46+
get_commit_messages() {
47+
# For tests and local overrides
48+
if [ -n "$COMMIT_MESSAGES" ]; then
49+
echo "$COMMIT_MESSAGES"
50+
return 0
51+
fi
52+
53+
# In GitHub Actions PRs, scan all commit subjects + bodies in the PR range.
54+
if [ -n "$GITHUB_BASE_REF" ]; then
55+
git log --format='%s%n%b%n----' "origin/$GITHUB_BASE_REF..HEAD" 2>/dev/null || true
56+
return 0
57+
fi
58+
59+
# Local default: inspect HEAD commit only.
60+
git log -1 --format='%s%n%b' 2>/dev/null || true
61+
}
62+
2963
# Parse arguments
3064
for arg in "$@"; do
3165
if [ "$arg" = "--fail-on-breaking" ]; then
@@ -37,26 +71,14 @@ for arg in "$@"; do
3771
fi
3872
done
3973

40-
# Detect current branch: GitHub Actions PR branch, or local git branch
41-
# Allow CURRENT_BRANCH env var to override for testing
42-
if [ -n "$CURRENT_BRANCH" ]; then
43-
# Use explicitly set CURRENT_BRANCH (for testing)
44-
:
45-
elif [ -n "$GITHUB_HEAD_REF" ]; then
46-
# Use GitHub Actions PR branch
47-
CURRENT_BRANCH="$GITHUB_HEAD_REF"
48-
else
49-
# Use local git branch
50-
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
51-
fi
52-
53-
# If --fail-on-breaking not explicitly set, determine based on branch name
74+
# If --fail-on-breaking not explicitly set, determine based on conventional commit markers.
5475
if [ "$FAIL_ON_BREAKING" = false ]; then
55-
if [[ "$CURRENT_BRANCH" == *breaking* ]]; then
56-
echo "Branch '$CURRENT_BRANCH' contains 'breaking', allowing breaking changes"
76+
COMMIT_TEXT=$(get_commit_messages)
77+
if detect_breaking_commit_marker "$COMMIT_TEXT"; then
78+
echo "Detected conventional commit breaking marker ('!' in header or 'BREAKING CHANGE:' footer), allowing breaking changes"
5779
FAIL_ON_BREAKING=false
5880
else
59-
echo "Branch '$CURRENT_BRANCH' does not contain 'breaking', failing on breaking changes"
81+
echo "No conventional commit breaking marker found, failing on breaking changes"
6082
FAIL_ON_BREAKING=true
6183
fi
6284
fi
@@ -67,7 +89,7 @@ if [ "$DRY_RUN" = true ]; then
6789
else
6890
echo "Mode: Allowing breaking changes"
6991
fi
70-
echo "Dry run mode, exiting after branch check"
92+
echo "Dry run mode, exiting after commit message check"
7193
exit 0
7294
fi
7395

@@ -119,56 +141,56 @@ for file in $files; do
119141
TOTAL_FILES=$((TOTAL_FILES + 1))
120142
echo ""
121143
echo "========== $file =========="
122-
144+
123145
# Get the file from master branch
124146
if ! git show "$BASE_BRANCH:$file" > "$TEMP_DIR/$file" 2>/dev/null; then
125147
echo "ℹ️ New file (does not exist in master branch)"
126148
continue
127149
fi
128-
150+
129151
# Verify the temp file was created
130152
if [ ! -f "$TEMP_DIR/$file" ]; then
131153
echo "❌ Failed to create temp file"
132154
continue
133155
fi
134-
156+
135157
# Note: oasdiff has some non-deterministic behavior in change counts due to
136158
# unordered map iteration in Go. Error counts are consistent, but warning
137159
# counts may vary by ~2-3% between runs. This is a known limitation.
138-
160+
139161
# Run oasdiff changelog
140162
echo "--- Changelog ---"
141163
set +e
142164
CHANGELOG_OUTPUT=$(docker run --rm -v "$(pwd)":/current -v "$TEMP_DIR":/base "$DOCKER_IMAGE" changelog --include-path-params /base/"$file" /current/"$file" 2>&1)
143165
CHANGELOG_EXIT=$?
144166
set -e
145-
167+
146168
echo "$CHANGELOG_OUTPUT"
147-
169+
148170
if [ $CHANGELOG_EXIT -eq 0 ]; then
149171
echo "✓ Changelog generated successfully"
150172
else
151173
echo "⚠ Could not generate changelog (exit code: $CHANGELOG_EXIT)"
152174
fi
153-
175+
154176
# Run breaking changes check
155177
echo ""
156178
echo "--- Breaking changes check ---"
157179
set +e
158180
BREAKING_OUTPUT=$(docker run --rm -v "$(pwd)":/current -v "$TEMP_DIR":/base "$DOCKER_IMAGE" breaking --fail-on WARN --include-path-params /base/"$file" /current/"$file" 2>&1)
159181
BREAKING_EXIT=$?
160182
set -e
161-
183+
162184
echo "$BREAKING_OUTPUT"
163-
185+
164186
if [ $BREAKING_EXIT -eq 0 ]; then
165187
echo "✓ No breaking changes detected"
166188
else
167189
echo "⚠ Breaking changes detected (exit code: $BREAKING_EXIT)"
168190
BREAKING_CHANGES_FOUND=true
169191
FILES_WITH_BREAKING_CHANGES+=("$file")
170192
fi
171-
193+
172194
PROCESSED_FILES=$((PROCESSED_FILES + 1))
173195
done
174196

@@ -189,7 +211,7 @@ if [ "$BREAKING_CHANGES_FOUND" = true ]; then
189211
echo "::warning file=${file}::Breaking changes detected in this API spec file"
190212
fi
191213
done
192-
214+
193215
if [ "$FAIL_ON_BREAKING" = true ]; then
194216
echo ""
195217
echo "Exiting with error due to breaking changes"

0 commit comments

Comments
 (0)