git-commit-guard 0.22.2__py3-none-any.whl → 0.23.0__py3-none-any.whl

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.
@@ -146,6 +146,10 @@ class Result:
146
146
  def ok(self):
147
147
  return not any(lvl == Level.ERROR for _, lvl, _ in self.errors)
148
148
 
149
+ @property
150
+ def noteworthy(self):
151
+ return any(lvl in (Level.ERROR, Level.WARN) for _, lvl, _ in self.errors)
152
+
149
153
 
150
154
  def _ensure_nltk_data():
151
155
  _download_if_missing("taggers/averaged_perceptron_tagger_eng")
@@ -295,9 +299,9 @@ def check_required_trailers(message, required, result):
295
299
  result.error(f"missing required trailer: {trailer}")
296
300
 
297
301
 
298
- def _get_author_email(rev):
302
+ def _get_committer_email(rev):
299
303
  return subprocess.check_output( # noqa: S603
300
- ["git", "log", "-1", "--format=%ae", rev], # noqa: S607
304
+ ["git", "log", "-1", "--format=%ce", rev], # noqa: S607
301
305
  text=True,
302
306
  stderr=subprocess.PIPE,
303
307
  timeout=_git_timeout(),
@@ -320,7 +324,7 @@ def _get_github_remote_info():
320
324
  return match.group("owner"), match.group("repo")
321
325
 
322
326
 
323
- def _fetch_github_commit_author(owner, repo, sha):
327
+ def _fetch_github_commit_committer(owner, repo, sha):
324
328
  url = f"https://api.github.com/repos/{owner}/{repo}/commits/{sha}"
325
329
  headers = {"Accept": "application/vnd.github+json"}
326
330
  token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
@@ -329,8 +333,8 @@ def _fetch_github_commit_author(owner, repo, sha):
329
333
  req = urllib.request.Request(url, headers=headers) # noqa: S310 Audit URL open for permitted schemes
330
334
  with urllib.request.urlopen(req, timeout=_git_timeout()) as resp: # noqa: S310 Audit URL open for permitted schemes
331
335
  data = json.loads(resp.read())
332
- author = data.get("author")
333
- return author["login"] if author else None
336
+ committer = data.get("committer")
337
+ return committer["login"] if committer else None
334
338
 
335
339
 
336
340
  def _parse_noreply_username(email):
@@ -435,7 +439,7 @@ def _resolve_github_username(rev, email):
435
439
  if remote:
436
440
  owner, repo = remote
437
441
  try:
438
- username = _fetch_github_commit_author(owner, repo, rev)
442
+ username = _fetch_github_commit_committer(owner, repo, rev)
439
443
  except urllib.error.HTTPError as e:
440
444
  if e.code == HTTPStatus.NOT_FOUND:
441
445
  commits_api_404 = True
@@ -450,24 +454,24 @@ def _resolve_github_username(rev, email):
450
454
  return username, commits_api_404
451
455
 
452
456
 
453
- def _author_not_found_message(commits_api_404):
457
+ def _committer_not_found_message(commits_api_404):
454
458
  had_token = bool(os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN"))
455
459
  if commits_api_404 and not had_token:
456
460
  return (
457
- "commit author not found on GitHub — if the repo is private, "
461
+ "committer not found on GitHub — if the repo is private, "
458
462
  "set GITHUB_TOKEN in the workflow step "
459
463
  "(env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }})"
460
464
  )
461
- return "commit author not found on GitHub — cannot verify signature"
465
+ return "committer not found on GitHub — cannot verify signature"
462
466
 
463
467
 
464
468
  def check_signature(rev, result):
465
469
  try:
466
- email = _get_author_email(rev)
470
+ email = _get_committer_email(rev)
467
471
  username, commits_api_404 = _resolve_github_username(rev, email)
468
472
  if username is None:
469
473
  result.error(
470
- _author_not_found_message(commits_api_404),
474
+ _committer_not_found_message(commits_api_404),
471
475
  check=Check.SIGNATURE,
472
476
  )
473
477
  return
@@ -565,6 +569,7 @@ class Args:
565
569
  subject_pattern: re.Pattern | None
566
570
  output: OutputFormat
567
571
  output_file: Path | None
572
+ quiet: bool
568
573
 
569
574
 
570
575
  def _resolve_enabled(args, config, parser):
@@ -662,7 +667,7 @@ def _parse_checks(parser, value):
662
667
  parser.error(str(e))
663
668
 
664
669
 
665
- def _parse_args(): # noqa: PLR0915 Too many statements (59 > 50)
670
+ def _parse_args(): # noqa: PLR0915 Too many statements (60 > 50)
666
671
  checks_list = ",".join(sorted(Check))
667
672
  parser = ArgumentParser(description="conventional commit checker")
668
673
  parser.add_argument("rev", nargs="?", default=None)
@@ -761,6 +766,13 @@ def _parse_args(): # noqa: PLR0915 Too many statements (59 > 50)
761
766
  metavar="FILE",
762
767
  help="write JSONL results to FILE (text still goes to stdout)",
763
768
  )
769
+ parser.add_argument(
770
+ "-q",
771
+ "--quiet",
772
+ action="store_true",
773
+ default=False,
774
+ help="suppress output for commits without errors or warnings",
775
+ )
764
776
  args = parser.parse_args()
765
777
  config = _load_config()
766
778
  enabled = _resolve_enabled(args, config, parser)
@@ -823,6 +835,7 @@ def _parse_args(): # noqa: PLR0915 Too many statements (59 > 50)
823
835
  subject_pattern=subject_pattern,
824
836
  output=OutputFormat(args.output),
825
837
  output_file=args.output_file,
838
+ quiet=args.quiet,
826
839
  )
827
840
 
828
841
 
@@ -838,7 +851,9 @@ def _jsonl_record(result, sha, subject):
838
851
  }
839
852
 
840
853
 
841
- def _report_jsonl(result, sha, subject):
854
+ def _report_jsonl(result, sha, subject, *, quiet=False):
855
+ if quiet and not result.noteworthy:
856
+ return 0
842
857
  print(json.dumps(_jsonl_record(result, sha, subject)))
843
858
  return 0 if result.ok else 1
844
859
 
@@ -847,7 +862,9 @@ def _write_jsonl_record(result, sha, subject, file):
847
862
  file.write(json.dumps(_jsonl_record(result, sha, subject)) + "\n")
848
863
 
849
864
 
850
- def _report_text(result):
865
+ def _report_text(result, *, quiet=False):
866
+ if quiet and not result.noteworthy:
867
+ return 0
851
868
  color = sys.stdout.isatty()
852
869
  for check, level, msg in result.errors:
853
870
  prefix = f"[{check}] " if check else ""
@@ -892,6 +909,14 @@ def _run_checks(args, rev, message, result):
892
909
  check_signature(rev, result)
893
910
 
894
911
 
912
+ def _report_commit(args, result, sha, subject):
913
+ if args.output == OutputFormat.JSONL:
914
+ return _report_jsonl(result, sha, subject, quiet=args.quiet)
915
+ if args.rev_range and (not args.quiet or result.noteworthy):
916
+ print(f"{sha[:7]} {subject}")
917
+ return _report_text(result, quiet=args.quiet)
918
+
919
+
895
920
  def main():
896
921
  args = _parse_args()
897
922
 
@@ -911,13 +936,8 @@ def main():
911
936
  subject = message.split("\n")[0]
912
937
  result = Result()
913
938
  _run_checks(args, rev, message, result)
914
- if args.output == OutputFormat.JSONL:
915
- if _report_jsonl(result, rev, subject) != 0:
916
- failed = True
917
- else:
918
- print(f"{rev[:7]} {subject}")
919
- if _report_text(result) != 0:
920
- failed = True
939
+ if _report_commit(args, result, rev, subject) != 0:
940
+ failed = True
921
941
  if out_file:
922
942
  _write_jsonl_record(result, rev, subject, out_file)
923
943
  return 1 if failed else 0
@@ -927,6 +947,4 @@ def main():
927
947
  _run_checks(args, args.rev, args.message, result)
928
948
  if out_file:
929
949
  _write_jsonl_record(result, args.rev, subject, out_file)
930
- if args.output == OutputFormat.JSONL:
931
- return _report_jsonl(result, args.rev, subject)
932
- return _report_text(result)
950
+ return _report_commit(args, result, args.rev, subject)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-guard
3
- Version: 0.22.2
3
+ Version: 0.23.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
@@ -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.23.0
320
320
  env:
321
321
  COMMIT_GUARD_GIT_TIMEOUT: 30
322
322
  with:
@@ -352,6 +352,22 @@ misconfigured range specs in CI. Use `--allow-empty` to exit 0 instead:
352
352
  commit-guard --range origin/main..HEAD --allow-empty
353
353
  ```
354
354
 
355
+ ### Quiet mode
356
+
357
+ Use `--quiet` (or `-q`) to suppress output for commits that have nothing to
358
+ report — only commits with errors or warnings are printed. On a long range
359
+ this leaves exactly the offending commits in the output; a fully compliant
360
+ range prints nothing and exits 0:
361
+
362
+ ```bash
363
+ commit-guard --range origin/main..HEAD --quiet
364
+ ```
365
+
366
+ Quiet mode applies to single-commit and range mode, in both text and
367
+ `--output jsonl` formats. Exit codes are unchanged, so it composes with CI
368
+ gating. `--output-file` is not affected — the file always receives the
369
+ complete record stream.
370
+
355
371
  ### Machine-readable output
356
372
 
357
373
  Use `--output jsonl` to emit one JSON line per commit to stdout instead of the
@@ -400,7 +416,7 @@ steps:
400
416
  - uses: actions/checkout@v4
401
417
  with:
402
418
  fetch-depth: 0
403
- - uses: benner/commit-guard@v0.22.2
419
+ - uses: benner/commit-guard@v0.23.0
404
420
  ```
405
421
 
406
422
  Check all commits in a pull request:
@@ -416,7 +432,7 @@ jobs:
416
432
  - uses: actions/checkout@v4
417
433
  with:
418
434
  fetch-depth: 0
419
- - uses: benner/commit-guard@v0.22.2
435
+ - uses: benner/commit-guard@v0.23.0
420
436
  with:
421
437
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
422
438
  ```
@@ -424,7 +440,7 @@ jobs:
424
440
  Check a specific commit SHA (mirrors the positional CLI argument):
425
441
 
426
442
  ```yaml
427
- - uses: benner/commit-guard@v0.22.2
443
+ - uses: benner/commit-guard@v0.23.0
428
444
  with:
429
445
  rev: ${{ github.sha }}
430
446
  ```
@@ -442,7 +458,7 @@ jobs:
442
458
  - uses: actions/checkout@v4
443
459
  with:
444
460
  fetch-depth: 0
445
- - uses: benner/commit-guard@v0.22.2
461
+ - uses: benner/commit-guard@v0.23.0
446
462
  with:
447
463
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
448
464
  disable: signed-off,signature
@@ -462,7 +478,7 @@ jobs:
462
478
  When `output-file` is set the action exposes the path as an output:
463
479
 
464
480
  ```yaml
465
- - uses: benner/commit-guard@v0.22.2
481
+ - uses: benner/commit-guard@v0.23.0
466
482
  id: cg
467
483
  with:
468
484
  range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
@@ -478,7 +494,7 @@ Add to your `.pre-commit-config.yaml`:
478
494
  ---
479
495
  repos:
480
496
  - repo: https://github.com/benner/commit-guard
481
- rev: v0.22.2
497
+ rev: v0.23.0
482
498
  hooks:
483
499
  - id: commit-guard
484
500
  - id: commit-guard-signature
@@ -0,0 +1,6 @@
1
+ git_commit_guard/__init__.py,sha256=wiXXUQPw2NWPgWUh83EREtjp26L62X7fPNFZ0GcPbfQ,30139
2
+ git_commit_guard-0.23.0.dist-info/METADATA,sha256=pGircQocHHj1TqurmthduMAdh4ovzZza9r056hlF6iY,16043
3
+ git_commit_guard-0.23.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
4
+ git_commit_guard-0.23.0.dist-info/entry_points.txt,sha256=24HK4TwCgn3tSQHOWnGE6zJK6nvg7EMAut7VHe9nuH4,55
5
+ git_commit_guard-0.23.0.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
6
+ git_commit_guard-0.23.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,6 +0,0 @@
1
- git_commit_guard/__init__.py,sha256=FrKpmqyW_YO2RgMj8Ff2m1TVEqQqBDhvm1pFtfem8rQ,29599
2
- git_commit_guard-0.22.2.dist-info/METADATA,sha256=4pWaUYZ4ta6OwftzPFenE_KtBxzrFlmKcl1tKVi9qdE,15450
3
- git_commit_guard-0.22.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
4
- git_commit_guard-0.22.2.dist-info/entry_points.txt,sha256=24HK4TwCgn3tSQHOWnGE6zJK6nvg7EMAut7VHe9nuH4,55
5
- git_commit_guard-0.22.2.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
6
- git_commit_guard-0.22.2.dist-info/RECORD,,