git-commit-guard 0.16.0__tar.gz → 0.18.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.
- git_commit_guard-0.18.0/.github/workflows/coverage-baseline.yml +33 -0
- git_commit_guard-0.18.0/.github/workflows/coverage-comment.yml +97 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.github/workflows/lint-commits.yml +1 -1
- git_commit_guard-0.18.0/.github/workflows/lint-workflows.yml +47 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.github/workflows/release.yml +1 -1
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.github/workflows/test.yml +9 -5
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/PKG-INFO +52 -11
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/README.md +51 -10
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/action.yml +11 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/docs/index.html +11 -7
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/src/git_commit_guard/__init__.py +43 -4
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/tests/test_git_commit_guard.py +98 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.editorconfig +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.github/workflows/lint-md.yml +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.github/workflows/lint-python.yml +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.gitignore +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.markdownlint.json +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.pre-commit-hooks.yaml +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/.python-version +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/LICENSE +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/docs/commit-guard-icon.svg +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/pyproject.toml +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/ruff.toml +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/tests/__init__.py +0 -0
- {git_commit_guard-0.16.0 → git_commit_guard-0.18.0}/uv.lock +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Coverage Baseline
|
|
3
|
+
on: # yamllint disable-line rule:truthy
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
baseline:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout code
|
|
13
|
+
# yamllint disable-line rule:line-length
|
|
14
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6
|
|
15
|
+
with:
|
|
16
|
+
persist-credentials: false
|
|
17
|
+
- name: Install uv
|
|
18
|
+
# yamllint disable-line rule:line-length
|
|
19
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
|
|
20
|
+
- name: Cache NLTK data
|
|
21
|
+
# yamllint disable-line rule:line-length
|
|
22
|
+
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # ratchet:actions/cache@v5
|
|
23
|
+
with:
|
|
24
|
+
path: ~/nltk_data
|
|
25
|
+
key: nltk-averaged-perceptron-tagger-punkt
|
|
26
|
+
- name: Run tests with coverage
|
|
27
|
+
run: uv run --dev pytest tests/ --cov=git_commit_guard --cov-report=xml
|
|
28
|
+
- name: Upload coverage baseline
|
|
29
|
+
# yamllint disable-line rule:line-length
|
|
30
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: main-coverage
|
|
33
|
+
path: coverage.xml
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Coverage Comment
|
|
3
|
+
on: # yamllint disable-line rule:truthy # zizmor: ignore[dangerous-triggers]
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: [Test]
|
|
6
|
+
types: [completed]
|
|
7
|
+
permissions:
|
|
8
|
+
actions: read
|
|
9
|
+
pull-requests: write
|
|
10
|
+
jobs:
|
|
11
|
+
comment:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
if: >-
|
|
14
|
+
github.event.workflow_run.event == 'pull_request' &&
|
|
15
|
+
github.event.workflow_run.conclusion == 'success'
|
|
16
|
+
steps:
|
|
17
|
+
- name: Download artifact
|
|
18
|
+
# yamllint disable-line rule:line-length
|
|
19
|
+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # ratchet:actions/download-artifact@v4
|
|
20
|
+
with:
|
|
21
|
+
run-id: ${{ github.event.workflow_run.id }}
|
|
22
|
+
name: pr
|
|
23
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
- name: Read PR number
|
|
25
|
+
id: pr
|
|
26
|
+
run: |
|
|
27
|
+
pr_number=$(cat NR)
|
|
28
|
+
if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
29
|
+
echo "Invalid PR number: $pr_number" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
echo "number=$pr_number" >> "$GITHUB_OUTPUT"
|
|
33
|
+
- name: Find baseline run
|
|
34
|
+
id: baseline
|
|
35
|
+
env:
|
|
36
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
37
|
+
run: |
|
|
38
|
+
python3 - <<'PYEOF'
|
|
39
|
+
import json, os, urllib.request
|
|
40
|
+
token = os.environ['GITHUB_TOKEN']
|
|
41
|
+
repo = os.environ['GITHUB_REPOSITORY']
|
|
42
|
+
url = (
|
|
43
|
+
f'https://api.github.com/repos/{repo}/actions/workflows'
|
|
44
|
+
f'/coverage-baseline.yml/runs'
|
|
45
|
+
f'?branch=main&status=success&per_page=1'
|
|
46
|
+
)
|
|
47
|
+
req = urllib.request.Request(url, headers={
|
|
48
|
+
'Authorization': f'Bearer {token}',
|
|
49
|
+
'Accept': 'application/vnd.github+json',
|
|
50
|
+
})
|
|
51
|
+
with urllib.request.urlopen(req) as r:
|
|
52
|
+
runs = json.loads(r.read()).get('workflow_runs', [])
|
|
53
|
+
run_id = str(runs[0]['id']) if runs else ''
|
|
54
|
+
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
|
|
55
|
+
f.write(f'run-id={run_id}\n')
|
|
56
|
+
PYEOF
|
|
57
|
+
- name: Download baseline coverage
|
|
58
|
+
if: steps.baseline.outputs.run-id != ''
|
|
59
|
+
# yamllint disable-line rule:line-length
|
|
60
|
+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # ratchet:actions/download-artifact@v4
|
|
61
|
+
with:
|
|
62
|
+
run-id: ${{ steps.baseline.outputs.run-id }}
|
|
63
|
+
name: main-coverage
|
|
64
|
+
path: main/
|
|
65
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
66
|
+
- name: Compute coverage delta
|
|
67
|
+
id: delta
|
|
68
|
+
if: steps.baseline.outputs.run-id != ''
|
|
69
|
+
run: |
|
|
70
|
+
python3 - <<'PYEOF'
|
|
71
|
+
import xml.etree.ElementTree as ET
|
|
72
|
+
import os, sys
|
|
73
|
+
try:
|
|
74
|
+
pr_rate = ET.parse('coverage.xml').getroot().get('line-rate')
|
|
75
|
+
main_xml = ET.parse('main/coverage.xml').getroot()
|
|
76
|
+
main_rate = main_xml.get('line-rate')
|
|
77
|
+
pr = float(pr_rate) * 100
|
|
78
|
+
main = float(main_rate) * 100
|
|
79
|
+
delta = pr - main
|
|
80
|
+
if not (-100 <= delta <= 100):
|
|
81
|
+
raise ValueError(f"delta out of range: {delta}")
|
|
82
|
+
sign = '+' if delta >= 0 else ''
|
|
83
|
+
title = f"Coverage Report (Δ {sign}{delta:.1f}%)"
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Warning: could not compute delta: {e}", file=sys.stderr)
|
|
86
|
+
title = "Coverage Report"
|
|
87
|
+
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
|
|
88
|
+
f.write(f"title={title}\n")
|
|
89
|
+
PYEOF
|
|
90
|
+
- name: Post coverage comment
|
|
91
|
+
# yamllint disable-line rule:line-length
|
|
92
|
+
uses: MishaKav/pytest-coverage-comment@dd5b80bde6d16941f336518e92929e89069d8451 # ratchet:MishaKav/pytest-coverage-comment@v1.7.2
|
|
93
|
+
with:
|
|
94
|
+
pytest-xml-coverage-path: coverage.xml
|
|
95
|
+
unique-id-for-comment: coverage
|
|
96
|
+
issue-number: ${{ steps.pr.outputs.number }}
|
|
97
|
+
title: ${{ steps.delta.outputs.title || 'Coverage Report' }}
|
|
@@ -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@
|
|
25
|
+
uses: benner/commit-guard@d4c9fbe4a203ab32f63f66e1902d780528f781f2 # v0.17.0
|
|
26
26
|
with:
|
|
27
27
|
range: origin/${{ github.base_ref }}..HEAD
|
|
28
28
|
disable: signature
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Lint GitHub Actions workflows
|
|
3
|
+
on: # yamllint disable-line rule:truthy
|
|
4
|
+
pull_request:
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
actionlint:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
pull-requests: write
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
# yamllint disable-line rule:line-length
|
|
15
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # ratchet:actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
persist-credentials: false
|
|
18
|
+
- name: Run actionlint
|
|
19
|
+
# yamllint disable-line rule:line-length
|
|
20
|
+
uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # ratchet:reviewdog/action-actionlint@v1
|
|
21
|
+
with:
|
|
22
|
+
github_token: ${{ github.token }}
|
|
23
|
+
reporter: github-pr-review
|
|
24
|
+
|
|
25
|
+
zizmor:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
permissions:
|
|
28
|
+
pull-requests: write
|
|
29
|
+
steps:
|
|
30
|
+
- name: Checkout code
|
|
31
|
+
# yamllint disable-line rule:line-length
|
|
32
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # ratchet:actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
- name: Set up uv
|
|
36
|
+
# yamllint disable-line rule:line-length
|
|
37
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
|
|
38
|
+
- name: Set up reviewdog
|
|
39
|
+
# yamllint disable-line rule:line-length
|
|
40
|
+
uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
|
|
41
|
+
- name: Run zizmor
|
|
42
|
+
env:
|
|
43
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}
|
|
44
|
+
run: |
|
|
45
|
+
uvx zizmor==1.24.1 --format=sarif . |
|
|
46
|
+
reviewdog -f=sarif -name=zizmor \
|
|
47
|
+
-reporter=github-pr-review -fail-on-error
|
|
@@ -33,7 +33,7 @@ jobs:
|
|
|
33
33
|
- name: Generate release notes
|
|
34
34
|
id: git-cliff
|
|
35
35
|
# yamllint disable-line rule:line-length
|
|
36
|
-
uses: orhun/git-cliff-action@
|
|
36
|
+
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # ratchet:orhun/git-cliff-action@v4.8.0
|
|
37
37
|
with:
|
|
38
38
|
args: --current
|
|
39
39
|
- name: Create GitHub Release
|
|
@@ -4,7 +4,6 @@ on: # yamllint disable-line rule:truthy
|
|
|
4
4
|
pull_request:
|
|
5
5
|
permissions:
|
|
6
6
|
contents: read
|
|
7
|
-
pull-requests: write
|
|
8
7
|
jobs:
|
|
9
8
|
test:
|
|
10
9
|
runs-on: ubuntu-latest
|
|
@@ -27,9 +26,14 @@ jobs:
|
|
|
27
26
|
uv run --dev pytest \
|
|
28
27
|
tests/ --cov=git_commit_guard --cov-report=term-missing \
|
|
29
28
|
--cov-report=xml
|
|
30
|
-
- name:
|
|
29
|
+
- name: Save coverage artifact
|
|
30
|
+
run: |
|
|
31
|
+
mkdir -p ./pr
|
|
32
|
+
echo ${{ github.event.number }} > ./pr/NR
|
|
33
|
+
cp coverage.xml ./pr/
|
|
34
|
+
- name: Upload PR artifact
|
|
31
35
|
# yamllint disable-line rule:line-length
|
|
32
|
-
uses:
|
|
36
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
|
|
33
37
|
with:
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
name: pr
|
|
39
|
+
path: pr/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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
|
|
@@ -94,7 +94,7 @@ commit-guard --disable body,signed-off,signature
|
|
|
94
94
|
Available checks:
|
|
95
95
|
|
|
96
96
|
* `subject` - Format matches `type(scope): description`, valid type,
|
|
97
|
-
lowercase start, no trailing
|
|
97
|
+
lowercase start, no trailing `.` `!` `?` or space, max 72 chars
|
|
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
|
|
@@ -116,6 +116,41 @@ By default there is no minimum description length. Enforce one with
|
|
|
116
116
|
commit-guard --min-description-length 10
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### Subject format
|
|
120
|
+
|
|
121
|
+
By default the description must start with a lowercase letter. To allow
|
|
122
|
+
uppercase descriptions:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
commit-guard --no-require-lowercase
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
In `.commit-guard.toml`:
|
|
129
|
+
|
|
130
|
+
```toml
|
|
131
|
+
require-lowercase = false
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
By default `.`, `!`, `?`, and space are forbidden as trailing characters.
|
|
135
|
+
To change the set (any character is valid):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
commit-guard --no-trailing-chars ".,"
|
|
139
|
+
commit-guard --no-trailing-chars ".,!"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In `.commit-guard.toml`:
|
|
143
|
+
|
|
144
|
+
```toml
|
|
145
|
+
no-trailing-chars = [".", "!"]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Pass an empty list to disable the check entirely:
|
|
149
|
+
|
|
150
|
+
```toml
|
|
151
|
+
no-trailing-chars = []
|
|
152
|
+
```
|
|
153
|
+
|
|
119
154
|
### Type validation
|
|
120
155
|
|
|
121
156
|
By default the standard conventional commit types are accepted. Use `--types`
|
|
@@ -170,7 +205,8 @@ independently of `--enable`/`--disable`.
|
|
|
170
205
|
|
|
171
206
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
172
207
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
173
|
-
`max-subject-length`, `min-description-length`,
|
|
208
|
+
`max-subject-length`, `min-description-length`, `require-lowercase`,
|
|
209
|
+
`no-trailing-chars`, and `require-trailers`.
|
|
174
210
|
commit-guard searches upward from the working directory and uses the first
|
|
175
211
|
file found.
|
|
176
212
|
|
|
@@ -182,6 +218,8 @@ require-scope = true
|
|
|
182
218
|
types = ["feat", "fix", "chore", "wip"]
|
|
183
219
|
max-subject-length = 100
|
|
184
220
|
min-description-length = 10
|
|
221
|
+
require-lowercase = false
|
|
222
|
+
no-trailing-chars = [".", "!"]
|
|
185
223
|
require-trailers = ["Closes", "Reviewed-by"]
|
|
186
224
|
```
|
|
187
225
|
|
|
@@ -191,7 +229,8 @@ enable = ["subject", "imperative"]
|
|
|
191
229
|
```
|
|
192
230
|
|
|
193
231
|
CLI flags (`--enable`, `--disable`, `--scopes`, `--require-scope`, `--types`,
|
|
194
|
-
`--max-subject-length`, `--min-description-length`, `--require-
|
|
232
|
+
`--max-subject-length`, `--min-description-length`, `--no-require-lowercase`,
|
|
233
|
+
`--no-trailing-chars`, `--require-trailer`) take
|
|
195
234
|
full precedence and ignore config file values when provided.
|
|
196
235
|
|
|
197
236
|
### Environment variables
|
|
@@ -207,7 +246,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
|
|
|
207
246
|
In GitHub Actions, set it at the step or job level:
|
|
208
247
|
|
|
209
248
|
```yaml
|
|
210
|
-
- uses: benner/commit-guard@v0.
|
|
249
|
+
- uses: benner/commit-guard@v0.18.0
|
|
211
250
|
env:
|
|
212
251
|
COMMIT_GUARD_GIT_TIMEOUT: 30
|
|
213
252
|
with:
|
|
@@ -291,7 +330,7 @@ steps:
|
|
|
291
330
|
- uses: actions/checkout@v4
|
|
292
331
|
with:
|
|
293
332
|
fetch-depth: 0
|
|
294
|
-
- uses: benner/commit-guard@v0.
|
|
333
|
+
- uses: benner/commit-guard@v0.18.0
|
|
295
334
|
```
|
|
296
335
|
|
|
297
336
|
Check all commits in a pull request:
|
|
@@ -307,7 +346,7 @@ jobs:
|
|
|
307
346
|
- uses: actions/checkout@v4
|
|
308
347
|
with:
|
|
309
348
|
fetch-depth: 0
|
|
310
|
-
- uses: benner/commit-guard@v0.
|
|
349
|
+
- uses: benner/commit-guard@v0.18.0
|
|
311
350
|
with:
|
|
312
351
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
313
352
|
```
|
|
@@ -315,7 +354,7 @@ jobs:
|
|
|
315
354
|
Check a specific commit SHA (mirrors the positional CLI argument):
|
|
316
355
|
|
|
317
356
|
```yaml
|
|
318
|
-
- uses: benner/commit-guard@v0.
|
|
357
|
+
- uses: benner/commit-guard@v0.18.0
|
|
319
358
|
with:
|
|
320
359
|
rev: ${{ github.sha }}
|
|
321
360
|
```
|
|
@@ -333,7 +372,7 @@ jobs:
|
|
|
333
372
|
- uses: actions/checkout@v4
|
|
334
373
|
with:
|
|
335
374
|
fetch-depth: 0
|
|
336
|
-
- uses: benner/commit-guard@v0.
|
|
375
|
+
- uses: benner/commit-guard@v0.18.0
|
|
337
376
|
with:
|
|
338
377
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
339
378
|
disable: signed-off,signature
|
|
@@ -342,6 +381,8 @@ jobs:
|
|
|
342
381
|
require-trailer: 'Closes,Reviewed-by'
|
|
343
382
|
max-subject-length: '100'
|
|
344
383
|
min-description-length: '10'
|
|
384
|
+
no-require-lowercase: 'true'
|
|
385
|
+
no-trailing-chars: '.,!'
|
|
345
386
|
allow-empty: 'true'
|
|
346
387
|
include-merges: 'true'
|
|
347
388
|
output-file: results.jsonl
|
|
@@ -350,7 +391,7 @@ jobs:
|
|
|
350
391
|
When `output-file` is set the action exposes the path as an output:
|
|
351
392
|
|
|
352
393
|
```yaml
|
|
353
|
-
- uses: benner/commit-guard@v0.
|
|
394
|
+
- uses: benner/commit-guard@v0.18.0
|
|
354
395
|
id: cg
|
|
355
396
|
with:
|
|
356
397
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -366,7 +407,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
366
407
|
---
|
|
367
408
|
repos:
|
|
368
409
|
- repo: https://github.com/benner/commit-guard
|
|
369
|
-
rev: v0.
|
|
410
|
+
rev: v0.18.0
|
|
370
411
|
hooks:
|
|
371
412
|
- id: commit-guard
|
|
372
413
|
- id: commit-guard-signature
|
|
@@ -73,7 +73,7 @@ commit-guard --disable body,signed-off,signature
|
|
|
73
73
|
Available checks:
|
|
74
74
|
|
|
75
75
|
* `subject` - Format matches `type(scope): description`, valid type,
|
|
76
|
-
lowercase start, no trailing
|
|
76
|
+
lowercase start, no trailing `.` `!` `?` or space, max 72 chars
|
|
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
|
|
@@ -95,6 +95,41 @@ By default there is no minimum description length. Enforce one with
|
|
|
95
95
|
commit-guard --min-description-length 10
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
### Subject format
|
|
99
|
+
|
|
100
|
+
By default the description must start with a lowercase letter. To allow
|
|
101
|
+
uppercase descriptions:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
commit-guard --no-require-lowercase
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
In `.commit-guard.toml`:
|
|
108
|
+
|
|
109
|
+
```toml
|
|
110
|
+
require-lowercase = false
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
By default `.`, `!`, `?`, and space are forbidden as trailing characters.
|
|
114
|
+
To change the set (any character is valid):
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
commit-guard --no-trailing-chars ".,"
|
|
118
|
+
commit-guard --no-trailing-chars ".,!"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
In `.commit-guard.toml`:
|
|
122
|
+
|
|
123
|
+
```toml
|
|
124
|
+
no-trailing-chars = [".", "!"]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Pass an empty list to disable the check entirely:
|
|
128
|
+
|
|
129
|
+
```toml
|
|
130
|
+
no-trailing-chars = []
|
|
131
|
+
```
|
|
132
|
+
|
|
98
133
|
### Type validation
|
|
99
134
|
|
|
100
135
|
By default the standard conventional commit types are accepted. Use `--types`
|
|
@@ -149,7 +184,8 @@ independently of `--enable`/`--disable`.
|
|
|
149
184
|
|
|
150
185
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
151
186
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
152
|
-
`max-subject-length`, `min-description-length`,
|
|
187
|
+
`max-subject-length`, `min-description-length`, `require-lowercase`,
|
|
188
|
+
`no-trailing-chars`, and `require-trailers`.
|
|
153
189
|
commit-guard searches upward from the working directory and uses the first
|
|
154
190
|
file found.
|
|
155
191
|
|
|
@@ -161,6 +197,8 @@ require-scope = true
|
|
|
161
197
|
types = ["feat", "fix", "chore", "wip"]
|
|
162
198
|
max-subject-length = 100
|
|
163
199
|
min-description-length = 10
|
|
200
|
+
require-lowercase = false
|
|
201
|
+
no-trailing-chars = [".", "!"]
|
|
164
202
|
require-trailers = ["Closes", "Reviewed-by"]
|
|
165
203
|
```
|
|
166
204
|
|
|
@@ -170,7 +208,8 @@ enable = ["subject", "imperative"]
|
|
|
170
208
|
```
|
|
171
209
|
|
|
172
210
|
CLI flags (`--enable`, `--disable`, `--scopes`, `--require-scope`, `--types`,
|
|
173
|
-
`--max-subject-length`, `--min-description-length`, `--require-
|
|
211
|
+
`--max-subject-length`, `--min-description-length`, `--no-require-lowercase`,
|
|
212
|
+
`--no-trailing-chars`, `--require-trailer`) take
|
|
174
213
|
full precedence and ignore config file values when provided.
|
|
175
214
|
|
|
176
215
|
### Environment variables
|
|
@@ -186,7 +225,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
|
|
|
186
225
|
In GitHub Actions, set it at the step or job level:
|
|
187
226
|
|
|
188
227
|
```yaml
|
|
189
|
-
- uses: benner/commit-guard@v0.
|
|
228
|
+
- uses: benner/commit-guard@v0.18.0
|
|
190
229
|
env:
|
|
191
230
|
COMMIT_GUARD_GIT_TIMEOUT: 30
|
|
192
231
|
with:
|
|
@@ -270,7 +309,7 @@ steps:
|
|
|
270
309
|
- uses: actions/checkout@v4
|
|
271
310
|
with:
|
|
272
311
|
fetch-depth: 0
|
|
273
|
-
- uses: benner/commit-guard@v0.
|
|
312
|
+
- uses: benner/commit-guard@v0.18.0
|
|
274
313
|
```
|
|
275
314
|
|
|
276
315
|
Check all commits in a pull request:
|
|
@@ -286,7 +325,7 @@ jobs:
|
|
|
286
325
|
- uses: actions/checkout@v4
|
|
287
326
|
with:
|
|
288
327
|
fetch-depth: 0
|
|
289
|
-
- uses: benner/commit-guard@v0.
|
|
328
|
+
- uses: benner/commit-guard@v0.18.0
|
|
290
329
|
with:
|
|
291
330
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
292
331
|
```
|
|
@@ -294,7 +333,7 @@ jobs:
|
|
|
294
333
|
Check a specific commit SHA (mirrors the positional CLI argument):
|
|
295
334
|
|
|
296
335
|
```yaml
|
|
297
|
-
- uses: benner/commit-guard@v0.
|
|
336
|
+
- uses: benner/commit-guard@v0.18.0
|
|
298
337
|
with:
|
|
299
338
|
rev: ${{ github.sha }}
|
|
300
339
|
```
|
|
@@ -312,7 +351,7 @@ jobs:
|
|
|
312
351
|
- uses: actions/checkout@v4
|
|
313
352
|
with:
|
|
314
353
|
fetch-depth: 0
|
|
315
|
-
- uses: benner/commit-guard@v0.
|
|
354
|
+
- uses: benner/commit-guard@v0.18.0
|
|
316
355
|
with:
|
|
317
356
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
318
357
|
disable: signed-off,signature
|
|
@@ -321,6 +360,8 @@ jobs:
|
|
|
321
360
|
require-trailer: 'Closes,Reviewed-by'
|
|
322
361
|
max-subject-length: '100'
|
|
323
362
|
min-description-length: '10'
|
|
363
|
+
no-require-lowercase: 'true'
|
|
364
|
+
no-trailing-chars: '.,!'
|
|
324
365
|
allow-empty: 'true'
|
|
325
366
|
include-merges: 'true'
|
|
326
367
|
output-file: results.jsonl
|
|
@@ -329,7 +370,7 @@ jobs:
|
|
|
329
370
|
When `output-file` is set the action exposes the path as an output:
|
|
330
371
|
|
|
331
372
|
```yaml
|
|
332
|
-
- uses: benner/commit-guard@v0.
|
|
373
|
+
- uses: benner/commit-guard@v0.18.0
|
|
333
374
|
id: cg
|
|
334
375
|
with:
|
|
335
376
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -345,7 +386,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
345
386
|
---
|
|
346
387
|
repos:
|
|
347
388
|
- repo: https://github.com/benner/commit-guard
|
|
348
|
-
rev: v0.
|
|
389
|
+
rev: v0.18.0
|
|
349
390
|
hooks:
|
|
350
391
|
- id: commit-guard
|
|
351
392
|
- id: commit-guard-signature
|
|
@@ -34,6 +34,13 @@ inputs:
|
|
|
34
34
|
min-description-length:
|
|
35
35
|
description: Minimum description length in characters (default 0, off)
|
|
36
36
|
required: false
|
|
37
|
+
no-require-lowercase:
|
|
38
|
+
description: Allow description to start with uppercase (default false)
|
|
39
|
+
required: false
|
|
40
|
+
default: 'false'
|
|
41
|
+
no-trailing-chars:
|
|
42
|
+
description: Forbidden trailing characters, comma-separated (default '.', '!', '?', ' ')
|
|
43
|
+
required: false
|
|
37
44
|
allow-empty:
|
|
38
45
|
description: Exit 0 when --range yields no commits
|
|
39
46
|
required: false
|
|
@@ -75,6 +82,8 @@ runs:
|
|
|
75
82
|
CG_TYPES: ${{ inputs.types }}
|
|
76
83
|
CG_MAX_SUBJECT_LENGTH: ${{ inputs.max-subject-length }}
|
|
77
84
|
CG_MIN_DESCRIPTION_LENGTH: ${{ inputs.min-description-length }}
|
|
85
|
+
CG_NO_REQUIRE_LOWERCASE: ${{ inputs.no-require-lowercase }}
|
|
86
|
+
CG_NO_TRAILING_CHARS: ${{ inputs.no-trailing-chars }}
|
|
78
87
|
CG_ALLOW_EMPTY: ${{ inputs.allow-empty }}
|
|
79
88
|
CG_INCLUDE_MERGES: ${{ inputs.include-merges }}
|
|
80
89
|
CG_REQUIRE_TRAILER: ${{ inputs.require-trailer }}
|
|
@@ -92,6 +101,8 @@ runs:
|
|
|
92
101
|
ARGS+=(--max-subject-length "$CG_MAX_SUBJECT_LENGTH")
|
|
93
102
|
[[ -n "$CG_MIN_DESCRIPTION_LENGTH" ]] && \
|
|
94
103
|
ARGS+=(--min-description-length "$CG_MIN_DESCRIPTION_LENGTH")
|
|
104
|
+
[[ "$CG_NO_REQUIRE_LOWERCASE" == "true" ]] && ARGS+=(--no-require-lowercase)
|
|
105
|
+
[[ -n "$CG_NO_TRAILING_CHARS" ]] && ARGS+=(--no-trailing-chars "$CG_NO_TRAILING_CHARS")
|
|
95
106
|
[[ "$CG_ALLOW_EMPTY" == "true" ]] && ARGS+=(--allow-empty)
|
|
96
107
|
[[ "$CG_INCLUDE_MERGES" == "true" ]] && ARGS+=(--include-merges)
|
|
97
108
|
[[ -n "$CG_REQUIRE_TRAILER" ]] && ARGS+=(--require-trailer "$CG_REQUIRE_TRAILER")
|
|
@@ -355,7 +355,7 @@ $ echo "fix(auth): add token refresh" | commit-guard</code></pre>
|
|
|
355
355
|
<td><code>subject</code></td>
|
|
356
356
|
<td>
|
|
357
357
|
<a href="https://www.conventionalcommits.org/" target="_blank" rel="noopener">Conventional Commits</a>
|
|
358
|
-
format: valid type, lowercase start, no trailing
|
|
358
|
+
format: valid type, lowercase start, no trailing <code>.</code> <code>!</code> <code>?</code> or space,
|
|
359
359
|
max length (default 72). All limits are configurable. Use
|
|
360
360
|
<code>!</code> before the colon for breaking changes:
|
|
361
361
|
<code>feat!: remove endpoint</code>
|
|
@@ -398,6 +398,8 @@ require-scope = true
|
|
|
398
398
|
types = ["feat", "fix", "chore", "wip"]
|
|
399
399
|
max-subject-length = 100
|
|
400
400
|
min-description-length = 10
|
|
401
|
+
require-lowercase = false
|
|
402
|
+
no-trailing-chars = [".", "!"]
|
|
401
403
|
require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
402
404
|
|
|
403
405
|
<h3>Required trailers</h3>
|
|
@@ -477,13 +479,13 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
477
479
|
- uses: actions/checkout@v4
|
|
478
480
|
with:
|
|
479
481
|
fetch-depth: 0
|
|
480
|
-
- uses: benner/commit-guard@v0.
|
|
482
|
+
- uses: benner/commit-guard@v0.18.0
|
|
481
483
|
with:
|
|
482
484
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
483
485
|
disable: signed-off,signature</code></pre>
|
|
484
486
|
|
|
485
487
|
<p>Check a specific commit SHA:</p>
|
|
486
|
-
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.
|
|
488
|
+
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.18.0
|
|
487
489
|
with:
|
|
488
490
|
rev: ${{ github.sha }}</code></pre>
|
|
489
491
|
|
|
@@ -492,15 +494,17 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
492
494
|
<code>range</code>, <code>enable</code>, <code>disable</code>,
|
|
493
495
|
<code>scopes</code>, <code>require-scope</code>, <code>types</code>,
|
|
494
496
|
<code>max-subject-length</code>, <code>min-description-length</code>,
|
|
495
|
-
<code>require-
|
|
496
|
-
<code>
|
|
497
|
+
<code>no-require-lowercase</code>, <code>no-trailing-chars</code>,
|
|
498
|
+
<code>require-trailer</code>,
|
|
499
|
+
<code>allow-empty</code>, <code>include-merges</code>,
|
|
500
|
+
<code>output-file</code>.
|
|
497
501
|
</p>
|
|
498
502
|
|
|
499
503
|
<p>
|
|
500
504
|
When <code>output-file</code> is set the action exposes the path as
|
|
501
505
|
a step output, making JSONL results available to subsequent steps:
|
|
502
506
|
</p>
|
|
503
|
-
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.
|
|
507
|
+
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.18.0
|
|
504
508
|
id: cg
|
|
505
509
|
with:
|
|
506
510
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -513,7 +517,7 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
513
517
|
<p>Add to <code>.pre-commit-config.yaml</code>:</p>
|
|
514
518
|
<pre><code class="language-yaml">repos:
|
|
515
519
|
- repo: https://github.com/benner/commit-guard
|
|
516
|
-
rev: v0.
|
|
520
|
+
rev: v0.18.0
|
|
517
521
|
hooks:
|
|
518
522
|
- id: commit-guard
|
|
519
523
|
- id: commit-guard-signature</code></pre>
|
|
@@ -146,15 +146,17 @@ def _strip_comments(message):
|
|
|
146
146
|
)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def check_subject( # noqa: PLR0913 Too many arguments in function definition (
|
|
149
|
+
def check_subject( # noqa: PLR0913 Too many arguments in function definition (9 > 5)
|
|
150
150
|
line,
|
|
151
151
|
result,
|
|
152
152
|
allowed_scopes=frozenset(),
|
|
153
153
|
allowed_types=TYPES,
|
|
154
154
|
max_subject_length=MAX_SUBJECT_LEN,
|
|
155
155
|
min_description_length=0,
|
|
156
|
+
no_trailing_chars=frozenset({".", "!", "?", " "}),
|
|
156
157
|
*,
|
|
157
158
|
require_scope=False,
|
|
159
|
+
require_lowercase=True,
|
|
158
160
|
):
|
|
159
161
|
m = SUBJECT_RE.match(line)
|
|
160
162
|
if not m:
|
|
@@ -174,10 +176,10 @@ def check_subject( # noqa: PLR0913 Too many arguments in function definition (7
|
|
|
174
176
|
result.error(f"unknown scope: {scope}", check=Check.SUBJECT)
|
|
175
177
|
|
|
176
178
|
desc = m.group("desc")
|
|
177
|
-
if desc[0].isupper():
|
|
179
|
+
if require_lowercase and desc[0].isupper():
|
|
178
180
|
result.error("description must not start with uppercase", check=Check.SUBJECT)
|
|
179
|
-
if desc
|
|
180
|
-
result.error("description must not end with
|
|
181
|
+
if no_trailing_chars and desc[-1] in no_trailing_chars:
|
|
182
|
+
result.error(f"description must not end with {desc[-1]!r}", check=Check.SUBJECT)
|
|
181
183
|
if len(line) > max_subject_length:
|
|
182
184
|
result.error(
|
|
183
185
|
f"subject too long: {len(line)} > {max_subject_length}", check=Check.SUBJECT
|
|
@@ -305,6 +307,8 @@ class Args:
|
|
|
305
307
|
allowed_types: frozenset
|
|
306
308
|
max_subject_length: int
|
|
307
309
|
min_description_length: int
|
|
310
|
+
require_lowercase: bool
|
|
311
|
+
no_trailing_chars: frozenset
|
|
308
312
|
rev_range: str | None
|
|
309
313
|
allow_empty: bool
|
|
310
314
|
include_merges: bool
|
|
@@ -345,6 +349,22 @@ def _resolve_min_description_length(args, config):
|
|
|
345
349
|
return 0
|
|
346
350
|
|
|
347
351
|
|
|
352
|
+
def _resolve_require_lowercase(args, config):
|
|
353
|
+
if args.require_lowercase is not None:
|
|
354
|
+
return args.require_lowercase
|
|
355
|
+
if "require-lowercase" in config:
|
|
356
|
+
return config["require-lowercase"]
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _resolve_no_trailing_chars(args, config):
|
|
361
|
+
if args.no_trailing_chars is not None:
|
|
362
|
+
return frozenset(c for c in args.no_trailing_chars.split(",") if c)
|
|
363
|
+
if "no-trailing-chars" in config:
|
|
364
|
+
return frozenset(config["no-trailing-chars"])
|
|
365
|
+
return frozenset({".", "!", "?", " "})
|
|
366
|
+
|
|
367
|
+
|
|
348
368
|
def _resolve_required_trailers(args, config):
|
|
349
369
|
if args.require_trailer:
|
|
350
370
|
return [t.strip() for t in args.require_trailer.split(",")]
|
|
@@ -431,6 +451,19 @@ def _parse_args():
|
|
|
431
451
|
metavar="N",
|
|
432
452
|
help="minimum description length in characters (default: 0, off)",
|
|
433
453
|
)
|
|
454
|
+
parser.add_argument(
|
|
455
|
+
"--no-require-lowercase",
|
|
456
|
+
dest="require_lowercase",
|
|
457
|
+
action="store_false",
|
|
458
|
+
default=None,
|
|
459
|
+
help="allow description to start with uppercase (default: disallowed)",
|
|
460
|
+
)
|
|
461
|
+
parser.add_argument(
|
|
462
|
+
"--no-trailing-chars",
|
|
463
|
+
default=None,
|
|
464
|
+
metavar="CHAR[,CHAR,...]",
|
|
465
|
+
help="forbidden trailing characters in description (default: '.')",
|
|
466
|
+
)
|
|
434
467
|
parser.add_argument(
|
|
435
468
|
"--range",
|
|
436
469
|
dest="rev_range",
|
|
@@ -473,6 +506,8 @@ def _parse_args():
|
|
|
473
506
|
allowed_types = _resolve_types(args, config)
|
|
474
507
|
max_subject_length = _resolve_max_subject_length(args, config)
|
|
475
508
|
min_description_length = _resolve_min_description_length(args, config)
|
|
509
|
+
require_lowercase = _resolve_require_lowercase(args, config)
|
|
510
|
+
no_trailing_chars = _resolve_no_trailing_chars(args, config)
|
|
476
511
|
required_trailers = _resolve_required_trailers(args, config)
|
|
477
512
|
|
|
478
513
|
if args.allow_empty and not args.rev_range:
|
|
@@ -507,6 +542,8 @@ def _parse_args():
|
|
|
507
542
|
allowed_types=allowed_types,
|
|
508
543
|
max_subject_length=max_subject_length,
|
|
509
544
|
min_description_length=min_description_length,
|
|
545
|
+
require_lowercase=require_lowercase,
|
|
546
|
+
no_trailing_chars=no_trailing_chars,
|
|
510
547
|
rev_range=args.rev_range,
|
|
511
548
|
allow_empty=args.allow_empty,
|
|
512
549
|
include_merges=args.include_merges,
|
|
@@ -560,7 +597,9 @@ def _run_checks(args, rev, message, result):
|
|
|
560
597
|
args.allowed_types,
|
|
561
598
|
args.max_subject_length,
|
|
562
599
|
args.min_description_length,
|
|
600
|
+
args.no_trailing_chars,
|
|
563
601
|
require_scope=args.require_scope,
|
|
602
|
+
require_lowercase=args.require_lowercase,
|
|
564
603
|
)
|
|
565
604
|
if Check.IMPERATIVE in args.enabled:
|
|
566
605
|
if desc is None:
|
|
@@ -22,6 +22,8 @@ from git_commit_guard import (
|
|
|
22
22
|
_report_text,
|
|
23
23
|
_resolve_max_subject_length,
|
|
24
24
|
_resolve_min_description_length,
|
|
25
|
+
_resolve_no_trailing_chars,
|
|
26
|
+
_resolve_require_lowercase,
|
|
25
27
|
_resolve_required_trailers,
|
|
26
28
|
_resolve_types,
|
|
27
29
|
_strip_comments,
|
|
@@ -113,11 +115,41 @@ class TestCheckSubject:
|
|
|
113
115
|
check_subject("fix: Add token", r)
|
|
114
116
|
assert not r.ok
|
|
115
117
|
|
|
118
|
+
def test_uppercase_description_allowed(self):
|
|
119
|
+
r = Result()
|
|
120
|
+
check_subject("fix: Add token", r, require_lowercase=False)
|
|
121
|
+
assert r.ok
|
|
122
|
+
|
|
123
|
+
def test_lowercase_required_by_default(self):
|
|
124
|
+
r = Result()
|
|
125
|
+
check_subject("fix: add token", r)
|
|
126
|
+
assert r.ok
|
|
127
|
+
|
|
116
128
|
def test_trailing_period(self):
|
|
117
129
|
r = Result()
|
|
118
130
|
check_subject("fix: add token.", r)
|
|
119
131
|
assert not r.ok
|
|
120
132
|
|
|
133
|
+
def test_trailing_char_custom(self):
|
|
134
|
+
r = Result()
|
|
135
|
+
check_subject("fix: add token!", r, no_trailing_chars=frozenset("!"))
|
|
136
|
+
assert not r.ok
|
|
137
|
+
|
|
138
|
+
def test_trailing_char_space(self):
|
|
139
|
+
r = Result()
|
|
140
|
+
check_subject("fix: add token ", r, no_trailing_chars=frozenset(". "))
|
|
141
|
+
assert not r.ok
|
|
142
|
+
|
|
143
|
+
def test_trailing_chars_empty_disables_check(self):
|
|
144
|
+
r = Result()
|
|
145
|
+
check_subject("fix: add token.", r, no_trailing_chars=frozenset())
|
|
146
|
+
assert r.ok
|
|
147
|
+
|
|
148
|
+
def test_trailing_chars_multiple(self):
|
|
149
|
+
r = Result()
|
|
150
|
+
check_subject("fix: add token!", r, no_trailing_chars=frozenset(".!"))
|
|
151
|
+
assert not r.ok
|
|
152
|
+
|
|
121
153
|
def test_subject_too_long(self):
|
|
122
154
|
r = Result()
|
|
123
155
|
check_subject("fix: " + "a" * 68, r) # 73 chars total
|
|
@@ -424,6 +456,19 @@ class TestCheckImperative:
|
|
|
424
456
|
check_imperative("", r)
|
|
425
457
|
assert r.ok
|
|
426
458
|
|
|
459
|
+
def test_pos_fallback_unknown_word_fails(self):
|
|
460
|
+
r = Result()
|
|
461
|
+
with (
|
|
462
|
+
patch("git_commit_guard.wordnet.morphy", return_value=None),
|
|
463
|
+
patch(
|
|
464
|
+
"git_commit_guard.nltk.pos_tag",
|
|
465
|
+
return_value=[("to", "TO"), ("xyzzy", "NN")],
|
|
466
|
+
),
|
|
467
|
+
):
|
|
468
|
+
check_imperative("xyzzy something", r)
|
|
469
|
+
assert not r.ok
|
|
470
|
+
assert "POS=NN" in r.errors[0][2]
|
|
471
|
+
|
|
427
472
|
|
|
428
473
|
class TestDownloadIfMissing:
|
|
429
474
|
def test_skips_download_when_present(self):
|
|
@@ -587,6 +632,38 @@ class TestResolveMinDescriptionLength:
|
|
|
587
632
|
assert result == 10
|
|
588
633
|
|
|
589
634
|
|
|
635
|
+
class TestResolveRequireLowercase:
|
|
636
|
+
def test_cli_flag_overrides_default(self):
|
|
637
|
+
assert (
|
|
638
|
+
_resolve_require_lowercase(Namespace(require_lowercase=False), {}) is False
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
def test_config_overrides_default(self):
|
|
642
|
+
result = _resolve_require_lowercase(
|
|
643
|
+
Namespace(require_lowercase=None), {"require-lowercase": False}
|
|
644
|
+
)
|
|
645
|
+
assert result is False
|
|
646
|
+
|
|
647
|
+
def test_default_is_true(self):
|
|
648
|
+
assert _resolve_require_lowercase(Namespace(require_lowercase=None), {}) is True
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
class TestResolveNoTrailingChars:
|
|
652
|
+
def test_cli_flag_overrides_default(self):
|
|
653
|
+
result = _resolve_no_trailing_chars(Namespace(no_trailing_chars=".,!"), {})
|
|
654
|
+
assert result == frozenset({".", "!"})
|
|
655
|
+
|
|
656
|
+
def test_config_overrides_default(self):
|
|
657
|
+
result = _resolve_no_trailing_chars(
|
|
658
|
+
Namespace(no_trailing_chars=None), {"no-trailing-chars": [".", "!"]}
|
|
659
|
+
)
|
|
660
|
+
assert result == frozenset({".", "!"})
|
|
661
|
+
|
|
662
|
+
def test_default_includes_common_punctuation_and_space(self):
|
|
663
|
+
result = _resolve_no_trailing_chars(Namespace(no_trailing_chars=None), {})
|
|
664
|
+
assert result == frozenset({".", "!", "?", " "})
|
|
665
|
+
|
|
666
|
+
|
|
590
667
|
class TestGitTimeout:
|
|
591
668
|
def test_default(self, monkeypatch):
|
|
592
669
|
monkeypatch.delenv("COMMIT_GUARD_GIT_TIMEOUT", raising=False)
|
|
@@ -1428,6 +1505,27 @@ class TestOutputJsonl:
|
|
|
1428
1505
|
assert data["sha"] == rev
|
|
1429
1506
|
assert data["ok"] is True
|
|
1430
1507
|
|
|
1508
|
+
def test_range_failing_commit_returns_nonzero(self, capsys):
|
|
1509
|
+
with (
|
|
1510
|
+
patch(
|
|
1511
|
+
"sys.argv",
|
|
1512
|
+
[
|
|
1513
|
+
"cg",
|
|
1514
|
+
"--range",
|
|
1515
|
+
"HEAD~1..HEAD",
|
|
1516
|
+
"--disable",
|
|
1517
|
+
"signature,imperative",
|
|
1518
|
+
"--output",
|
|
1519
|
+
"jsonl",
|
|
1520
|
+
],
|
|
1521
|
+
),
|
|
1522
|
+
patch("git_commit_guard._get_range_revs", return_value=["aaa"]),
|
|
1523
|
+
patch("git_commit_guard._get_message", return_value="bad message"),
|
|
1524
|
+
):
|
|
1525
|
+
assert main() == 1
|
|
1526
|
+
data = json.loads(capsys.readouterr().out)
|
|
1527
|
+
assert data["ok"] is False
|
|
1528
|
+
|
|
1431
1529
|
|
|
1432
1530
|
class TestOutputFile:
|
|
1433
1531
|
def test_single_commit_writes_jsonl_to_file(self, tmp_path, capsys):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|