git-commit-guard 0.9.0__tar.gz → 0.10.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 (27) hide show
  1. git_commit_guard-0.10.0/.github/workflows/release.yml +33 -0
  2. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/PKG-INFO +25 -10
  3. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/README.md +24 -9
  4. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/src/git_commit_guard/__init__.py +81 -13
  5. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/tests/test_git_commit_guard.py +165 -0
  6. git_commit_guard-0.9.0/htmlcov/.gitignore +0 -2
  7. git_commit_guard-0.9.0/htmlcov/class_index.html +0 -161
  8. git_commit_guard-0.9.0/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -735
  9. git_commit_guard-0.9.0/htmlcov/favicon_32_cb_c827f16f.png +0 -0
  10. git_commit_guard-0.9.0/htmlcov/function_index.html +0 -331
  11. git_commit_guard-0.9.0/htmlcov/index.html +0 -117
  12. git_commit_guard-0.9.0/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
  13. git_commit_guard-0.9.0/htmlcov/status.json +0 -1
  14. git_commit_guard-0.9.0/htmlcov/style_cb_9ff733b0.css +0 -389
  15. git_commit_guard-0.9.0/htmlcov/z_262b75d81d1cf686___init___py.html +0 -454
  16. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.github/workflows/lint-commits.yml +0 -0
  17. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.github/workflows/lint-md.yml +0 -0
  18. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.github/workflows/lint-python.yml +0 -0
  19. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.github/workflows/test.yml +0 -0
  20. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.gitignore +0 -0
  21. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.pre-commit-hooks.yaml +0 -0
  22. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/.python-version +0 -0
  23. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/LICENSE +0 -0
  24. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/pyproject.toml +0 -0
  25. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/ruff.toml +0 -0
  26. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/tests/__init__.py +0 -0
  27. {git_commit_guard-0.9.0 → git_commit_guard-0.10.0}/uv.lock +0 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Release
3
+ on: # yamllint disable-line rule:truthy
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ permissions:
8
+ contents: write
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout code
14
+ # yamllint disable-line rule:line-length
15
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16
+ with:
17
+ persist-credentials: false
18
+ fetch-depth: 0
19
+ - name: Generate release notes
20
+ id: git-cliff
21
+ # yamllint disable-line rule:line-length
22
+ uses: orhun/git-cliff-action@c93ef52f3d0ddcdcc9bd5447d98d458a11cd4f72 # v4.7.1
23
+ with:
24
+ args: --current
25
+ - name: Create GitHub Release
26
+ env:
27
+ GH_TOKEN: ${{ github.token }}
28
+ TAG: ${{ github.ref_name }}
29
+ NOTES_FILE: ${{ steps.git-cliff.outputs.changelog }}
30
+ run: |-
31
+ gh release create "$TAG" \
32
+ --title "$TAG" \
33
+ --notes-file "$NOTES_FILE"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-guard
3
- Version: 0.9.0
3
+ Version: 0.10.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
@@ -167,16 +167,31 @@ provided.
167
167
 
168
168
  ### Checking a range of commits
169
169
 
170
+ Use `--range` to check all commits in a revision range. All commits are
171
+ checked and a single non-zero exit code is returned if any fail:
172
+
173
+ ```bash
174
+ # check all commits in a PR
175
+ commit-guard --range origin/main..HEAD
176
+
177
+ # check between two tags
178
+ commit-guard --range v1.0..v2.0
179
+
180
+ # only subject checks on a range
181
+ commit-guard --range origin/main..HEAD --enable subject,imperative
182
+ ```
183
+
184
+ Merge commits are excluded by default. Use `--include-merges` to check them:
185
+
186
+ ```bash
187
+ commit-guard --range origin/main..HEAD --include-merges
188
+ ```
189
+
190
+ An empty range (no commits) exits non-zero by default — this catches
191
+ misconfigured range specs in CI. Use `--allow-empty` to exit 0 instead:
192
+
170
193
  ```bash
171
- # all non-merge commits between tags
172
- git rev-list --no-merges v1.0..v2.0 | while read -r rev; do
173
- commit-guard "$rev" || git log -1 --oneline "$rev"
174
- done
175
-
176
- # only subject checks on a PR range
177
- git rev-list --no-merges origin/main..HEAD | while read -r rev; do
178
- commit-guard "$rev" --enable subject,imperative
179
- done
194
+ commit-guard --range origin/main..HEAD --allow-empty
180
195
  ```
181
196
 
182
197
  ### pre-commit
@@ -146,16 +146,31 @@ provided.
146
146
 
147
147
  ### Checking a range of commits
148
148
 
149
+ Use `--range` to check all commits in a revision range. All commits are
150
+ checked and a single non-zero exit code is returned if any fail:
151
+
152
+ ```bash
153
+ # check all commits in a PR
154
+ commit-guard --range origin/main..HEAD
155
+
156
+ # check between two tags
157
+ commit-guard --range v1.0..v2.0
158
+
159
+ # only subject checks on a range
160
+ commit-guard --range origin/main..HEAD --enable subject,imperative
161
+ ```
162
+
163
+ Merge commits are excluded by default. Use `--include-merges` to check them:
164
+
165
+ ```bash
166
+ commit-guard --range origin/main..HEAD --include-merges
167
+ ```
168
+
169
+ An empty range (no commits) exits non-zero by default — this catches
170
+ misconfigured range specs in CI. Use `--allow-empty` to exit 0 instead:
171
+
149
172
  ```bash
150
- # all non-merge commits between tags
151
- git rev-list --no-merges v1.0..v2.0 | while read -r rev; do
152
- commit-guard "$rev" || git log -1 --oneline "$rev"
153
- done
154
-
155
- # only subject checks on a PR range
156
- git rev-list --no-merges origin/main..HEAD | while read -r rev; do
157
- commit-guard "$rev" --enable subject,imperative
158
- done
173
+ commit-guard --range origin/main..HEAD --allow-empty
159
174
  ```
160
175
 
161
176
  ### pre-commit
@@ -224,6 +224,23 @@ def _get_message(rev):
224
224
  sys.exit(f"git error: {stderr}")
225
225
 
226
226
 
227
+ def _get_range_revs(rev_range, *, include_merges=False):
228
+ cmd = ["git", "log", "--format=%H"]
229
+ if not include_merges:
230
+ cmd.append("--no-merges")
231
+ cmd.append(rev_range)
232
+ try:
233
+ output = subprocess.check_output( # noqa: S603
234
+ cmd,
235
+ text=True,
236
+ stderr=subprocess.PIPE,
237
+ timeout=GIT_TIMEOUT,
238
+ ).strip()
239
+ except subprocess.CalledProcessError as e:
240
+ sys.exit(f"git error: {e.stderr.strip()}")
241
+ return output.split("\n") if output else []
242
+
243
+
227
244
  @dataclass
228
245
  class Args:
229
246
  rev: str | None
@@ -233,6 +250,9 @@ class Args:
233
250
  require_scope: bool
234
251
  allowed_types: frozenset
235
252
  max_subject_length: int
253
+ rev_range: str | None
254
+ allow_empty: bool
255
+ include_merges: bool
236
256
 
237
257
 
238
258
  def _resolve_enabled(args, config, parser):
@@ -330,6 +350,24 @@ def _parse_args():
330
350
  metavar="N",
331
351
  help=f"maximum subject line length (default: {MAX_SUBJECT_LEN})",
332
352
  )
353
+ parser.add_argument(
354
+ "--range",
355
+ dest="rev_range",
356
+ metavar="REF..REF",
357
+ help="check all commits in the given revision range",
358
+ )
359
+ parser.add_argument(
360
+ "--allow-empty",
361
+ action="store_true",
362
+ default=False,
363
+ help="exit 0 when --range yields no commits (default: exit 1)",
364
+ )
365
+ parser.add_argument(
366
+ "--include-merges",
367
+ action="store_true",
368
+ default=False,
369
+ help="include merge commits when checking a range (default: excluded)",
370
+ )
333
371
  args = parser.parse_args()
334
372
  config = _load_config()
335
373
  enabled = _resolve_enabled(args, config, parser)
@@ -337,7 +375,17 @@ def _parse_args():
337
375
  allowed_types = _resolve_types(args, config)
338
376
  max_subject_length = _resolve_max_subject_length(args, config)
339
377
 
340
- if args.message_file:
378
+ if args.allow_empty and not args.rev_range:
379
+ parser.error("--allow-empty requires --range")
380
+ if args.include_merges and not args.rev_range:
381
+ parser.error("--include-merges requires --range")
382
+
383
+ if args.rev_range:
384
+ if args.rev is not None or args.message_file:
385
+ parser.error("--range cannot be combined with rev or --message-file")
386
+ rev = None
387
+ message = ""
388
+ elif args.message_file:
341
389
  rev = None
342
390
  message = _strip_comments(args.message_file.read_text().strip())
343
391
  elif args.rev:
@@ -358,6 +406,9 @@ def _parse_args():
358
406
  require_scope=require_scope,
359
407
  allowed_types=allowed_types,
360
408
  max_subject_length=max_subject_length,
409
+ rev_range=args.rev_range,
410
+ allow_empty=args.allow_empty,
411
+ include_merges=args.include_merges,
361
412
  )
362
413
 
363
414
 
@@ -371,15 +422,8 @@ def _report(result):
371
422
  return 0 if result.ok else 1
372
423
 
373
424
 
374
- def main():
375
- args = _parse_args()
376
- lines = args.message.split("\n")
377
-
378
- if Check.IMPERATIVE in args.enabled:
379
- _ensure_nltk_data()
380
-
381
- result = Result()
382
-
425
+ def _run_checks(args, rev, message, result):
426
+ lines = message.split("\n")
383
427
  desc = None
384
428
  if Check.SUBJECT in args.enabled:
385
429
  desc = check_subject(
@@ -399,8 +443,32 @@ def main():
399
443
  if Check.BODY in args.enabled:
400
444
  check_body(lines, result)
401
445
  if Check.SIGNED_OFF in args.enabled:
402
- check_signed_off(args.message, result)
403
- if Check.SIGNATURE in args.enabled and args.rev:
404
- check_signature(args.rev, result)
446
+ check_signed_off(message, result)
447
+ if Check.SIGNATURE in args.enabled and rev:
448
+ check_signature(rev, result)
405
449
 
450
+
451
+ def main():
452
+ args = _parse_args()
453
+
454
+ if Check.IMPERATIVE in args.enabled:
455
+ _ensure_nltk_data()
456
+
457
+ if args.rev_range:
458
+ revs = _get_range_revs(args.rev_range, include_merges=args.include_merges)
459
+ if not revs:
460
+ sys.stderr.write("no commits in range\n")
461
+ return 0 if args.allow_empty else 1
462
+ failed = False
463
+ for rev in revs:
464
+ message = _strip_comments(_get_message(rev))
465
+ sys.stderr.write(f"{rev[:7]} {message.split('\n')[0]}\n")
466
+ result = Result()
467
+ _run_checks(args, rev, message, result)
468
+ if _report(result) != 0:
469
+ failed = True
470
+ return 1 if failed else 0
471
+
472
+ result = Result()
473
+ _run_checks(args, args.rev, args.message, result)
406
474
  return _report(result)
@@ -11,6 +11,7 @@ from git_commit_guard import (
11
11
  _download_if_missing,
12
12
  _ensure_nltk_data,
13
13
  _get_message,
14
+ _get_range_revs,
14
15
  _load_config,
15
16
  _parse_checks,
16
17
  _parse_config_checks,
@@ -831,3 +832,167 @@ class TestMain:
831
832
  ),
832
833
  ):
833
834
  assert main() == 0
835
+
836
+ def test_range_all_pass(self):
837
+ with (
838
+ patch(
839
+ "sys.argv",
840
+ ["cg", "--range", "origin/main..HEAD", "--disable", "signature"],
841
+ ),
842
+ patch(
843
+ "git_commit_guard._get_range_revs",
844
+ return_value=["abc1234", "def5678"],
845
+ ),
846
+ patch("git_commit_guard._get_message", return_value=_VALID_MSG),
847
+ ):
848
+ assert main() == 0
849
+
850
+ def test_range_one_fails(self):
851
+ messages = {"abc1234": _VALID_MSG, "def5678": "not a valid commit message"}
852
+ with (
853
+ patch(
854
+ "sys.argv",
855
+ [
856
+ "cg",
857
+ "--range",
858
+ "origin/main..HEAD",
859
+ "--disable",
860
+ "signature,body,signed-off,imperative",
861
+ ],
862
+ ),
863
+ patch(
864
+ "git_commit_guard._get_range_revs",
865
+ return_value=["abc1234", "def5678"],
866
+ ),
867
+ patch(
868
+ "git_commit_guard._get_message",
869
+ side_effect=lambda rev: messages[rev],
870
+ ),
871
+ ):
872
+ assert main() == 1
873
+
874
+ def test_range_all_fail_returns_one(self):
875
+ with (
876
+ patch(
877
+ "sys.argv",
878
+ [
879
+ "cg",
880
+ "--range",
881
+ "origin/main..HEAD",
882
+ "--disable",
883
+ "signature,body,signed-off,imperative",
884
+ ],
885
+ ),
886
+ patch(
887
+ "git_commit_guard._get_range_revs",
888
+ return_value=["abc1234"],
889
+ ),
890
+ patch(
891
+ "git_commit_guard._get_message",
892
+ return_value="not a valid commit message",
893
+ ),
894
+ ):
895
+ assert main() == 1
896
+
897
+ def test_range_empty_returns_one(self, capsys):
898
+ with (
899
+ patch("sys.argv", ["cg", "--range", "origin/main..HEAD"]),
900
+ patch("git_commit_guard._get_range_revs", return_value=[]),
901
+ ):
902
+ assert main() == 1
903
+ assert "no commits in range" in capsys.readouterr().err
904
+
905
+ def test_range_empty_with_allow_empty_returns_zero(self, capsys):
906
+ with (
907
+ patch("sys.argv", ["cg", "--range", "origin/main..HEAD", "--allow-empty"]),
908
+ patch("git_commit_guard._get_range_revs", return_value=[]),
909
+ ):
910
+ assert main() == 0
911
+ assert "no commits in range" in capsys.readouterr().err
912
+
913
+ def test_allow_empty_without_range_exits(self):
914
+ with (
915
+ patch("sys.argv", ["cg", "--allow-empty"]),
916
+ pytest.raises(SystemExit),
917
+ ):
918
+ main()
919
+
920
+ def test_include_merges_without_range_exits(self):
921
+ with (
922
+ patch("sys.argv", ["cg", "--include-merges"]),
923
+ pytest.raises(SystemExit),
924
+ ):
925
+ main()
926
+
927
+ def test_range_include_merges_flag(self):
928
+ with (
929
+ patch(
930
+ "sys.argv",
931
+ [
932
+ "cg",
933
+ "--range",
934
+ "origin/main..HEAD",
935
+ "--include-merges",
936
+ "--disable",
937
+ "signature",
938
+ ],
939
+ ),
940
+ patch(
941
+ "git_commit_guard._get_range_revs",
942
+ return_value=["abc1234"],
943
+ ) as mock,
944
+ patch("git_commit_guard._get_message", return_value=_VALID_MSG),
945
+ ):
946
+ main()
947
+ mock.assert_called_once_with("origin/main..HEAD", include_merges=True)
948
+
949
+ def test_range_conflicts_with_rev(self):
950
+ with (
951
+ patch("sys.argv", ["cg", "abc123", "--range", "origin/main..HEAD"]),
952
+ pytest.raises(SystemExit),
953
+ ):
954
+ main()
955
+
956
+ def test_range_conflicts_with_message_file(self, tmp_path):
957
+ f = tmp_path / "msg"
958
+ f.write_text(_VALID_MSG)
959
+ with (
960
+ patch(
961
+ "sys.argv",
962
+ ["cg", "--message-file", str(f), "--range", "origin/main..HEAD"],
963
+ ),
964
+ pytest.raises(SystemExit),
965
+ ):
966
+ main()
967
+
968
+
969
+ class TestGetRangeRevs:
970
+ def test_returns_shas(self):
971
+ with patch(
972
+ "git_commit_guard.subprocess.check_output",
973
+ return_value="abc1234\ndef5678",
974
+ ):
975
+ assert _get_range_revs("origin/main..HEAD") == ["abc1234", "def5678"]
976
+
977
+ def test_excludes_merges_by_default(self):
978
+ with patch("git_commit_guard.subprocess.check_output", return_value="") as mock:
979
+ _get_range_revs("origin/main..HEAD")
980
+ assert "--no-merges" in mock.call_args[0][0]
981
+
982
+ def test_includes_merges_when_requested(self):
983
+ with patch("git_commit_guard.subprocess.check_output", return_value="") as mock:
984
+ _get_range_revs("origin/main..HEAD", include_merges=True)
985
+ assert "--no-merges" not in mock.call_args[0][0]
986
+
987
+ def test_empty_range_returns_empty_list(self):
988
+ with patch("git_commit_guard.subprocess.check_output", return_value=""):
989
+ assert _get_range_revs("origin/main..HEAD") == []
990
+
991
+ def test_invalid_range_exits(self):
992
+ err = subprocess.CalledProcessError(128, "git")
993
+ err.stderr = "fatal: bad revision 'bogus'"
994
+ with (
995
+ patch("git_commit_guard.subprocess.check_output", side_effect=err),
996
+ pytest.raises(SystemExit, match="git error"),
997
+ ):
998
+ _get_range_revs("bogus")
@@ -1,2 +0,0 @@
1
- # Created by coverage.py
2
- *
@@ -1,161 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
- <title>Coverage report</title>
6
- <link rel="icon" sizes="32x32" href="favicon_32_cb_c827f16f.png">
7
- <link rel="stylesheet" href="style_cb_9ff733b0.css" type="text/css">
8
- <script src="coverage_html_cb_dd2e7eb5.js" defer></script>
9
- </head>
10
- <body class="indexfile">
11
- <header>
12
- <div class="content">
13
- <h1>Coverage report:
14
- <span class="pc_cov">99%</span>
15
- </h1>
16
- <aside id="help_panel_wrapper">
17
- <input id="help_panel_state" type="checkbox">
18
- <label for="help_panel_state">
19
- <img id="keyboard_icon" src="keybd_closed_cb_900cfef5.png" alt="Show/hide keyboard shortcuts">
20
- </label>
21
- <div id="help_panel">
22
- <p class="legend">Shortcuts on this page</p>
23
- <div class="keyhelp">
24
- <p>
25
- <kbd>f</kbd>
26
- <kbd>n</kbd>
27
- <kbd>s</kbd>
28
- <kbd>m</kbd>
29
- <kbd>x</kbd>
30
- <kbd>c</kbd>
31
- &nbsp; change column sorting
32
- </p>
33
- <p>
34
- <kbd>[</kbd>
35
- <kbd>]</kbd>
36
- &nbsp; prev/next file
37
- </p>
38
- <p>
39
- <kbd>?</kbd> &nbsp; show/hide this help
40
- </p>
41
- </div>
42
- </div>
43
- </aside>
44
- <form id="filter_container">
45
- <input id="filter" type="text" value="" placeholder="filter...">
46
- <div>
47
- <input id="hide100" type="checkbox" >
48
- <label for="hide100">hide covered</label>
49
- </div>
50
- </form>
51
- <h2>
52
- <a class="button" href="index.html">Files</a>
53
- <a class="button" href="function_index.html">Functions</a>
54
- <a class="button current">Classes</a>
55
- </h2>
56
- <p class="text">
57
- <a class="nav" href="https://coverage.readthedocs.io/en/7.13.5">coverage.py v7.13.5</a>,
58
- created at 2026-04-18 23:09 +0300
59
- </p>
60
- </div>
61
- </header>
62
- <main id="index">
63
- <table class="index" data-sortable>
64
- <thead>
65
- <tr class="tablehead" title="Click to sort">
66
- <th id="file" class="name" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
67
- <th id="region" class="name" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">class<span class="arrows"></span></th>
68
- <th class="spacer">&nbsp;</th>
69
- <th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
70
- <th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
71
- <th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
72
- <th class="spacer">&nbsp;</th>
73
- <th id="coverage" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
74
- </tr>
75
- </thead>
76
- <tbody>
77
- <tr class="region">
78
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t44">src&#8201;/&#8201;git_commit_guard&#8201;/&#8201;__init__.py</a></td>
79
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t44"><data value='Check'>Check</data></a></td>
80
- <td class="spacer">&nbsp;</td>
81
- <td>0</td>
82
- <td>0</td>
83
- <td>0</td>
84
- <td class="spacer">&nbsp;</td>
85
- <td data-ratio="0 0">100%</td>
86
- </tr>
87
- <tr class="region">
88
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t72">src&#8201;/&#8201;git_commit_guard&#8201;/&#8201;__init__.py</a></td>
89
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t72"><data value='Level'>Level</data></a></td>
90
- <td class="spacer">&nbsp;</td>
91
- <td>0</td>
92
- <td>0</td>
93
- <td>0</td>
94
- <td class="spacer">&nbsp;</td>
95
- <td data-ratio="0 0">100%</td>
96
- </tr>
97
- <tr class="region">
98
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t86">src&#8201;/&#8201;git_commit_guard&#8201;/&#8201;__init__.py</a></td>
99
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t86"><data value='Result'>Result</data></a></td>
100
- <td class="spacer">&nbsp;</td>
101
- <td>4</td>
102
- <td>0</td>
103
- <td>0</td>
104
- <td class="spacer">&nbsp;</td>
105
- <td data-ratio="4 4">100%</td>
106
- </tr>
107
- <tr class="region">
108
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t218">src&#8201;/&#8201;git_commit_guard&#8201;/&#8201;__init__.py</a></td>
109
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html#t218"><data value='Args'>Args</data></a></td>
110
- <td class="spacer">&nbsp;</td>
111
- <td>0</td>
112
- <td>0</td>
113
- <td>0</td>
114
- <td class="spacer">&nbsp;</td>
115
- <td data-ratio="0 0">100%</td>
116
- </tr>
117
- <tr class="region">
118
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html">src&#8201;/&#8201;git_commit_guard&#8201;/&#8201;__init__.py</a></td>
119
- <td class="name"><a href="z_262b75d81d1cf686___init___py.html"><data value=''><span class='no-noun'>(no class)</span></data></a></td>
120
- <td class="spacer">&nbsp;</td>
121
- <td>212</td>
122
- <td>2</td>
123
- <td>0</td>
124
- <td class="spacer">&nbsp;</td>
125
- <td data-ratio="210 212">99%</td>
126
- </tr>
127
- </tbody>
128
- <tfoot>
129
- <tr class="total">
130
- <td class="name">Total</td>
131
- <td class="name">&nbsp;</td>
132
- <td class="spacer">&nbsp;</td>
133
- <td>216</td>
134
- <td>2</td>
135
- <td>0</td>
136
- <td class="spacer">&nbsp;</td>
137
- <td data-ratio="214 216">99%</td>
138
- </tr>
139
- </tfoot>
140
- </table>
141
- <p id="no_rows">
142
- No items found using the specified filter.
143
- </p>
144
- </main>
145
- <footer>
146
- <div class="content">
147
- <p>
148
- <a class="nav" href="https://coverage.readthedocs.io/en/7.13.5">coverage.py v7.13.5</a>,
149
- created at 2026-04-18 23:09 +0300
150
- </p>
151
- </div>
152
- <aside class="hidden">
153
- <a id="prevFileLink" class="nav" href=""></a>
154
- <a id="nextFileLink" class="nav" href=""></a>
155
- <button type="button" class="button_prev_file" data-shortcut="["></button>
156
- <button type="button" class="button_next_file" data-shortcut="]"></button>
157
- <button type="button" class="button_show_hide_help" data-shortcut="?"></button>
158
- </aside>
159
- </footer>
160
- </body>
161
- </html>