git-commit-guard 0.17.0__tar.gz → 0.19.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.17.0 → git_commit_guard-0.19.0}/.github/workflows/lint-commits.yml +1 -1
- git_commit_guard-0.19.0/.github/workflows/lint-workflows.yml +70 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/release.yml +4 -2
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/PKG-INFO +32 -12
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/README.md +31 -11
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/action.yml +7 -1
- git_commit_guard-0.19.0/cliff.toml +56 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/docs/index.html +22 -6
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/src/git_commit_guard/__init__.py +37 -3
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/tests/test_git_commit_guard.py +183 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.editorconfig +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/coverage-baseline.yml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/coverage-comment.yml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/lint-md.yml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/lint-python.yml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.github/workflows/test.yml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.gitignore +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.markdownlint.json +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.pre-commit-hooks.yaml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/.python-version +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/LICENSE +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/docs/commit-guard-icon.svg +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/pyproject.toml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/ruff.toml +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/tests/__init__.py +0 -0
- {git_commit_guard-0.17.0 → git_commit_guard-0.19.0}/uv.lock +0 -0
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
key: nltk-averaged-perceptron-tagger-punkt
|
|
23
23
|
- name: Lint commits
|
|
24
24
|
# yamllint disable-line rule:line-length
|
|
25
|
-
uses: benner/commit-guard@
|
|
25
|
+
uses: benner/commit-guard@8a007fe3ebc1346ec111f7782b26af7e4ca32025 # v0.18.0
|
|
26
26
|
with:
|
|
27
27
|
range: origin/${{ github.base_ref }}..HEAD
|
|
28
28
|
disable: signature
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
yamlfix:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
pull-requests: write
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout code
|
|
30
|
+
# yamllint disable-line rule:line-length
|
|
31
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # ratchet:actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
- name: Set up uv
|
|
35
|
+
# yamllint disable-line rule:line-length
|
|
36
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
|
|
37
|
+
- name: Set up reviewdog
|
|
38
|
+
# yamllint disable-line rule:line-length
|
|
39
|
+
uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
|
|
40
|
+
- name: Run yamlfix
|
|
41
|
+
env:
|
|
42
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}
|
|
43
|
+
run: |-
|
|
44
|
+
uvx yamlfix .github/workflows/
|
|
45
|
+
git diff .github/workflows/ |
|
|
46
|
+
reviewdog -f=diff -name=yamlfix \
|
|
47
|
+
-reporter=github-pr-review -fail-on-error
|
|
48
|
+
zizmor:
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
pull-requests: write
|
|
52
|
+
steps:
|
|
53
|
+
- name: Checkout code
|
|
54
|
+
# yamllint disable-line rule:line-length
|
|
55
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # ratchet:actions/checkout@v4
|
|
56
|
+
with:
|
|
57
|
+
persist-credentials: false
|
|
58
|
+
- name: Set up uv
|
|
59
|
+
# yamllint disable-line rule:line-length
|
|
60
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # ratchet:astral-sh/setup-uv@v7
|
|
61
|
+
- name: Set up reviewdog
|
|
62
|
+
# yamllint disable-line rule:line-length
|
|
63
|
+
uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # ratchet:reviewdog/action-setup@v1
|
|
64
|
+
- name: Run zizmor
|
|
65
|
+
env:
|
|
66
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}
|
|
67
|
+
run: |-
|
|
68
|
+
uvx zizmor==1.24.1 --format=sarif . |
|
|
69
|
+
reviewdog -f=sarif -name=zizmor \
|
|
70
|
+
-reporter=github-pr-review -fail-on-error
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
name: Release
|
|
3
3
|
on: # yamllint disable-line rule:truthy
|
|
4
4
|
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
5
|
+
tags: [v*]
|
|
7
6
|
permissions:
|
|
8
7
|
contents: write
|
|
9
8
|
id-token: write
|
|
9
|
+
pull-requests: read
|
|
10
10
|
jobs:
|
|
11
11
|
release:
|
|
12
12
|
runs-on: ubuntu-latest
|
|
@@ -34,6 +34,8 @@ jobs:
|
|
|
34
34
|
id: git-cliff
|
|
35
35
|
# yamllint disable-line rule:line-length
|
|
36
36
|
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # ratchet:orhun/git-cliff-action@v4.8.0
|
|
37
|
+
env:
|
|
38
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
37
39
|
with:
|
|
38
40
|
args: --current
|
|
39
41
|
- name: Create GitHub Release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.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
|
|
@@ -131,8 +131,8 @@ In `.commit-guard.toml`:
|
|
|
131
131
|
require-lowercase = false
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
By default
|
|
135
|
-
|
|
134
|
+
By default `.`, `!`, `?`, and space are forbidden as trailing characters.
|
|
135
|
+
To change the set (any character is valid):
|
|
136
136
|
|
|
137
137
|
```bash
|
|
138
138
|
commit-guard --no-trailing-chars ".,"
|
|
@@ -181,6 +181,25 @@ commit-guard --require-scope
|
|
|
181
181
|
commit-guard --scopes auth,api --require-scope
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
### Required subject pattern
|
|
185
|
+
|
|
186
|
+
Require the commit subject to match a regular expression. Useful for
|
|
187
|
+
enforcing ticket references or any custom naming convention:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"
|
|
191
|
+
commit-guard --require-subject-pattern "#[0-9]+"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
In `.commit-guard.toml`:
|
|
195
|
+
|
|
196
|
+
```toml
|
|
197
|
+
require-subject-pattern = "[A-Z]+-[0-9]+"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
An invalid regex causes an immediate error at startup (exit 2). This
|
|
201
|
+
check runs independently of `--enable`/`--disable`.
|
|
202
|
+
|
|
184
203
|
### Required custom trailers
|
|
185
204
|
|
|
186
205
|
Require arbitrary trailers to be present in the commit message. Multiple
|
|
@@ -206,7 +225,7 @@ independently of `--enable`/`--disable`.
|
|
|
206
225
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
207
226
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
208
227
|
`max-subject-length`, `min-description-length`, `require-lowercase`,
|
|
209
|
-
`no-trailing-chars`, and `require-trailers`.
|
|
228
|
+
`no-trailing-chars`, `require-subject-pattern`, and `require-trailers`.
|
|
210
229
|
commit-guard searches upward from the working directory and uses the first
|
|
211
230
|
file found.
|
|
212
231
|
|
|
@@ -246,7 +265,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
|
|
|
246
265
|
In GitHub Actions, set it at the step or job level:
|
|
247
266
|
|
|
248
267
|
```yaml
|
|
249
|
-
- uses: benner/commit-guard@v0.
|
|
268
|
+
- uses: benner/commit-guard@v0.19.0
|
|
250
269
|
env:
|
|
251
270
|
COMMIT_GUARD_GIT_TIMEOUT: 30
|
|
252
271
|
with:
|
|
@@ -330,7 +349,7 @@ steps:
|
|
|
330
349
|
- uses: actions/checkout@v4
|
|
331
350
|
with:
|
|
332
351
|
fetch-depth: 0
|
|
333
|
-
- uses: benner/commit-guard@v0.
|
|
352
|
+
- uses: benner/commit-guard@v0.19.0
|
|
334
353
|
```
|
|
335
354
|
|
|
336
355
|
Check all commits in a pull request:
|
|
@@ -346,7 +365,7 @@ jobs:
|
|
|
346
365
|
- uses: actions/checkout@v4
|
|
347
366
|
with:
|
|
348
367
|
fetch-depth: 0
|
|
349
|
-
- uses: benner/commit-guard@v0.
|
|
368
|
+
- uses: benner/commit-guard@v0.19.0
|
|
350
369
|
with:
|
|
351
370
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
352
371
|
```
|
|
@@ -354,7 +373,7 @@ jobs:
|
|
|
354
373
|
Check a specific commit SHA (mirrors the positional CLI argument):
|
|
355
374
|
|
|
356
375
|
```yaml
|
|
357
|
-
- uses: benner/commit-guard@v0.
|
|
376
|
+
- uses: benner/commit-guard@v0.19.0
|
|
358
377
|
with:
|
|
359
378
|
rev: ${{ github.sha }}
|
|
360
379
|
```
|
|
@@ -372,12 +391,13 @@ jobs:
|
|
|
372
391
|
- uses: actions/checkout@v4
|
|
373
392
|
with:
|
|
374
393
|
fetch-depth: 0
|
|
375
|
-
- uses: benner/commit-guard@v0.
|
|
394
|
+
- uses: benner/commit-guard@v0.19.0
|
|
376
395
|
with:
|
|
377
396
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
378
397
|
disable: signed-off,signature
|
|
379
398
|
scopes: auth,api,db
|
|
380
399
|
require-scope: 'true'
|
|
400
|
+
require-subject-pattern: '[A-Z]+-[0-9]+'
|
|
381
401
|
require-trailer: 'Closes,Reviewed-by'
|
|
382
402
|
max-subject-length: '100'
|
|
383
403
|
min-description-length: '10'
|
|
@@ -391,7 +411,7 @@ jobs:
|
|
|
391
411
|
When `output-file` is set the action exposes the path as an output:
|
|
392
412
|
|
|
393
413
|
```yaml
|
|
394
|
-
- uses: benner/commit-guard@v0.
|
|
414
|
+
- uses: benner/commit-guard@v0.19.0
|
|
395
415
|
id: cg
|
|
396
416
|
with:
|
|
397
417
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -407,7 +427,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
407
427
|
---
|
|
408
428
|
repos:
|
|
409
429
|
- repo: https://github.com/benner/commit-guard
|
|
410
|
-
rev: v0.
|
|
430
|
+
rev: v0.19.0
|
|
411
431
|
hooks:
|
|
412
432
|
- id: commit-guard
|
|
413
433
|
- 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
|
|
@@ -110,8 +110,8 @@ In `.commit-guard.toml`:
|
|
|
110
110
|
require-lowercase = false
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
By default
|
|
114
|
-
|
|
113
|
+
By default `.`, `!`, `?`, and space are forbidden as trailing characters.
|
|
114
|
+
To change the set (any character is valid):
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
117
|
commit-guard --no-trailing-chars ".,"
|
|
@@ -160,6 +160,25 @@ commit-guard --require-scope
|
|
|
160
160
|
commit-guard --scopes auth,api --require-scope
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
### Required subject pattern
|
|
164
|
+
|
|
165
|
+
Require the commit subject to match a regular expression. Useful for
|
|
166
|
+
enforcing ticket references or any custom naming convention:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"
|
|
170
|
+
commit-guard --require-subject-pattern "#[0-9]+"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
In `.commit-guard.toml`:
|
|
174
|
+
|
|
175
|
+
```toml
|
|
176
|
+
require-subject-pattern = "[A-Z]+-[0-9]+"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
An invalid regex causes an immediate error at startup (exit 2). This
|
|
180
|
+
check runs independently of `--enable`/`--disable`.
|
|
181
|
+
|
|
163
182
|
### Required custom trailers
|
|
164
183
|
|
|
165
184
|
Require arbitrary trailers to be present in the commit message. Multiple
|
|
@@ -185,7 +204,7 @@ independently of `--enable`/`--disable`.
|
|
|
185
204
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
186
205
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
187
206
|
`max-subject-length`, `min-description-length`, `require-lowercase`,
|
|
188
|
-
`no-trailing-chars`, and `require-trailers`.
|
|
207
|
+
`no-trailing-chars`, `require-subject-pattern`, and `require-trailers`.
|
|
189
208
|
commit-guard searches upward from the working directory and uses the first
|
|
190
209
|
file found.
|
|
191
210
|
|
|
@@ -225,7 +244,7 @@ COMMIT_GUARD_GIT_TIMEOUT=30 commit-guard --range origin/main..HEAD
|
|
|
225
244
|
In GitHub Actions, set it at the step or job level:
|
|
226
245
|
|
|
227
246
|
```yaml
|
|
228
|
-
- uses: benner/commit-guard@v0.
|
|
247
|
+
- uses: benner/commit-guard@v0.19.0
|
|
229
248
|
env:
|
|
230
249
|
COMMIT_GUARD_GIT_TIMEOUT: 30
|
|
231
250
|
with:
|
|
@@ -309,7 +328,7 @@ steps:
|
|
|
309
328
|
- uses: actions/checkout@v4
|
|
310
329
|
with:
|
|
311
330
|
fetch-depth: 0
|
|
312
|
-
- uses: benner/commit-guard@v0.
|
|
331
|
+
- uses: benner/commit-guard@v0.19.0
|
|
313
332
|
```
|
|
314
333
|
|
|
315
334
|
Check all commits in a pull request:
|
|
@@ -325,7 +344,7 @@ jobs:
|
|
|
325
344
|
- uses: actions/checkout@v4
|
|
326
345
|
with:
|
|
327
346
|
fetch-depth: 0
|
|
328
|
-
- uses: benner/commit-guard@v0.
|
|
347
|
+
- uses: benner/commit-guard@v0.19.0
|
|
329
348
|
with:
|
|
330
349
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
331
350
|
```
|
|
@@ -333,7 +352,7 @@ jobs:
|
|
|
333
352
|
Check a specific commit SHA (mirrors the positional CLI argument):
|
|
334
353
|
|
|
335
354
|
```yaml
|
|
336
|
-
- uses: benner/commit-guard@v0.
|
|
355
|
+
- uses: benner/commit-guard@v0.19.0
|
|
337
356
|
with:
|
|
338
357
|
rev: ${{ github.sha }}
|
|
339
358
|
```
|
|
@@ -351,12 +370,13 @@ jobs:
|
|
|
351
370
|
- uses: actions/checkout@v4
|
|
352
371
|
with:
|
|
353
372
|
fetch-depth: 0
|
|
354
|
-
- uses: benner/commit-guard@v0.
|
|
373
|
+
- uses: benner/commit-guard@v0.19.0
|
|
355
374
|
with:
|
|
356
375
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
357
376
|
disable: signed-off,signature
|
|
358
377
|
scopes: auth,api,db
|
|
359
378
|
require-scope: 'true'
|
|
379
|
+
require-subject-pattern: '[A-Z]+-[0-9]+'
|
|
360
380
|
require-trailer: 'Closes,Reviewed-by'
|
|
361
381
|
max-subject-length: '100'
|
|
362
382
|
min-description-length: '10'
|
|
@@ -370,7 +390,7 @@ jobs:
|
|
|
370
390
|
When `output-file` is set the action exposes the path as an output:
|
|
371
391
|
|
|
372
392
|
```yaml
|
|
373
|
-
- uses: benner/commit-guard@v0.
|
|
393
|
+
- uses: benner/commit-guard@v0.19.0
|
|
374
394
|
id: cg
|
|
375
395
|
with:
|
|
376
396
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -386,7 +406,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
386
406
|
---
|
|
387
407
|
repos:
|
|
388
408
|
- repo: https://github.com/benner/commit-guard
|
|
389
|
-
rev: v0.
|
|
409
|
+
rev: v0.19.0
|
|
390
410
|
hooks:
|
|
391
411
|
- id: commit-guard
|
|
392
412
|
- id: commit-guard-signature
|
|
@@ -39,7 +39,7 @@ inputs:
|
|
|
39
39
|
required: false
|
|
40
40
|
default: 'false'
|
|
41
41
|
no-trailing-chars:
|
|
42
|
-
description: Forbidden trailing characters, comma-separated (default '.')
|
|
42
|
+
description: Forbidden trailing characters, comma-separated (default '.', '!', '?', ' ')
|
|
43
43
|
required: false
|
|
44
44
|
allow-empty:
|
|
45
45
|
description: Exit 0 when --range yields no commits
|
|
@@ -49,6 +49,9 @@ inputs:
|
|
|
49
49
|
description: Include merge commits when checking a range
|
|
50
50
|
required: false
|
|
51
51
|
default: 'false'
|
|
52
|
+
require-subject-pattern:
|
|
53
|
+
description: Regex the subject line must match (e.g. '[A-Z]+-[0-9]+')
|
|
54
|
+
required: false
|
|
52
55
|
require-trailer:
|
|
53
56
|
description: Comma-separated list of required trailers (e.g. Closes,Reviewed-by)
|
|
54
57
|
required: false
|
|
@@ -86,6 +89,7 @@ runs:
|
|
|
86
89
|
CG_NO_TRAILING_CHARS: ${{ inputs.no-trailing-chars }}
|
|
87
90
|
CG_ALLOW_EMPTY: ${{ inputs.allow-empty }}
|
|
88
91
|
CG_INCLUDE_MERGES: ${{ inputs.include-merges }}
|
|
92
|
+
CG_REQUIRE_SUBJECT_PATTERN: ${{ inputs.require-subject-pattern }}
|
|
89
93
|
CG_REQUIRE_TRAILER: ${{ inputs.require-trailer }}
|
|
90
94
|
CG_OUTPUT_FILE: ${{ inputs.output-file }}
|
|
91
95
|
run: |
|
|
@@ -105,6 +109,8 @@ runs:
|
|
|
105
109
|
[[ -n "$CG_NO_TRAILING_CHARS" ]] && ARGS+=(--no-trailing-chars "$CG_NO_TRAILING_CHARS")
|
|
106
110
|
[[ "$CG_ALLOW_EMPTY" == "true" ]] && ARGS+=(--allow-empty)
|
|
107
111
|
[[ "$CG_INCLUDE_MERGES" == "true" ]] && ARGS+=(--include-merges)
|
|
112
|
+
[[ -n "$CG_REQUIRE_SUBJECT_PATTERN" ]] && \
|
|
113
|
+
ARGS+=(--require-subject-pattern "$CG_REQUIRE_SUBJECT_PATTERN")
|
|
108
114
|
[[ -n "$CG_REQUIRE_TRAILER" ]] && ARGS+=(--require-trailer "$CG_REQUIRE_TRAILER")
|
|
109
115
|
[[ -n "$CG_OUTPUT_FILE" ]] && ARGS+=(--output-file "$CG_OUTPUT_FILE")
|
|
110
116
|
commit-guard "${ARGS[@]}"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[changelog]
|
|
2
|
+
body = """
|
|
3
|
+
{% if version %}\
|
|
4
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
5
|
+
{% else %}\
|
|
6
|
+
## [unreleased]
|
|
7
|
+
{% endif %}\
|
|
8
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
9
|
+
### {{ group | striptags | trim | upper_first }}
|
|
10
|
+
{% for commit in commits %}
|
|
11
|
+
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
|
12
|
+
{% if commit.breaking %}[**breaking**] {% endif %}\
|
|
13
|
+
{{ commit.message | upper_first }} \
|
|
14
|
+
([`{{ commit.id | truncate(length=7, end="") }}`]\
|
|
15
|
+
(https://github.com/benner/commit-guard/commit/{{ commit.id }}))\
|
|
16
|
+
{% endfor %}
|
|
17
|
+
{% endfor %}
|
|
18
|
+
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
|
19
|
+
|
|
20
|
+
### New Contributors
|
|
21
|
+
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
|
22
|
+
- @{{ contributor.username }} made their first contribution\
|
|
23
|
+
{% if contributor.pr_number %} in \
|
|
24
|
+
[#{{ contributor.pr_number }}]\
|
|
25
|
+
(https://github.com/benner/commit-guard/pull/{{ contributor.pr_number }})\
|
|
26
|
+
{% endif %}
|
|
27
|
+
{% endfor %}
|
|
28
|
+
{%- endif %}
|
|
29
|
+
"""
|
|
30
|
+
trim = true
|
|
31
|
+
|
|
32
|
+
[remote.github]
|
|
33
|
+
owner = "benner"
|
|
34
|
+
repo = "commit-guard"
|
|
35
|
+
|
|
36
|
+
[git]
|
|
37
|
+
conventional_commits = true
|
|
38
|
+
filter_unconventional = true
|
|
39
|
+
split_commits = false
|
|
40
|
+
protect_breaking_commits = false
|
|
41
|
+
commit_parsers = [
|
|
42
|
+
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
|
|
43
|
+
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
|
|
44
|
+
{ message = "^perf", group = "<!-- 2 -->⚡ Performance" },
|
|
45
|
+
{ message = "^refactor", group = "<!-- 3 -->🚜 Refactor" },
|
|
46
|
+
{ message = "^style", group = "<!-- 4 -->🎨 Styling" },
|
|
47
|
+
{ message = "^test", group = "<!-- 5 -->🧪 Testing" },
|
|
48
|
+
{ message = "^docs", skip = true },
|
|
49
|
+
{ message = "^ci", skip = true },
|
|
50
|
+
{ message = "^chore", group = "<!-- 6 -->⚙️ Miscellaneous Tasks" },
|
|
51
|
+
{ message = "^revert", group = "<!-- 7 -->◀️ Revert" },
|
|
52
|
+
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
|
|
53
|
+
]
|
|
54
|
+
filter_commits = true
|
|
55
|
+
topo_order = false
|
|
56
|
+
sort_commits = "oldest"
|
|
@@ -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>
|
|
@@ -400,7 +400,23 @@ max-subject-length = 100
|
|
|
400
400
|
min-description-length = 10
|
|
401
401
|
require-lowercase = false
|
|
402
402
|
no-trailing-chars = [".", "!"]
|
|
403
|
-
require-trailers = ["Closes", "Reviewed-by"]
|
|
403
|
+
require-trailers = ["Closes", "Reviewed-by"]
|
|
404
|
+
require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
|
|
405
|
+
|
|
406
|
+
<h3>Required subject pattern</h3>
|
|
407
|
+
<p>
|
|
408
|
+
Require the commit subject to match a regular expression. Useful for
|
|
409
|
+
enforcing ticket references or any custom naming convention:
|
|
410
|
+
</p>
|
|
411
|
+
<pre><code class="language-bash">commit-guard --require-subject-pattern "[A-Z]+-[0-9]+"</code></pre>
|
|
412
|
+
<p>
|
|
413
|
+
In <code>.commit-guard.toml</code>:
|
|
414
|
+
</p>
|
|
415
|
+
<pre><code class="language-toml">require-subject-pattern = "[A-Z]+-[0-9]+"</code></pre>
|
|
416
|
+
<p>
|
|
417
|
+
An invalid regex causes an immediate error at startup (exit 2). This
|
|
418
|
+
check runs independently of <code>--enable</code>/<code>--disable</code>.
|
|
419
|
+
</p>
|
|
404
420
|
|
|
405
421
|
<h3>Required trailers</h3>
|
|
406
422
|
<p>
|
|
@@ -479,13 +495,13 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
479
495
|
- uses: actions/checkout@v4
|
|
480
496
|
with:
|
|
481
497
|
fetch-depth: 0
|
|
482
|
-
- uses: benner/commit-guard@v0.
|
|
498
|
+
- uses: benner/commit-guard@v0.19.0
|
|
483
499
|
with:
|
|
484
500
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
485
501
|
disable: signed-off,signature</code></pre>
|
|
486
502
|
|
|
487
503
|
<p>Check a specific commit SHA:</p>
|
|
488
|
-
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.
|
|
504
|
+
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.19.0
|
|
489
505
|
with:
|
|
490
506
|
rev: ${{ github.sha }}</code></pre>
|
|
491
507
|
|
|
@@ -504,7 +520,7 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
504
520
|
When <code>output-file</code> is set the action exposes the path as
|
|
505
521
|
a step output, making JSONL results available to subsequent steps:
|
|
506
522
|
</p>
|
|
507
|
-
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.
|
|
523
|
+
<pre><code class="language-yaml"> - uses: benner/commit-guard@v0.19.0
|
|
508
524
|
id: cg
|
|
509
525
|
with:
|
|
510
526
|
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
@@ -517,7 +533,7 @@ require-trailers = ["Closes", "Reviewed-by"]</code></pre>
|
|
|
517
533
|
<p>Add to <code>.pre-commit-config.yaml</code>:</p>
|
|
518
534
|
<pre><code class="language-yaml">repos:
|
|
519
535
|
- repo: https://github.com/benner/commit-guard
|
|
520
|
-
rev: v0.
|
|
536
|
+
rev: v0.19.0
|
|
521
537
|
hooks:
|
|
522
538
|
- id: commit-guard
|
|
523
539
|
- id: commit-guard-signature</code></pre>
|
|
@@ -153,7 +153,7 @@ def check_subject( # noqa: PLR0913 Too many arguments in function definition (9
|
|
|
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
|
+
no_trailing_chars=frozenset({".", "!", "?", " "}),
|
|
157
157
|
*,
|
|
158
158
|
require_scope=False,
|
|
159
159
|
require_lowercase=True,
|
|
@@ -241,6 +241,14 @@ def check_signed_off(message, result):
|
|
|
241
241
|
result.error("missing 'Signed-off-by' trailer", check=Check.SIGNED_OFF)
|
|
242
242
|
|
|
243
243
|
|
|
244
|
+
def check_subject_pattern(subject, pattern, result):
|
|
245
|
+
if not pattern.search(subject):
|
|
246
|
+
result.error(
|
|
247
|
+
f"subject must match pattern '{pattern.pattern}'",
|
|
248
|
+
check=Check.SUBJECT,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
244
252
|
def check_required_trailers(message, required, result):
|
|
245
253
|
for trailer in required:
|
|
246
254
|
pattern = re.compile(rf"^{re.escape(trailer)}:\s+\S", re.MULTILINE)
|
|
@@ -313,6 +321,7 @@ class Args:
|
|
|
313
321
|
allow_empty: bool
|
|
314
322
|
include_merges: bool
|
|
315
323
|
required_trailers: list
|
|
324
|
+
subject_pattern: re.Pattern | None
|
|
316
325
|
output: OutputFormat
|
|
317
326
|
output_file: Path | None
|
|
318
327
|
|
|
@@ -362,7 +371,7 @@ def _resolve_no_trailing_chars(args, config):
|
|
|
362
371
|
return frozenset(c for c in args.no_trailing_chars.split(",") if c)
|
|
363
372
|
if "no-trailing-chars" in config:
|
|
364
373
|
return frozenset(config["no-trailing-chars"])
|
|
365
|
-
return frozenset(".")
|
|
374
|
+
return frozenset({".", "!", "?", " "})
|
|
366
375
|
|
|
367
376
|
|
|
368
377
|
def _resolve_required_trailers(args, config):
|
|
@@ -373,6 +382,12 @@ def _resolve_required_trailers(args, config):
|
|
|
373
382
|
return []
|
|
374
383
|
|
|
375
384
|
|
|
385
|
+
def _resolve_subject_pattern(args, config):
|
|
386
|
+
if args.require_subject_pattern is not None:
|
|
387
|
+
return args.require_subject_pattern
|
|
388
|
+
return config.get("require-subject-pattern")
|
|
389
|
+
|
|
390
|
+
|
|
376
391
|
def _resolve_types(args, config):
|
|
377
392
|
if args.types:
|
|
378
393
|
return frozenset(t.strip() for t in args.types.split(","))
|
|
@@ -406,7 +421,7 @@ def _parse_checks(parser, value):
|
|
|
406
421
|
parser.error(str(e))
|
|
407
422
|
|
|
408
423
|
|
|
409
|
-
def _parse_args():
|
|
424
|
+
def _parse_args(): # noqa: PLR0915 Too many statements (59 > 50)
|
|
410
425
|
checks_list = ",".join(sorted(Check))
|
|
411
426
|
parser = ArgumentParser(description="conventional commit checker")
|
|
412
427
|
parser.add_argument("rev", nargs="?", default=None)
|
|
@@ -476,6 +491,12 @@ def _parse_args():
|
|
|
476
491
|
default=False,
|
|
477
492
|
help="exit 0 when --range yields no commits (default: exit 1)",
|
|
478
493
|
)
|
|
494
|
+
parser.add_argument(
|
|
495
|
+
"--require-subject-pattern",
|
|
496
|
+
default=None,
|
|
497
|
+
metavar="REGEX",
|
|
498
|
+
help="require subject line to match this regular expression",
|
|
499
|
+
)
|
|
479
500
|
parser.add_argument(
|
|
480
501
|
"--require-trailer",
|
|
481
502
|
metavar="TRAILER[,TRAILER,...]",
|
|
@@ -509,6 +530,16 @@ def _parse_args():
|
|
|
509
530
|
require_lowercase = _resolve_require_lowercase(args, config)
|
|
510
531
|
no_trailing_chars = _resolve_no_trailing_chars(args, config)
|
|
511
532
|
required_trailers = _resolve_required_trailers(args, config)
|
|
533
|
+
subject_pattern_str = _resolve_subject_pattern(args, config)
|
|
534
|
+
if subject_pattern_str is not None:
|
|
535
|
+
try:
|
|
536
|
+
subject_pattern = re.compile(subject_pattern_str)
|
|
537
|
+
except re.error as e:
|
|
538
|
+
parser.error(
|
|
539
|
+
f"invalid regex for --require-subject-pattern {subject_pattern_str!r}: {e}" # noqa: E501 Line too long
|
|
540
|
+
)
|
|
541
|
+
else:
|
|
542
|
+
subject_pattern = None
|
|
512
543
|
|
|
513
544
|
if args.allow_empty and not args.rev_range:
|
|
514
545
|
parser.error("--allow-empty requires --range")
|
|
@@ -548,6 +579,7 @@ def _parse_args():
|
|
|
548
579
|
allow_empty=args.allow_empty,
|
|
549
580
|
include_merges=args.include_merges,
|
|
550
581
|
required_trailers=required_trailers,
|
|
582
|
+
subject_pattern=subject_pattern,
|
|
551
583
|
output=OutputFormat(args.output),
|
|
552
584
|
output_file=args.output_file,
|
|
553
585
|
)
|
|
@@ -613,6 +645,8 @@ def _run_checks(args, rev, message, result):
|
|
|
613
645
|
check_signed_off(message, result)
|
|
614
646
|
if args.required_trailers:
|
|
615
647
|
check_required_trailers(message, args.required_trailers, result)
|
|
648
|
+
if args.subject_pattern:
|
|
649
|
+
check_subject_pattern(lines[0], args.subject_pattern, result)
|
|
616
650
|
if Check.SIGNATURE in args.enabled and rev:
|
|
617
651
|
check_signature(rev, result)
|
|
618
652
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
import subprocess
|
|
3
4
|
from argparse import ArgumentParser, Namespace
|
|
4
5
|
from unittest.mock import MagicMock, patch
|
|
@@ -22,7 +23,10 @@ from git_commit_guard import (
|
|
|
22
23
|
_report_text,
|
|
23
24
|
_resolve_max_subject_length,
|
|
24
25
|
_resolve_min_description_length,
|
|
26
|
+
_resolve_no_trailing_chars,
|
|
27
|
+
_resolve_require_lowercase,
|
|
25
28
|
_resolve_required_trailers,
|
|
29
|
+
_resolve_subject_pattern,
|
|
26
30
|
_resolve_types,
|
|
27
31
|
_strip_comments,
|
|
28
32
|
check_body,
|
|
@@ -31,6 +35,7 @@ from git_commit_guard import (
|
|
|
31
35
|
check_signature,
|
|
32
36
|
check_signed_off,
|
|
33
37
|
check_subject,
|
|
38
|
+
check_subject_pattern,
|
|
34
39
|
main,
|
|
35
40
|
)
|
|
36
41
|
|
|
@@ -392,6 +397,55 @@ class TestResolveRequiredTrailers:
|
|
|
392
397
|
assert result == ["Fixes"]
|
|
393
398
|
|
|
394
399
|
|
|
400
|
+
class TestCheckSubjectPattern:
|
|
401
|
+
def test_matching_subject_passes(self):
|
|
402
|
+
r = Result()
|
|
403
|
+
check_subject_pattern("feat: add PROJ-123 login", re.compile(r"[A-Z]+-\d+"), r)
|
|
404
|
+
assert r.ok
|
|
405
|
+
|
|
406
|
+
def test_non_matching_subject_fails(self):
|
|
407
|
+
r = Result()
|
|
408
|
+
check_subject_pattern(
|
|
409
|
+
"feat: implement OAuth login flow", re.compile(r"[A-Z]+-\d+"), r
|
|
410
|
+
)
|
|
411
|
+
assert not r.ok
|
|
412
|
+
assert "must match pattern" in r.errors[0][2]
|
|
413
|
+
assert "[A-Z]+-\\d+" in r.errors[0][2]
|
|
414
|
+
|
|
415
|
+
def test_error_includes_pattern(self):
|
|
416
|
+
r = Result()
|
|
417
|
+
check_subject_pattern("fix: oops", re.compile(r"#\d+"), r)
|
|
418
|
+
assert "#\\d+" in r.errors[0][2]
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class TestResolveSubjectPattern:
|
|
422
|
+
def test_defaults_to_none(self):
|
|
423
|
+
assert (
|
|
424
|
+
_resolve_subject_pattern(Namespace(require_subject_pattern=None), {})
|
|
425
|
+
is None
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def test_cli_flag(self):
|
|
429
|
+
result = _resolve_subject_pattern(
|
|
430
|
+
Namespace(require_subject_pattern="[A-Z]+-\\d+"), {}
|
|
431
|
+
)
|
|
432
|
+
assert result == "[A-Z]+-\\d+"
|
|
433
|
+
|
|
434
|
+
def test_config(self):
|
|
435
|
+
result = _resolve_subject_pattern(
|
|
436
|
+
Namespace(require_subject_pattern=None),
|
|
437
|
+
{"require-subject-pattern": "#\\d+"},
|
|
438
|
+
)
|
|
439
|
+
assert result == "#\\d+"
|
|
440
|
+
|
|
441
|
+
def test_cli_overrides_config(self):
|
|
442
|
+
result = _resolve_subject_pattern(
|
|
443
|
+
Namespace(require_subject_pattern="[A-Z]+-\\d+"),
|
|
444
|
+
{"require-subject-pattern": "#\\d+"},
|
|
445
|
+
)
|
|
446
|
+
assert result == "[A-Z]+-\\d+"
|
|
447
|
+
|
|
448
|
+
|
|
395
449
|
class TestCheckImperative:
|
|
396
450
|
def test_imperative_verb_passes(self):
|
|
397
451
|
r = Result()
|
|
@@ -454,6 +508,19 @@ class TestCheckImperative:
|
|
|
454
508
|
check_imperative("", r)
|
|
455
509
|
assert r.ok
|
|
456
510
|
|
|
511
|
+
def test_pos_fallback_unknown_word_fails(self):
|
|
512
|
+
r = Result()
|
|
513
|
+
with (
|
|
514
|
+
patch("git_commit_guard.wordnet.morphy", return_value=None),
|
|
515
|
+
patch(
|
|
516
|
+
"git_commit_guard.nltk.pos_tag",
|
|
517
|
+
return_value=[("to", "TO"), ("xyzzy", "NN")],
|
|
518
|
+
),
|
|
519
|
+
):
|
|
520
|
+
check_imperative("xyzzy something", r)
|
|
521
|
+
assert not r.ok
|
|
522
|
+
assert "POS=NN" in r.errors[0][2]
|
|
523
|
+
|
|
457
524
|
|
|
458
525
|
class TestDownloadIfMissing:
|
|
459
526
|
def test_skips_download_when_present(self):
|
|
@@ -617,6 +684,38 @@ class TestResolveMinDescriptionLength:
|
|
|
617
684
|
assert result == 10
|
|
618
685
|
|
|
619
686
|
|
|
687
|
+
class TestResolveRequireLowercase:
|
|
688
|
+
def test_cli_flag_overrides_default(self):
|
|
689
|
+
assert (
|
|
690
|
+
_resolve_require_lowercase(Namespace(require_lowercase=False), {}) is False
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
def test_config_overrides_default(self):
|
|
694
|
+
result = _resolve_require_lowercase(
|
|
695
|
+
Namespace(require_lowercase=None), {"require-lowercase": False}
|
|
696
|
+
)
|
|
697
|
+
assert result is False
|
|
698
|
+
|
|
699
|
+
def test_default_is_true(self):
|
|
700
|
+
assert _resolve_require_lowercase(Namespace(require_lowercase=None), {}) is True
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class TestResolveNoTrailingChars:
|
|
704
|
+
def test_cli_flag_overrides_default(self):
|
|
705
|
+
result = _resolve_no_trailing_chars(Namespace(no_trailing_chars=".,!"), {})
|
|
706
|
+
assert result == frozenset({".", "!"})
|
|
707
|
+
|
|
708
|
+
def test_config_overrides_default(self):
|
|
709
|
+
result = _resolve_no_trailing_chars(
|
|
710
|
+
Namespace(no_trailing_chars=None), {"no-trailing-chars": [".", "!"]}
|
|
711
|
+
)
|
|
712
|
+
assert result == frozenset({".", "!"})
|
|
713
|
+
|
|
714
|
+
def test_default_includes_common_punctuation_and_space(self):
|
|
715
|
+
result = _resolve_no_trailing_chars(Namespace(no_trailing_chars=None), {})
|
|
716
|
+
assert result == frozenset({".", "!", "?", " "})
|
|
717
|
+
|
|
718
|
+
|
|
620
719
|
class TestGitTimeout:
|
|
621
720
|
def test_default(self, monkeypatch):
|
|
622
721
|
monkeypatch.delenv("COMMIT_GUARD_GIT_TIMEOUT", raising=False)
|
|
@@ -1391,6 +1490,69 @@ class TestRequireTrailerIntegration:
|
|
|
1391
1490
|
assert main() == 0
|
|
1392
1491
|
|
|
1393
1492
|
|
|
1493
|
+
class TestRequireSubjectPatternIntegration:
|
|
1494
|
+
def test_matching_pattern_passes(self, tmp_path):
|
|
1495
|
+
f = tmp_path / "msg"
|
|
1496
|
+
f.write_text(
|
|
1497
|
+
"fix: resolve PROJ-42 auth timeout\n\nbody\n\nSigned-off-by: A <a@b.com>"
|
|
1498
|
+
)
|
|
1499
|
+
argv = [
|
|
1500
|
+
"cg",
|
|
1501
|
+
"--message-file",
|
|
1502
|
+
str(f),
|
|
1503
|
+
"--disable",
|
|
1504
|
+
"signature,imperative",
|
|
1505
|
+
"--require-subject-pattern",
|
|
1506
|
+
"[A-Z]+-[0-9]+",
|
|
1507
|
+
]
|
|
1508
|
+
with patch("sys.argv", argv):
|
|
1509
|
+
assert main() == 0
|
|
1510
|
+
|
|
1511
|
+
def test_non_matching_pattern_fails(self, tmp_path):
|
|
1512
|
+
f = tmp_path / "msg"
|
|
1513
|
+
f.write_text("fix: resolve auth timeout\n\nbody\n\nSigned-off-by: A <a@b.com>")
|
|
1514
|
+
argv = [
|
|
1515
|
+
"cg",
|
|
1516
|
+
"--message-file",
|
|
1517
|
+
str(f),
|
|
1518
|
+
"--disable",
|
|
1519
|
+
"signature,imperative",
|
|
1520
|
+
"--require-subject-pattern",
|
|
1521
|
+
"[A-Z]+-[0-9]+",
|
|
1522
|
+
]
|
|
1523
|
+
with patch("sys.argv", argv):
|
|
1524
|
+
assert main() == 1
|
|
1525
|
+
|
|
1526
|
+
def test_invalid_regex_exits(self, tmp_path):
|
|
1527
|
+
f = tmp_path / "msg"
|
|
1528
|
+
f.write_text("fix: add thing\n\nbody\n\nSigned-off-by: A <a@b.com>")
|
|
1529
|
+
argv = [
|
|
1530
|
+
"cg",
|
|
1531
|
+
"--message-file",
|
|
1532
|
+
str(f),
|
|
1533
|
+
"--disable",
|
|
1534
|
+
"signature,imperative",
|
|
1535
|
+
"--require-subject-pattern",
|
|
1536
|
+
"[unclosed",
|
|
1537
|
+
]
|
|
1538
|
+
with patch("sys.argv", argv), pytest.raises(SystemExit) as exc:
|
|
1539
|
+
main()
|
|
1540
|
+
assert exc.value.code == 2
|
|
1541
|
+
|
|
1542
|
+
def test_pattern_from_config(self, tmp_path):
|
|
1543
|
+
f = tmp_path / "msg"
|
|
1544
|
+
f.write_text("fix: resolve auth timeout\n\nbody\n\nSigned-off-by: A <a@b.com>")
|
|
1545
|
+
argv = ["cg", "--message-file", str(f), "--disable", "signature,imperative"]
|
|
1546
|
+
with (
|
|
1547
|
+
patch("sys.argv", argv),
|
|
1548
|
+
patch(
|
|
1549
|
+
"git_commit_guard._load_config",
|
|
1550
|
+
return_value={"require-subject-pattern": "[A-Z]+-[0-9]+"},
|
|
1551
|
+
),
|
|
1552
|
+
):
|
|
1553
|
+
assert main() == 1
|
|
1554
|
+
|
|
1555
|
+
|
|
1394
1556
|
class TestOutputJsonl:
|
|
1395
1557
|
def test_single_commit_ok(self, tmp_path, capsys):
|
|
1396
1558
|
|
|
@@ -1458,6 +1620,27 @@ class TestOutputJsonl:
|
|
|
1458
1620
|
assert data["sha"] == rev
|
|
1459
1621
|
assert data["ok"] is True
|
|
1460
1622
|
|
|
1623
|
+
def test_range_failing_commit_returns_nonzero(self, capsys):
|
|
1624
|
+
with (
|
|
1625
|
+
patch(
|
|
1626
|
+
"sys.argv",
|
|
1627
|
+
[
|
|
1628
|
+
"cg",
|
|
1629
|
+
"--range",
|
|
1630
|
+
"HEAD~1..HEAD",
|
|
1631
|
+
"--disable",
|
|
1632
|
+
"signature,imperative",
|
|
1633
|
+
"--output",
|
|
1634
|
+
"jsonl",
|
|
1635
|
+
],
|
|
1636
|
+
),
|
|
1637
|
+
patch("git_commit_guard._get_range_revs", return_value=["aaa"]),
|
|
1638
|
+
patch("git_commit_guard._get_message", return_value="bad message"),
|
|
1639
|
+
):
|
|
1640
|
+
assert main() == 1
|
|
1641
|
+
data = json.loads(capsys.readouterr().out)
|
|
1642
|
+
assert data["ok"] is False
|
|
1643
|
+
|
|
1461
1644
|
|
|
1462
1645
|
class TestOutputFile:
|
|
1463
1646
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|