git-commit-guard 0.18.0__tar.gz → 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/lint-commits.yml +1 -1
  2. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/lint-workflows.yml +25 -2
  3. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/release.yml +5 -2
  4. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/PKG-INFO +64 -13
  5. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/README.md +63 -12
  6. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/action.yml +6 -0
  7. git_commit_guard-0.20.0/cliff.toml +56 -0
  8. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/docs/index.html +65 -6
  9. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/src/git_commit_guard/__init__.py +192 -13
  10. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/tests/test_git_commit_guard.py +518 -13
  11. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.editorconfig +0 -0
  12. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/coverage-baseline.yml +0 -0
  13. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/coverage-comment.yml +0 -0
  14. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/lint-md.yml +0 -0
  15. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/lint-python.yml +0 -0
  16. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.github/workflows/test.yml +0 -0
  17. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.gitignore +0 -0
  18. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.markdownlint.json +0 -0
  19. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.pre-commit-hooks.yaml +0 -0
  20. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/.python-version +0 -0
  21. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/LICENSE +0 -0
  22. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/docs/commit-guard-icon.svg +0 -0
  23. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/pyproject.toml +0 -0
  24. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/ruff.toml +0 -0
  25. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/tests/__init__.py +0 -0
  26. {git_commit_guard-0.18.0 → git_commit_guard-0.20.0}/uv.lock +0 -0
@@ -22,7 +22,7 @@ jobs:
22
22
  key: nltk-averaged-perceptron-tagger-punkt
23
23
  - name: Lint commits
24
24
  # yamllint disable-line rule:line-length
25
- uses: benner/commit-guard@d4c9fbe4a203ab32f63f66e1902d780528f781f2 # v0.17.0
25
+ uses: benner/commit-guard@7704c563540b24bb10394e373e508dc664a7f01f # v0.19.0
26
26
  with:
27
27
  range: origin/${{ github.base_ref }}..HEAD
28
28
  disable: signature
@@ -21,7 +21,30 @@ jobs:
21
21
  with:
22
22
  github_token: ${{ github.token }}
23
23
  reporter: github-pr-review
24
-
24
+ yamlfix:
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ pull-requests: write
28
+ steps:
29
+ - name: Checkout code
30
+ # yamllint disable-line rule:line-length
31
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # ratchet:actions/checkout@v4
32
+ with:
33
+ persist-credentials: false
34
+ - name: Set up uv
35
+ # yamllint disable-line rule:line-length
36
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
37
+ - name: Set up reviewdog
38
+ # yamllint disable-line rule:line-length
39
+ uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
40
+ - name: Run yamlfix
41
+ env:
42
+ REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}
43
+ run: |-
44
+ uvx yamlfix .github/workflows/
45
+ git diff .github/workflows/ |
46
+ reviewdog -f=diff -name=yamlfix \
47
+ -reporter=github-pr-review -fail-on-error
25
48
  zizmor:
26
49
  runs-on: ubuntu-latest
27
50
  permissions:
@@ -41,7 +64,7 @@ jobs:
41
64
  - name: Run zizmor
42
65
  env:
43
66
  REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}
44
- run: |
67
+ run: |-
45
68
  uvx zizmor==1.24.1 --format=sarif . |
46
69
  reviewdog -f=sarif -name=zizmor \
47
70
  -reporter=github-pr-review -fail-on-error
@@ -2,14 +2,15 @@
2
2
  name: Release
3
3
  on: # yamllint disable-line rule:truthy
4
4
  push:
5
- tags:
6
- - 'v*'
5
+ tags: [v*]
7
6
  permissions:
8
7
  contents: write
9
8
  id-token: write
9
+ pull-requests: read
10
10
  jobs:
11
11
  release:
12
12
  runs-on: ubuntu-latest
13
+ environment: pypi
13
14
  steps:
14
15
  - name: Checkout code
15
16
  # yamllint disable-line rule:line-length
@@ -34,6 +35,8 @@ jobs:
34
35
  id: git-cliff
35
36
  # yamllint disable-line rule:line-length
36
37
  uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # ratchet:orhun/git-cliff-action@v4.8.0
38
+ env:
39
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37
40
  with:
38
41
  args: --current
39
42
  - name: Create GitHub Release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-guard
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Opinionated conventional commit message linter with imperative mood detection
5
5
  Project-URL: Homepage, https://github.com/benner/commit-guard
6
6
  Project-URL: Repository, https://github.com/benner/commit-guard
@@ -98,7 +98,8 @@ Available checks:
98
98
  * `imperative` - First word is an imperative verb (for example `add` not `added`)
99
99
  * `body` - Blank line separates subject from body, and body is non-empty
100
100
  * `signed-off` - `Signed-off-by:` trailer exists
101
- * `signature` - Verify GPG or SSH signature
101
+ * `signature` - Verify GPG or SSH signature via the GitHub Commits API or
102
+ public key lookup
102
103
 
103
104
  ### Subject length
104
105
 
@@ -181,6 +182,25 @@ commit-guard --require-scope
181
182
  commit-guard --scopes auth,api --require-scope
182
183
  ```
183
184
 
185
+ ### Required subject pattern
186
+
187
+ Require the commit subject to match a regular expression. Useful for
188
+ enforcing ticket references or any custom naming convention:
189
+
190
+ ```bash
191
+ commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"
192
+ commit-guard --require-subject-pattern "#[0-9]+"
193
+ ```
194
+
195
+ In `.commit-guard.toml`:
196
+
197
+ ```toml
198
+ require-subject-pattern = "[A-Z]+-[0-9]+"
199
+ ```
200
+
201
+ An invalid regex causes an immediate error at startup (exit 2). This
202
+ check runs independently of `--enable`/`--disable`.
203
+
184
204
  ### Required custom trailers
185
205
 
186
206
  Require arbitrary trailers to be present in the commit message. Multiple
@@ -201,12 +221,40 @@ Trailer matching is case-sensitive and requires at least one non-space
201
221
  character after the colon (e.g. `Closes: #42`). This check runs
202
222
  independently of `--enable`/`--disable`.
203
223
 
224
+ ### Signature verification
225
+
226
+ The `signature` check verifies the commit without any local keyring setup:
227
+
228
+ 1. If the repo has a GitHub remote, call the Commits API
229
+ (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the author's GitHub
230
+ username — this works for corporate emails, noreply addresses, or any email
231
+ not listed publicly on a GitHub profile.
232
+ 2. If the Commits API is unavailable (no GitHub remote, commit not yet pushed,
233
+ or API error), parse the username directly from a GitHub noreply address
234
+ (`{id}+{username}@users.noreply.github.com` or
235
+ `{username}@users.noreply.github.com`) — no API call needed.
236
+ 3. If neither of the above resolves a username, fall back to searching GitHub
237
+ by the commit author's email.
238
+ 4. Fetch the resolved user's public keys from `github.com/{username}.gpg` and
239
+ `github.com/{username}.keys`.
240
+ 5. Try GPG verification: import the fetched key into a temporary keyring and
241
+ run `git verify-commit`.
242
+ 6. Try SSH verification: write a temporary `allowed_signers` file and run
243
+ `git verify-commit` with the SSH allowed-signers config.
244
+ 7. If any key verifies, the check passes. If none do, it fails.
245
+
246
+ If the author cannot be resolved via either method, or the GitHub API is
247
+ unreachable, the check fails with a clear error.
248
+
249
+ For private repositories, set `GITHUB_TOKEN` or `GH_TOKEN` so the Commits API
250
+ can authenticate.
251
+
204
252
  ### Configuration file
205
253
 
206
254
  Place `.commit-guard.toml` in your project root (or any parent directory) to
207
255
  set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
208
256
  `max-subject-length`, `min-description-length`, `require-lowercase`,
209
- `no-trailing-chars`, and `require-trailers`.
257
+ `no-trailing-chars`, `require-subject-pattern`, and `require-trailers`.
210
258
  commit-guard searches upward from the working directory and uses the first
211
259
  file found.
212
260
 
@@ -235,9 +283,11 @@ full precedence and ignore config file values when provided.
235
283
 
236
284
  ### Environment variables
237
285
 
238
- | Variable | Default | Description |
239
- | -------------------------- | ------- | -------------------------------------------- |
240
- | `COMMIT_GUARD_GIT_TIMEOUT` | `10` | Timeout in seconds for git subprocess calls. |
286
+ | Variable | Default | Description |
287
+ | -------------------------- | ------- | ------------------------------------------------------------------------- |
288
+ | `COMMIT_GUARD_GIT_TIMEOUT` | `10` | Timeout in seconds for git subprocess calls. |
289
+ | `GITHUB_TOKEN` | — | GitHub token for Commits API access on private repos (signature check). |
290
+ | `GH_TOKEN` | — | Alias for `GITHUB_TOKEN`; used when `GITHUB_TOKEN` is not set. |
241
291
 
242
292
  ```bash
243
293
  COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
@@ -246,7 +296,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
246
296
  In GitHub Actions, set it at the step or job level:
247
297
 
248
298
  ```yaml
249
- - uses: benner/commit-guard@v0.18.0
299
+ - uses: benner/commit-guard@v0.19.0
250
300
  env:
251
301
  COMMIT_GUARD_GIT_TIMEOUT: 30
252
302
  with:
@@ -330,7 +380,7 @@ steps:
330
380
  - uses: actions/checkout@v4
331
381
  with:
332
382
  fetch-depth: 0
333
- - uses: benner/commit-guard@v0.18.0
383
+ - uses: benner/commit-guard@v0.19.0
334
384
  ```
335
385
 
336
386
  Check all commits in a pull request:
@@ -346,7 +396,7 @@ jobs:
346
396
  - uses: actions/checkout@v4
347
397
  with:
348
398
  fetch-depth: 0
349
- - uses: benner/commit-guard@v0.18.0
399
+ - uses: benner/commit-guard@v0.19.0
350
400
  with:
351
401
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
352
402
  ```
@@ -354,7 +404,7 @@ jobs:
354
404
  Check a specific commit SHA (mirrors the positional CLI argument):
355
405
 
356
406
  ```yaml
357
- - uses: benner/commit-guard@v0.18.0
407
+ - uses: benner/commit-guard@v0.19.0
358
408
  with:
359
409
  rev: ${{ github.sha }}
360
410
  ```
@@ -372,12 +422,13 @@ jobs:
372
422
  - uses: actions/checkout@v4
373
423
  with:
374
424
  fetch-depth: 0
375
- - uses: benner/commit-guard@v0.18.0
425
+ - uses: benner/commit-guard@v0.19.0
376
426
  with:
377
427
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
378
428
  disable: signed-off,signature
379
429
  scopes: auth,api,db
380
430
  require-scope: 'true'
431
+ require-subject-pattern: '[A-Z]+-[0-9]+'
381
432
  require-trailer: 'Closes,Reviewed-by'
382
433
  max-subject-length: '100'
383
434
  min-description-length: '10'
@@ -391,7 +442,7 @@ jobs:
391
442
  When `output-file` is set the action exposes the path as an output:
392
443
 
393
444
  ```yaml
394
- - uses: benner/commit-guard@v0.18.0
445
+ - uses: benner/commit-guard@v0.19.0
395
446
  id: cg
396
447
  with:
397
448
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -407,7 +458,7 @@ Add to your `.pre-commit-config.yaml`:
407
458
  ---
408
459
  repos:
409
460
  - repo: https://github.com/benner/commit-guard
410
- rev: v0.18.0
461
+ rev: v0.19.0
411
462
  hooks:
412
463
  - id: commit-guard
413
464
  - id: commit-guard-signature
@@ -77,7 +77,8 @@ Available checks:
77
77
  * `imperative` - First word is an imperative verb (for example `add` not `added`)
78
78
  * `body` - Blank line separates subject from body, and body is non-empty
79
79
  * `signed-off` - `Signed-off-by:` trailer exists
80
- * `signature` - Verify GPG or SSH signature
80
+ * `signature` - Verify GPG or SSH signature via the GitHub Commits API or
81
+ public key lookup
81
82
 
82
83
  ### Subject length
83
84
 
@@ -160,6 +161,25 @@ commit-guard --require-scope
160
161
  commit-guard --scopes auth,api --require-scope
161
162
  ```
162
163
 
164
+ ### Required subject pattern
165
+
166
+ Require the commit subject to match a regular expression. Useful for
167
+ enforcing ticket references or any custom naming convention:
168
+
169
+ ```bash
170
+ commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"
171
+ commit-guard --require-subject-pattern "#[0-9]+"
172
+ ```
173
+
174
+ In `.commit-guard.toml`:
175
+
176
+ ```toml
177
+ require-subject-pattern = "[A-Z]+-[0-9]+"
178
+ ```
179
+
180
+ An invalid regex causes an immediate error at startup (exit 2). This
181
+ check runs independently of `--enable`/`--disable`.
182
+
163
183
  ### Required custom trailers
164
184
 
165
185
  Require arbitrary trailers to be present in the commit message. Multiple
@@ -180,12 +200,40 @@ Trailer matching is case-sensitive and requires at least one non-space
180
200
  character after the colon (e.g. `Closes: #42`). This check runs
181
201
  independently of `--enable`/`--disable`.
182
202
 
203
+ ### Signature verification
204
+
205
+ The `signature` check verifies the commit without any local keyring setup:
206
+
207
+ 1. If the repo has a GitHub remote, call the Commits API
208
+ (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the author's GitHub
209
+ username — this works for corporate emails, noreply addresses, or any email
210
+ not listed publicly on a GitHub profile.
211
+ 2. If the Commits API is unavailable (no GitHub remote, commit not yet pushed,
212
+ or API error), parse the username directly from a GitHub noreply address
213
+ (`{id}+{username}@users.noreply.github.com` or
214
+ `{username}@users.noreply.github.com`) — no API call needed.
215
+ 3. If neither of the above resolves a username, fall back to searching GitHub
216
+ by the commit author's email.
217
+ 4. Fetch the resolved user's public keys from `github.com/{username}.gpg` and
218
+ `github.com/{username}.keys`.
219
+ 5. Try GPG verification: import the fetched key into a temporary keyring and
220
+ run `git verify-commit`.
221
+ 6. Try SSH verification: write a temporary `allowed_signers` file and run
222
+ `git verify-commit` with the SSH allowed-signers config.
223
+ 7. If any key verifies, the check passes. If none do, it fails.
224
+
225
+ If the author cannot be resolved via either method, or the GitHub API is
226
+ unreachable, the check fails with a clear error.
227
+
228
+ For private repositories, set `GITHUB_TOKEN` or `GH_TOKEN` so the Commits API
229
+ can authenticate.
230
+
183
231
  ### Configuration file
184
232
 
185
233
  Place `.commit-guard.toml` in your project root (or any parent directory) to
186
234
  set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
187
235
  `max-subject-length`, `min-description-length`, `require-lowercase`,
188
- `no-trailing-chars`, and `require-trailers`.
236
+ `no-trailing-chars`, `require-subject-pattern`, and `require-trailers`.
189
237
  commit-guard searches upward from the working directory and uses the first
190
238
  file found.
191
239
 
@@ -214,9 +262,11 @@ full precedence and ignore config file values when provided.
214
262
 
215
263
  ### Environment variables
216
264
 
217
- | Variable | Default | Description |
218
- | -------------------------- | ------- | -------------------------------------------- |
219
- | `COMMIT_GUARD_GIT_TIMEOUT` | `10` | Timeout in seconds for git subprocess calls. |
265
+ | Variable | Default | Description |
266
+ | -------------------------- | ------- | ------------------------------------------------------------------------- |
267
+ | `COMMIT_GUARD_GIT_TIMEOUT` | `10` | Timeout in seconds for git subprocess calls. |
268
+ | `GITHUB_TOKEN` | — | GitHub token for Commits API access on private repos (signature check). |
269
+ | `GH_TOKEN` | — | Alias for `GITHUB_TOKEN`; used when `GITHUB_TOKEN` is not set. |
220
270
 
221
271
  ```bash
222
272
  COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
@@ -225,7 +275,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
225
275
  In GitHub Actions, set it at the step or job level:
226
276
 
227
277
  ```yaml
228
- - uses: benner/commit-guard@v0.18.0
278
+ - uses: benner/commit-guard@v0.19.0
229
279
  env:
230
280
  COMMIT_GUARD_GIT_TIMEOUT: 30
231
281
  with:
@@ -309,7 +359,7 @@ steps:
309
359
  - uses: actions/checkout@v4
310
360
  with:
311
361
  fetch-depth: 0
312
- - uses: benner/commit-guard@v0.18.0
362
+ - uses: benner/commit-guard@v0.19.0
313
363
  ```
314
364
 
315
365
  Check all commits in a pull request:
@@ -325,7 +375,7 @@ jobs:
325
375
  - uses: actions/checkout@v4
326
376
  with:
327
377
  fetch-depth: 0
328
- - uses: benner/commit-guard@v0.18.0
378
+ - uses: benner/commit-guard@v0.19.0
329
379
  with:
330
380
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
331
381
  ```
@@ -333,7 +383,7 @@ jobs:
333
383
  Check a specific commit SHA (mirrors the positional CLI argument):
334
384
 
335
385
  ```yaml
336
- - uses: benner/commit-guard@v0.18.0
386
+ - uses: benner/commit-guard@v0.19.0
337
387
  with:
338
388
  rev: ${{ github.sha }}
339
389
  ```
@@ -351,12 +401,13 @@ jobs:
351
401
  - uses: actions/checkout@v4
352
402
  with:
353
403
  fetch-depth: 0
354
- - uses: benner/commit-guard@v0.18.0
404
+ - uses: benner/commit-guard@v0.19.0
355
405
  with:
356
406
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
357
407
  disable: signed-off,signature
358
408
  scopes: auth,api,db
359
409
  require-scope: 'true'
410
+ require-subject-pattern: '[A-Z]+-[0-9]+'
360
411
  require-trailer: 'Closes,Reviewed-by'
361
412
  max-subject-length: '100'
362
413
  min-description-length: '10'
@@ -370,7 +421,7 @@ jobs:
370
421
  When `output-file` is set the action exposes the path as an output:
371
422
 
372
423
  ```yaml
373
- - uses: benner/commit-guard@v0.18.0
424
+ - uses: benner/commit-guard@v0.19.0
374
425
  id: cg
375
426
  with:
376
427
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -386,7 +437,7 @@ Add to your `.pre-commit-config.yaml`:
386
437
  ---
387
438
  repos:
388
439
  - repo: https://github.com/benner/commit-guard
389
- rev: v0.18.0
440
+ rev: v0.19.0
390
441
  hooks:
391
442
  - id: commit-guard
392
443
  - id: commit-guard-signature
@@ -49,6 +49,9 @@ inputs:
49
49
  description: Include merge commits when checking a range
50
50
  required: false
51
51
  default: 'false'
52
+ require-subject-pattern:
53
+ description: Regex the subject line must match (e.g. '[A-Z]+-[0-9]+')
54
+ required: false
52
55
  require-trailer:
53
56
  description: Comma-separated list of required trailers (e.g. Closes,Reviewed-by)
54
57
  required: false
@@ -86,6 +89,7 @@ runs:
86
89
  CG_NO_TRAILING_CHARS: ${{ inputs.no-trailing-chars }}
87
90
  CG_ALLOW_EMPTY: ${{ inputs.allow-empty }}
88
91
  CG_INCLUDE_MERGES: ${{ inputs.include-merges }}
92
+ CG_REQUIRE_SUBJECT_PATTERN: ${{ inputs.require-subject-pattern }}
89
93
  CG_REQUIRE_TRAILER: ${{ inputs.require-trailer }}
90
94
  CG_OUTPUT_FILE: ${{ inputs.output-file }}
91
95
  run: |
@@ -105,6 +109,8 @@ runs:
105
109
  [[ -n "$CG_NO_TRAILING_CHARS" ]] && ARGS+=(--no-trailing-chars "$CG_NO_TRAILING_CHARS")
106
110
  [[ "$CG_ALLOW_EMPTY" == "true" ]] && ARGS+=(--allow-empty)
107
111
  [[ "$CG_INCLUDE_MERGES" == "true" ]] && ARGS+=(--include-merges)
112
+ [[ -n "$CG_REQUIRE_SUBJECT_PATTERN" ]] && \
113
+ ARGS+=(--require-subject-pattern "$CG_REQUIRE_SUBJECT_PATTERN")
108
114
  [[ -n "$CG_REQUIRE_TRAILER" ]] && ARGS+=(--require-trailer "$CG_REQUIRE_TRAILER")
109
115
  [[ -n "$CG_OUTPUT_FILE" ]] && ARGS+=(--output-file "$CG_OUTPUT_FILE")
110
116
  commit-guard "${ARGS[@]}"
@@ -0,0 +1,56 @@
1
+ [changelog]
2
+ body = """
3
+ {% if version %}\
4
+ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
5
+ {% else %}\
6
+ ## [unreleased]
7
+ {% endif %}\
8
+ {% for group, commits in commits | group_by(attribute="group") %}
9
+ ### {{ group | striptags | trim | upper_first }}
10
+ {% for commit in commits %}
11
+ - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
12
+ {% if commit.breaking %}[**breaking**] {% endif %}\
13
+ {{ commit.message | upper_first }} \
14
+ ([`{{ commit.id | truncate(length=7, end="") }}`]\
15
+ (https://github.com/benner/commit-guard/commit/{{ commit.id }}))\
16
+ {% endfor %}
17
+ {% endfor %}
18
+ {%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
19
+
20
+ ### New Contributors
21
+ {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
22
+ - @{{ contributor.username }} made their first contribution\
23
+ {% if contributor.pr_number %} in \
24
+ [#{{ contributor.pr_number }}]\
25
+ (https://github.com/benner/commit-guard/pull/{{ contributor.pr_number }})\
26
+ {% endif %}
27
+ {% endfor %}
28
+ {%- endif %}
29
+ """
30
+ trim = true
31
+
32
+ [remote.github]
33
+ owner = "benner"
34
+ repo = "commit-guard"
35
+
36
+ [git]
37
+ conventional_commits = true
38
+ filter_unconventional = true
39
+ split_commits = false
40
+ protect_breaking_commits = false
41
+ commit_parsers = [
42
+ { message = "^feat", group = "<!-- 0 -->🚀 Features" },
43
+ { message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
44
+ { message = "^perf", group = "<!-- 2 -->⚡ Performance" },
45
+ { message = "^refactor", group = "<!-- 3 -->🚜 Refactor" },
46
+ { message = "^style", group = "<!-- 4 -->🎨 Styling" },
47
+ { message = "^test", group = "<!-- 5 -->🧪 Testing" },
48
+ { message = "^docs", skip = true },
49
+ { message = "^ci", skip = true },
50
+ { message = "^chore", group = "<!-- 6 -->⚙️ Miscellaneous Tasks" },
51
+ { message = "^revert", group = "<!-- 7 -->◀️ Revert" },
52
+ { body = ".*security", group = "<!-- 8 -->🛡️ Security" },
53
+ ]
54
+ filter_commits = true
55
+ topo_order = false
56
+ sort_commits = "oldest"
@@ -377,7 +377,7 @@ $ echo "fix(auth): add token refresh" | commit-guard</code></pre>
377
377
  </tr>
378
378
  <tr>
379
379
  <td><code>signature</code></td>
380
- <td>GPG or SSH signature is valid</td>
380
+ <td>GPG or SSH signature is valid — verified via GitHub Commits API or public key lookup</td>
381
381
  </tr>
382
382
  </tbody>
383
383
  </table>
@@ -400,7 +400,23 @@ max-subject-length = 100
400
400
  min-description-length = 10
401
401
  require-lowercase = false
402
402
  no-trailing-chars = [".", "!"]
403
- require-trailers = ["Closes", "Reviewed-by"]</code></pre>
403
+ require-trailers = ["Closes", "Reviewed-by"]
404
+ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
405
+
406
+ <h3>Required subject pattern</h3>
407
+ <p>
408
+ Require the commit subject to match a regular expression. Useful for
409
+ enforcing ticket references or any custom naming convention:
410
+ </p>
411
+ <pre><code class="language-bash">commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"</code></pre>
412
+ <p>
413
+ In <code>.commit-guard.toml</code>:
414
+ </p>
415
+ <pre><code class="language-toml">require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
416
+ <p>
417
+ An invalid regex causes an immediate error at startup (exit 2). This
418
+ check runs independently of <code>--enable</code>/<code>--disable</code>.
419
+ </p>
404
420
 
405
421
  <h3>Required trailers</h3>
406
422
  <p>
@@ -410,6 +426,39 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
410
426
  </p>
411
427
  <pre><code class="language-bash">commit-guard --require-trailer "Closes,Reviewed-by"</code></pre>
412
428
 
429
+ <h3>Signature verification</h3>
430
+ <p>
431
+ The <code>signature</code> check verifies commits without requiring a
432
+ pre-configured local keyring:
433
+ </p>
434
+ <ol>
435
+ <li>If the repo has a GitHub remote, call the Commits API
436
+ (<code>GET /repos/{owner}/{repo}/commits/{sha}</code>) to resolve
437
+ the author's GitHub username — works for corporate emails, noreply
438
+ addresses, or any email not listed publicly on a GitHub profile.</li>
439
+ <li>If the Commits API is unavailable (no GitHub remote, commit not
440
+ yet pushed, or API error), parse the username directly from a
441
+ GitHub noreply address
442
+ (<code>{id}+{username}@users.noreply.github.com</code>) — no API
443
+ call needed.</li>
444
+ <li>If neither of the above resolves a username, fall back to
445
+ searching GitHub by the commit author's email.</li>
446
+ <li>Fetch the resolved user's public keys from
447
+ <code>github.com/{username}.gpg</code> and
448
+ <code>github.com/{username}.keys</code>.</li>
449
+ <li>Try GPG verification using a temporary keyring.</li>
450
+ <li>Try SSH verification using a temporary <code>allowed_signers</code> file.</li>
451
+ <li>Pass if any key verifies; fail if none do.</li>
452
+ </ol>
453
+ <p>
454
+ If the author cannot be resolved via either method, or the GitHub API
455
+ is unreachable, the check fails with a clear error. For private
456
+ repositories, set <code>GITHUB_TOKEN</code> or <code>GH_TOKEN</code>
457
+ so the Commits API can authenticate. Disable the
458
+ <code>signature</code> check if GitHub API access is unavailable:
459
+ </p>
460
+ <pre><code class="language-bash">commit-guard --disable signature</code></pre>
461
+
413
462
  <h3>Range options</h3>
414
463
  <p>
415
464
  When using <code>--range</code>, merge commits are excluded by
@@ -435,6 +484,16 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
435
484
  <td><code>10</code></td>
436
485
  <td>Timeout in seconds for git subprocess calls</td>
437
486
  </tr>
487
+ <tr>
488
+ <td><code>GITHUB_TOKEN</code></td>
489
+ <td>—</td>
490
+ <td>GitHub token for Commits API access on private repos (signature check)</td>
491
+ </tr>
492
+ <tr>
493
+ <td><code>GH_TOKEN</code></td>
494
+ <td>—</td>
495
+ <td>Alias for <code>GITHUB_TOKEN</code>; used when <code>GITHUB_TOKEN</code> is not set</td>
496
+ </tr>
438
497
  </tbody>
439
498
  </table>
440
499
  </figure>
@@ -479,13 +538,13 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
479
538
  - uses: actions/checkout@v4
480
539
  with:
481
540
  fetch-depth: 0
482
- - uses: benner/commit-guard@v0.18.0
541
+ - uses: benner/commit-guard@v0.19.0
483
542
  with:
484
543
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
485
544
  disable: signed-off,signature</code></pre>
486
545
 
487
546
  <p>Check a specific commit SHA:</p>
488
- <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.18.0
547
+ <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.19.0
489
548
  with:
490
549
  rev: ${{ github.sha }}</code></pre>
491
550
 
@@ -504,7 +563,7 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
504
563
  When <code>output-file</code> is set the action exposes the path as
505
564
  a step output, making JSONL results available to subsequent steps:
506
565
  </p>
507
- <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.18.0
566
+ <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.19.0
508
567
  id: cg
509
568
  with:
510
569
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -517,7 +576,7 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
517
576
  <p>Add to <code>.pre-commit-config.yaml</code>:</p>
518
577
  <pre><code class="language-yaml">repos:
519
578
  - repo: https://github.com/benner/commit-guard
520
- rev: v0.18.0
579
+ rev: v0.19.0
521
580
  hooks:
522
581
  - id: commit-guard
523
582
  - id: commit-guard-signature</code></pre>