From 59190b1ea2b55e6e3a5224ea846085abdf9b1a8e Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:55:45 +0000 Subject: [PATCH 1/4] docs: add a gitlint row and migration section, and a release tools guide gitlint's latest release is 0.19.1 from March 2023 and its maintainer is looking for co-maintainers, while pre-commit's hooks page still features it. The comparison page did not mention it. Add it to the table, a "Coming from gitlint" section that swaps the hook and maps .gitlint rules to cchk.toml, including the ones with no equivalent, and a line under "Which to choose". Add "With release tools": release-please, semantic-release, git-cliff and cz bump read commit messages, and a message they cannot parse is silently dropped. The page says which message to check for each merge style, how to let release-please's and changesets' own branches through on 2.19.4, and how to give changesets a title that passes. --- docs/compare/tools.md | 59 ++++++++++++++++++++++- docs/guides/release-tools.md | 90 ++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 docs/guides/release-tools.md diff --git a/docs/compare/tools.md b/docs/compare/tools.md index 1b0f820..79e4db3 100644 --- a/docs/compare/tools.md +++ b/docs/compare/tools.md @@ -2,7 +2,7 @@ What else checks commit messages, branch names and pull request titles on GitHub, what each one does, and when it is the better choice. Prices and -features are as the projects list them in September 2026; follow the links +features are as the projects list them in September and October 2026; follow the links for the current state. GitHub's own rulesets are on [their own page](github-rules.md). @@ -11,6 +11,7 @@ for the current state. GitHub's own rulesets are on | Tool | What it is | What it checks | Where it runs | Price | |---|---|---|---|---| | [commitlint](https://commitlint.js.org/) with [commitlint-github-action](https://github.com/wagoid/commitlint-github-action) | The reference Conventional Commits linter in the JavaScript world: shared configs, plugins, a [commitizen](https://commitizen.github.io/cz-cli/) prompt | Commit messages | Node hook (husky), CI Action | Free, open source | +| [gitlint](https://github.com/jorisroovers/gitlint) | A Python commit message linter with a `commit-msg` hook, configured in a `.gitlint` file; one of the two commit linters the [pre-commit hooks page](https://pre-commit.com/hooks.html) features. Its latest release, 0.19.1, is from March 2023, and its [PyPI page](https://pypi.org/project/gitlint/) says the maintainer is looking for co-maintainers | Commit messages: title and body length, whitespace, required words, regexes; Conventional Commits and sign-off as opt-in rules | Git hook, pre-commit, CLI | Free, open source | | [action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) | The most-used pull request title check; Electron, Vite and Vercel run it | The pull request title (optionally the single commit, for squash merges) | GitHub Action | Free, open source | | [Semantic PRs](https://github.com/Ezard/semantic-prs) | A GitHub App with the same idea: type and scope on the title or the commits | Pull request title or commits | GitHub App | Free, open source | | [commitcheck](https://github.com/marketplace/commitcheck) | A hosted App that applies one regular expression, set in its web admin | Commit message, pull request title or description, by regex | GitHub App | Free for public repositories; $25 a month for private ones | @@ -53,11 +54,67 @@ for the current state. GitHub's own rulesets are on [rulesets page](github-rules.md). Every tool above, Commit Check included, marks the commit or blocks the merge. +## Coming from gitlint + +Both are Python, both run as a `commit-msg` hook through pre-commit, so the +switch is a change to `.pre-commit-config.yaml` and a translation of +`.gitlint` into `cchk.toml`. + +Replace the hook: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.19.4 + hooks: + - id: check-message +``` + +Install it the same way: `pre-commit install --hook-type commit-msg`. + +Two defaults differ. Commit Check requires +[Conventional Commits](../rules.md#cc001) unless you turn it off, where +gitlint makes it an opt-in rule (`contrib=CT1`). And gitlint requires a body +out of the box (B6), where Commit Check does not. This keeps gitlint's +defaults: + +```toml title="cchk.toml" +[commit] +conventional_commits = false # drop if you used contrib=CT1 +subject_max_length = 72 # T1 +subject_min_length = 5 # T8 +allow_wip_commits = false # T5's default word, WIP +require_body = true # B6 +``` + +Rule by rule: + +| gitlint | Commit Check | +|---|---| +| T1 `title-max-length` | `subject_max_length` ([CC004](../rules.md#cc004)) | +| T5 `title-must-not-contain-word` (default `WIP`) | `allow_wip_commits = false` ([CC010](../rules.md#cc010)), which rejects a subject that starts with `WIP` | +| T7 `title-match-regex`, B8 `body-match-regex` | `message_pattern`, one regex over the whole message ([CC001](../rules.md#cc001)) | +| T8 `title-min-length` | `subject_min_length` ([CC005](../rules.md#cc005)) | +| B5 `body-min-length`, B6 `body-is-missing` | `require_body` ([CC011](../rules.md#cc011)); there is no minimum length | +| M1 `author-valid-email` | the `check-author-email` hook ([CC102](../rules.md#cc102)), with `author_email_pattern` | +| CT1 `contrib-title-conventional-commits` | `conventional_commits`, on by default ([CC001](../rules.md#cc001)) | +| CC1 `contrib-body-requires-signed-off-by` | `require_signed_off_by` ([CC012](../rules.md#cc012)) | +| CC2 `contrib-disallow-cleanup-commits` | `allow_fixup_commits = false` ([CC009](../rules.md#cc009)) | +| I4 `ignore-by-author-name` | `ignore_authors` | +| merge, revert, fixup commits ignored | the format rules already skip the subjects git writes; [CC006](../rules.md#cc006), [CC007](../rules.md#cc007) and [CC009](../rules.md#cc009) decide whether they are allowed | +| T2–T4, T6 whitespace and tab rules, T3 trailing punctuation, B1–B4 body line rules, B7 changed-file mention, user-defined Python rules | no equivalent | + +The last row is the honest gap: if your policy depends on body line length or +a custom rule written in Python, gitlint still does that and Commit Check does +not. What you gain is the rest of this page: branch names, authors, the +Action, the App, and a release that is current. + ## Which to choose - You want the pull request title checked and nothing else: `action-semantic-pull-request`. - You are in a JavaScript repository with husky and want plugins: commitlint. - You need sign-off with a remediation flow: the DCO app, or DCO-2. +- You rely on gitlint's body line rules or your own Python rules: gitlint. - You want one policy for messages, branches and authors, the same everywhere a commit is made and checked, with a hosted option that needs no workflow file: Commit Check. diff --git a/docs/guides/release-tools.md b/docs/guides/release-tools.md new file mode 100644 index 0000000..9a5176e --- /dev/null +++ b/docs/guides/release-tools.md @@ -0,0 +1,90 @@ +# With release tools + +release-please, semantic-release, git-cliff and `cz bump` read your commit +messages to decide the next version and write the changelog. A commit they +cannot parse is not an error to them: it is left out of the changelog, or it +does not count toward a release. Commit Check is the gate in front of them, +so what they read always parses. + +## Check the message the tool will read + +Which message that is depends on how pull requests are merged. + +- **Merge commits or rebase.** Every commit on the branch lands on `main`, and + the release tool reads each one. Check each one: the + [pre-commit hook](pre-commit.md) while it is written, and the + [Action](github-actions.md) with its default `message: true` on the pull + request. +- **Squash merge.** The branch's commits are thrown away, and the pull + request title becomes the commit on `main`. release-please, for one, + "highly recommend[s]" squash merges. Check the title: `pr-title: true` on + the Action, or `check = "squash"` on the [App](github-app.md#pull-requests), + which checks the full message a squash merge would land. + +```yaml title=".github/workflows/commit-check.yml" +name: Commit Check + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + commit-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: commit-check/commit-check-action@v2 + with: + message: false # the branch's commits do not reach main + pr-title: true +``` + +The default [commit types](../configuration.md) are the Conventional Commits +ones the tools release on (`feat`, `fix`, `perf`) plus the ones they record +without releasing. A breaking change written as `feat!:` or `feat(api)!:` +passes, and so does a `BREAKING CHANGE:` footer. + +## Let the tool's own pull request through + +release-please and changesets open their release pull request from a branch +whose name you cannot choose: + +| Tool | Branch | Default title | +|---|---|---| +| release-please | `release-please--branches--main`, plus `--components--` in a monorepo | `chore(main): release 1.2.3` | +| [changesets/action](https://github.com/changesets/action) | `changeset-release/main` | `Version Packages` | + +Commit Check 2.19.4 rejects both branch names under +[CC201](../rules.md#cc201). Allow them: + +```toml title="cchk.toml" +[branch] +allow_branch_names = ["release-please--branches--.+", "changeset-release/.+"] +``` + +Each entry is a regular expression that has to match the whole branch name, +so these allow the tools' branches and nothing that merely contains them. +[commit-check#648](https://github.com/commit-check/commit-check/pull/648) +accepts both names by default; once it is released, this section is no longer +needed. + +release-please's title passes [CC001](../rules.md#cc001) as it is. +changesets' `Version Packages` does not, so with `pr-title: true` give it a +Conventional Commits title and commit message: + +```yaml +- uses: changesets/action@v1 + with: + commit-message: "chore: version packages" + pr-title: "chore: version packages" +``` + +## Per tool + +| Tool | What it reads | Notes | +|---|---|---| +| [release-please](https://github.com/googleapis/release-please) | Conventional Commits on the release branch since the last release | Branch name above. Its README recommends squash merges, so check the title. | +| [semantic-release](https://github.com/semantic-release/semantic-release) | Commits since the last release, with the Angular preset by default | The Angular types are Commit Check's defaults. | +| [git-cliff](https://github.com/orhun/git-cliff) | Commits, parsed as Conventional Commits when `conventional_commits = true` in `cliff.toml` | With `filter_unconventional = true`, a commit that does not parse is dropped from the changelog without a word. | +| [Commitizen](https://commitizen-tools.github.io/commitizen/) `cz bump` | Conventional Commits since the last tag | Commitizen has its own `cz check`. Commit Check adds branch names, authors, sign-off and the App, from one config. | +| [changesets](https://github.com/changesets/changesets) | Changeset files, not commit messages | Only its branch name and PR title matter here, both above. | diff --git a/mkdocs.yml b/mkdocs.yml index 0bddd7e..713556a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -175,6 +175,7 @@ nav: - GitHub Action: guides/github-actions.md - GitHub App: guides/github-app.md - GitLab, Bitbucket, Azure: guides/other-ci.md + - With release tools: guides/release-tools.md - Across an organization: guides/organization.md - MCP server: guides/mcp.md - Python API: guides/python-api.md From 5e65d825dd802b733c31fe86221db2ff82907cef Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:57:20 +0000 Subject: [PATCH 2/4] docs: note that a single-commit squash lands the commit message by default GitHub's default squash message uses the pull request title only when the pull request has more than one commit. The App follows that setting; the Action checks the title, so point Action users at the "Pull request title" squash setting. --- docs/guides/release-tools.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/guides/release-tools.md b/docs/guides/release-tools.md index 9a5176e..31e8395 100644 --- a/docs/guides/release-tools.md +++ b/docs/guides/release-tools.md @@ -19,7 +19,11 @@ Which message that is depends on how pull requests are merged. request title becomes the commit on `main`. release-please, for one, "highly recommend[s]" squash merges. Check the title: `pr-title: true` on the Action, or `check = "squash"` on the [App](github-app.md#pull-requests), - which checks the full message a squash merge would land. + which checks the full message a squash merge would land. With GitHub's + default squash message, a pull request of a single commit lands that + commit's message rather than the title. The App follows that setting; with + the Action, set the squash commit message to "Pull request title" in the + repository settings so the title you check is the one that lands. ```yaml title=".github/workflows/commit-check.yml" name: Commit Check From 6e4607521d7a3204494c9bb69081c8ecb05af82d Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:33:22 +0000 Subject: [PATCH 3/4] docs: keep the release tools branch names as an opt-in setting The default branch rule will not accept release-please's and changesets' branches, so drop the note that a later release does; the allow_branch_names setting is how a repository lets them through. --- docs/guides/release-tools.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/guides/release-tools.md b/docs/guides/release-tools.md index 31e8395..aec5931 100644 --- a/docs/guides/release-tools.md +++ b/docs/guides/release-tools.md @@ -58,8 +58,8 @@ whose name you cannot choose: | release-please | `release-please--branches--main`, plus `--components--` in a monorepo | `chore(main): release 1.2.3` | | [changesets/action](https://github.com/changesets/action) | `changeset-release/main` | `Version Packages` | -Commit Check 2.19.4 rejects both branch names under -[CC201](../rules.md#cc201). Allow them: +The default branch rule, [CC201](../rules.md#cc201), rejects both names. +Allow the ones you use: ```toml title="cchk.toml" [branch] @@ -68,9 +68,6 @@ allow_branch_names = ["release-please--branches--.+", "changeset-release/.+"] Each entry is a regular expression that has to match the whole branch name, so these allow the tools' branches and nothing that merely contains them. -[commit-check#648](https://github.com/commit-check/commit-check/pull/648) -accepts both names by default; once it is released, this section is no longer -needed. release-please's title passes [CC001](../rules.md#cc001) as it is. changesets' `Version Packages` does not, so with `pr-title: true` give it a From a41a3777e957fea87e5120cabc760829d15e8aa2 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen <3353385+shenxianpeng@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:43:05 +0000 Subject: [PATCH 4/4] docs: correct release tool details and the gitlint T5 mapping - changesets/action v1 takes commit and title, not commit-message and pr-title (those are on its main branch only). - semantic-release's default Angular preset does not read the ! in feat!:, so point at the BREAKING CHANGE footer or the conventionalcommits preset. - Types such as docs and chore are parsed but not released on; whether they reach the changelog depends on the tool, not "recorded". - gitlint T5 rejects WIP anywhere in the title; CC010 only at the start. --- docs/compare/tools.md | 2 +- docs/guides/release-tools.md | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/compare/tools.md b/docs/compare/tools.md index 79e4db3..c6a76e5 100644 --- a/docs/compare/tools.md +++ b/docs/compare/tools.md @@ -92,7 +92,7 @@ Rule by rule: | gitlint | Commit Check | |---|---| | T1 `title-max-length` | `subject_max_length` ([CC004](../rules.md#cc004)) | -| T5 `title-must-not-contain-word` (default `WIP`) | `allow_wip_commits = false` ([CC010](../rules.md#cc010)), which rejects a subject that starts with `WIP` | +| T5 `title-must-not-contain-word` (default `WIP`) | `allow_wip_commits = false` ([CC010](../rules.md#cc010)), which rejects a subject that starts with `WIP`; gitlint rejects the word anywhere in the title | | T7 `title-match-regex`, B8 `body-match-regex` | `message_pattern`, one regex over the whole message ([CC001](../rules.md#cc001)) | | T8 `title-min-length` | `subject_min_length` ([CC005](../rules.md#cc005)) | | B5 `body-min-length`, B6 `body-is-missing` | `require_body` ([CC011](../rules.md#cc011)); there is no minimum length | diff --git a/docs/guides/release-tools.md b/docs/guides/release-tools.md index aec5931..93ad4aa 100644 --- a/docs/guides/release-tools.md +++ b/docs/guides/release-tools.md @@ -44,9 +44,13 @@ jobs: ``` The default [commit types](../configuration.md) are the Conventional Commits -ones the tools release on (`feat`, `fix`, `perf`) plus the ones they record -without releasing. A breaking change written as `feat!:` or `feat(api)!:` -passes, and so does a `BREAKING CHANGE:` footer. +ones the tools release on (`feat`, `fix`, `perf`) plus the ones they parse but +do not release on, such as `docs` and `chore`; whether those show in the +changelog is the tool's setting. A breaking change written as `feat!:` or +`feat(api)!:` passes Commit Check, and so does a `BREAKING CHANGE:` footer. +semantic-release's default Angular preset does not read the `!`, though: give +the commit a `BREAKING CHANGE:` footer, or switch it to the +`conventionalcommits` preset. ## Let the tool's own pull request through @@ -76,8 +80,8 @@ Conventional Commits title and commit message: ```yaml - uses: changesets/action@v1 with: - commit-message: "chore: version packages" - pr-title: "chore: version packages" + commit: "chore: version packages" + title: "chore: version packages" ``` ## Per tool