Skip to content

feat(rules): add breaking-change-exclamation-mark#4548

Merged
escapedcat merged 3 commits intoconventional-changelog:masterfrom
adamchristiansen:master
Oct 3, 2025
Merged

feat(rules): add breaking-change-exclamation-mark#4548
escapedcat merged 3 commits intoconventional-changelog:masterfrom
adamchristiansen:master

Conversation

@adamchristiansen
Copy link
Copy Markdown
Contributor

Description

A breaking change can be denoted by either ! in the header (e.g., feat!: add advanced search filters) or by including BREAKING CHANGE in the footer (e.g., BREAKING CHANGE: old queries no longer work with the new API.). This pull request adds the breaking-change-exclamation-mark rule that requires a BREAKING CHANGE footer if ! appears in the header and a ! in the header if BREAKING CHANGE appears in the footer. The idea is that it is all or nothing, so if one breaking change marker is present the other must be present too.

Motivation and Context

Implements and closes #4547.

This feature enforces a solution to three problems:

  1. Using just ! might not convey enough information. For example, feat!: add advanced search filters. Why is this a breaking change? How do I know what broke unless the footer is present? Using this rule will fail because a footer indicating a breaking change must be added in this case.
  2. Using just BREAKING CHANGE might cause confusion. For example, if I use something like git log --online to quickly look at changes and I see feat: add advanced search filters, I have no idea that it is a breaking change without looking at that specific commit in greater detail. This rule will fail because an exclamation mark must be added to the head in this case.
  3. This rule enforces the broadest tooling coverage since both means of denoting breaking changes are present in commit messages.

Usage examples

// commitlint.config.js
module.exports = {
  rules: {
    'breaking-change-exclamation-mark': [2, 'always']
  }
};
echo "feat!: add something\n\nBREAKING CHANGE: breaks something" | commitlint # passes
echo "feat: add something" | commitlint # passes
echo "feat!: add something" | commitlint # fails
echo "feat: add something\n\nBREAKING CHANGE: breaks something" | commitlint # fails

How Has This Been Tested?

I added a new test @commitlint/rules/src/breaking-change-exclamation-mark.test.ts that follows the structure of existing tests. The commit messages to tests were decided based on equivalence partitions.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented Sep 24, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@escapedcat escapedcat requested a review from JounQin September 24, 2025 08:17
return [true];
}

const hasExclamationMark = !!header && /!:/.test(header);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make this change.

Should this be updated to use the breakingHeaderPattern too? To me, that is outside of the scope of this pull request and should be a separate commit.

const hasExclamationMark = /!:/.test(input);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think such tiny change is fine to be inclined in one PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make the change.

}

const hasExclamationMark = !!header && /!:/.test(header);
const hasBreakingChange = !!footer && /BREAKING[ -]CHANGE:/.test(footer);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need make sure the footer line starts with BREAKING not only includes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I will modify the regex for a multiline search.

import message from "@commitlint/message";
import { SyncRule } from "@commitlint/types";

export const breakingChangeExclamationMark: SyncRule = (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new rule should be added into

export type RulesConfig<V = RuleConfigQuality.User> = {

Comment thread @commitlint/rules/src/index.ts Outdated
import { typeMinLength } from "./type-min-length.js";

export default {
"breaking-change-exclamation-mark": breakingChangeExclamationMark,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we resort them alphabetically?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I will fix the sorting.

Comment thread docs/reference/rules.md Outdated
@@ -1,5 +1,19 @@
# Rules

## breaking-change-exclamation-mark
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above about the order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed again; I will fix this. It has bothered me in the past looking things up on this page as it is about 90% in the right order.

Addresses review feedback from @JounQin about pull request #4548.

- Use `breakingHeaderPattern` to search for the exclamation mark in the header.
- Correct the regular expression to require that BREAKING CHANGE in the footer be anchored at the
  beginning of a line.
- Updated the `RulesConfig` type.
Address pull request feedback from @JounQin.

- Fixed regex for `subject-exclamation-mark`.
- Fixed sorting for `RulesConfig`.
- Fixed sorting for `rules.md`.
@adamchristiansen
Copy link
Copy Markdown
Contributor Author

I made several pushes because I resolved a conflict with a merge instead of performing a rebase 🤦. I have implemented all suggestions and rebased the branch onto the latest master, so I believe it is ready now.

@escapedcat
Copy link
Copy Markdown
Member

Thanks @adamchristiansen !

@escapedcat escapedcat merged commit c4d419b into conventional-changelog:master Oct 3, 2025
12 checks passed
immxmmi pushed a commit to immxmmi/gitea-helm-actions that referenced this pull request Apr 20, 2026
This PR contains the following updates:

| Package | Type | Update | Change | Age | Confidence |
|---|---|---|---|---|---|
| [commitlint/commitlint](https://github.com/conventional-changelog/commitlint) | container | minor | `20.1.0` -> `20.2.0` | [![age](https://developer.mend.io/api/mc/badges/age/docker/commitlint%2fcommitlint/20.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/docker/commitlint%2fcommitlint/20.1.0/20.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [docker.io/thegeeklab/git-sv](https://github.com/thegeeklab/git-sv) | container | patch | `2.0.8` -> `2.0.9` | [![age](https://developer.mend.io/api/mc/badges/age/docker/docker.io%2fthegeeklab%2fgit-sv/2.0.9?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/docker/docker.io%2fthegeeklab%2fgit-sv/2.0.8/2.0.9?slim=true)](https://docs.renovatebot.com/merge-confidence/) |
| [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) | devDependencies | minor | [`^0.46.0` -> `^0.47.0`](https://renovatebot.com/diffs/npm/markdownlint-cli/0.46.0/0.47.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/markdownlint-cli/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/markdownlint-cli/0.46.0/0.47.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>conventional-changelog/commitlint (commitlint/commitlint)</summary>

### [`v20.2.0`](https://github.com/conventional-changelog/commitlint/blob/HEAD/CHANGELOG.md#2020-2025-12-05)

[Compare Source](conventional-changelog/commitlint@v20.1.0...v20.2.0)

##### Bug Fixes

- update dependency glob to v11 \[security] ([#&#8203;4561](conventional-changelog/commitlint#4561)) ([a89a08b](conventional-changelog/commitlint@a89a08b))

##### Features

- **cz-commitlint:** support customizable commit prompt with emojis ([#&#8203;4540](conventional-changelog/commitlint#4540)) ([803c05b](conventional-changelog/commitlint@803c05b))
- **lint:** update ESLint configuration to use Vitest and remove Jest plugin ([#&#8203;4542](conventional-changelog/commitlint#4542)) ([63e7ad4](conventional-changelog/commitlint@63e7ad4))
- **rules:** add breaking-change-exclamation-mark ([#&#8203;4548](conventional-changelog/commitlint#4548)) ([c4d419b](conventional-changelog/commitlint@c4d419b)), closes [#&#8203;4547](conventional-changelog/commitlint#4547)

</details>

<details>
<summary>thegeeklab/git-sv (docker.io/thegeeklab/git-sv)</summary>

### [`v2.0.9`](https://github.com/thegeeklab/git-sv/releases/tag/v2.0.9)

[Compare Source](thegeeklab/git-sv@v2.0.8...v2.0.9)

#### v2.0.9 (2025-12-05)

##### Others

- **deps:** update docker.io/library/alpine docker tag to v3.23 ([#&#8203;260](thegeeklab/git-sv#260)) ([`2f6207a`](thegeeklab/git-sv@2f6207a))
- **deps:** update golang patch version ([#&#8203;261](thegeeklab/git-sv#261)) ([`a4cc5da`](thegeeklab/git-sv@a4cc5da))

</details>

<details>
<summary>igorshubovych/markdownlint-cli (markdownlint-cli)</summary>

### [`v0.47.0`](https://github.com/igorshubovych/markdownlint-cli/releases/tag/v0.47.0)

[Compare Source](igorshubovych/markdownlint-cli@v0.46.0...v0.47.0)

- Add output and exit code support for warnings
- Update `markdownlint` dependency to `0.40.0`
  - Improve `MD011`/`MD013`/`MD051`/`MD060`
- Update all dependencies via `Dependabot`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Only on Sunday and Saturday ( * * * * 0,6 ) (UTC), Automerge - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (UTC).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xOC4xIiwidXBkYXRlZEluVmVyIjoiNDEuMTguMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsia2luZC9kZXBlbmRlbmN5Il19-->

Reviewed-on: https://gitea.com/gitea/helm-actions/pulls/88
Reviewed-by: DaanSelen <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

feat: rule to require both ! and BREAKING CHANGE

3 participants