diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 40fd745c..cacf5708 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -189,7 +189,7 @@ cchk --help # Verify alias works │ ├── engine_test.py # Validator tests │ ├── config_merger_test.py # Config merging tests │ └── rule_builder_test.py # Rule building tests -├── docs/ # Sphinx documentation +├── assets/ # README assets (demo recording) ├── cchk.toml # Example TOML configuration (v2.0 format) ├── pyproject.toml # Package metadata, build config, tool settings ├── noxfile.py # Build automation sessions diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fffc07db..96c7b52a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,6 @@ on: - pyproject.toml - ".github/workflows/main.yml" - ".pre-commit-config.yaml" - - "!docs/**" workflow_dispatch: permissions: @@ -84,38 +83,3 @@ jobs: # using a wildcard as filename on Windows requires a bash shell shell: bash run: nox -s install - - docs: - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.10" - - - name: Install nox - run: | - python -m pip install --only-binary :all: --upgrade pip - python -m pip install --only-binary :all: ".[dev]" - - - name: Build docs - run: nox -s docs - - - name: Save built docs as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: "commit-check_docs" - path: ${{ github.workspace }}/site - - - name: Upload docs to github pages - # only publish doc changes from main branch - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' - uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./site - # Pinned explicitly so the custom domain cannot be lost when the - # publish branch is replaced. - cname: docs.commit-check.com diff --git a/.gitignore b/.gitignore index d881b6ae..029a8c41 100644 --- a/.gitignore +++ b/.gitignore @@ -14,15 +14,3 @@ tests/__pycache__ coverage.xml .nox _build/ - -# docs -docs/_build -docs/cli_args.rst -docs/__pycache__ - -# MkDocs -site/ -docs/cli.md - -# Social card cache written by the docs build -.cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 50a3a9c8..efb135f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,11 +12,6 @@ repos: rev: v6.0.0 hooks: - id: check-yaml - # mkdocs.yml carries the `!!python/name:` tags that Material's emoji - # extension requires, which yaml.safe_load cannot construct. MkDocs - # validates the file itself on every build (`mkdocs build --strict`), - # so it is checked more thoroughly than this hook would manage. - exclude: ^mkdocs\.yml$ - id: check-toml - id: end-of-file-fixer - id: trailing-whitespace diff --git a/README.md b/README.md index c9a2a026..f1ec9590 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ local hooks, CI, GitHub Actions, and AI automation. - **Multiple enforcement points:** CLI, pre-commit, CI / GitHub Actions - **Machine-readable output:** JSON + Python API for automation and AI agents -![commit-check demo](https://github.com/commit-check/commit-check/raw/main/docs/demo.gif) +![commit-check demo](https://github.com/commit-check/commit-check/raw/main/assets/demo.gif)
@@ -100,7 +100,7 @@ Commit Check can be configured in three ways (in order of priority): ### Use Default Configuration -- **Commit Check** uses a [default configuration](https://github.com/commit-check/commit-check/blob/main/docs/configuration.rst) if you do not provide a `cchk.toml` or `commit-check.toml` file. +- **Commit Check** uses a [default configuration](https://commit-check.com/configuration/) if you do not provide a `cchk.toml` or `commit-check.toml` file. - The default configuration is lenient — it only checks whether commit messages follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification and branch names follow the [Conventional Branch](https://conventionalbranch.org#summary) convention. @@ -130,7 +130,7 @@ conventional_branch = true # Optional: the defaults are a superset of the Conventional Branch spec — spec # types plus Conventional Commit types (build, ci, docs, perf, refactor, style, # test) and AI/bot prefixes (ai, claude, codex, copilot, cursor, dependabot, -# renovate), see docs/configuration.rst. Omit this option to use the defaults. +# renovate), see https://commit-check.com/configuration/. Omit this option to use the defaults. allow_branch_types = [ "feature", "bugfix", diff --git a/docs/demo.gif b/assets/demo.gif similarity index 100% rename from docs/demo.gif rename to assets/demo.gif diff --git a/docs/demo.tape b/assets/demo.tape similarity index 100% rename from docs/demo.tape rename to assets/demo.tape diff --git a/docs/assets/extra.css b/docs/assets/extra.css deleted file mode 100644 index 5aab2bf1..00000000 --- a/docs/assets/extra.css +++ /dev/null @@ -1,91 +0,0 @@ -/* Brand palette ----------------------------------------------------------- - Material's "custom" primary/accent read these variables. */ -:root { - --cc-brand: #2c9ccd; - --cc-brand-dark: #1e85a8; - --cc-brand-light: #5bb3d9; - /* The brand blue only reaches 4.2:1 on white; links need 4.5:1. */ - --cc-link: #176b89; - - --md-primary-fg-color: var(--cc-brand); - --md-primary-fg-color--light: var(--cc-brand-light); - --md-primary-fg-color--dark: var(--cc-brand-dark); - --md-accent-fg-color: var(--cc-brand-dark); -} - -[data-md-color-scheme="slate"] { - --md-primary-fg-color: var(--cc-brand); - --md-accent-fg-color: var(--cc-brand-light); - --md-typeset-a-color: var(--cc-brand-light); -} - -[data-md-color-scheme="default"] { - --md-typeset-a-color: var(--cc-link); -} - -/* Landing page ------------------------------------------------------------- - The hero is the first thing a visitor sees, so it carries the value - proposition rather than a badge wall. */ -.cc-hero { - text-align: center; - padding: 3.5rem 1rem 2.5rem; -} - -.cc-hero h1 { - font-size: 2.9rem; - line-height: 1.15; - font-weight: 800; - letter-spacing: -0.02em; - margin: 0 0 0.6rem; -} - -.cc-hero p { - font-size: 1.05rem; - max-width: 40rem; - margin: 0 auto 1.8rem; - color: var(--md-default-fg-color--light); -} - -.cc-hero .md-button { - margin: 0.3rem; -} - -/* Hide the redundant H1 that MkDocs renders above a custom hero. */ -.cc-hero + hr { - display: none; -} - -/* Feature grid: uses Material's card grid, tightened up a little. */ -.md-typeset .grid.cards > ul > li { - border-radius: 0.4rem; - transition: border-color 125ms, box-shadow 125ms; -} - -.md-typeset .grid.cards > ul > li:hover { - border-color: var(--cc-brand); - box-shadow: 0 0 0 1px var(--cc-brand); -} - -.md-typeset .grid.cards > ul > li > hr { - margin: 0.6rem 0; -} - -/* Tables ------------------------------------------------------------------- - Cells align to the top so short values line up with a wrapped message. */ -.md-typeset table:not([class]) td, -.md-typeset table:not([class]) th { - vertical-align: top; -} - -/* In the rule index tables only, keep codes, names and CLI flags on one line - so the tables stay scannable. Other tables hold long regexes and lists that - must stay wrappable. */ -.md-typeset table.rules-index td code { - white-space: nowrap; -} - -/* Sidebar section headings ("Getting started", "Reference", ...) */ -.md-nav__item--section > .md-nav__link { - font-weight: 700; - color: var(--md-default-fg-color); -} diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg deleted file mode 100644 index 896e52aa..00000000 --- a/docs/assets/favicon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg deleted file mode 100644 index a4d11323..00000000 --- a/docs/assets/logo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index f05bc244..00000000 --- a/docs/changelog.md +++ /dev/null @@ -1,188 +0,0 @@ -# Changelog - -All **notable changes** to this project will be documented in this file. - -Full changelog available at [GitHub releases](https://github.com/commit-check/commit-check/releases). - -## v2.11.0 (2026-07-06) - -### New Features - -* **AI attribution governance** — Added support for forbidding known AI tool - signatures (e.g., `Co-authored-by: Copilot`) in commit messages. New - `[commit]` config option `ai_attribution` (default `"ignore"`) rejects - commits carrying known AI tool signatures when set to `"forbid"`. See PR [#456](https://github.com/commit-check/commit-check/pull/456). - -### Bug Fixes - -* Fixed `MergeBaseValidator` branch detection — replaced `git branch -a` - regex matching with `git rev-parse --verify` to avoid false positives - (e.g., pattern `main` matching `main-staging`). See PR [#451](https://github.com/commit-check/commit-check/pull/451). - -### Chores - -* Added OpenSSF Scorecard workflow, badge, and pinned dependency SHAs for CI -* Migrated PyPI publishing to `pypa/gh-action-pypi-publish` -* Removed OpenSSF Scorecard badge after evaluation (moved to Scorecard dashboard) - -## v2.10.1 (2026-06-30) - -### Bug Fixes - -* **WIP detection case-insensitivity** — `WIP` (`[WIP]`, `WIP:`, `wip:`, - etc.) is now recognized regardless of case across all common patterns. - See PR [#448](https://github.com/commit-check/commit-check/pull/448). -* **Conventional commit special characters** — Allowed special characters - (parentheses, brackets, etc.) in the description part of conventional commit - messages. See PR [#447](https://github.com/commit-check/commit-check/pull/447). - -### Refactors - -* Extracted `_get_commit_message` to `BaseValidator` to remove code - duplication across validators. See PR [#445](https://github.com/commit-check/commit-check/pull/445). -* Removed legacy YAML config parsing code from `util.py`. - See PR [#444](https://github.com/commit-check/commit-check/pull/444). - -## v2.10.0 (2026-06-26) - -### New Features - -* **Dependabot / Renovate as default branch type** — `dependabot/` and - `renovate/` branch prefixes are now included in `DEFAULT_BRANCH_TYPES`, - so dependency update branches are automatically recognized. - See PR [#442](https://github.com/commit-check/commit-check/pull/442). - -## v2.9.0 (2026-06-22) - -### New Features - -* **AI agent branch prefixes (Conventional Branch v1.1.0)** — Added - `ai/`, `claude/`, `codex/`, `copilot/`, and `cursor/` to - `DEFAULT_BRANCH_TYPES` so branches created by AI coding agents are - recognized as valid. See PR [#438](https://github.com/commit-check/commit-check/pull/438). - -## v2.8.1 (2026-06-22) - -### Chores - -* Fixed 27 SonarQube code-quality issues across source and test files, - including path traversal vulnerability fix, cognitive complexity - reduction, and duplicate branch consolidation. See PR [#436](https://github.com/commit-check/commit-check/pull/436). -* Added SchemaStore IDE autocompletion support for `cchk.toml`. - See PR [#433](https://github.com/commit-check/commit-check/pull/433). - -## v2.8.0 (2026-06-13) - -### New Features - -* **Custom commit message pattern** — New `message_pattern` option in the - `[commit]` config section allows replacing the built-in Conventional Commits - regex with a user-defined regex pattern. Also supported via the - `CCHK_MESSAGE_PATTERN` environment variable. See PR [#427](https://github.com/commit-check/commit-check/pull/427). - -### Breaking Changes - -* **Dropped Python 3.9 support** — Minimum required Python version is now - 3.10. Type annotations have been modernized (PEP 604/585) and the - `py.typed` marker added for downstream type checkers. - See PR [#424](https://github.com/commit-check/commit-check/pull/424). - -## v2.7.1 (2026-06-08) - -### Chores - -* Added `auto` to the list of imperative verbs. See PR [#417](https://github.com/commit-check/commit-check/pull/417). -* Added commit-check vs GitHub Rulesets comparison table to the README. - See PR [#419](https://github.com/commit-check/commit-check/pull/419). - -## v2.7.0 (2026-05-16) - -### New Features - -* **Force push detection and blocking** — Added `--no-force-push` CLI flag and - `check-no-force-push` pre-push hook that inspect pushed ref ancestry via - `git merge-base --is-ancestor` to detect and block `git push --force` and - `git push -f`. A new `[push]` TOML config section with - `allow_force_push` (default `true`) controls the behavior. Environment - variable `CCHK_ALLOW_FORCE_PUSH` is also supported. - -* **`validate_push()` API** — New `commit_check.api.validate_push()` - function for programmatic push safety checks, matching the `--no-force-push` - CLI behavior without spawning a subprocess. - -* **Standalone mode** — When `--no-force-push` is run outside a pre-push hook - (no stdin), it checks whether pushing `HEAD` to its configured upstream - would require force, using `git ls-remote` and optional `git fetch` to - resolve the remote commit. - -* **Expanded imperative verbs** — Added 156 new imperative verbs across 10 - categories (auth/security, data ops, lifecycle, I/O, debugging, UI/UX, - engineering, general), growing the total from 234 to 390. - See PR [#414](https://github.com/commit-check/commit-check/pull/414). - -## v2.6.0 (2026-04-20) - -### New Features - -* **Lower-noise CLI failure output** — Added `--no-banner` to suppress the ASCII art header while preserving detailed errors and suggestions. -* **Compact failure mode** — Added `--compact` to print one `[FAIL]` line per failing check for CI logs and automation-friendly terminal output. This mode also suppresses the banner. - -### Bug Fixes - -* Fixed `print_error_header` state handling so repeated validations stay consistent when `--compact` is used. - -## v2.5.0 (2026-04-03) - -### New Features - -* **Co-author bypass in `ignore_authors`** — `_should_skip_commit_validation()` now parses `Co-authored-by:` trailers in the commit message body. If any co-author name matches `ignore_authors`, all commit checks are skipped. Useful for AI bots that co-author commits (e.g., `coderabbitai[bot]`). -* **Organization-level config inheritance via `inherit_from`** — New top-level TOML key that loads a parent config from a GitHub shorthand (`github:owner/repo:path`), a local file path, or an HTTPS URL, then deep-merges it with local settings. HTTP (non-TLS) URLs are rejected to prevent MITM attacks. -* **Git config author validation** — `AuthorValidator` now checks `git config user.name` / `user.email` first (the identity used for the *next* commit), falling back to `git log` if unset. Previously, a misconfigured identity would pass if the last commit had a valid author. - -### Bug Fixes - -* Fixed incorrect mock target in `test_main_with_message_empty_string_no_stdin_with_git`: was patching `commit_check.util.get_commit_info` (ineffective) instead of `commit_check.engine.get_commit_info`. - -## v2.0.0 (2025-10-01) - -!!! warning - - This major release introduces significant architectural changes and breaking updates to commit-check. Please review carefully before upgrading. - -### What's New - -* **TOML Configuration** — Replaces the old `.commit-check.yml` with `cchk.toml` or `commit-check.toml` for clearer syntax. -* **Simplified CLI & Hooks** — Legacy pre-commit hooks and command-line options have been removed for a cleaner, more consistent interface. -* **New Validation Engine** — The validation system has been completely redesigned around a new ValidationEngine to improve maintainability and flexibility. - -#### Breaking Changes - -Configuration Format: - -* `.commit-check.yml` has been replaced with `cchk.toml` or `commit-check.toml`. -* All YAML configurations must be migrated to TOML from this version onward. -* See the [Migration Guide](migration.md) for step-by-step instructions. - -Removed Pre-commit Hooks and CLI Options: - -* Several legacy hooks and command-line flags have been removed in favor of a simplified interface. -* Removed hooks: `check-commit-signoff`, `check-merge-base`, `check-imperative`. -* Removed CLI options: `--signoff`, `--merge-base`, `--imperative`. - -Module Removal: - -* The following legacy modules have been removed: `author.py`, `branch.py`, `commit.py`, `error.py`. - -Architecture Redesign: - -* The validation system has been completely restructured around the new `ValidationEngine`, breaking compatibility with any code or integrations relying on the old module structure. - -See PR [#280](https://github.com/commit-check/commit-check/pull/280) - -## v0.10.2 (2025-08-26) - -Last release before the big v2.0 changes. - -## v0.1.0 (2022-11-02) - -Initial release of commit-check. diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 736511ef..00000000 --- a/docs/configuration.md +++ /dev/null @@ -1,305 +0,0 @@ -# Configuration - -`commit-check` can be configured in three ways with the following priority (highest to lowest): - -1. **Command-line arguments** (`--subject-imperative=true`) -2. **Environment variables** (`CCHK_SUBJECT_IMPERATIVE=true`) -3. **Configuration files** (`cchk.toml` or `commit-check.toml`) -4. **Built-in defaults** - -This flexibility allows you to: - -* Use configuration files for project-wide settings -* Override with environment variables in CI/CD pipelines -* Override specific settings via CLI for one-off checks -* Use without any configuration files (relies on defaults) - -## Configuration Files - -`commit-check` configuration files support the TOML format. See `cchk.toml` for an example configuration. - -!!! tip "Default Behavior" - - * When no configuration file exists, commit-check uses sensible defaults with minimal restrictions. - * Enforced by default: the Conventional Commits format ([CC001](rules.md#cc001)), the Conventional Branch format ([CC201](rules.md#cc201)), the subject length limits of 5–80 characters ([CC004](rules.md#cc004), [CC005](rules.md#cc005)), and the author name and email patterns ([CC101](rules.md#cc101), [CC102](rules.md#cc102)). - * **Off** by default: subject capitalization, imperative mood, body and signoff requirements, rebase requirements, and every `allow_*` restriction. - - See [rules](rules.md) for the default state of every rule. - -commit-check can be configured via a `cchk.toml` or `commit-check.toml` file. - -The file should be placed in the root of your repository or in the `.github` folder. - -## Configuration File Locations - -commit-check searches for configuration files in the following order (first found is used): - -1. `cchk.toml` (root directory) -2. `commit-check.toml` (root directory) -3. `.github/cchk.toml` -4. `.github/commit-check.toml` - -!!! tip "GitHub Best Practice" - - Placing configuration files in the `.github` folder helps keep your repository root clean and follows GitHub conventions used by tools like Dependabot and Renovate. - -!!! tip "IDE Autocompletion" - - commit-check's TOML schema is published on [SchemaStore](https://www.schemastore.org/), - so editors like VS Code (via [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)), - PyCharm, and IntelliJ provide autocompletion, validation, and documentation - tooltips for `cchk.toml` out of the box — no manual schema path configuration needed. - -## Organization-Level Configuration (inherit_from) - -For organizations that want to share a common base configuration across many repositories, commit-check supports an `inherit_from` directive at the top level of your TOML config file. - -**How it works:** - -1. The `inherit_from` value can be a `github:` shorthand, a local file path, or an HTTPS URL. -2. The parent (inherited) configuration is loaded first. -3. Local settings in the current config file **override** the parent values. -4. The `inherit_from` key itself is not passed to the validation engine. - -**Example — inherit from a GitHub repository (recommended):** - -```toml -# .github/cchk.toml -inherit_from = "github:my-org/.github:cchk.toml" - -[commit] -subject_max_length = 72 # Overrides parent value -``` - -**GitHub shorthand format:** - -* `github:owner/repo:path/to/cchk.toml` — uses `HEAD` (default branch) -* `github:owner/repo@main:path/to/cchk.toml` — pins to the `main` branch - -**Example — inherit from a local file:** - -```toml -# repo/.github/cchk.toml -inherit_from = "../../shared/org-cchk.toml" - -[commit] -allow_wip_commits = true # Override for this project only -``` - -**Example — inherit from an HTTPS URL:** - -```toml -# .github/cchk.toml -inherit_from = "https://example.com/shared/cchk.toml" -``` - -!!! note - - If the `inherit_from` target is unreachable or the format is unrecognized, commit-check silently ignores the inheritance and uses only the local configuration. HTTP (non-TLS) URLs are rejected for security. - -## Example Configuration - -```toml -[commit] -# https://www.conventionalcommits.org -conventional_commits = true -# message_pattern = "" # Optional - custom regex (overrides conventional_commits) -subject_capitalized = false -subject_imperative = false -subject_max_length = 80 # Default - set to your own limit -subject_min_length = 5 # Default - set to your own minimum -allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore"] -allow_merge_commits = true -allow_revert_commits = true -allow_empty_commits = false -allow_fixup_commits = true -allow_wip_commits = false -require_body = false -# ignore_authors = [] # Optional - bypass checks for these commit/co-authors -require_signed_off_by = false -ai_attribution = "forbid" # "ignore" (default) or "forbid" — rejects AI tool trailers - -[push] -# Block force pushes when used as a pre-push hook or with --no-force-push -allow_force_push = true # Set to false to block force pushes - -[branch] -# https://conventionalbranch.org -conventional_branch = true -# Optional: defaults are a superset of the Conventional Branch spec — the -# spec types plus Conventional Commit types, AI agent prefixes and bot -# prefixes (see the Options table below for the full list). Omit this -# option to use the defaults, or set your own list for a strict subset. -allow_branch_types = [ - "feature", - "bugfix", - "hotfix", - "release", - "chore", - "feat", - "fix", - "build", - "ci", - "docs", - "perf", - "refactor", - "style", - "test", -] -# allow_branch_names = [] # Optional - additional standalone branch names (e.g., ["develop", "staging"]) -# require_rebase_target = "main" # Optional - no rebase requirement by default -# ignore_authors = [] # Optional - no authors ignored by default -``` - -## Command-Line Arguments - -All configuration options can be specified via command-line arguments, which take precedence over environment variables and configuration files. - -**Syntax:** - -* Boolean options: `--option-name=true` or `--option-name=false` -* Integer options: `--option-name=80` -* List options: `--option-name=value1,value2,value3` (comma-separated) -* String options: `--option-name=value` - -**Examples:** - -```bash -# Disable imperative mood check -commit-check --message --subject-imperative=false - -# Set custom subject length limit -commit-check --message --subject-max-length=72 - -# Restrict allowed commit types -commit-check --message --allow-commit-types=feat,fix,docs - -# Combine multiple options -commit-check --message --subject-imperative=true --subject-max-length=50 --allow-commit-types=feat,fix - -# Branch configuration via CLI -commit-check --branch --allow-branch-types=feature,bugfix,hotfix -``` - -**Pre-commit Hook Usage:** - -The primary use case for CLI arguments is configuring commit-check in `.pre-commit-config.yaml` without requiring a TOML file: - -```yaml -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.5.0 - hooks: - - id: check-message - args: - - --subject-imperative=false - - --subject-max-length=100 - - --allow-merge-commits=false -``` - -## Environment Variables - -Configuration can also be set via environment variables with the `CCHK_` prefix. This is useful for CI/CD pipelines and temporary overrides. - -**Naming Convention:** - -* Convert option name to uppercase -* Replace hyphens with underscores -* Add `CCHK_` prefix - -**Examples:** - -```bash -# Set boolean options -export CCHK_SUBJECT_IMPERATIVE=true -export CCHK_SUBJECT_CAPITALIZED=false - -# Set integer options -export CCHK_SUBJECT_MAX_LENGTH=72 -export CCHK_SUBJECT_MIN_LENGTH=10 - -# Set list options (comma-separated) -export CCHK_ALLOW_COMMIT_TYPES=feat,fix,docs,chore -export CCHK_ALLOW_BRANCH_TYPES=feature,bugfix,hotfix - -# Set string options -export CCHK_REQUIRE_REBASE_TARGET=main - -# Use in CI/CD -CCHK_SUBJECT_MAX_LENGTH=100 commit-check --message -``` - -**Complete Mapping:** - -| TOML Config | Environment Variable | CLI Argument | -|---|---|---| -| `conventional_commits = true` | `CCHK_CONVENTIONAL_COMMITS=true` | `--conventional-commits=true` | -| `message_pattern = "^PROJ-\\d+: .+"` | `CCHK_MESSAGE_PATTERN=^PROJ-\\d+: .+` | N/A (config file only) | -| `subject_capitalized = false` | `CCHK_SUBJECT_CAPITALIZED=false` | `--subject-capitalized=false` | -| `subject_imperative = true` | `CCHK_SUBJECT_IMPERATIVE=true` | `--subject-imperative=true` | -| `subject_max_length = 80` | `CCHK_SUBJECT_MAX_LENGTH=80` | `--subject-max-length=80` | -| `subject_min_length = 5` | `CCHK_SUBJECT_MIN_LENGTH=5` | `--subject-min-length=5` | -| `allow_commit_types = ["feat", "fix"]` | `CCHK_ALLOW_COMMIT_TYPES=feat,fix` | `--allow-commit-types=feat,fix` | -| `allow_merge_commits = true` | `CCHK_ALLOW_MERGE_COMMITS=true` | `--allow-merge-commits=true` | -| `allow_revert_commits = true` | `CCHK_ALLOW_REVERT_COMMITS=true` | `--allow-revert-commits=true` | -| `allow_empty_commits = false` | `CCHK_ALLOW_EMPTY_COMMITS=false` | `--allow-empty-commits=false` | -| `allow_fixup_commits = true` | `CCHK_ALLOW_FIXUP_COMMITS=true` | `--allow-fixup-commits=true` | -| `allow_wip_commits = false` | `CCHK_ALLOW_WIP_COMMITS=false` | `--allow-wip-commits=false` | -| `require_body = false` | `CCHK_REQUIRE_BODY=false` | `--require-body=false` | -| `require_signed_off_by = false` | `CCHK_REQUIRE_SIGNED_OFF_BY=false` | `--require-signed-off-by=false` | -| `ignore_authors = ["bot"]` | `CCHK_IGNORE_AUTHORS=bot,user` | `--ignore-authors=bot,user` | -| `author_email_pattern=^.+@example\.com$` | `CCHK_AUTHOR_EMAIL_PATTERN=^.+@example\.com$` | `--author-email-pattern=^.+@example\.com$` | -| `author_name_pattern=^.+ .+$` | `CCHK_AUTHOR_NAME_PATTERN=^.+ .+$` | `--author-name-pattern=^.+ .+$` | -| `conventional_branch = true` | `CCHK_CONVENTIONAL_BRANCH=true` | `--conventional-branch=true` | -| `allow_branch_types = ["feature"]` | `CCHK_ALLOW_BRANCH_TYPES=feature,bugfix` | `--allow-branch-types=feature,bugfix` | -| `allow_branch_names = ["develop"]` | `CCHK_ALLOW_BRANCH_NAMES=develop,staging` | `--allow-branch-names=develop,staging` | -| `require_rebase_target = "main"` | `CCHK_REQUIRE_REBASE_TARGET=main` | `--require-rebase-target=main` | -| `allow_force_push = true` | `CCHK_ALLOW_FORCE_PUSH=true` | `--no-force-push` (sets `allow_force_push` to `false`) | -| `ai_attribution = "forbid"` | `CCHK_AI_ATTRIBUTION=forbid` | `--ai-attribution=forbid` | -| `ignore_authors = ["bot"]` (in branch section) | `CCHK_BRANCH_IGNORE_AUTHORS=bot,user` | `--branch-ignore-authors=bot,user` | - -## Configuration Priority Example - -When the same option is specified in multiple places, the priority determines which value is used: - -```bash -# In cchk.toml: -# subject_max_length = 100 - -# Set via environment: -export CCHK_SUBJECT_MAX_LENGTH=80 - -# Override via CLI: -commit-check --message --subject-max-length=50 - -# Result: subject_max_length = 50 (CLI wins) -``` - -## Options Table Description - -| Section | Option | Type | Default | Description | -|---|---|---|---|---| -| commit | conventional_commits | bool | true | Enforce Conventional Commits specification. | -| commit | message_pattern | str | "" (disabled) | Custom regex pattern for commit message validation. When set, this pattern replaces the auto-generated Conventional Commits regex entirely, making it possible to enforce custom formats such as JIRA smart commits (e.g., `"^PROJ-\\d+: .+"`). When `message_pattern` is set (non-empty) it takes precedence over `conventional_commits`. | -| commit | subject_capitalized | bool | false | Subject must start with a capital letter. | -| commit | subject_imperative | bool | false | Subject must be in imperative mood. Forms of verbs can be found at [imperatives.py](https://github.com/commit-check/commit-check/blob/main/commit_check/imperatives.py) | -| commit | subject_max_length | int | 80 | Maximum length of the subject line. | -| commit | subject_min_length | int | 5 | Minimum length of the subject line. | -| commit | allow_commit_types | list[str] | ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "build", "ci"] | Allowed commit types when conventional_commits is true. | -| commit | allow_merge_commits | bool | true | Allow merge commits. | -| commit | allow_revert_commits | bool | true | Allow revert commits. | -| commit | allow_empty_commits | bool | true | Allow empty commits. | -| commit | allow_fixup_commits | bool | true | Allow fixup commits (e.g., "fixup! "). | -| commit | allow_wip_commits | bool | true | Allow work-in-progress commits (e.g., "WIP: "). | -| commit | require_body | bool | false | Require a body in the commit message. | -| commit | ignore_authors | list[str] | [] (none ignored) | List of commit authors **or co-authors** (`Co-authored-by:` lines) to bypass all commit checks. Useful for bots (e.g., `"dependabot[bot]"`, `"coderabbitai[bot]"`). | -| commit | author_email_pattern | str | `^.+@.+$` | Custom regex for the author email check. When empty, the built-in default pattern is used. This option only takes effect when the author_email check is enabled (`-e` / `--author-email`). | -| commit | author_name_pattern | str | "" (built-in default) | Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). This option only takes effect when the author_name check is enabled (`-n` / `--author-name`). | -| commit | require_signed_off_by | bool | false | Require "Signed-off-by" line in the commit message footer. | -| commit | ai_attribution | str | "ignore" | AI attribution policy. `"forbid"` rejects any commit containing known AI tool signatures (Claude Code, Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, and generic AI model patterns). `"ignore"` disables the check. This feature is a response to the industry-wide discussion on AI disclosure in open source (Linux kernel `Assisted-by:` trailer, CPython, VS Code, Apache, Fedora policies). | -| branch | conventional_branch | bool | true | Enforce Conventional Branch specification. | -| branch | allow_branch_types | list[str] | ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] | Allowed branch types when `conventional_branch` is true. The default is a superset of the [Conventional Branch spec](https://conventionalbranch.org/): the spec types (`feature`, `bugfix`, `hotfix`, `release`, `chore`) plus the Conventional Commit types (`build`, `ci`, `docs`, `perf`, `refactor`, `style`, `test`), AI agent prefixes (`ai`, `claude`, `codex`, `copilot`, `cursor`) and bot prefixes (`dependabot`, `renovate`). For strict spec-only validation, set this option explicitly (e.g. `["feature", "bugfix", "hotfix", "release", "chore"]`). | -| branch | allow_branch_names | list[str] | [] (empty list) | Additional standalone branch names allowed when conventional_branch is true (e.g., ["develop", "staging"]). By default, master, main, HEAD, and PR-* are always allowed. | -| branch | require_rebase_target | str | "" (no requirement) | Target branch for rebase requirement. If not set, no rebase validation is performed. | -| push | allow_force_push | bool | true | Allow force pushes. Set to `false` to block force pushes when used as a pre-push hook or with `--no-force-push`. | -| branch | ignore_authors | list[str] | [] (none ignored) | List of authors to ignore (i.e., always allow). | diff --git a/docs/example.md b/docs/example.md deleted file mode 100644 index df5fcaf3..00000000 --- a/docs/example.md +++ /dev/null @@ -1,376 +0,0 @@ -# Usage Examples - -This guide demonstrates how to use commit-check to validate commit messages, branch names, and author information. - -There are several ways to use commit-check: as a pre-commit hook, via STDIN, or directly with files. - -## Running as GitHub Action - -Please see [commit-check/commit-check-action](https://github.com/commit-check/commit-check-action) - -## Running as pre-commit hook - -1. **Install pre-commit:** - -!!! tip - - Make sure `pre-commit` is [installed](https://pre-commit.com/#install). - -```bash -pip install pre-commit -``` - -2. **Create .pre-commit-config.yaml:** - -```yaml -- repo: https://github.com/commit-check/commit-check - rev: the tag or revision - hooks: - - id: check-message - stages: [commit-msg] - - id: check-branch - - id: check-author-name - - id: check-author-email -``` - -3. **Install the hooks:** - -```bash -pre-commit install --hook-type pre-commit --hook-type commit-msg -``` - -4. **Test the integration:** - -```bash -# This will trigger validation automatically -git commit -m "feat: add new user authentication system" -``` - -### Pre-commit Validation Examples - -**✅ Successful Validation:** - -```text -$ git commit -m "feat: add user authentication system" - -check commit message.....................................................Passed -check committer name.....................................................Passed -check committer email....................................................Passed -[main abc1234] feat: add user authentication system -``` - -**❌ Failed Validation:** - -```text -$ git commit -m "bad commit message" - -check commit message.....................................................Failed -- hook id: check-message -- exit code: 1 - -Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ -(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ -(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - -Commit rejected. - -Type message check failed ==> bad commit message -It doesn't match regex: ^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+ -The commit message should follow Conventional Commits. See https://www.conventionalcommits.org -Suggest: Use (): with allowed types -``` - -## Running as CLI - -Commit-check provides several command-line options for different validation scenarios. via options or STDIN - -!!! tip - - Validate commit messages by piping them through STDIN. This is useful for - testing or scripting. - -Available Commands see [commit-check --help](cli.md) - -### Message Validation Examples - -```bash -# Validate message from STDIN -echo "feat: new feature" | commit-check -m - -# Validate message from file -commit-check -m commit_message.txt - -# Validate current git commit message (from git log) -commit-check -m -``` - -**Reading from file:** - -```bash -# Create a commit message file -cat > commit_message.txt << EOF -fix(auth): resolve login timeout issue - -Users were experiencing timeouts during login. -Increased session timeout and improved error handling. - -Fixes #123 -EOF - -# Validate from file -commit-check -m commit_message.txt - -# Or pipe file content -cat commit_message.txt | commit-check -m -``` - -### Branch Validation Examples - -```bash -# Check current branch name -commit-check --branch - -# Example valid branch names: -# - feature/user-auth -# - fix/login-bug -# - hotfix/security-patch -# - release/v1.2.0 -``` - -### Push Validation Examples - -```bash -# Check whether pushing HEAD to its configured upstream would require force -commit-check --no-force-push -``` - -```yaml -# Configure the dedicated pre-push hook -- repo: https://github.com/commit-check/commit-check - rev: the tag or revision - hooks: - - id: check-no-force-push - stages: [pre-push] -``` - -`git push | commit-check --no-force-push` is not a prevention mechanism. The -push has already started, and normal `git push` output does not include the -pre-push ref lines that Git provides to hooks. - -### Author Validation Examples - -```bash -# Check author name -commit-check --author-name - -# Check author email -commit-check --author-email - -# Check both author name and email -commit-check --author-name --author-email -``` - -### Configuration Examples - -```bash -# Use custom configuration file -echo "feat: test" | commit-check --config my-config.toml -m - -# Use configuration from different directory -commit-check --config /path/to/config/cchk.toml -m -``` - -### Valid Commit Message Examples - -```bash -# Basic feature -echo "feat: add user registration" | commit-check -m - -# Feature with scope -echo "feat(auth): implement OAuth2 login" | commit-check -m - -# Bug fix -echo "fix: resolve memory leak in parser" | commit-check -m - -# Documentation update -echo "docs: add installation guide" | commit-check -m - -# Breaking change -echo "feat!: redesign API endpoints" | commit-check -m - -# Merge commit (automatically allowed) -echo "Merge pull request #123 from feature/new-api" | commit-check -m -``` - -### Invalid Commit Message Examples - -```bash -# No type prefix -echo "added new feature" | commit-check -m - -# Capitalized (if configured to disallow) -echo "feat: Add new feature" | commit-check -m - -# Too short -echo "fix" | commit-check -m - -# Non-imperative mood -echo "feat: added login functionality" | commit-check -m - -# Unknown type -echo "unknown: some changes" | commit-check -m -``` - -### Error Output Examples - -**Commit Message Validation Failure:** - -```text -Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ -(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ -(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - -Commit rejected. - -Type message check failed ==> test commit message check -It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) -The commit message should follow Conventional Commits. See https://www.conventionalcommits.org -Suggest: Use (): with allowed types -``` - -**Branch Name Validation Failure:** - -```text -Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ -(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ -(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - -Commit rejected. - -Type branch check failed ==> test-branch -It doesn't match regex: ^(feature|bugfix|hotfix|release|chore|feat|fix)\/.+|(master)|(main)|(HEAD)|(PR-.+) -The branch should follow Conventional Branch. See https://conventionalbranch.org -Suggest: Use / with allowed types or ignore_authors in config branch section to bypass -``` - -**Commit Signature Validation Failure:** - -```text -Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ -(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ -(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - -Commit rejected. - -Type require_signed_off_by check failed ==> fix: add missing file -It doesn't match regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> -Signed-off-by not found in latest commit -Suggest: git commit --amend --signoff or use --signoff on commit -``` - -**Commit Message Validation Failure without ASCII Banner (`--no-banner`):** - -```text -Type message check failed ==> test commit message check -It doesn't match regex: ^(chore|ci|docs|feat|fix|refactor|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*) -The commit message should follow Conventional Commits. See https://www.conventionalcommits.org -Suggest: Use (): with allowed types -``` - -**Compact Failure Output (`--compact`):** - -```text -[FAIL] message: test commit message check -``` - -**Imperative Mood Validation Failure:** - -```text -Commit rejected by Commit-Check. - - (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) - / ._. \ / ._. \ / ._. \ / ._. \ / ._. \ - __\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__ -(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._) - || E || || R || || R || || O || || R || - _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ -(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.) - `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ - -Commit rejected. - -Type imperative check failed ==> fix: added missing file -It doesn't match regex: -Commit message should use imperative mood (e.g., 'Add feature' not 'Added feature') -Suggest: Use imperative mood in the subject line -``` - -## Integration Tips - -### CI/CD Integration - -You can use commit-check in CI/CD pipelines: - -```bash -# In your CI script -git log --format="%s" -n 1 | commit-check -m - -# or just -commit-check -m - -# Keep plain-text output but remove the ASCII art banner -git log --format="%s" -n 1 | commit-check -m --no-banner - -# Emit one machine-friendly line per failure without switching to JSON -git log --format="%s" -n 1 | commit-check -m --compact -``` - -### Scripting - -Use commit-check in scripts to validate commit messages programmatically: - -```bash -#!/bin/bash -# validate-commits.sh - -# Get all commit messages from last 10 commits -for i in {0..9}; do - msg=$(git log --format="%s" -n 1 --skip=$i) - if [ -n "$msg" ]; then - echo "Validating: $msg" - echo "$msg" | commit-check -m || exit 1 - fi -done - -echo "All commits are valid!" -``` - -For more configuration options, see the [Configuration Documentation](configuration.md). diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md deleted file mode 100644 index c915fc6a..00000000 --- a/docs/getting-started/installation.md +++ /dev/null @@ -1,78 +0,0 @@ -# Installation - -Commit Check runs anywhere Python does, and ships as a GitHub Action and an MCP -server for the places it doesn't. - -## Command line - -=== "pip" - - ```console - $ pip install commit-check - ``` - -=== "uv" - - ```console - $ uv tool install commit-check - ``` - -=== "pipx" - - ```console - $ pipx install commit-check - ``` - -Verify the install: - -```console -$ commit-check --version -``` - -The CLI is also available as `cchk`, which is the same program under a shorter -name. - -!!! tip "Supported Python versions" - - Commit Check supports Python 3.10 through 3.14, on Linux, macOS and Windows. - -## As a pre-commit hook - -No installation step — [pre-commit](https://pre-commit.com) fetches it for you. -See the [pre-commit guide](../guides/pre-commit.md). - -## As a GitHub Action - -No installation step. See the -[GitHub Actions guide](../guides/github-actions.md). - -## Verifying the download - -Releases are built with [SLSA Level 3](https://slsa.dev) provenance. To verify a -release artifact came from this repository's build pipeline: - -```console -$ gh attestation verify commit_check-*.whl --repo commit-check/commit-check -``` - -## Next steps - -
- -- :material-rocket-launch-outline:{ .lg .middle } __Quick start__ - - --- - - Catch your first bad commit in five minutes. - - [:octicons-arrow-right-24: Quick start](quickstart.md) - -- :material-book-open-variant:{ .lg .middle } __Rules reference__ - - --- - - Every rule, what it does, and why it matters. - - [:octicons-arrow-right-24: Rules](../rules.md) - -
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md deleted file mode 100644 index 11eef029..00000000 --- a/docs/getting-started/quickstart.md +++ /dev/null @@ -1,148 +0,0 @@ -# Quick start - -By the end of this page you will have Commit Check rejecting a bad commit -message on your machine, and you will understand what it is telling you. - -It takes about five minutes and needs nothing but a Git repository. - -## 1. Install - -```console -$ pip install commit-check -``` - -## 2. Watch it reject something - -Commit Check works with no configuration at all. Make a deliberately bad commit -in a scratch repository: - -```console -$ git init demo && cd demo -$ git commit --allow-empty -m "updated the parser" -``` - -Now check it: - -```console -$ commit-check --message -``` - -```text -CC001 message check failed ==> updated the parser -The commit message should follow Conventional Commits. See https://www.conventionalcommits.org -Suggest: Use (): , where is one of: feat, fix, docs, ... -Docs: https://docs.commit-check.com/rules/#cc001 -``` - -Four things are happening in that output, and each is deliberate: - -| Part | What it gives you | -|---|---| -| `CC001` | A stable rule ID. It will mean the same thing in five years. | -| `==> updated the parser` | The exact value that failed, not just "invalid message". | -| `Suggest:` | What to do about it. | -| `Docs:` | Why the rule exists, and how to configure or disable it. | - -## 3. Fix it - -```console -$ git commit --amend -m "fix(parser): handle empty input" -$ commit-check --message -``` - -No output and an exit code of `0`. Commit Check is quiet when it is happy. - -## 4. Check the branch too - -```console -$ git switch -c my-changes -$ commit-check --branch -``` - -```text -CC201 branch check failed ==> my-changes -The branch should follow Conventional Branch. See https://conventionalbranch.org -Suggest: Use / with allowed types -Docs: https://docs.commit-check.com/rules/#cc201 -``` - -Rename it to something structured and it passes: - -```console -$ git branch -m fix/empty-input -$ commit-check --branch -``` - -!!! tip "Checks are opt-in per run" - - `commit-check --message` never evaluates branch rules, and vice versa. Each - check is selected by its own flag, so you can run exactly what a given hook - or CI job needs. The - [rules reference](../rules.md) lists which flag activates each rule. - -## 5. Write down your policy - -So far you have been running the defaults. Create a `cchk.toml` in the -repository root — or in `.github/` — to make the policy explicit: - -```toml title="cchk.toml" -[commit] -conventional_commits = true -subject_imperative = true # (1)! -subject_max_length = 72 -allow_wip_commits = false # (2)! - -[branch] -conventional_branch = true -``` - -1. Off by default. Turning it on rejects `fixed a bug` in favour of `fix a bug`. -2. `allow_*` options describe what is *permitted*. Set to `false` to enforce. - -Run it again and the new rules apply: - -```console -$ commit-check --message -``` - -!!! warning "Defaults are not "nothing"" - - Even with no config file, Conventional Commits, Conventional Branch, subject - length limits of 5–80 characters, and author name/email patterns are - enforced. Check the *Default* column in the - [rules reference](../rules.md) before assuming a rule is off. - -## 6. Make it automatic - -Running the command by hand does not scale. Wire it into the two places it -belongs: - -
- -- :material-git:{ .lg .middle } __Before the commit lands__ - - --- - - A pre-commit hook rejects the message as you write it, so nothing bad - reaches the branch in the first place. - - [:octicons-arrow-right-24: Pre-commit guide](../guides/pre-commit.md) - -- :material-github:{ .lg .middle } __On every pull request__ - - --- - - A GitHub Action checks every commit in the PR and can comment on the PR - with what needs fixing. - - [:octicons-arrow-right-24: GitHub Actions guide](../guides/github-actions.md) - -
- -## Where to go next - -- **[Rules reference](../rules.md)** — every rule, what it does, why it matters, - and how to configure it. -- **[Configuration](../configuration.md)** — every option, its type and default, - plus the environment variable and CLI flag that override it. -- **[Why Commit Check](why.md)** — the reasoning behind the tool. diff --git a/docs/getting-started/why.md b/docs/getting-started/why.md deleted file mode 100644 index f1143126..00000000 --- a/docs/getting-started/why.md +++ /dev/null @@ -1,56 +0,0 @@ -# Why Commit Check - -## The problem - -Git history is a database that every team writes to and almost nobody validates. - -The cost shows up later, and indirectly. Release notes get written by hand -because commit subjects cannot be grouped. `git bisect` walks through merge -commits that record nothing but a sync. A commit is attributed to `ec2-user` -because a build box had no `user.name`. A contribution has to be rejected -months after the fact because it never carried a `Signed-off-by` trailer. - -None of these are caught by a linter, a type checker, or a test suite. They are -all caught by review, which means they are caught inconsistently, by whoever -happens to be looking, and only after the work is done. - -## The approach - -Commit Check treats commit metadata the way linters treat code: a policy written -down once, enforced identically everywhere, with a stable identifier for every -diagnostic so that findings can be discussed, suppressed, and tracked. - -**One config.** A single `cchk.toml` drives the CLI, the pre-commit hook, the -GitHub Action, and the MCP server. There is no second place where the rules can -disagree with themselves. - -**Fails where it is cheap.** The same check that runs in CI runs in your -`commit-msg` hook. Finding out that a subject is malformed takes a second -locally and a full CI cycle plus a force-push remotely. - -**Stable rule IDs.** Every rule has an ID like [CC003](../rules.md#cc003) that -never changes once released. You can cite it in a review comment, link to its -documentation, and eventually suppress it per-rule. - -**Explains itself.** A failure names the rule, quotes the offending value, says -how to fix it, and links to the reasoning. - -## Where it fits - -Commit Check is deliberately narrow: it validates *metadata*, not code. It is a -lightweight, open alternative to -[GitHub Enterprise metadata restrictions](https://docs.github.com/en/enterprise-server@3.11/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#metadata-restrictions) -and Bitbucket's paid -[Yet Another Commit Checker](https://marketplace.atlassian.com/apps/1211854/yet-another-commit-checker), -without requiring a particular forge or an enterprise plan. - -If you already run `ruff`, `eslint`, or `golangci-lint` on your source, Commit -Check is the equivalent for the commits that carry it. - -## What it is not - -- **Not a code linter.** It never reads your source files. -- **Not a replacement for review.** It enforces the mechanical rules so review - can spend its attention on the change itself. -- **Not opinionated by default.** Most rules are off until you turn them on. See - the [rules reference](../rules.md) for what applies out of the box. diff --git a/docs/guides/ai-attribution.md b/docs/guides/ai-attribution.md deleted file mode 100644 index 3f627e16..00000000 --- a/docs/guides/ai-attribution.md +++ /dev/null @@ -1,75 +0,0 @@ -# Set an AI attribution policy - -AI coding tools add trailers to commit messages identifying themselves. Whether -that is welcome, required, or unacceptable is a decision each project makes for -itself — and the industry has landed in different places: - -- The **Linux kernel** added an `Assisted-by:` trailer, treating AI assistance - as something to disclose. -- **Some projects disallow AI-assisted contributions outright**, usually over - provenance and licensing. -- **Most projects have no stated position**, which means the question resurfaces - in every code review. - -Commit Check does not take a side. It gives you a way to enforce whichever -position your project has already taken, so it stops being relitigated. - -## The default: no opinion - -```toml -[commit] -ai_attribution = "ignore" # the default -``` - -[CC013](../rules.md#cc013) is off. Commits carrying AI trailers pass, and so do -commits without them. - -## Forbidding AI-attributed commits - -```toml title="cchk.toml" -[commit] -ai_attribution = "forbid" -``` - -Commits carrying a recognised AI signature now fail: - -```text -CC013 ai_attribution check failed ==> feat: add caching layer -AI attribution policy violation -Suggest: This project forbids AI-assisted commits. Remove AI trailers and re-commit. -Docs: https://docs.commit-check.com/rules/#cc013 -``` - -### What counts as a signature - -Trailers and co-author lines naming Claude Code, GitHub Copilot, Codex, Gemini, -Cursor, Devin, Aider, Windsurf and Tabby, plus generic AI model patterns. - -!!! warning "This checks disclosure, not authorship" - - CC013 reads commit metadata. It detects a commit that *says* it was - AI-assisted; it cannot detect one that was AI-assisted and did not say so. - - Set against a policy of "no AI contributions", it is an honesty check on - contributors who are already following the rules — not an enforcement - mechanism against those who aren't. Be clear with yourself about which of - those you are buying. - -## Exempting automation - -Bots that legitimately carry AI trailers can be excluded: - -```toml title="cchk.toml" -[commit] -ai_attribution = "forbid" -ignore_authors = ["dependabot[bot]", "renovate[bot]"] -``` - -## Documenting the decision - -Whichever way you go, the config file is not where contributors look. State the -policy where they will see it — `CONTRIBUTING.md`, the pull request template — -and let Commit Check be the mechanism rather than the announcement. - -Enforcing an undocumented policy produces a confusing failure for somebody -acting in good faith. diff --git a/docs/guides/github-actions.md b/docs/guides/github-actions.md deleted file mode 100644 index 4dc95ae1..00000000 --- a/docs/guides/github-actions.md +++ /dev/null @@ -1,102 +0,0 @@ -# Run in GitHub Actions - -Local hooks can be skipped with `--no-verify`. A CI check cannot, which makes -GitHub Actions the place where your policy is actually a policy. - -## Minimal setup - -```yaml title=".github/workflows/commit-check.yml" -name: Commit Check - -on: - push: - pull_request: - branches: [main] - -jobs: - commit-check: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 # (1)! - - uses: commit-check/commit-check-action@v1 - with: - message: true - branch: true - author-name: true - author-email: true -``` - -1. Commit Check needs the full history to inspect every commit in the pull - request. Without this it only sees the most recent one. - -## Commenting on the pull request - -Instead of making contributors open the job log, have the Action post what -needs fixing directly on the PR: - -```yaml - - uses: commit-check/commit-check-action@v1 - with: - message: true - branch: true - pr-comments: ${{ github.event_name == 'pull_request' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -This needs extra permissions on the job: - -```yaml - permissions: - contents: read - pull-requests: write -``` - -## Reporting without failing - -While a team is adopting the policy, it is often better to report problems -without blocking merges. `dry-run` always exits `0`: - -```yaml - - uses: commit-check/commit-check-action@v1 - with: - message: true - dry-run: true -``` - -Turn it off once the history is clean. - -## Sharing config with local hooks - -The Action reads the same `cchk.toml` as the CLI, so a repository that already -has one needs no Action-specific configuration. That is the point: the rules -cannot drift between what a developer sees locally and what CI enforces. - -See [Configuration](../configuration.md) for where the file may live, and -[Organization-wide policy](organization.md) for sharing one across repositories. - -## Pull requests from forks - -A `pull_request` workflow triggered by a fork receives a **read-only** -`GITHUB_TOKEN`, and `permissions: pull-requests: write` does not override that. -`pr-comments` will therefore fail to post on fork pull requests unless the -repository has *Send write tokens to workflows from pull requests* enabled under -**Settings → Actions → General**. - -!!! warning "Do not reach for `pull_request_target` casually" - - `pull_request_target` does get a write token, but it runs in the context of - the base repository with access to its secrets. Checking out and executing - the fork's code under that trigger is the "pwn request" pattern and hands - repository access to anyone who can open a pull request. - - If you use it, check out the base branch only and never run code from the - pull request. - -The checks themselves still run on fork pull requests and still fail the build; -only the commenting is affected. diff --git a/docs/guides/organization.md b/docs/guides/organization.md deleted file mode 100644 index 1da9fd0e..00000000 --- a/docs/guides/organization.md +++ /dev/null @@ -1,100 +0,0 @@ -# Enforce one policy across an organization - -Copying `cchk.toml` into forty repositories works until the day you want to -change it. `inherit_from` lets each repository pull a shared base config and -override only what it genuinely needs. - -## The shared config - -Put the policy in a repository every project can read — GitHub's `.github` -repository is the conventional home: - -```toml title="my-org/.github → cchk.toml" -[commit] -conventional_commits = true -subject_imperative = true -subject_max_length = 72 -allow_merge_commits = false - -[branch] -conventional_branch = true -allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"] -``` - -## Inheriting it - -Each repository then needs one line: - -```toml title="any-repo → .github/cchk.toml" -inherit_from = "github:my-org/.github:cchk.toml" -``` - -Local settings win, so a project with a different constraint overrides just that -one option: - -```toml title="a-repo-with-longer-subjects → .github/cchk.toml" -inherit_from = "github:my-org/.github:cchk.toml" - -[commit] -subject_max_length = 100 # everything else comes from the org config -``` - -## Pinning the version - -By default the shorthand resolves to the parent repository's default branch, -which means a change to the org config takes effect everywhere on the next run. -That is usually what you want. When it isn't, pin to a ref: - -```toml -inherit_from = "github:my-org/.github@v1:cchk.toml" -``` - -## Other sources - -=== "GitHub shorthand" - - ```toml - inherit_from = "github:my-org/.github:cchk.toml" - ``` - -=== "Local path" - - ```toml - inherit_from = "../../shared/org-cchk.toml" - ``` - - Useful in a monorepo, where the shared config is already checked out. - -=== "HTTPS URL" - - ```toml - inherit_from = "https://example.com/shared/cchk.toml" - ``` - - Plain HTTP is rejected. - -!!! warning "Inheritance fails open" - - If the parent config is unreachable — a network blip, a renamed file, a - private repository — Commit Check silently falls back to the local config - rather than failing the build. This keeps CI green during an outage, but it - also means a typo in `inherit_from` is easy to miss. Verify the merged - result when you first set it up: - - ```console - $ commit-check --message --format json - ``` - -## Rolling it out - -Turning on a strict policy across an organization at once produces a wall of -red. A gentler sequence: - -1. Ship the org config with `dry-run` enabled in CI, so violations are reported - but nothing blocks. -2. Look at what actually fails. Some rules will turn out to be wrong for some - teams — that is information, not an obstacle. -3. Turn off `dry-run` for repositories whose history is already clean. -4. Tighten the shared config over time. - -See the [GitHub Actions guide](github-actions.md) for the `dry-run` input. diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md deleted file mode 100644 index 193f0f97..00000000 --- a/docs/guides/pre-commit.md +++ /dev/null @@ -1,89 +0,0 @@ -# Run as a pre-commit hook - -A pre-commit hook is the cheapest place to enforce commit policy: the developer -finds out while they are still writing the message, not after a CI round trip. - -## Setup - -Add Commit Check to `.pre-commit-config.yaml`: - -```yaml title=".pre-commit-config.yaml" -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 - hooks: - - id: check-message - - id: check-branch - - id: check-author-name - - id: check-author-email -``` - -Then install the hooks. `check-message` runs at the `commit-msg` stage, so it -needs its own install step: - -```console -$ pre-commit install --hook-type commit-msg -$ pre-commit install -``` - -That is it. The next malformed commit message is rejected before it exists. - -## Available hooks - -| Hook ID | Stage | Rules | -|---|---|---| -| `check-message` | `commit-msg` | [CC001–CC013](../rules.md#commit-message-rules) | -| `check-branch` | `pre-commit` | [CC201–CC202](../rules.md#branch-rules) | -| `check-author-name` | `pre-commit` | [CC101](../rules.md#cc101) | -| `check-author-email` | `pre-commit` | [CC102](../rules.md#cc102) | -| `check-no-force-push` | `pre-push` | [CC301](../rules.md#cc301) | - -`check-no-force-push` also needs its own install: - -```console -$ pre-commit install --hook-type pre-push -``` - -## Configuring without a TOML file - -Options can be passed as hook arguments, which keeps everything in one file: - -```yaml title=".pre-commit-config.yaml" -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 - hooks: - - id: check-message - args: - - --subject-imperative=true - - --subject-max-length=72 - - --allow-merge-commits=false -``` - -A `cchk.toml` is usually the better choice once you have more than a couple of -options, because CI and the CLI read it too. See -[Configuration](../configuration.md) for the precedence rules. - -## Skipping a hook - -Occasionally you need to get a commit through — a mid-rebase fixup, an -automated migration. `pre-commit` supports this natively: - -```console -$ SKIP=check-message git commit -m "wip" -``` - -!!! warning "Local hooks are not a policy boundary" - - Anyone can pass `--no-verify`. Hooks exist to give fast feedback to people - who want to follow the policy, not to stop people who don't. Pair them with - the [GitHub Action](github-actions.md), which runs where it cannot be - skipped. - -## Troubleshooting - -If `check-message` never seems to run, it is almost always because -`pre-commit install --hook-type commit-msg` was not run — a plain -`pre-commit install` only wires up the `pre-commit` stage. - -More in [Troubleshooting](../troubleshoot.md). diff --git a/docs/guides/signoff.md b/docs/guides/signoff.md deleted file mode 100644 index 578c37fb..00000000 --- a/docs/guides/signoff.md +++ /dev/null @@ -1,81 +0,0 @@ -# Require signoff (DCO) - -Projects that use the [Developer Certificate of Origin](https://developercertificate.org/) -require every commit to carry a `Signed-off-by` trailer. The Linux kernel and -much of the CNCF work this way. - -A DCO bot rejecting a pull request after the fact is a poor experience: the -contributor has to rewrite history for every commit in the branch. Checking -locally fixes it before it becomes a problem. - -## Turn it on - -```toml title="cchk.toml" -[commit] -require_signed_off_by = true -``` - -This enables [CC012](../rules.md#cc012), which is off by default. - -## Signing off - -```console -$ git commit --signoff -m "fix: handle an empty config file" -``` - -The trailer is appended automatically from your `user.name` and `user.email`: - -```text -fix: handle an empty config file - -Signed-off-by: Your Name -``` - -Forgot it? Fix the last commit in place: - -```console -$ git commit --amend --signoff --no-edit -``` - -Fix a whole branch: - -```console -$ git rebase --signoff main -``` - -!!! tip "Make it automatic" - - Signing off is easy to forget. Combine this rule with the - [pre-commit hook](pre-commit.md) so a missing trailer is caught at commit - time, not at review time. - -## Identity matters - -The DCO is a statement about who wrote the code, so it only means something if -the identity is real. [CC101](../rules.md#cc101) and -[CC102](../rules.md#cc102) check the committer name and email, and are enabled -by default when their check runs: - -```console -$ commit-check --author-name --author-email -``` - -To require a company address: - -```toml title="cchk.toml" -[commit] -author_email_pattern = "^.+@example\\.com$" -``` - -## Bots - -Automation cannot meaningfully sign the DCO, and forcing it to produces -meaningless trailers. Exempt bots instead: - -```toml title="cchk.toml" -[commit] -require_signed_off_by = true -ignore_authors = ["dependabot[bot]", "renovate[bot]"] -``` - -`ignore_authors` matches the commit author and any `Co-authored-by:` trailers. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 1e203451..00000000 --- a/docs/index.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: Commit Check -description: Enforce commit message, branch naming, author and signoff standards across your CLI, pre-commit hooks, CI, and AI agents. -hide: - - navigation - - toc ---- - -
- -# Clean commits. Clear standards. - -Commit Check enforces the rules your Git history already depends on — commit -messages, branch names, committer identity, signoff — from one config, in every -place your team writes code. - -[Get started :octicons-arrow-right-24:](getting-started/quickstart.md){ .md-button .md-button--primary } -[Browse the rules](rules.md){ .md-button } - -
- ---- - -## One config, enforced everywhere - -Write the policy once. The same rules run on a developer's laptop, in CI, and in -whatever your AI agent is committing on your behalf. - -=== "Command line" - - ```console - $ commit-check --message --branch - CC003 subject_imperative check failed ==> docs: revamped the profile - Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') - Suggest: Change the first verb to imperative form - Docs: https://docs.commit-check.com/rules/#cc003 - ``` - -=== "pre-commit" - - ```yaml title=".pre-commit-config.yaml" - repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 - hooks: - - id: check-message - - id: check-branch - - id: check-author-email - ``` - -=== "GitHub Actions" - - ```yaml title=".github/workflows/commit-check.yml" - - uses: commit-check/commit-check-action@v1 - with: - message: true - branch: true - pr-comments: ${{ github.event_name == 'pull_request' }} - ``` - -=== "AI agents" - - ```json title="MCP server" - { - "mcpServers": { - "commit-check": { "command": "commit-check-mcp" } - } - } - ``` - -## What it checks - -
- -- :material-message-text-outline:{ .lg .middle } __Commit messages__ - - --- - - Conventional Commits by default, or your own pattern. Subject length, mood, - capitalisation, required body, forbidden merge/fixup/WIP commits. - - [:octicons-arrow-right-24: CC001–CC013](rules.md#commit-message-rules) - -- :material-source-branch:{ .lg .middle } __Branch names__ - - --- - - Conventional Branch naming, plus rebase checks that catch a branch drifting - behind its target before CI wastes a run on stale code. - - [:octicons-arrow-right-24: CC201–CC202](rules.md#branch-rules) - -- :material-account-check-outline:{ .lg .middle } __Committer identity__ - - --- - - Catch commits authored by `ec2-user` on a build box, or require everyone to - contribute from a company address. - - [:octicons-arrow-right-24: CC101–CC102](rules.md#author-rules) - -- :material-file-sign:{ .lg .middle } __Signoff and DCO__ - - --- - - Require the `Signed-off-by` trailer locally, so contributors find out before - CI rejects the pull request. - - [:octicons-arrow-right-24: Signoff guide](guides/signoff.md) - -- :material-robot-outline:{ .lg .middle } __AI attribution__ - - --- - - Whatever your project has decided about AI-assisted commits, enforce it - mechanically instead of relitigating it in review. - - [:octicons-arrow-right-24: AI attribution guide](guides/ai-attribution.md) - -- :material-office-building-outline:{ .lg .middle } __Org-wide policy__ - - --- - - Inherit a base config from a shared repository, then let each project - override only what it needs. - - [:octicons-arrow-right-24: Organization guide](guides/organization.md) - -
- -## Built to be trusted - -
- -- :material-shield-check:{ .lg .middle } __SLSA Level 3__ - - --- - - Build provenance with artifact attestation you can verify before - installing. - -- :material-tag-outline:{ .lg .middle } __Stable rule IDs__ - - --- - - Every diagnostic carries an ID like `CC003` that never changes, so you can - cite it in review, suppress it, or feed it to tooling. - -- :material-source-commit:{ .lg .middle } __Used in production__ - - --- - - Running at Apache, Texas Instruments, Mila, and - [many more](https://github.com/commit-check/commit-check-action/network/dependents). - -
- -## Ready in two minutes - -```console -$ pip install commit-check -$ commit-check --message --branch -``` - -No configuration file needed to start — sensible defaults apply immediately, and -you tighten them when you are ready. - -[Install :octicons-arrow-right-24:](getting-started/installation.md){ .md-button .md-button--primary } -[Why Commit Check?](getting-started/why.md){ .md-button } diff --git a/docs/migration.md b/docs/migration.md deleted file mode 100644 index 45d57ffa..00000000 --- a/docs/migration.md +++ /dev/null @@ -1,189 +0,0 @@ -# Migration Guide - -This guide helps you migrate from commit-check v1.x (YAML configuration) to v2.0+ (TOML configuration). - -## Overview - -Version 2.0 introduces significant changes to commit-check: - -* **Configuration format**: `.commit-check.yml` → `cchk.toml` or `commit-check.toml` -* **Simplified architecture**: New validation engine with cleaner design -* **Enhanced functionality**: Better error messages and more flexible configuration options - -## Quick Migration Steps - -1. **Backup your existing configuration**: - -```bash -cp .commit-check.yml .commit-check.yml.backup -``` - -2. **Create new TOML configuration**: - -```bash -touch cchk.toml # or commit-check.toml -``` - -3. **Convert YAML to TOML format** (see examples below) - -4. **Test the new configuration**: - -```bash -commit-check --help -commit-check --message --branch --author-name --author-email --dry-run -``` - -5. **Remove old YAML file**: - -```bash -rm .commit-check.yml.backup # after confirming everything works -``` - -## Configuration Format Changes - -The configuration structure has changed from YAML to TOML format - -### YAML (v1.x) vs TOML (v2.0+) - -**Old YAML format** (`.commit-check.yml`): - -```yaml -checks: -- check: message - regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' - error: "The commit message should be structured as follows:\n\n - [optional scope]: \n - [optional body]\n - [optional footer(s)]\n\n - More details please refer to https://www.conventionalcommits.org" - suggest: please check your commit message whether matches above regex - -- check: branch - regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) - error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" - suggest: run command `git checkout -b type/branch_name` - -- check: author_name - regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) - error: The committer name seems invalid - suggest: run command `git config user.name "Your Name"` - -- check: author_email - regex: ^.+@.+$ - error: The committer email seems invalid - suggest: run command `git config user.email yourname@example.com` - -- check: commit_signoff - regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> - error: Signed-off-by not found in latest commit - suggest: run command `git commit -m "conventional commit message" --signoff` - -- check: merge_base - regex: main # it can be master, develop, devel etc based on your project. - error: Current branch is not rebased onto target branch - suggest: Please ensure your branch is rebased with the target branch - -- check: imperative - regex: '' # Not used for imperative mood check - error: 'Commit message should use imperative mood (e.g., "Add feature" not "Added feature")' - suggest: 'Use imperative mood in commit message like "Add", "Fix", "Update", "Remove"' -``` - -**New TOML format** (`cchk.toml` or `commit-check.toml`): - -```toml -[commit] -# https://www.conventionalcommits.org -conventional_commits = true -subject_capitalized = false -subject_imperative = true -subject_max_length = 80 -subject_min_length = 5 -allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "ci"] -allow_merge_commits = true -allow_revert_commits = true -allow_empty_commits = false -allow_fixup_commits = true -allow_wip_commits = false -require_body = false -require_signed_off_by = false -ignore_authors = ["dependabot[bot]", "copilot[bot]"] - -[branch] -# https://conventionalbranch.org -conventional_branch = true -allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix"] -require_rebase_target = "main" -``` - -### CLI Changes - -The command-line interface has been simplified: - -**Old CLI** (v1.x): - -```bash -commit-check --config .commit-check.yml -``` - -**New CLI** (v2.0+): - -```bash -commit-check --config cchk.toml # or commit-check.toml -# Or use defaults (no config file needed) -commit-check --message --branch -``` - -### Custom Regex (`message_pattern`) - -If you relied on the custom `regex` field in v1.x to enforce a non-Conventional-Commits -format (e.g. JIRA smart commits `PROJ-123: description`), use the `message_pattern` -option in the `[commit]` section: - -```toml -[commit] -message_pattern = "^PROJ-\\d+: .+" -``` - -When `message_pattern` is set (non-empty), it replaces the auto-generated Conventional -Commits regex entirely, giving you full control over the accepted message format. - -## Troubleshooting - -### Common Issues - -**Issue**: "Configuration file not found" - -**Solution**: Ensure your file is named `cchk.toml` or `commit-check.toml` and placed in the repository root or in the `.github` folder. - -**Issue**: "Invalid TOML syntax" - -**Solution**: Use a TOML validator or check the syntax. Common issues include: - -* Missing quotes around strings -* Incorrect boolean values (use `true`/`false`, not `True`/`False`) -* Invalid array syntax - -**Issue**: "Validation rules not working as expected" - -**Solution**: Check the [Configuration Documentation](configuration.md) for the correct option names and formats. - -### Validation and Testing - -After migration, test your configuration: - -```bash -# Test commit message validation -echo "feat: test commit message" | commit-check --message - -# Test branch validation -commit-check --branch - -# Test with dry-run flag -commit-check --message --branch --author-name --author-email --dry-run -``` - -## Getting Help - -* **Documentation**: Check the [Configuration Guide](configuration.md) -* **Issues**: Report problems on [GitHub Issues](https://github.com/commit-check/commit-check/issues) diff --git a/docs/rules.md b/docs/rules.md deleted file mode 100644 index fdd0db18..00000000 --- a/docs/rules.md +++ /dev/null @@ -1,696 +0,0 @@ -# Rules - -Every check that can report a failure has a **stable rule ID**. Rule IDs never -change once released, so they are safe to reference in commit messages, code -review comments, issue templates, and tooling. - -Rule IDs appear in commit-check's output and in `--format json` results: - -```text -CC003 subject_imperative check failed ==> docs: revamped the profile -Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') -Suggest: Change the first verb to imperative form, e.g., 'fix' instead of 'fixed' -Docs: https://docs.commit-check.com/rules.html#cc003 -``` - -`--compact` prints one line per failure, keeping the rule ID and dropping the -explanation, suggestion, and documentation link: - -```text -[FAIL] CC003 subject_imperative: docs: revamped the profile -``` - -## How to read this page - -Rule IDs are grouped by what they inspect: - -| Range | Category | Inspects | -|---|---|---| -| `CC0xx` | [Commit message](#commit-message-rules) | The subject, body, and trailers of a commit message | -| `CC1xx` | [Author](#author-rules) | The committer's configured name and email | -| `CC2xx` | [Branch](#branch-rules) | The current branch's name and its position relative to a target branch | -| `CC3xx` | [Push](#push-rules) | The push operation itself | - -Two things determine whether a rule runs: - -**The check you select.** commit-check only evaluates the checks you ask for on -the command line. `commit-check --message` never reports a branch rule. The -*Check* column in the tables below shows which flag activates each rule. - -**Its configuration.** Within a selected check, the *Default* column shows -whether the rule is active with no configuration at all: - -| Default | Meaning | -|---|---| -| ✅ On | Enforced out of the box. Disable it through the listed option. | -| ⚪ Off | Not enforced until you opt in through the listed option. | - -Rules that are off by default are not lesser rules — they encode conventions -that are right for some projects and wrong for others. Turning on -[CC002](#cc002) makes sense for a project that capitalizes subjects, and is -actively harmful for one that does not. - -!!! tip - - Every option named below is documented in full — with its type, default, and - the matching environment variable and CLI flag — in - [configuration](configuration.md). - -## Rule index - -### Commit message rules (`CC0xx`) { #commit-message-rules } - -Run with `-m` / `--message`. - -| Code | Name | Message | Check | Default | -|---|---|---|---|---| -| [CC001](#cc001) | `message` | The commit message should follow Conventional Commits | `-m` | ✅ On | -| [CC002](#cc002) | `subject-capitalized` | Subject must start with a capital letter | `-m` | ⚪ Off | -| [CC003](#cc003) | `subject-imperative` | Commit message should use imperative mood | `-m` | ⚪ Off | -| [CC004](#cc004) | `subject-max-length` | Subject must be at most `{max_len}` characters | `-m` | ✅ On | -| [CC005](#cc005) | `subject-min-length` | Subject must be at least `{min_len}` characters | `-m` | ✅ On | -| [CC006](#cc006) | `allow-merge-commits` | Merge commits are not allowed | `-m` | ⚪ Off | -| [CC007](#cc007) | `allow-revert-commits` | Revert commits are not allowed | `-m` | ⚪ Off | -| [CC008](#cc008) | `allow-empty-commits` | Empty commit messages are not allowed | `-m` | ⚪ Off | -| [CC009](#cc009) | `allow-fixup-commits` | Fixup commits are not allowed | `-m` | ⚪ Off | -| [CC010](#cc010) | `allow-wip-commits` | WIP commits are not allowed | `-m` | ⚪ Off | -| [CC011](#cc011) | `require-body` | Commit body is required | `-m` | ⚪ Off | -| [CC012](#cc012) | `require-signed-off-by` | Signed-off-by not found in latest commit | `-m` | ⚪ Off | -| [CC013](#cc013) | `ai-attribution` | AI attribution policy violation | `-m` | ⚪ Off | - -### Author rules (`CC1xx`) { #author-rules } - -| Code | Name | Message | Check | Default | -|---|---|---|---|---| -| [CC101](#cc101) | `author-name` | The committer name seems invalid | `-n` | ✅ On | -| [CC102](#cc102) | `author-email` | The committer's email seems invalid | `-e` | ✅ On | - -### Branch rules (`CC2xx`) { #branch-rules } - -Run with `-b` / `--branch`. - -| Code | Name | Message | Check | Default | -|---|---|---|---|---| -| [CC201](#cc201) | `branch` | The branch should follow Conventional Branch | `-b` | ✅ On | -| [CC202](#cc202) | `merge-base` | Current branch is not rebased onto target branch | `-b` | ⚪ Off | - -### Push rules (`CC3xx`) { #push-rules } - -| Code | Name | Message | Check | Default | -|---|---|---|---|---| -| [CC301](#cc301) | `no-force-push` | Force push is not allowed | `--no-force-push` | ⚪ Off | - -## Commit message rules - -### message (CC001) { #cc001 } - -**What it does** - -Checks that the commit message subject follows the -[Conventional Commits](https://www.conventionalcommits.org) specification: -`()!: `. - -**Why is this bad?** - -A free-form subject can only be read by a human. A structured one can be read by -tooling: release-drafting can group changes by type, semantic versioning can -infer whether a release is a patch, minor, or major, and `git log` becomes -filterable by area of the codebase. Once a fraction of the history is -unstructured, every consumer of that history needs a fallback path. - -**Example** - -```text -updated the parser -``` - -Use instead: - -```text -fix(parser): handle empty input -``` - -**Options** - -* `commit.conventional_commits` — set to `false` to disable this rule. -* `commit.allow_commit_types` — the accepted `` values. -* `commit.message_pattern` — a custom regex that replaces the generated - Conventional Commits pattern entirely, for formats such as JIRA smart commits - (`"^PROJ-\\d+: .+"`). - -### subject-capitalized (CC002) { #cc002 } - -**What it does** - -Checks that the description in the subject line starts with a capital letter. - -**Why is this bad?** - -Nothing is inherently wrong with either casing — but mixing them is. A history -where half the subjects read `fix: handle empty input` and the other half read -`fix: Handle empty input` looks careless in `git log --oneline`, and gives -reviewers a pointless thing to comment on. This rule picks the capitalized -convention and enforces it. - -Leave it off if your project deliberately uses lowercase descriptions, which is -the more common convention among projects that follow Conventional Commits. - -**Example** - -```text -fix: handle empty input -``` - -Use instead: - -```text -fix: Handle empty input -``` - -**Options** - -* `commit.subject_capitalized` — set to `true` to enable this rule. - -### subject-imperative (CC003) { #cc003 } - -**What it does** - -Checks that the first word of the description is in the imperative mood — -`fix`, not `fixed`, `fixes`, or `fixing`. - -**Why is this bad?** - -This is Git's own convention: a subject should complete the sentence *"If -applied, this commit will ___"*. `If applied, this commit will fixed a crash` -does not read as English. Beyond grammar, the imperative form is the shortest of -the three, which matters on a line that tooling truncates around 50 characters. - -**Example** - -```text -fix: fixed a crash when the config file is empty -``` - -Use instead: - -```text -fix: handle an empty config file -``` - -**Options** - -* `commit.subject_imperative` — set to `true` to enable this rule. - -The list of recognised non-imperative verb forms lives in -[imperatives.py](https://github.com/commit-check/commit-check/blob/main/commit_check/imperatives.py). - -### subject-max-length (CC004) { #cc004 } - -**What it does** - -Checks that the subject line is at most a configured number of characters. - -**Why is this bad?** - -Long subjects get truncated by the tools that display them — -`git log --oneline`, `git shortlog`, GitHub's commit list, and most Git -GUIs all cut off somewhere between 50 and 72 columns. A subject that carries its -meaning past that point loses it exactly where people skim. Detail belongs in -the body, which nothing truncates. - -**Example** - -```text -fix: handle an empty config file and also fix the unrelated crash in the branch parser that happens on Windows -``` - -Use instead: - -```text -fix: handle an empty config file - -Also fixes the branch parser crash on Windows, which shared the -same root cause. -``` - -**Options** - -* `commit.subject_max_length` — the limit, in characters. Defaults to `80`. - `50` and `72` are the other conventional choices. - -### subject-min-length (CC005) { #cc005 } - -**What it does** - -Checks that the subject line is at least a configured number of characters. - -**Why is this bad?** - -Subjects like `fix`, `wip`, or `.` describe nothing. They are invisible in -a blame view and useless in a bisect session, and they are almost always the -result of a hurried commit rather than a deliberate one. - -**Example** - -```text -fix: bug -``` - -Use instead: - -```text -fix: reject config files with a null inherit_from -``` - -**Options** - -* `commit.subject_min_length` — the minimum, in characters. Defaults to `5`. - -### allow-merge-commits (CC006) { #cc006 } - -**What it does** - -Rejects merge commits — the `Merge branch '...'` commits that `git pull` -creates. - -**Why is this bad?** - -Merge commits created by `git pull` carry no information: they record that -someone synced, not that anything was decided. On a busy repository they can -outnumber real commits, which makes `git log` unreadable, adds branches for -`git bisect` to walk, and breaks the assumption behind -`git log --first-parent`. Projects that want a linear history rebase instead. - -**Example** - -```bash -git pull -``` - -Use instead: - -```bash -git pull --rebase - -# or make it the default -git config --global pull.rebase true -``` - -**Options** - -* `commit.allow_merge_commits` — set to `false` to enable this rule. - -### allow-revert-commits (CC007) { #cc007 } - -**What it does** - -Rejects the `Revert "..."` commits that `git revert` generates. - -**Why is this bad?** - -A generated revert subject describes the mechanics of the change and nothing -about the reason for it. Six months later, `Revert "feat: add caching layer"` -answers "what happened" but not the only question that matters: why the feature -was backed out, and whether it is safe to try again. - -**Example** - -```text -Revert "feat: add caching layer" -``` - -Use instead: - -```text -fix: remove the caching layer - -The cache served stale permissions after a role change (#412). -Reverts 4a1c9f2; re-land once invalidation is keyed on role version. -``` - -**Options** - -* `commit.allow_revert_commits` — set to `false` to enable this rule. - -### allow-empty-commits (CC008) { #cc008 } - -**What it does** - -Rejects commits with an empty message. - -**Why is this bad?** - -A commit with no subject cannot be searched for, summarised, or reviewed. It is -a gap in the history that nobody can fill in later. - -**Options** - -* `commit.allow_empty_commits` — set to `false` to enable this rule. - -### allow-fixup-commits (CC009) { #cc009 } - -**What it does** - -Rejects `fixup!` and `squash!` commits. - -**Why is this bad?** - -These commits exist to be consumed by `git rebase --autosquash` before a -branch is merged. One that survives to the target branch means the autosquash -was forgotten — leaving behind a commit that, by construction, does not stand on -its own. - -**Example** - -```text -fixup! feat: add the caching layer -``` - -Use instead: - -```bash -git rebase -i --autosquash main -``` - -**Options** - -* `commit.allow_fixup_commits` — set to `false` to enable this rule. - -### allow-wip-commits (CC010) { #cc010 } - -**What it does** - -Rejects work-in-progress commits — subjects beginning with `WIP`. - -**Why is this bad?** - -A WIP commit is an explicit statement that the change is not finished. That is -useful on a local branch and wrong on a shared one, where every commit is -something another developer may bisect through or build on. - -**Example** - -```text -WIP: caching -``` - -Use instead: - -```bash -# keep the work, drop the marker -git commit --amend -m "feat: add a caching layer for role lookups" -``` - -**Options** - -* `commit.allow_wip_commits` — set to `false` to enable this rule. - -### require-body (CC011) { #cc011 } - -**What it does** - -Requires a non-empty body after the subject line. - -**Why is this bad?** - -The subject says *what* changed; the diff already says that too. The body says -*why* — the constraint, the bug report, the rejected alternative. That reasoning -is the one thing that cannot be recovered from the code later, and it is exactly -what the next person to touch the change needs. - -**Example** - -```text -fix: cap the retry backoff at 30s -``` - -Use instead: - -```text -fix: cap the retry backoff at 30s - -The unbounded exponential backoff reached 45 minutes during the -incident on 2026-05-11, long after the upstream had recovered. -``` - -**Options** - -* `commit.require_body` — set to `true` to enable this rule. - -### require-signed-off-by (CC012) { #cc012 } - -**What it does** - -Requires a `Signed-off-by:` trailer in the commit message. - -**Why is this bad?** - -Projects that use the -[Developer Certificate of Origin](https://developercertificate.org/) — the -Linux kernel, and much of the -CNCF — treat that trailer as the contributor's statement that they have the -right to submit the code. A commit without it cannot be merged, so catching it -locally saves a round trip through CI. - -**Example** - -```bash -git commit -m "fix: handle an empty config file" -``` - -Use instead: - -```bash -git commit --signoff -m "fix: handle an empty config file" - -# or fix the commit you already made -git commit --amend --signoff -``` - -**Options** - -* `commit.require_signed_off_by` — set to `true` to enable this rule. - -### ai-attribution (CC013) { #cc013 } - -**What it does** - -Rejects commits carrying the signatures that AI coding tools add to commit -messages — trailers naming Claude Code, Copilot, Codex, Gemini, Cursor, Devin, -Aider, Windsurf, Tabby, and generic AI model patterns. - -**Why is this bad?** - -Whether AI-assisted commits are acceptable is a policy question, and projects -have landed on different answers: the Linux kernel added an `Assisted-by:` -trailer, while others disallow the practice outright. This rule exists for -projects that have made that decision and want it enforced mechanically rather -than relitigated in every code review. - -It is off by default, and the default policy is `"ignore"`. Enable it only if -your project has a stated position. - -**Options** - -* `commit.ai_attribution` — `"forbid"` enables this rule, `"ignore"` - (the default) disables it. - -## Author rules - -### author-name (CC101) { #cc101 } - -**What it does** - -Checks the committer's configured name against a pattern. The built-in pattern -accepts letters (including accented Latin characters), spaces, and -`, . ' -`, and always allows `[bot]` accounts. - -**Why is this bad?** - -When `user.name` is unset, Git falls back to the machine's account name. -Histories built in CI containers and on fresh VMs fill up with commits by -machine accounts — authorship that cannot be traced back to a person, which -matters for both code archaeology and compliance. - -**Example** - -```bash -git config user.name ec2-user -``` - -Use instead: - -```bash -git config --global user.name "Your Name" -``` - -**Options** - -* `commit.author_name_pattern` — a custom regex replacing the built-in - pattern. For example, `"^.+ .+$"` to require a full name. - -!!! note - - The built-in pattern accepts any name made of letters, spaces, and - `, . ' -`, so plain account names such as `root` or `ubuntu` still - pass it — only names containing digits or other symbols are rejected. Set - `author_name_pattern` if you need something stricter. - -### author-email (CC102) { #cc102 } - -**What it does** - -Checks the committer's configured email against a pattern. The built-in pattern -is `^.+@.+$`, which only requires an `@` with something on either side. - -**Why is this bad?** - -An address with no `@` is not routable, so it breaks the link between a commit -and its author: forges cannot attribute the commit to an account, and -mailmap-based tooling cannot merge identities. - -The built-in pattern is deliberately permissive — it is a sanity check, not a -policy. Its real value comes from replacing it, which is how organisations -require contributions to come from a corporate address. - -**Example** - -With `author_email_pattern = "^.+@example\\.com$"` configured: - -```bash -git config user.email you@gmail.com -``` - -Use instead: - -```bash -git config --global user.email you@example.com -``` - -**Options** - -* `commit.author_email_pattern` — the regex to match against. Defaults to - `^.+@.+$`; set something like `"^.+@example\\.com$"` to require a company - domain. - -!!! note - - Because the built-in pattern only looks for an `@`, local and placeholder - addresses such as `root@localhost` pass it. Set `author_email_pattern` - if you need to reject those. - -## Branch rules - -### branch (CC201) { #cc201 } - -**What it does** - -Checks that the current branch name follows the -[Conventional Branch](https://conventionalbranch.org/) specification: -`/`. - -`master`, `main`, `HEAD`, and `PR-*` are always accepted. - -**Why is this bad?** - -A predictable prefix is something automation can act on: CI can skip expensive -jobs for `docs/` branches, deployment workflows can key off `release/`, and -branch protection rules can be written per type. It also makes a list of a -hundred open branches scannable, which an unstructured list never is. - -**Example** - -```text -my-fix -johns-branch-2 -``` - -Use instead: - -```text -fix/empty-config-crash -feature/role-caching -``` - -**Options** - -* `branch.conventional_branch` — set to `false` to disable this rule. -* `branch.allow_branch_types` — the accepted `` values. The default is - a superset of the specification: the spec types plus the Conventional Commit - types, AI agent prefixes (`ai`, `claude`, `codex`, `copilot`, - `cursor`), and bot prefixes (`dependabot`, `renovate`). Set it - explicitly for strict spec-only validation. -* `branch.allow_branch_names` — additional standalone names to accept, such as - `["develop", "staging"]`. -* `branch.ignore_authors` — bypass the check for specific authors. - -### merge-base (CC202) { #cc202 } - -**What it does** - -Checks that the current branch is rebased onto a target branch. - -**Why is this bad?** - -A branch that has fallen behind is tested against code that no longer exists on -the target. CI passing on it says little about whether it will pass after -merging, and the failures it hides — a renamed function, a changed migration — -surface on the target branch instead of the pull request. - -**Example** - -```bash -# branch was cut from main three weeks ago -git push -``` - -Use instead: - -```bash -git fetch origin -git rebase origin/main -git push --force-with-lease -``` - -**Options** - -* `branch.require_rebase_target` — the target branch, for example `"main"`. - Unset by default, meaning no rebase requirement. - -## Push rules - -### no-force-push (CC301) { #cc301 } - -**What it does** - -Blocks force pushes. Run it as a `pre-push` hook, where it reads the push -details from stdin, or with `--no-force-push`, where it compares the current -branch against its upstream. - -**Why is this bad?** - -A force push to a shared branch rewrites history that other people have already -based work on. Their next pull produces conflicts against commits that no longer -exist, and any commit pushed between their fetch and the force push is silently -dropped. On a personal branch this is a routine part of rebasing; on a shared -one it is a data-loss event. - -**Example** - -```bash -git push --force -``` - -Use instead: - -```bash -# on a shared branch, add a commit rather than rewriting -git revert - -# on your own branch, at least refuse to clobber someone else's work -git push --force-with-lease -``` - -**Options** - -* `push.allow_force_push` — set to `false` to enable this rule. diff --git a/docs/troubleshoot.md b/docs/troubleshoot.md deleted file mode 100644 index a84ca418..00000000 --- a/docs/troubleshoot.md +++ /dev/null @@ -1,41 +0,0 @@ -# Troubleshooting - -## How to Skip Author Name Check - -In some cases, Commit Check may fail due to an invalid `author_name`, as shown below: - -```shell -check committer name.....................................................Failed -- hook id: check-author-name -- exit code: 1 - -Commit rejected by Commit-Check. - -Type author_name check failed => 12 -It doesn't match regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) -The committer name seems invalid -Suggest: run command `git config user.name "Your Name"` -``` - -To fix it, you can either update your Git config or temporarily skip the check using one of the following methods. - -### Bypass All Hooks - -Use the `--no-verify` flag to skip the pre-commit hook: - -```shell -# Amend the commit without running hooks -git commit --amend --author="Xianpeng Shen " --no-edit --no-verify -``` - -### Bypass A Specific Hook - -Alternatively, use the `SKIP=your-hook-name` environment variable, like below: - -```shell -# Set the correct Git author name -git config user.name "Xianpeng Shen" - -# Force amend while skipping the specified hook -SKIP=check-author-name git commit --amend --author="Xianpeng Shen " --no-edit -``` diff --git a/docs/what-is-new.md b/docs/what-is-new.md deleted file mode 100644 index b45c3a02..00000000 --- a/docs/what-is-new.md +++ /dev/null @@ -1,365 +0,0 @@ -# What's New - -This document highlights the major changes and improvements in each version of commit-check. - -## Version 2.11.0 — AI Attribution Governance - -### Enforce Your Project's AI Contribution Policy - -commit-check now supports **AI attribution governance** — a neutral enforcement -layer for the industry-wide discussion on AI disclosure in open source. - -Configured under `[commit]`: - -```toml -[commit] -# "ignore" (default) | "forbid" -ai_attribution = "forbid" -``` - -When set to `"forbid"`, any commit containing known AI tool signatures is -rejected. The built-in signature database detects trailers and markers from: - -* **Claude Code** — `Co-authored-by: Claude`, `Assisted-by: Claude:...`, - `🤖 Generated with Claude`, `Claude-Session:`, `Claude-Workflow:` -* **GitHub Copilot** — `Co-authored-by: Copilot` -* **OpenAI Codex** — `Co-authored-by: Codex` -* **Gemini** — `Co-authored-by: Gemini` -* **Cursor** — `Co-authored-by: Cursor` -* **Devin** — `Co-authored-by: Devin` -* **Aider** — `Co-authored-by: Aider`, `Co-authored-by: ... (aider)` -* **Windsurf** — `Co-authored-by: Windsurf` -* **Tabby** — `Co-authored-by: Tabby` -* **Generic AI** — `Assisted-by:` (Linux kernel style, with tool list), - model names like `claude-sonnet-4`, `gpt-4-turbo` - -The signature database is designed to be extensible — adding a new tool is as -simple as adding a `KnownAiTool` entry with the tool's patterns. - -This feature is motivated by ongoing discussions in the CPython core -development community, the Linux kernel's `Assisted-by:` trailer standard, -VS Code, Apache, Fedora, and other foundations. - -See [Configuration Documentation](configuration.md) for details. - -## Version 2.10.0 — Bot Branch Types as Default - -### `dependabot/` and `renovate/` branches now pass by default - -`dependabot` and `renovate` are now included in `DEFAULT_BRANCH_TYPES`, -so branches like `dependabot/go_modules/go-deps-c57c3fe1e0` and -`renovate/lodash-5.x` are automatically accepted without manual -`allow_branch_types` configuration. - -## Version 2.9.0 — AI Agent Branch Prefixes - -### Conventional Branch v1.1.0 AI agent prefixes supported by default - -`ai/`, `claude/`, `codex/`, `copilot/`, and `cursor/` have been -added to `DEFAULT_BRANCH_TYPES` as defined in [Conventional Branch v1.1.0](https://conventional-branch.github.io/). Branches created by AI -coding agents are now valid out of the box without extra configuration. - -## Version 2.7.0 — Force Push Blocking - -### Force Push Detection and Prevention - -commit-check now includes a **force push detection** feature that blocks -accidental `git push --force` / `git push -f` by inspecting pushed ref -ancestry via `git merge-base --is-ancestor`. - -**How it works:** - -* Runs inside a Git `pre-push` hook — receives pushed ref metadata on stdin - and inspects the ancestry relationship. -* New branch pushes (remote SHA is all zeros) always pass. -* Fast-forward pushes (remote is ancestor of local) pass. -* When the remote commit is **not** an ancestor of the local commit, a force - push is detected and **blocked**. -* Git errors (e.g., unknown SHA) result in a safe pass. - -**Usage:** - -```bash -# Standalone: check whether pushing HEAD to its upstream requires force -commit-check --no-force-push -``` - -```yaml -# As a pre-commit pre-push hook -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.7.0 - hooks: - - id: check-no-force-push - stages: [pre-push] -``` - -```toml -# Configurable in cchk.toml -[push] -allow_force_push = false # default: true (force pushes allowed) -``` - -**New Python API:** - -```python -from commit_check.api import validate_push - -zero = "0000000000000000000000000000000000000000" -result = validate_push(f"refs/heads/main abc123 refs/heads/main {zero}") -print(result["status"]) # "pass" -``` - -See the [Push Safety section in README](https://github.com/commit-check/commit-check#check-push-safety) -and [Push Validation Examples](https://docs.commit-check.com/example.html#push-validation-examples) -for more details. - -## Version 2.6.0 — Output Controls for CLI Workflows - -### Quieter Human-Readable Failure Output - -commit-check now includes two CLI flags for workflows that want less verbose -terminal output without switching to JSON mode: - -* `--no-banner` suppresses the ASCII art failure banner while keeping the - detailed error message and suggestion output. -* `--compact` prints a single `[FAIL]` line per failing check and implies - `--no-banner`. - -These flags are useful in CI logs, pre-commit output, and agent-driven terminal -sessions where the full banner is noisy but plain-text diagnostics are still -helpful. - -## Version 2.5.0 — New Features - -### Co-author Bypass in `ignore_authors` - -commit-check can now skip validation when a **co-author** of the commit matches an entry in `ignore_authors`, not just the primary commit author. - -This is especially useful for AI-assisted workflows where a bot (e.g., `coderabbitai[bot]`, `copilot[bot]`) co-authors a commit that does not follow Conventional Commits format: - -```toml -[commit] -ignore_authors = ["dependabot[bot]", "renovate[bot]", "coderabbitai[bot]", "copilot[bot]"] -``` - -When a `Co-authored-by:` trailer in the commit message body matches any entry in the list, all commit checks are skipped for that commit. - -### Organization-Level Config Inheritance (`inherit_from`) - -Teams can now share a **centralized base configuration** across all repositories in an organization using the new `inherit_from` top-level key. - -```toml -# .github/cchk.toml — in every repo -inherit_from = "github:my-org/.github:cchk.toml" - -[commit] -subject_max_length = 72 # Local override -``` - -**Supported source formats:** - -* `github:owner/repo:path/to/cchk.toml` — fetches from the default branch via `raw.githubusercontent.com` -* `github:owner/repo@main:path/to/cchk.toml` — pins to a specific branch, tag, or SHA -* A local file path (relative or absolute) -* An HTTPS URL - -Local settings always **override** the inherited configuration. HTTP (non-TLS) URLs are rejected for security. If the source is unreachable, the local config is used as-is. - -### Git Config Author Validation - -Author name and email validation now checks **`git config user.name` / `user.email`** first — the identity that will be used for the *next* commit — and falls back to the last commit's author only if git config is unset. - -Previously, a developer with a misconfigured `user.name` (e.g., starting with a digit) would pass validation as long as their most recent commit had a valid author name. This fix closes that gap. - -## Version 2.0.0 - Major Release - -Version 2.0.0 represents a complete architectural overhaul of commit-check, introducing significant improvements in configuration, usability, and maintainability. - -### **Overview** - -The most significant change in v2.0.0 is the transition from YAML to TOML configuration format, along with a complete redesign of the validation engine using SOLID principles. - -**Key Benefits:** - -* **Simplified Configuration**: More intuitive TOML syntax -* **Better Defaults**: Sensible out-of-the-box behavior -* **Enhanced Validation**: Built-in support for Conventional Commits and Conventional Branches -* **Improved Architecture**: Modular, maintainable codebase -* **Better Documentation**: Comprehensive guides and examples - -### **Documentation & Migration** - -* **Configuration Guide**: Updated [Configuration Documentation](configuration.md) with comprehensive examples -* **Migration Support**: Complete [Migration Guide](migration.md) for upgrading from v1.x to v2.0+ - -### **Configuration Format Migration** - -The configuration format has changed from YAML to TOML, providing better readability and easier maintenance. - -**Format Comparison:** - -| Feature | YAML (v1.x) | TOML (v2.0+) | -|---|---|---| -| **Syntax** | Complex nested structure | Simple key-value pairs | -| **Validation** | Custom regex patterns | Built-in conventional standards | -| **Configuration** | `.commit-check.yml` | `cchk.toml` or `commit-check.toml` | -| **Maintainability** | Manual regex maintenance | Standardized patterns | - -### **Configuration Examples** - -Below are side-by-side comparisons showing how common configurations translate from v1.x to v2.0+. - -#### Commit Message Validation - -Transform complex regex patterns into simple, standardized configuration. - -**Before (YAML v1.x):** - -```yaml -checks: - - check: message - regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)|(Merge).*|(fixup!.*)' - error: "The commit message should be structured as follows:\n\n - [optional scope]: \n - [optional body]\n - [optional footer(s)]\n\n - More details please refer to https://www.conventionalcommits.org" - suggest: please check your commit message whether matches above regex -``` - -**After (TOML v2.0+):** - -```toml -[commit] -conventional_commits = true -allow_commit_types = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"] -``` - -**Benefits**: No more complex regex patterns, built-in [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) support, clearer configuration. - -#### Branch Naming Validation - -Standardize branch naming with conventional patterns. - -**Before (YAML v1.x):** - -```yaml -checks: - - check: branch - regex: ^(bugfix|feature|release|hotfix|task|chore)\/.+|(master)|(main)|(HEAD)|(PR-.+) - error: "Branches must begin with these types: bugfix/ feature/ release/ hotfix/ task/ chore/" - suggest: run command `git checkout -b type/branch_name` -``` - -**After (TOML v2.0+):** - -```toml -[branch] -conventional_branch = true -allow_branch_types = ["bugfix", "feature", "release", "hotfix", "task", "chore"] -``` - -**Benefits**: Built-in [Conventional Branch](https://conventionalbranch.org) support, automatic handling of special branches (main, master, HEAD, PR-\*). - -#### Author Validation - -Flexible author validation with allow/ignore lists. - -**Before (YAML v1.x):** - -```yaml -checks: - - check: author_name - regex: ^[A-Za-zÀ-ÖØ-öø-ÿ\u0100-\u017F\u0180-\u024F ,.\'-]+$|.*(\[bot]) - error: The committer name seems invalid - suggest: run command `git config user.name "Your Name"` -``` - -**After (TOML v2.0+):** - -```toml -[commit] -# Built-in validation with sensible defaults for author name/email -# Optional: ignore specific authors (e.g., bots) -ignore_authors = ["dependabot[bot]", "renovate[bot]"] -``` - -**Benefits**: Built-in validation patterns, flexible ignore lists, automatic bot detection. - -#### Signed-off-by Requirements - -Simple boolean flag for DCO compliance. - -**Before (YAML v1.x):** - -```yaml -checks: - - check: commit_signoff - regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> - error: Signed-off-by not found in latest commit - suggest: run command `git commit -m "conventional commit message" --signoff` -``` - -**After (TOML v2.0+):** - -```toml -[commit] -require_signed_off_by = true -``` - -**Benefits**: Simple boolean configuration, built-in DCO validation, clear error messages. - -### **Architecture Improvements** - -#### **New Validation Engine** - -* **SOLID Principles**: Maintainable, extensible design -* **Specialized Validators**: Dedicated classes for each validation type -* **Centralized Rules**: Rule catalog with consistent error messages -* **Flexible Configuration**: Dynamic rule building from configuration - -#### **Module Organization** - -| Module | Purpose | -|---|---| -| `config.py` | TOML configuration loading and validation | -| `engine.py` | Core validation engine and specialized validators | -| `rule_builder.py` | Builds validation rules from configuration | -| `rules_catalog.py` | Centralized catalog of validation rules and messages | -| `main.py` | CLI interface and orchestration | - -### **Getting Started with v2.0** - -#### **For New Users:** - -1. **Install commit-check v2.0+**: - -```bash -pip install commit-check>=2.0.0 -``` - -2. **Start with defaults** (no configuration needed): - -```bash -commit-check --message --branch -``` - -3. **Customize as needed** with `cchk.toml`: - -```toml -[commit] -conventional_commits = true -subject_max_length = 72 -``` - -#### For Existing Users - -1. **Follow the Migration Guide**: See [Migration Guide](migration.md) -2. **Test thoroughly**: Validate your new configuration before deploying - -### **Additional Resources** - -* [Configuration Reference](configuration.md) - Complete configuration options -* [Migration Guide](migration.md) - Step-by-step upgrade instructions -* [CLI Reference](cli.md) - Command-line interface documentation diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 04669a01..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,130 +0,0 @@ -site_name: Commit Check -# Deploy previews override this so the preview is self-consistent; see -# netlify.toml. -site_url: !ENV [SITE_URL, 'https://docs.commit-check.com/'] -site_description: >- - Enforce commit message, branch naming, author and signoff standards — - one policy, across your CLI, pre-commit hooks, CI, and AI agents. -site_author: shenxianpeng -copyright: Copyright © 2022 Commit Check - -repo_url: https://github.com/commit-check/commit-check -repo_name: commit-check/commit-check -edit_uri: edit/main/docs/ - -docs_dir: docs - -theme: - name: material - language: en - logo: assets/logo.svg - favicon: assets/favicon.svg - icon: - repo: fontawesome/brands/github - font: - text: Inter - code: JetBrains Mono - palette: - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Follow system theme - - media: "(prefers-color-scheme: light)" - scheme: default - primary: custom - accent: custom - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: custom - accent: custom - toggle: - icon: material/brightness-4 - name: Switch to light mode - features: - # Navigation lives in the left sidebar, grouped into sections. - - navigation.sections - - navigation.top - - navigation.tracking - - navigation.instant - - navigation.instant.progress - - navigation.footer - - toc.follow - - search.suggest - - search.highlight - - search.share - - content.code.copy - - content.code.annotate - - content.tabs.link - - content.action.edit - -extra_css: - - assets/extra.css - -extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/commit-check - name: Commit Check on GitHub - - icon: fontawesome/brands/python - link: https://pypi.org/project/commit-check/ - name: commit-check on PyPI - -markdown_extensions: - - abbr - - admonition - - attr_list - - def_list - - footnotes - - md_in_html - - tables - - toc: - permalink: true - permalink_title: Link to this section - - pymdownx.details - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.keys - - pymdownx.snippets - - pymdownx.superfences - - pymdownx.tabbed: - alternate_style: true - - pymdownx.tasklist: - custom_checkbox: true - -plugins: - - search - -hooks: - - scripts/mkdocs_hooks.py - -nav: - - Home: index.md - - Getting started: - - Installation: getting-started/installation.md - - Quick start: getting-started/quickstart.md - - Why Commit Check: getting-started/why.md - - Guides: - - Pre-commit hook: guides/pre-commit.md - - GitHub Actions: guides/github-actions.md - - Organization-wide policy: guides/organization.md - - Signoff and DCO: guides/signoff.md - - AI attribution policy: guides/ai-attribution.md - - Command-line recipes: example.md - - Reference: - - Rules: rules.md - - Configuration: configuration.md - - CLI: cli.md - - About: - - Migrating from v1: migration.md - - Troubleshooting: troubleshoot.md - - Release highlights: what-is-new.md - - Changelog: changelog.md diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index b0e18c1e..00000000 --- a/netlify.toml +++ /dev/null @@ -1,100 +0,0 @@ -# Netlify builds the documentation for deploy previews on pull requests. -# -# This file exists so the build is defined in version control rather than in -# Netlify's web UI: when the docs toolchain changes, the preview build changes -# with it in the same commit, and a mismatch shows up in review instead of -# silently producing an empty preview. - -[build] - command = "pip install '.[docs]' && mkdocs build --strict" - publish = "site" - -[build.environment] - # MkDocs Material needs a modern Python; the package itself supports 3.10+. - PYTHON_VERSION = "3.12" - -# On a deploy preview, point the site at the preview itself. Without this the -# canonical links and the generated redirect stubs would send reviewers from -# the preview back to the production site, which defeats the purpose. -# -# SITE_URL has to be set here rather than in a [context.*.environment] block: -# those values are literals, so "${DEPLOY_PRIME_URL}" would reach MkDocs -# unexpanded and be rejected as a URL without a scheme. The build command runs -# in a shell, where the variable actually expands. -[context.deploy-preview] - command = "pip install '.[docs]' && SITE_URL=\"$DEPLOY_PRIME_URL/\" mkdocs build --strict" - -[context.branch-deploy] - command = "pip install '.[docs]' && SITE_URL=\"$DEPLOY_PRIME_URL/\" mkdocs build --strict" - -# The URLs the Sphinx site served. The build does not emit redirect stubs on -# Netlify (see scripts/mkdocs_hooks.py): a stub at rules.html would be served -# for /rules/ requests too, shadowing the real page and redirecting to itself. -# Declaring the URLs here resolves them before file lookup, so they cannot -# collide with the pages they point at. Browsers carry the fragment of the -# requested URL across the redirect, so /rules.html#cc003 lands on /rules/#cc003. -[[redirects]] - from = "/configuration.html" - to = "/configuration/" - status = 301 - force = true - -[[redirects]] - from = "/rules.html" - to = "/rules/" - status = 301 - force = true - -[[redirects]] - from = "/example.html" - to = "/example/" - status = 301 - force = true - -[[redirects]] - from = "/migration.html" - to = "/migration/" - status = 301 - force = true - -[[redirects]] - from = "/troubleshoot.html" - to = "/troubleshoot/" - status = 301 - force = true - -[[redirects]] - from = "/changelog.html" - to = "/changelog/" - status = 301 - force = true - -[[redirects]] - from = "/what-is-new.html" - to = "/what-is-new/" - status = 301 - force = true - -[[redirects]] - from = "/cli_args.html" - to = "/cli/" - status = 301 - force = true - -[[redirects]] - from = "/README.html" - to = "/getting-started/installation/" - status = 301 - force = true - -[[redirects]] - from = "/index.html" - to = "/" - status = 301 - force = true - -[[redirects]] - from = "/genindex.html" - to = "/" - status = 301 - force = true diff --git a/noxfile.py b/noxfile.py index 3ed32045..36de2e0e 100644 --- a/noxfile.py +++ b/noxfile.py @@ -48,15 +48,3 @@ def coverage(session): session.run("coverage", "run", "--source", "commit_check", "-m", "pytest") session.run("coverage", "report") session.run("coverage", "xml") - - -@nox.session() -def docs(session): - session.install(".[docs]") - session.run("mkdocs", "build", "--strict") - - -@nox.session(name="docs-live") -def docs_live(session): - session.install(".[docs]") - session.run("mkdocs", "serve") diff --git a/pyproject.toml b/pyproject.toml index 889ab4da..3f788b85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ tracker = "https://github.com/commit-check/commit-check/issues" [project.optional-dependencies] dev = ['nox==2026.7.11'] test = ['coverage', 'pytest', 'pytest-mock', 'pytest-codspeed'] -docs = ['mkdocs-material>=9.7'] ci = ['twine==7.0.0'] [tool.setuptools] diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py deleted file mode 100644 index d8b439e7..00000000 --- a/scripts/mkdocs_hooks.py +++ /dev/null @@ -1,117 +0,0 @@ -"""MkDocs build hooks. - -Two jobs: - -* generate ``docs/cli.md`` from the CLI's own ``--help`` output, so the - documented interface cannot drift from the shipped one; -* emit redirect stubs for the ``.html`` URLs the previous Sphinx site served, - so links already published elsewhere keep working on hosts that have no - other redirect mechanism (GitHub Pages). Netlify skips the stubs: it - declares the same URLs as ``[[redirects]]`` in ``netlify.toml``, which - resolve before file lookup and cannot collide with the pages they target. -""" - -from __future__ import annotations - -import os -import subprocess -from pathlib import Path - -#: Sphinx page name -> path under the MkDocs site. -LEGACY_URLS = { - "configuration": "configuration/", - "rules": "rules/", - "example": "example/", - "migration": "migration/", - "troubleshoot": "troubleshoot/", - "changelog": "changelog/", - "what-is-new": "what-is-new/", - "cli_args": "cli/", - "README": "getting-started/installation/", - "genindex": "", -} - -# Redirect stubs for the URLs the Sphinx site served. -# The script carries the fragment across, because the links most worth keeping -# alive are the per-rule ones (``rules.html#cc003``) and a plain redirect drops -# the ``#cc003``. It also refuses to redirect a page to itself: a host that -# normalises ``/rules`` and ``/rules/`` to the same resource would otherwise -# serve this stub in place of the real page and loop forever. -REDIRECT = """ - - - -Redirecting… - - - - -Redirecting to {url}… - -""" - -PAGE = """# CLI reference - -Generated from `commit-check --help`. The CLI is also available as `cchk`. - -```console -$ commit-check --help -``` - -```text -{help} -``` - -## See also - -- [Configuration](configuration.md) — every option, with the environment - variable and TOML key that set it. -- [Rules](rules.md) — which flag activates which rule. -""" - - -def on_pre_build(config, **kwargs) -> None: - """Write ``cli.md`` before the build reads the docs directory.""" - try: - result = subprocess.run( - ["commit-check", "--help"], - capture_output=True, - encoding="utf-8", - check=True, - ) - except (OSError, subprocess.CalledProcessError) as exc: # pragma: no cover - raise RuntimeError( - "could not run 'commit-check --help' to generate the CLI reference; " - "install the package first (pip install -e .)" - ) from exc - - text = result.stdout.rstrip() - target = Path(config["docs_dir"], "cli.md") - target.write_text(PAGE.format(help=text), encoding="utf-8") - - -def on_post_build(config, **kwargs) -> None: - """Write a redirect stub for each URL the Sphinx site used to serve.""" - site = Path(config["site_dir"]) - # On Netlify the stubs would be served in place of the real pages: Netlify - # resolves ``/rules/`` to the file ``rules.html``, so the stub that - # redirects to ``/rules/`` would shadow ``rules/index.html`` and loop. The - # ``[[redirects]]`` table in netlify.toml covers the same URLs with real - # 301s, so the stubs are only written for hosts without redirects. - if os.environ.get("NETLIFY") == "true": - return - # Deploy previews pass their own URL in, and it may arrive without the - # trailing slash the targets below are joined onto. - base = (config["site_url"] or "/").rstrip("/") + "/" - for legacy, target in LEGACY_URLS.items(): - (site / f"{legacy}.html").write_text( - REDIRECT.format(url=base + target), encoding="utf-8" - ) diff --git a/tests/rules_catalog_test.py b/tests/rules_catalog_test.py index 7cf8f9c1..fdd82fcb 100644 --- a/tests/rules_catalog_test.py +++ b/tests/rules_catalog_test.py @@ -1,8 +1,10 @@ -"""Tests for stable rule IDs in the rules catalog.""" +"""Tests for stable rule IDs in the rules catalog. + +The checks that compared these rules against the documentation now live in +the commit-check.com repository, next to the pages they read. +""" import re -from pathlib import Path -from typing import Any import pytest @@ -16,7 +18,6 @@ RuleCatalogEntry, ) from commit_check.rule_builder import RuleBuilder -from commit_check.config_merger import get_default_config ALL_ENTRIES = [*COMMIT_RULES, *BRANCH_RULES, *PUSH_RULES] @@ -118,170 +119,3 @@ def test_internal_entries_have_no_id(self): rule = next(r for r in rules if r.check == "ignore_authors") assert rule.rule_id is None assert rule.docs_url is None - - -class TestRulesDocumentation: - """Anti-drift guard: every documented rule stays documented. - - These tests read files rather than exercising the package, so they carry no - ``benchmark`` marker: their cost tracks the size of the documentation, and - benchmarking them would report a performance regression every time somebody - writes more of it. - """ - - def test_every_rule_is_documented(self): - """Each rule ID must have an anchor in the rules reference page. - - This prevents shipping a new rule without documenting it. - """ - content = _read_doc("rules.md") - for entry in ALL_RULES: - anchor = f"{{ #{entry.rule_id.lower()} }}" - assert anchor in content, ( - f"{entry.rule_id} ({entry.check}) is missing from docs/rules.md" - ) - - def test_every_rule_has_a_section_heading(self): - """Each rule needs a ``name (CCxxx)`` heading, not just an anchor. - - Checked inside the rule's own section: an anchor alone, or a heading - that survives elsewhere on the page, would otherwise pass. - """ - content = _read_doc("rules.md") - for entry in ALL_RULES: - heading = ( - f"### {entry.name} ({entry.rule_id}) {{ #{entry.rule_id.lower()} }}" - ) - assert heading in content, ( - f"docs/rules.md has no section titled '{heading}'" - ) - - def test_every_rule_explains_itself(self): - """Each rule section must answer what it does and why it matters.""" - content = _read_doc("rules.md") - for entry in ALL_RULES: - section = _rule_section(content, entry.rule_id) - for required in ("**What it does**", "**Why is this bad?**", "**Options**"): - assert required in section, ( - f"{entry.rule_id} ({entry.check}) section is missing {required}" - ) - - -class TestDocumentedDefaults: - """The documented defaults must match the ones the code actually uses. - - Not benchmarked, for the same reason as :class:`TestRulesDocumentation`. - """ - - def test_every_runtime_option_is_documented(self): - """Every option the runtime defines has a row in the options table.""" - documented = _parse_options_table(_read_doc("configuration.md")) - for section, options in get_default_config().items(): - for option in options: - assert (section, option) in documented, ( - f"[{section}] {option} exists in get_default_config() but " - f"has no row in the options table of docs/configuration.md" - ) - - def test_no_invented_options_are_documented(self): - """The options table does not document options that do not exist.""" - runtime = get_default_config() - for section, option in _parse_options_table(_read_doc("configuration.md")): - assert option in runtime.get(section, {}), ( - f"docs/configuration.md documents [{section}] {option}, which " - f"does not exist in get_default_config()" - ) - - def test_documented_defaults_match_the_runtime(self): - """Every documented default equals the value the runtime actually uses. - - ``get_default_config()`` is what ``ConfigMerger.from_all_sources()`` - starts from, so it is the single source of truth for "what happens with - no config file". The options table is maintained by hand and silently - drifts away from it without this guard. - """ - documented = _parse_options_table(_read_doc("configuration.md")) - runtime = get_default_config() - - for (section, option), (type_, cell) in sorted(documented.items()): - if option not in runtime.get(section, {}): - continue # reported by test_no_invented_options_are_documented - expected = runtime[section][option] - actual = _documented_default(type_, cell) - if isinstance(expected, list): - # Allow-lists: order carries no meaning, membership does. - assert set(actual or []) == set(expected), ( - f"docs/configuration.md documents [{section}] {option} " - f"with {sorted(set(actual or []) - set(expected))} that are " - f"not defaults, and is missing " - f"{sorted(set(expected) - set(actual or []))}" - ) - else: - assert actual == expected, ( - f"docs/configuration.md documents [{section}] {option} as " - f"{cell.strip()!r}, but the runtime default is {expected!r}" - ) - - -def _read_doc(name: str) -> str: - """Read a file from the ``docs`` directory.""" - return (Path(__file__).parent.parent / "docs" / name).read_text(encoding="utf-8") - - -def _rule_section(content: str, rule_id: str) -> str: - """Return just the part of the rules page belonging to one rule.""" - _, _, after = content.partition(f"{{ #{rule_id.lower()} }}") - return re.split(r"\{ #cc\d{3} \}", after)[0] - - -_OPTIONS_ROW = re.compile( - r"^\|\s*(commit|branch|push)\s*" # section - r"\|\s*(\w+)\s*" # option name - r"\|\s*(bool|int|str|list\[str\])\s*" # type - r"\|\s*(.+?)\s*\|", # documented default - re.M, -) - - -def _parse_options_table(content: str) -> dict[tuple[str, str], tuple[str, str]]: - """Map ``(section, option) -> (type, raw default cell)``. - - Parses the five-cell ``list-table`` rows of the options table, e.g.:: - - * - commit - - subject_max_length - - int - - 80 - - Maximum length of the subject line. - """ - return { - (section, option): (type_, cell) - for section, option, type_, cell in _OPTIONS_ROW.findall(content) - } - - -# Markdown code spans use single backticks; RST used double. -_QUOTED = re.compile(r'^(?:`+(.*?)`+|"(.*?)")') - - -def _documented_default(type_: str, cell: str) -> Any: - """Turn a documented default cell into a comparable Python value. - - Cells carry a human annotation after the value itself (``"" (disabled)``), - so the value is read from the front of the cell and the rest ignored. - """ - cell = cell.strip() - if type_ == "bool": - return cell.startswith("true") - if type_ == "int": - match = re.match(r"-?\d+", cell) - return int(match.group()) if match else None - if type_ == "list[str]": - return re.findall(r'"(.*?)"', cell) - quoted = _QUOTED.match(cell) - if quoted is None: - return cell - # Group 1 is the ``literal`` form, group 2 the "literal" form; exactly one - # of them matched. - backticked, double_quoted = quoted.groups() - return backticked if backticked is not None else double_quoted