git_commit_message
Validate commit-message shape via regex, max-subject-length, or required-body. At least one of the three must be set; combine all three for full Conventional-Commits-style enforcement. Subject length counts characters, not bytes (a 50-char emoji subject is 50, not 200).
Two modes, selected by the optional since: field:
- HEAD-only (default,
since:omitted): validate the tip commit. Right shape for push-trigger CI and post-commit hooks. - Range (
since:set): validate every commit reachable from HEAD but not fromsince. Right shape forpull_request-trigger CI, whereactions/checkoutchecks out a synthetic merge commit whose subject the rule would always flag.
since: semantics
Section titled "since: semantics"since: accepts anything git rev-parse resolves: a 40-char or abbreviated SHA, a branch (origin/main), a tag (v1.2.3), or a relative ref (HEAD~5). The rule walks <since>..HEAD oldest-first, validates each commit, and emits one violation per failing commit with the short SHA + a subject snippet so you know which to amend.
Use alint's standard template syntax for environment values:
{{env.VAR}}requiresVARto be set and non-empty.{{env.VAR | default('value')}}supplies a fallback, such assince: "{{env.ALINT_BASE_SHA | default('origin/main')}}"for local runs.
GitHub Actions expressions are evaluated only in workflow files, not inside .alint.yml; export the expression to an environment variable in the workflow and read it through {{env.VAR}} in the alint config. The older POSIX-style ${VAR} / ${VAR:-default} syntax still works for this field but is deprecated and will be removed in v1.0.
include_merges:
Section titled "include_merges:"In range mode, merge commits are skipped by default (include_merges: false). Merge subjects in PR contexts are typically actions/checkout-generated or maintainer-resolved and uninteresting. Set include_merges: true to lint them too. Has no effect when since: is unset; combining include_merges: true with no since: is a load-time error.
Failure modes
Section titled "Failure modes"- No Git,
gitnot onPATH, or the lint root is not a repository: aresource_readanalysis failure. since:ref doesn't resolve: hard error with a shallow-clone hint. The common cause isactions/checkout@v4with its defaultfetch-depth: 1, which doesn't fetch the base ref's commits. Usefetch-depth: 0to fetch full history.- Range is empty or HEAD is unborn: normal pass. No commits, no policy input.
GitHub Actions PR-validation recipe
Section titled "GitHub Actions PR-validation recipe"name: linton: pull_request: branches: [main]jobs: alint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # Range mode walks <base>..HEAD. fetch-depth: 0 makes # both refs reachable; the default depth of 1 leaves # the base ref out of local objects and the rule errors. fetch-depth: 0 - name: alint check env: ALINT_BASE_SHA: ${{ github.event.pull_request.base.sha }} uses: asamarts/alint@8d7a60f6642cb1b59537202ae81df7d951a078ad # v0.22.0Options
Section titled "Options"| Option | Type | Required | Default | Description |
|---|---|---|---|---|
include_merges | boolean | false | When validating a range (since: set), include merge commits. Defaults to false because merge commits in PR contexts are typically the synthetic merge actions/checkout produces (with an auto-generated subject the rule would always flag) or maintainer-resolved merges from the base branch. Has no effect when since: is unset; combining include_merges: true with no since: is a load-time error. | |
pattern | string | null | Rust-regex pattern the full message (subject + body, joined with newlines) must match. Use (?s) to make . match newlines. | |
requires_body | boolean | false | When true, the message must have a non-empty body, that is, at least one line of content after the subject's blank-line separator. | |
since | string | null | Git ref to use as the base of the commit range. When set, validates every commit in <since>..HEAD instead of just HEAD. Accepts anything git rev-parse does: SHA (full or abbreviated), branch (origin/main), tag (v1.2.3), or relative ref (HEAD~5). Supports alint's template syntax, so CI can pass a SHA through an environment variable (for example, since: "{{env.ALINT_BASE_SHA | default('origin/main')}}"). The older POSIX ${VAR} / ${VAR:-default} form is deprecated and will be removed in v1.0. | |
subject_max_length | integer (>= 1) | null | Maximum number of characters allowed in the subject line. Common values: 50 (Tim Pope's recommendation), 72 (GitHub PR-title cutoff). |
Plus the common level, id, and when fields. This rule analyses the whole repository, so it takes no paths or expect_matches. This table is generated from the JSON Schema; option types and defaults are authoritative.
Example
Section titled "Example"A commit with a non-conventional subject
Section titled "A commit with a non-conventional subject"The rule fires on this repository:
- the repository
✗ error conventional-commits
commit HEAD: commit message does not match pattern `^(feat|fix|chore): ` (subject: "wip nonsense")
- README.md✓
# demoWith this .alint.yml:
version: 1rules: - id: conventional-commits kind: git_commit_message pattern: '^(feat|fix|chore): ' level: errorcommitted with this history (oldest first):
wip nonsense (adds README.md)alint check reports:
--- Repository-level ----------------------------------------------------------- x error conventional-commits commit HEAD: commit message does not match pattern `^(feat|fix|chore): ` (subject: "wip nonsense")
Summary (1 violation): x 1 error 0 passing * 1 failingA commit that follows the convention
Section titled "A commit that follows the convention"This repository is compliant:
- README.md✓
# demoWith this .alint.yml:
version: 1rules: - id: conventional-commits kind: git_commit_message pattern: '^(feat|fix|chore): ' level: errorcommitted with this history (oldest first):
feat: add the readme (adds README.md)alint check reports:
v All 1 rule(s) passed.