git-commit-guard 0.22.2__tar.gz → 0.22.3__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 (27) hide show
  1. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/coverage-baseline.yml +2 -2
  2. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/coverage-comment.yml +2 -2
  3. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/lint-commits.yml +1 -1
  4. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/lint-md.yml +1 -1
  5. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/lint-workflows.yml +2 -2
  6. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/release.yml +1 -1
  7. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/security-audit.yml +4 -2
  8. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/test.yml +2 -2
  9. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/PKG-INFO +12 -12
  10. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/README.md +11 -11
  11. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/docs/index.html +8 -8
  12. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/src/git_commit_guard/__init__.py +11 -11
  13. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/tests/test_git_commit_guard.py +63 -48
  14. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.editorconfig +0 -0
  15. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.github/workflows/lint-python.yml +0 -0
  16. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.gitignore +0 -0
  17. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.markdownlint.json +0 -0
  18. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.pre-commit-hooks.yaml +0 -0
  19. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/.python-version +0 -0
  20. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/LICENSE +0 -0
  21. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/action.yml +0 -0
  22. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/cliff.toml +0 -0
  23. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/docs/commit-guard-icon.svg +0 -0
  24. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/pyproject.toml +0 -0
  25. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/ruff.toml +0 -0
  26. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/tests/__init__.py +0 -0
  27. {git_commit_guard-0.22.2 → git_commit_guard-0.22.3}/uv.lock +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  persist-credentials: false
17
17
  - name: Install uv
18
18
  # yamllint disable-line rule:line-length
19
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
19
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0
20
20
  - name: Cache NLTK data
21
21
  # yamllint disable-line rule:line-length
22
22
  uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
@@ -27,7 +27,7 @@ jobs:
27
27
  run: uv run --dev pytest tests/ --cov=git_commit_guard --cov-report=xml
28
28
  - name: Upload coverage baseline
29
29
  # yamllint disable-line rule:line-length
30
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
30
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7
31
31
  with:
32
32
  name: main-coverage
33
33
  path: coverage.xml
@@ -16,7 +16,7 @@ jobs:
16
16
  steps:
17
17
  - name: Download artifact
18
18
  # yamllint disable-line rule:line-length
19
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # ratchet:actions/download-artifact@v4
19
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8
20
20
  with:
21
21
  run-id: ${{ github.event.workflow_run.id }}
22
22
  name: pr
@@ -57,7 +57,7 @@ jobs:
57
57
  - name: Download baseline coverage
58
58
  if: steps.baseline.outputs.run-id != ''
59
59
  # yamllint disable-line rule:line-length
60
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # ratchet:actions/download-artifact@v4
60
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8
61
61
  with:
62
62
  run-id: ${{ steps.baseline.outputs.run-id }}
63
63
  name: main-coverage
@@ -22,6 +22,6 @@ 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@9162df75e7a6eba395bf7c07c1a0952fc6d04ea0 # v0.22.1
25
+ uses: benner/commit-guard@e156ca25789cc1824f745d74209f9c2654135dca # v0.22.2
26
26
  with:
27
27
  range: origin/${{ github.base_ref }}..HEAD
@@ -37,7 +37,7 @@ jobs:
37
37
  persist-credentials: false
38
38
  - name: Set up uv
39
39
  # yamllint disable-line rule:line-length
40
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
40
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
41
41
  with:
42
42
  enable-cache: false
43
43
  - name: Set up reviewdog
@@ -34,7 +34,7 @@ jobs:
34
34
  persist-credentials: false
35
35
  - name: Set up uv
36
36
  # yamllint disable-line rule:line-length
37
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
37
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0
38
38
  - name: Set up reviewdog
39
39
  # yamllint disable-line rule:line-length
40
40
  uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
@@ -59,7 +59,7 @@ jobs:
59
59
  persist-credentials: false
60
60
  - name: Set up uv
61
61
  # yamllint disable-line rule:line-length
62
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
62
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # ratchet:astral-sh/setup-uv@v8.2.0
63
63
  - name: Set up reviewdog
64
64
  # yamllint disable-line rule:line-length
65
65
  uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
@@ -20,7 +20,7 @@ jobs:
20
20
  fetch-depth: 0
21
21
  - name: Set up uv
22
22
  # yamllint disable-line rule:line-length
23
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
23
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
24
24
  with:
25
25
  enable-cache: false
26
26
  - name: Build package
@@ -17,6 +17,8 @@ jobs:
17
17
  persist-credentials: false
18
18
  - name: Install uv
19
19
  # yamllint disable-line rule:line-length
20
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
20
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
21
21
  - name: Audit dependencies
22
- run: uv audit --frozen --preview-features audit-command
22
+ run: >-
23
+ uv audit --frozen --preview-features audit-command
24
+ --ignore-until-fixed GHSA-p4gq-832x-fm9v
@@ -15,7 +15,7 @@ jobs:
15
15
  persist-credentials: false
16
16
  - name: Install uv
17
17
  # yamllint disable-line rule:line-length
18
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
18
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
19
19
  - name: Cache NLTK data
20
20
  uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
21
21
  with:
@@ -35,7 +35,7 @@ jobs:
35
35
  cp coverage.xml ./pr/
36
36
  - name: Upload PR artifact
37
37
  # yamllint disable-line rule:line-length
38
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
38
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7
39
39
  with:
40
40
  name: pr
41
41
  path: pr/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-guard
3
- Version: 0.22.2
3
+ Version: 0.22.3
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
@@ -36,7 +36,7 @@ Opinionated conventional commit message linter with imperative mood detection.
36
36
  verb, verified via nltk POS tagging — not a hand-coded regex of "bad"
37
37
  words.
38
38
  * **Signature verification without a local keyring.** Resolves the commit
39
- author via the GitHub API and verifies GPG/SSH against their published
39
+ committer via the GitHub API and verifies GPG/SSH against their published
40
40
  `.gpg`/`.keys` — no per-runner key management.
41
41
  * **Strict by default.** Subject format, body, trailers, `Signed-off-by`,
42
42
  and signature all enforced out of the box; opt out with `--disable`.
@@ -242,7 +242,7 @@ independently of `--enable`/`--disable`.
242
242
  The `signature` check verifies the commit without any local keyring setup:
243
243
 
244
244
  1. If the repo has a GitHub remote, call the Commits API
245
- (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the author's GitHub
245
+ (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the committer's GitHub
246
246
  username — this works for corporate emails, noreply addresses, or any email
247
247
  not listed publicly on a GitHub profile.
248
248
  2. If the Commits API is unavailable (no GitHub remote, commit not yet pushed,
@@ -250,7 +250,7 @@ The `signature` check verifies the commit without any local keyring setup:
250
250
  (`{id}+{username}@users.noreply.github.com` or
251
251
  `{username}@users.noreply.github.com`) — no API call needed.
252
252
  3. If neither of the above resolves a username, fall back to searching GitHub
253
- by the commit author's email.
253
+ by the commit committer's email.
254
254
  4. Fetch the resolved user's public keys from `github.com/{username}.gpg`
255
255
  (GPG) and the `/users/{username}/ssh_signing_keys` API (SSH keys tagged
256
256
  with the **Signing key** role). Auth-only SSH keys are deliberately not
@@ -261,7 +261,7 @@ The `signature` check verifies the commit without any local keyring setup:
261
261
  `git verify-commit` with the SSH allowed-signers config.
262
262
  7. If any key verifies, the check passes. If none do, it fails.
263
263
 
264
- If the author cannot be resolved via either method, or the GitHub API is
264
+ If the committer cannot be resolved via either method, or the GitHub API is
265
265
  unreachable, the check fails with a clear error.
266
266
 
267
267
  For private repositories, set `GITHUB_TOKEN` or `GH_TOKEN` so the Commits API
@@ -316,7 +316,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
316
316
  In GitHub Actions, set it at the step or job level:
317
317
 
318
318
  ```yaml
319
- - uses: benner/commit-guard@v0.22.2
319
+ - uses: benner/commit-guard@v0.22.3
320
320
  env:
321
321
  COMMIT_GUARD_GIT_TIMEOUT: 30
322
322
  with:
@@ -400,7 +400,7 @@ steps:
400
400
  - uses: actions/checkout@v4
401
401
  with:
402
402
  fetch-depth: 0
403
- - uses: benner/commit-guard@v0.22.2
403
+ - uses: benner/commit-guard@v0.22.3
404
404
  ```
405
405
 
406
406
  Check all commits in a pull request:
@@ -416,7 +416,7 @@ jobs:
416
416
  - uses: actions/checkout@v4
417
417
  with:
418
418
  fetch-depth: 0
419
- - uses: benner/commit-guard@v0.22.2
419
+ - uses: benner/commit-guard@v0.22.3
420
420
  with:
421
421
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
422
422
  ```
@@ -424,7 +424,7 @@ jobs:
424
424
  Check a specific commit SHA (mirrors the positional CLI argument):
425
425
 
426
426
  ```yaml
427
- - uses: benner/commit-guard@v0.22.2
427
+ - uses: benner/commit-guard@v0.22.3
428
428
  with:
429
429
  rev: ${{ github.sha }}
430
430
  ```
@@ -442,7 +442,7 @@ jobs:
442
442
  - uses: actions/checkout@v4
443
443
  with:
444
444
  fetch-depth: 0
445
- - uses: benner/commit-guard@v0.22.2
445
+ - uses: benner/commit-guard@v0.22.3
446
446
  with:
447
447
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
448
448
  disable: signed-off,signature
@@ -462,7 +462,7 @@ jobs:
462
462
  When `output-file` is set the action exposes the path as an output:
463
463
 
464
464
  ```yaml
465
- - uses: benner/commit-guard@v0.22.2
465
+ - uses: benner/commit-guard@v0.22.3
466
466
  id: cg
467
467
  with:
468
468
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -478,7 +478,7 @@ Add to your `.pre-commit-config.yaml`:
478
478
  ---
479
479
  repos:
480
480
  - repo: https://github.com/benner/commit-guard
481
- rev: v0.22.2
481
+ rev: v0.22.3
482
482
  hooks:
483
483
  - id: commit-guard
484
484
  - id: commit-guard-signature
@@ -15,7 +15,7 @@ Opinionated conventional commit message linter with imperative mood detection.
15
15
  verb, verified via nltk POS tagging — not a hand-coded regex of "bad"
16
16
  words.
17
17
  * **Signature verification without a local keyring.** Resolves the commit
18
- author via the GitHub API and verifies GPG/SSH against their published
18
+ committer via the GitHub API and verifies GPG/SSH against their published
19
19
  `.gpg`/`.keys` — no per-runner key management.
20
20
  * **Strict by default.** Subject format, body, trailers, `Signed-off-by`,
21
21
  and signature all enforced out of the box; opt out with `--disable`.
@@ -221,7 +221,7 @@ independently of `--enable`/`--disable`.
221
221
  The `signature` check verifies the commit without any local keyring setup:
222
222
 
223
223
  1. If the repo has a GitHub remote, call the Commits API
224
- (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the author's GitHub
224
+ (`GET /repos/{owner}/{repo}/commits/{sha}`) to resolve the committer's GitHub
225
225
  username — this works for corporate emails, noreply addresses, or any email
226
226
  not listed publicly on a GitHub profile.
227
227
  2. If the Commits API is unavailable (no GitHub remote, commit not yet pushed,
@@ -229,7 +229,7 @@ The `signature` check verifies the commit without any local keyring setup:
229
229
  (`{id}+{username}@users.noreply.github.com` or
230
230
  `{username}@users.noreply.github.com`) — no API call needed.
231
231
  3. If neither of the above resolves a username, fall back to searching GitHub
232
- by the commit author's email.
232
+ by the commit committer's email.
233
233
  4. Fetch the resolved user's public keys from `github.com/{username}.gpg`
234
234
  (GPG) and the `/users/{username}/ssh_signing_keys` API (SSH keys tagged
235
235
  with the **Signing key** role). Auth-only SSH keys are deliberately not
@@ -240,7 +240,7 @@ The `signature` check verifies the commit without any local keyring setup:
240
240
  `git verify-commit` with the SSH allowed-signers config.
241
241
  7. If any key verifies, the check passes. If none do, it fails.
242
242
 
243
- If the author cannot be resolved via either method, or the GitHub API is
243
+ If the committer cannot be resolved via either method, or the GitHub API is
244
244
  unreachable, the check fails with a clear error.
245
245
 
246
246
  For private repositories, set `GITHUB_TOKEN` or `GH_TOKEN` so the Commits API
@@ -295,7 +295,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
295
295
  In GitHub Actions, set it at the step or job level:
296
296
 
297
297
  ```yaml
298
- - uses: benner/commit-guard@v0.22.2
298
+ - uses: benner/commit-guard@v0.22.3
299
299
  env:
300
300
  COMMIT_GUARD_GIT_TIMEOUT: 30
301
301
  with:
@@ -379,7 +379,7 @@ steps:
379
379
  - uses: actions/checkout@v4
380
380
  with:
381
381
  fetch-depth: 0
382
- - uses: benner/commit-guard@v0.22.2
382
+ - uses: benner/commit-guard@v0.22.3
383
383
  ```
384
384
 
385
385
  Check all commits in a pull request:
@@ -395,7 +395,7 @@ jobs:
395
395
  - uses: actions/checkout@v4
396
396
  with:
397
397
  fetch-depth: 0
398
- - uses: benner/commit-guard@v0.22.2
398
+ - uses: benner/commit-guard@v0.22.3
399
399
  with:
400
400
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
401
401
  ```
@@ -403,7 +403,7 @@ jobs:
403
403
  Check a specific commit SHA (mirrors the positional CLI argument):
404
404
 
405
405
  ```yaml
406
- - uses: benner/commit-guard@v0.22.2
406
+ - uses: benner/commit-guard@v0.22.3
407
407
  with:
408
408
  rev: ${{ github.sha }}
409
409
  ```
@@ -421,7 +421,7 @@ jobs:
421
421
  - uses: actions/checkout@v4
422
422
  with:
423
423
  fetch-depth: 0
424
- - uses: benner/commit-guard@v0.22.2
424
+ - uses: benner/commit-guard@v0.22.3
425
425
  with:
426
426
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
427
427
  disable: signed-off,signature
@@ -441,7 +441,7 @@ jobs:
441
441
  When `output-file` is set the action exposes the path as an output:
442
442
 
443
443
  ```yaml
444
- - uses: benner/commit-guard@v0.22.2
444
+ - uses: benner/commit-guard@v0.22.3
445
445
  id: cg
446
446
  with:
447
447
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -457,7 +457,7 @@ Add to your `.pre-commit-config.yaml`:
457
457
  ---
458
458
  repos:
459
459
  - repo: https://github.com/benner/commit-guard
460
- rev: v0.22.2
460
+ rev: v0.22.3
461
461
  hooks:
462
462
  - id: commit-guard
463
463
  - id: commit-guard-signature
@@ -315,7 +315,7 @@
315
315
  </li>
316
316
  <li>
317
317
  <strong>Signature verification without a local keyring.</strong>
318
- Resolves the commit author via the GitHub API and verifies GPG/SSH
318
+ Resolves the commit committer via the GitHub API and verifies GPG/SSH
319
319
  against their published <code>.gpg</code>/<code>.keys</code> — no
320
320
  per-runner key management.
321
321
  </li>
@@ -456,7 +456,7 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
456
456
  <ol>
457
457
  <li>If the repo has a GitHub remote, call the Commits API
458
458
  (<code>GET /repos/{owner}/{repo}/commits/{sha}</code>) to resolve
459
- the author's GitHub username — works for corporate emails, noreply
459
+ the committer's GitHub username — works for corporate emails, noreply
460
460
  addresses, or any email not listed publicly on a GitHub profile.</li>
461
461
  <li>If the Commits API is unavailable (no GitHub remote, commit not
462
462
  yet pushed, or API error), parse the username directly from a
@@ -464,7 +464,7 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
464
464
  (<code>{id}+{username}@users.noreply.github.com</code>) — no API
465
465
  call needed.</li>
466
466
  <li>If neither of the above resolves a username, fall back to
467
- searching GitHub by the commit author's email.</li>
467
+ searching GitHub by the commit committer's email.</li>
468
468
  <li>Fetch the resolved user's public keys from
469
469
  <code>github.com/{username}.gpg</code> (GPG) and
470
470
  <code>/users/{username}/ssh_signing_keys</code> (SSH keys tagged
@@ -476,7 +476,7 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
476
476
  <li>Pass if any key verifies; fail if none do.</li>
477
477
  </ol>
478
478
  <p>
479
- If the author cannot be resolved via either method, or the GitHub API
479
+ If the committer cannot be resolved via either method, or the GitHub API
480
480
  is unreachable, the check fails with a clear error. For private
481
481
  repositories, set <code>GITHUB_TOKEN</code> or <code>GH_TOKEN</code>
482
482
  so the Commits API can authenticate. The official GitHub Action wires
@@ -566,13 +566,13 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
566
566
  - uses: actions/checkout@v4
567
567
  with:
568
568
  fetch-depth: 0
569
- - uses: benner/commit-guard@v0.22.2
569
+ - uses: benner/commit-guard@v0.22.3
570
570
  with:
571
571
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
572
572
  disable: signed-off,signature</code></pre>
573
573
 
574
574
  <p>Check a specific commit SHA:</p>
575
- <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.22.2
575
+ <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.22.3
576
576
  with:
577
577
  rev: ${{ github.sha }}</code></pre>
578
578
 
@@ -591,7 +591,7 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
591
591
  When <code>output-file</code> is set the action exposes the path as
592
592
  a step output, making JSONL results available to subsequent steps:
593
593
  </p>
594
- <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.22.2
594
+ <pre><code class="language-yaml"> - uses: benner/commit-guard@v0.22.3
595
595
  id: cg
596
596
  with:
597
597
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -604,7 +604,7 @@ require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
604
604
  <p>Add to <code>.pre-commit-config.yaml</code>:</p>
605
605
  <pre><code class="language-yaml">repos:
606
606
  - repo: https://github.com/benner/commit-guard
607
- rev: v0.22.2
607
+ rev: v0.22.3
608
608
  hooks:
609
609
  - id: commit-guard
610
610
  - id: commit-guard-signature</code></pre>
@@ -295,9 +295,9 @@ def check_required_trailers(message, required, result):
295
295
  result.error(f"missing required trailer: {trailer}")
296
296
 
297
297
 
298
- def _get_author_email(rev):
298
+ def _get_committer_email(rev):
299
299
  return subprocess.check_output( # noqa: S603
300
- ["git", "log", "-1", "--format=%ae", rev], # noqa: S607
300
+ ["git", "log", "-1", "--format=%ce", rev], # noqa: S607
301
301
  text=True,
302
302
  stderr=subprocess.PIPE,
303
303
  timeout=_git_timeout(),
@@ -320,7 +320,7 @@ def _get_github_remote_info():
320
320
  return match.group("owner"), match.group("repo")
321
321
 
322
322
 
323
- def _fetch_github_commit_author(owner, repo, sha):
323
+ def _fetch_github_commit_committer(owner, repo, sha):
324
324
  url = f"https://api.github.com/repos/{owner}/{repo}/commits/{sha}"
325
325
  headers = {"Accept": "application/vnd.github+json"}
326
326
  token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
@@ -329,8 +329,8 @@ def _fetch_github_commit_author(owner, repo, sha):
329
329
  req = urllib.request.Request(url, headers=headers) # noqa: S310 Audit URL open for permitted schemes
330
330
  with urllib.request.urlopen(req, timeout=_git_timeout()) as resp: # noqa: S310 Audit URL open for permitted schemes
331
331
  data = json.loads(resp.read())
332
- author = data.get("author")
333
- return author["login"] if author else None
332
+ committer = data.get("committer")
333
+ return committer["login"] if committer else None
334
334
 
335
335
 
336
336
  def _parse_noreply_username(email):
@@ -435,7 +435,7 @@ def _resolve_github_username(rev, email):
435
435
  if remote:
436
436
  owner, repo = remote
437
437
  try:
438
- username = _fetch_github_commit_author(owner, repo, rev)
438
+ username = _fetch_github_commit_committer(owner, repo, rev)
439
439
  except urllib.error.HTTPError as e:
440
440
  if e.code == HTTPStatus.NOT_FOUND:
441
441
  commits_api_404 = True
@@ -450,24 +450,24 @@ def _resolve_github_username(rev, email):
450
450
  return username, commits_api_404
451
451
 
452
452
 
453
- def _author_not_found_message(commits_api_404):
453
+ def _committer_not_found_message(commits_api_404):
454
454
  had_token = bool(os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN"))
455
455
  if commits_api_404 and not had_token:
456
456
  return (
457
- "commit author not found on GitHub — if the repo is private, "
457
+ "committer not found on GitHub — if the repo is private, "
458
458
  "set GITHUB_TOKEN in the workflow step "
459
459
  "(env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }})"
460
460
  )
461
- return "commit author not found on GitHub — cannot verify signature"
461
+ return "committer not found on GitHub — cannot verify signature"
462
462
 
463
463
 
464
464
  def check_signature(rev, result):
465
465
  try:
466
- email = _get_author_email(rev)
466
+ email = _get_committer_email(rev)
467
467
  username, commits_api_404 = _resolve_github_username(rev, email)
468
468
  if username is None:
469
469
  result.error(
470
- _author_not_found_message(commits_api_404),
470
+ _committer_not_found_message(commits_api_404),
471
471
  check=Check.SIGNATURE,
472
472
  )
473
473
  return
@@ -15,12 +15,12 @@ from git_commit_guard import (
15
15
  Result,
16
16
  _download_if_missing,
17
17
  _ensure_nltk_data,
18
- _fetch_github_commit_author,
18
+ _fetch_github_commit_committer,
19
19
  _fetch_github_keys,
20
20
  _fetch_github_signing_keys,
21
21
  _fetch_github_username,
22
22
  _fetch_url,
23
- _get_author_email,
23
+ _get_committer_email,
24
24
  _get_github_remote_info,
25
25
  _get_message,
26
26
  _get_range_revs,
@@ -625,13 +625,14 @@ class TestDownloadIfMissing:
625
625
  mock_dl.assert_called_once_with("punkt_tab", quiet=True)
626
626
 
627
627
 
628
- class TestGetAuthorEmail:
628
+ class TestGetCommitterEmail:
629
629
  def test_returns_stripped_email(self):
630
630
  with patch(
631
631
  "git_commit_guard.subprocess.check_output",
632
632
  return_value="user@example.com\n",
633
- ):
634
- assert _get_author_email("abc123") == "user@example.com"
633
+ ) as mock_check:
634
+ assert _get_committer_email("abc123") == "user@example.com"
635
+ assert "--format=%ce" in mock_check.call_args.args[0]
635
636
 
636
637
 
637
638
  class TestFetchUrl:
@@ -716,7 +717,7 @@ class TestGetGithubRemoteInfo:
716
717
  assert _get_github_remote_info() is None
717
718
 
718
719
 
719
- class TestFetchGithubCommitAuthor:
720
+ class TestFetchGithubCommitCommitter:
720
721
  def _mock_response(self, data):
721
722
  mock_resp = MagicMock()
722
723
  mock_resp.__enter__ = lambda s: s
@@ -724,20 +725,31 @@ class TestFetchGithubCommitAuthor:
724
725
  mock_resp.read.return_value = json.dumps(data).encode()
725
726
  return mock_resp
726
727
 
727
- def test_returns_author_login(self):
728
- resp = self._mock_response({"author": {"login": "commituser"}})
728
+ def test_returns_committer_login(self):
729
+ resp = self._mock_response({"committer": {"login": "commituser"}})
730
+ with patch("git_commit_guard.urllib.request.urlopen", return_value=resp):
731
+ assert (
732
+ _fetch_github_commit_committer("owner", "repo", "abc123")
733
+ == "commituser"
734
+ )
735
+
736
+ def test_returns_committer_login_not_author(self):
737
+ resp = self._mock_response(
738
+ {"author": {"login": "the-author"}, "committer": {"login": "the-committer"}}
739
+ )
729
740
  with patch("git_commit_guard.urllib.request.urlopen", return_value=resp):
730
741
  assert (
731
- _fetch_github_commit_author("owner", "repo", "abc123") == "commituser"
742
+ _fetch_github_commit_committer("owner", "repo", "abc123")
743
+ == "the-committer"
732
744
  )
733
745
 
734
- def test_null_author_returns_none(self):
735
- resp = self._mock_response({"author": None})
746
+ def test_null_committer_returns_none(self):
747
+ resp = self._mock_response({"committer": None})
736
748
  with patch("git_commit_guard.urllib.request.urlopen", return_value=resp):
737
- assert _fetch_github_commit_author("owner", "repo", "abc123") is None
749
+ assert _fetch_github_commit_committer("owner", "repo", "abc123") is None
738
750
 
739
751
  def test_github_token_sent_in_header(self):
740
- resp = self._mock_response({"author": {"login": "user"}})
752
+ resp = self._mock_response({"committer": {"login": "user"}})
741
753
  captured = []
742
754
 
743
755
  def mock_urlopen(req, **_):
@@ -748,11 +760,11 @@ class TestFetchGithubCommitAuthor:
748
760
  patch("git_commit_guard.urllib.request.urlopen", side_effect=mock_urlopen),
749
761
  patch.dict("os.environ", {"GITHUB_TOKEN": "mytoken"}, clear=False),
750
762
  ):
751
- _fetch_github_commit_author("owner", "repo", "abc123")
763
+ _fetch_github_commit_committer("owner", "repo", "abc123")
752
764
  assert captured[0].get_header("Authorization") == "Bearer mytoken"
753
765
 
754
766
  def test_gh_token_used_when_github_token_absent(self):
755
- resp = self._mock_response({"author": {"login": "user"}})
767
+ resp = self._mock_response({"committer": {"login": "user"}})
756
768
  captured = []
757
769
 
758
770
  def mock_urlopen(req, **_):
@@ -765,7 +777,7 @@ class TestFetchGithubCommitAuthor:
765
777
  patch("git_commit_guard.urllib.request.urlopen", side_effect=mock_urlopen),
766
778
  patch.dict("os.environ", env, clear=True),
767
779
  ):
768
- _fetch_github_commit_author("owner", "repo", "abc123")
780
+ _fetch_github_commit_committer("owner", "repo", "abc123")
769
781
  assert captured[0].get_header("Authorization") == "Bearer ghtoken"
770
782
 
771
783
 
@@ -894,7 +906,7 @@ class TestCheckSignature:
894
906
  r = Result()
895
907
  with (
896
908
  patch(
897
- "git_commit_guard._get_author_email", return_value="user@example.com"
909
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
898
910
  ),
899
911
  patch("git_commit_guard._get_github_remote_info", return_value=None),
900
912
  patch("git_commit_guard._fetch_github_username", return_value="testuser"),
@@ -909,7 +921,7 @@ class TestCheckSignature:
909
921
  r = Result()
910
922
  with (
911
923
  patch(
912
- "git_commit_guard._get_author_email", return_value="user@example.com"
924
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
913
925
  ),
914
926
  patch("git_commit_guard._get_github_remote_info", return_value=None),
915
927
  patch("git_commit_guard._fetch_github_username", return_value="testuser"),
@@ -925,7 +937,7 @@ class TestCheckSignature:
925
937
  r = Result()
926
938
  with (
927
939
  patch(
928
- "git_commit_guard._get_author_email", return_value="user@example.com"
940
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
929
941
  ),
930
942
  patch("git_commit_guard._get_github_remote_info", return_value=None),
931
943
  patch("git_commit_guard._fetch_github_username", return_value="testuser"),
@@ -940,7 +952,7 @@ class TestCheckSignature:
940
952
  r = Result()
941
953
  with (
942
954
  patch(
943
- "git_commit_guard._get_author_email", return_value="user@example.com"
955
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
944
956
  ),
945
957
  patch("git_commit_guard._get_github_remote_info", return_value=None),
946
958
  patch("git_commit_guard._fetch_github_username", return_value=None),
@@ -956,14 +968,14 @@ class TestCheckSignature:
956
968
  }
957
969
  with (
958
970
  patch(
959
- "git_commit_guard._get_author_email", return_value="user@example.com"
971
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
960
972
  ),
961
973
  patch(
962
974
  "git_commit_guard._get_github_remote_info",
963
975
  return_value=("owner", "repo"),
964
976
  ),
965
977
  patch(
966
- "git_commit_guard._fetch_github_commit_author",
978
+ "git_commit_guard._fetch_github_commit_committer",
967
979
  side_effect=urllib.error.HTTPError(
968
980
  url="", code=404, msg="Not Found", hdrs=None, fp=None
969
981
  ),
@@ -979,14 +991,14 @@ class TestCheckSignature:
979
991
  r = Result()
980
992
  with (
981
993
  patch(
982
- "git_commit_guard._get_author_email", return_value="user@example.com"
994
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
983
995
  ),
984
996
  patch(
985
997
  "git_commit_guard._get_github_remote_info",
986
998
  return_value=("owner", "repo"),
987
999
  ),
988
1000
  patch(
989
- "git_commit_guard._fetch_github_commit_author",
1001
+ "git_commit_guard._fetch_github_commit_committer",
990
1002
  side_effect=urllib.error.HTTPError(
991
1003
  url="", code=404, msg="Not Found", hdrs=None, fp=None
992
1004
  ),
@@ -1003,14 +1015,14 @@ class TestCheckSignature:
1003
1015
  r = Result()
1004
1016
  with (
1005
1017
  patch(
1006
- "git_commit_guard._get_author_email", return_value="user@example.com"
1018
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1007
1019
  ),
1008
1020
  patch(
1009
1021
  "git_commit_guard._get_github_remote_info",
1010
1022
  return_value=("owner", "repo"),
1011
1023
  ),
1012
1024
  patch(
1013
- "git_commit_guard._fetch_github_commit_author",
1025
+ "git_commit_guard._fetch_github_commit_committer",
1014
1026
  side_effect=urllib.error.HTTPError(
1015
1027
  url="", code=500, msg="Server Error", hdrs=None, fp=None
1016
1028
  ),
@@ -1026,14 +1038,14 @@ class TestCheckSignature:
1026
1038
  r = Result()
1027
1039
  with (
1028
1040
  patch(
1029
- "git_commit_guard._get_author_email", return_value="user@example.com"
1041
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1030
1042
  ),
1031
1043
  patch(
1032
1044
  "git_commit_guard._get_github_remote_info",
1033
1045
  return_value=("owner", "repo"),
1034
1046
  ),
1035
1047
  patch(
1036
- "git_commit_guard._fetch_github_commit_author",
1048
+ "git_commit_guard._fetch_github_commit_committer",
1037
1049
  side_effect=urllib.error.HTTPError(
1038
1050
  url="", code=401, msg="Unauthorized", hdrs=None, fp=None
1039
1051
  ),
@@ -1047,14 +1059,14 @@ class TestCheckSignature:
1047
1059
  r = Result()
1048
1060
  with (
1049
1061
  patch(
1050
- "git_commit_guard._get_author_email", return_value="user@example.com"
1062
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1051
1063
  ),
1052
1064
  patch(
1053
1065
  "git_commit_guard._get_github_remote_info",
1054
1066
  return_value=("owner", "repo"),
1055
1067
  ),
1056
1068
  patch(
1057
- "git_commit_guard._fetch_github_commit_author",
1069
+ "git_commit_guard._fetch_github_commit_committer",
1058
1070
  side_effect=urllib.error.HTTPError(
1059
1071
  url="", code=403, msg="Forbidden", hdrs=None, fp=None
1060
1072
  ),
@@ -1068,7 +1080,7 @@ class TestCheckSignature:
1068
1080
  r = Result()
1069
1081
  with (
1070
1082
  patch(
1071
- "git_commit_guard._get_author_email", return_value="corp@example.com"
1083
+ "git_commit_guard._get_committer_email", return_value="corp@example.com"
1072
1084
  ),
1073
1085
  patch("git_commit_guard._get_github_remote_info", return_value=None),
1074
1086
  patch(
@@ -1086,7 +1098,7 @@ class TestCheckSignature:
1086
1098
  r = Result()
1087
1099
  with (
1088
1100
  patch(
1089
- "git_commit_guard._get_author_email", return_value="corp@example.com"
1101
+ "git_commit_guard._get_committer_email", return_value="corp@example.com"
1090
1102
  ),
1091
1103
  patch("git_commit_guard._get_github_remote_info", return_value=None),
1092
1104
  patch(
@@ -1103,7 +1115,7 @@ class TestCheckSignature:
1103
1115
  def test_url_error_fails(self):
1104
1116
  r = Result()
1105
1117
  with patch(
1106
- "git_commit_guard._get_author_email",
1118
+ "git_commit_guard._get_committer_email",
1107
1119
  side_effect=urllib.error.URLError("unreachable"),
1108
1120
  ):
1109
1121
  check_signature("abc123", r)
@@ -1112,7 +1124,7 @@ class TestCheckSignature:
1112
1124
 
1113
1125
  def test_timeout_error_fails(self):
1114
1126
  r = Result()
1115
- with patch("git_commit_guard._get_author_email", side_effect=TimeoutError()):
1127
+ with patch("git_commit_guard._get_committer_email", side_effect=TimeoutError()):
1116
1128
  check_signature("abc123", r)
1117
1129
  assert not r.ok
1118
1130
  assert any("API unreachable" in msg for _, _, msg in r.errors)
@@ -1120,7 +1132,7 @@ class TestCheckSignature:
1120
1132
  def test_subprocess_timeout_fails_gracefully(self):
1121
1133
  r = Result()
1122
1134
  with patch(
1123
- "git_commit_guard._get_author_email",
1135
+ "git_commit_guard._get_committer_email",
1124
1136
  side_effect=subprocess.TimeoutExpired(cmd="git", timeout=10),
1125
1137
  ):
1126
1138
  check_signature("abc123", r)
@@ -1131,14 +1143,15 @@ class TestCheckSignature:
1131
1143
  r = Result()
1132
1144
  with (
1133
1145
  patch(
1134
- "git_commit_guard._get_author_email", return_value="corp@example.com"
1146
+ "git_commit_guard._get_committer_email", return_value="corp@example.com"
1135
1147
  ),
1136
1148
  patch(
1137
1149
  "git_commit_guard._get_github_remote_info",
1138
1150
  return_value=("owner", "repo"),
1139
1151
  ),
1140
1152
  patch(
1141
- "git_commit_guard._fetch_github_commit_author", return_value="corpuser"
1153
+ "git_commit_guard._fetch_github_commit_committer",
1154
+ return_value="corpuser",
1142
1155
  ),
1143
1156
  patch("git_commit_guard._fetch_github_username") as mock_email_search,
1144
1157
  patch("git_commit_guard._fetch_github_keys", return_value=("GPG KEY", "")),
@@ -1152,14 +1165,14 @@ class TestCheckSignature:
1152
1165
  r = Result()
1153
1166
  with (
1154
1167
  patch(
1155
- "git_commit_guard._get_author_email", return_value="corp@example.com"
1168
+ "git_commit_guard._get_committer_email", return_value="corp@example.com"
1156
1169
  ),
1157
1170
  patch(
1158
1171
  "git_commit_guard._get_github_remote_info",
1159
1172
  return_value=("owner", "repo"),
1160
1173
  ),
1161
1174
  patch(
1162
- "git_commit_guard._fetch_github_commit_author",
1175
+ "git_commit_guard._fetch_github_commit_committer",
1163
1176
  side_effect=urllib.error.URLError("not found"),
1164
1177
  ),
1165
1178
  patch("git_commit_guard._fetch_github_username", return_value="emailuser"),
@@ -1169,17 +1182,17 @@ class TestCheckSignature:
1169
1182
  check_signature("abc123", r)
1170
1183
  assert r.ok
1171
1184
 
1172
- def test_commits_api_null_author_falls_back_to_email_search(self):
1185
+ def test_commits_api_null_committer_falls_back_to_email_search(self):
1173
1186
  r = Result()
1174
1187
  with (
1175
1188
  patch(
1176
- "git_commit_guard._get_author_email", return_value="user@example.com"
1189
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1177
1190
  ),
1178
1191
  patch(
1179
1192
  "git_commit_guard._get_github_remote_info",
1180
1193
  return_value=("owner", "repo"),
1181
1194
  ),
1182
- patch("git_commit_guard._fetch_github_commit_author", return_value=None),
1195
+ patch("git_commit_guard._fetch_github_commit_committer", return_value=None),
1183
1196
  patch("git_commit_guard._fetch_github_username", return_value="emailuser"),
1184
1197
  patch("git_commit_guard._fetch_github_keys", return_value=("", "SSH KEY")),
1185
1198
  patch("git_commit_guard._verify_gpg", return_value=False),
@@ -1192,10 +1205,12 @@ class TestCheckSignature:
1192
1205
  r = Result()
1193
1206
  with (
1194
1207
  patch(
1195
- "git_commit_guard._get_author_email", return_value="user@example.com"
1208
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1196
1209
  ),
1197
1210
  patch("git_commit_guard._get_github_remote_info", return_value=None),
1198
- patch("git_commit_guard._fetch_github_commit_author") as mock_commits_api,
1211
+ patch(
1212
+ "git_commit_guard._fetch_github_commit_committer"
1213
+ ) as mock_commits_api,
1199
1214
  patch("git_commit_guard._fetch_github_username", return_value="emailuser"),
1200
1215
  patch("git_commit_guard._fetch_github_keys", return_value=("GPG KEY", "")),
1201
1216
  patch("git_commit_guard._verify_gpg", return_value=True),
@@ -1208,7 +1223,7 @@ class TestCheckSignature:
1208
1223
  r = Result()
1209
1224
  with (
1210
1225
  patch(
1211
- "git_commit_guard._get_author_email",
1226
+ "git_commit_guard._get_committer_email",
1212
1227
  return_value="12345678+alice@users.noreply.github.com",
1213
1228
  ),
1214
1229
  patch("git_commit_guard._get_github_remote_info", return_value=None),
@@ -1224,7 +1239,7 @@ class TestCheckSignature:
1224
1239
  r = Result()
1225
1240
  with (
1226
1241
  patch(
1227
- "git_commit_guard._get_author_email",
1242
+ "git_commit_guard._get_committer_email",
1228
1243
  return_value="12345678+alice@users.noreply.github.com",
1229
1244
  ),
1230
1245
  patch(
@@ -1232,7 +1247,7 @@ class TestCheckSignature:
1232
1247
  return_value=("owner", "repo"),
1233
1248
  ),
1234
1249
  patch(
1235
- "git_commit_guard._fetch_github_commit_author",
1250
+ "git_commit_guard._fetch_github_commit_committer",
1236
1251
  side_effect=urllib.error.URLError("not found"),
1237
1252
  ),
1238
1253
  patch("git_commit_guard._fetch_github_username") as mock_email_search,
@@ -1630,7 +1645,7 @@ class TestMain:
1630
1645
  patch("sys.argv", ["cg", "abc123", "--enable", "signature"]),
1631
1646
  patch("git_commit_guard._get_message", return_value=_VALID_MSG),
1632
1647
  patch(
1633
- "git_commit_guard._get_author_email", return_value="user@example.com"
1648
+ "git_commit_guard._get_committer_email", return_value="user@example.com"
1634
1649
  ),
1635
1650
  patch("git_commit_guard._get_github_remote_info", return_value=None),
1636
1651
  patch("git_commit_guard._fetch_github_username", return_value="testuser"),