git-commit-guard 0.11.0__tar.gz → 0.13.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.11.0 → git_commit_guard-0.13.0}/.github/workflows/lint-commits.yml +6 -22
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/PKG-INFO +80 -6
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/README.md +79 -5
- git_commit_guard-0.13.0/action.yml +85 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/src/git_commit_guard/__init__.py +26 -3
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/tests/test_git_commit_guard.py +165 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.github/workflows/lint-md.yml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.github/workflows/lint-python.yml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.github/workflows/release.yml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.github/workflows/test.yml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.gitignore +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.pre-commit-hooks.yaml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/.python-version +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/LICENSE +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/pyproject.toml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/ruff.toml +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/tests/__init__.py +0 -0
- {git_commit_guard-0.11.0 → git_commit_guard-0.13.0}/uv.lock +0 -0
|
@@ -7,8 +7,6 @@ permissions:
|
|
|
7
7
|
jobs:
|
|
8
8
|
lint-commits:
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
|
-
permissions:
|
|
11
|
-
contents: write
|
|
12
10
|
steps:
|
|
13
11
|
- name: Checkout code
|
|
14
12
|
# yamllint disable-line rule:line-length
|
|
@@ -16,29 +14,15 @@ jobs:
|
|
|
16
14
|
with:
|
|
17
15
|
persist-credentials: false
|
|
18
16
|
fetch-depth: 0
|
|
19
|
-
- name: Install Python
|
|
20
|
-
# yamllint disable-line rule:line-length
|
|
21
|
-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
22
|
-
with:
|
|
23
|
-
python-version: '3.12'
|
|
24
|
-
- name: Install uv
|
|
25
|
-
# yamllint disable-line rule:line-length
|
|
26
|
-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
27
|
-
- name: Install commit-guard
|
|
28
|
-
run: uv pip install --system .
|
|
29
17
|
- name: Cache NLTK data
|
|
18
|
+
# yamllint disable-line rule:line-length
|
|
30
19
|
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
31
20
|
with:
|
|
32
21
|
path: ~/nltk_data
|
|
33
22
|
key: nltk-averaged-perceptron-tagger-punkt
|
|
34
23
|
- name: Lint commits
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for sha in $commits; do
|
|
41
|
-
echo "--- checking $sha ---"
|
|
42
|
-
commit-guard --disable signature "$sha" || failed=1
|
|
43
|
-
done
|
|
44
|
-
exit $failed
|
|
24
|
+
# yamllint disable-line rule:line-length
|
|
25
|
+
uses: benner/commit-guard@cad366366cd6d2691f7c36ff5e7a9999279906dd # v0.12.0
|
|
26
|
+
with:
|
|
27
|
+
range: origin/${{ github.base_ref }}..HEAD
|
|
28
|
+
disable: signature
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -147,12 +147,33 @@ commit-guard --require-scope
|
|
|
147
147
|
commit-guard --scopes auth,api --require-scope
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
### Required custom trailers
|
|
151
|
+
|
|
152
|
+
Require arbitrary trailers to be present in the commit message. Multiple
|
|
153
|
+
trailers can be specified as a comma-separated list:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
commit-guard --require-trailer Closes
|
|
157
|
+
commit-guard --require-trailer "Closes,Reviewed-by"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
In `.commit-guard.toml`:
|
|
161
|
+
|
|
162
|
+
```toml
|
|
163
|
+
require-trailers = ["Closes", "Reviewed-by"]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Trailer matching is case-sensitive and requires at least one non-space
|
|
167
|
+
character after the colon (e.g. `Closes: #42`). This check runs
|
|
168
|
+
independently of `--enable`/`--disable`.
|
|
169
|
+
|
|
150
170
|
### Configuration file
|
|
151
171
|
|
|
152
172
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
153
173
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
154
|
-
`max-subject-length`,
|
|
155
|
-
upward from the working directory and uses the first
|
|
174
|
+
`max-subject-length`, `min-description-length`, and `require-trailers`.
|
|
175
|
+
commit-guard searches upward from the working directory and uses the first
|
|
176
|
+
file found.
|
|
156
177
|
|
|
157
178
|
```toml
|
|
158
179
|
# .commit-guard.toml
|
|
@@ -162,6 +183,7 @@ require-scope = true
|
|
|
162
183
|
types = ["feat", "fix", "chore", "wip"]
|
|
163
184
|
max-subject-length = 100
|
|
164
185
|
min-description-length = 10
|
|
186
|
+
require-trailers = ["Closes", "Reviewed-by"]
|
|
165
187
|
```
|
|
166
188
|
|
|
167
189
|
```toml
|
|
@@ -170,8 +192,8 @@ enable = ["subject", "imperative"]
|
|
|
170
192
|
```
|
|
171
193
|
|
|
172
194
|
CLI flags (`--enable`, `--disable`, `--scopes`, `--require-scope`, `--types`,
|
|
173
|
-
`--max-subject-length`, `--min-description-length`) take
|
|
174
|
-
ignore config file values when provided.
|
|
195
|
+
`--max-subject-length`, `--min-description-length`, `--require-trailer`) take
|
|
196
|
+
full precedence and ignore config file values when provided.
|
|
175
197
|
|
|
176
198
|
### Checking a range of commits
|
|
177
199
|
|
|
@@ -202,6 +224,58 @@ misconfigured range specs in CI. Use `--allow-empty` to exit 0 instead:
|
|
|
202
224
|
commit-guard --range origin/main..HEAD --allow-empty
|
|
203
225
|
```
|
|
204
226
|
|
|
227
|
+
### GitHub Actions
|
|
228
|
+
|
|
229
|
+
```yaml
|
|
230
|
+
steps:
|
|
231
|
+
- uses: actions/checkout@v4
|
|
232
|
+
with:
|
|
233
|
+
fetch-depth: 0
|
|
234
|
+
- uses: benner/commit-guard@v0.13.0
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Check all commits in a pull request:
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
jobs:
|
|
241
|
+
lint-commits:
|
|
242
|
+
runs-on: ubuntu-latest
|
|
243
|
+
env:
|
|
244
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
245
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
246
|
+
steps:
|
|
247
|
+
- uses: actions/checkout@v4
|
|
248
|
+
with:
|
|
249
|
+
fetch-depth: 0
|
|
250
|
+
- uses: benner/commit-guard@v0.13.0
|
|
251
|
+
with:
|
|
252
|
+
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
All inputs are optional and mirror the CLI flags:
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
jobs:
|
|
259
|
+
lint-commits:
|
|
260
|
+
runs-on: ubuntu-latest
|
|
261
|
+
env:
|
|
262
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
263
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
264
|
+
steps:
|
|
265
|
+
- uses: actions/checkout@v4
|
|
266
|
+
with:
|
|
267
|
+
fetch-depth: 0
|
|
268
|
+
- uses: benner/commit-guard@v0.13.0
|
|
269
|
+
with:
|
|
270
|
+
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
271
|
+
disable: signed-off,signature
|
|
272
|
+
scopes: auth,api,db
|
|
273
|
+
require-scope: 'true'
|
|
274
|
+
require-trailer: 'Closes,Reviewed-by'
|
|
275
|
+
max-subject-length: '100'
|
|
276
|
+
min-description-length: '10'
|
|
277
|
+
```
|
|
278
|
+
|
|
205
279
|
### pre-commit
|
|
206
280
|
|
|
207
281
|
Add to your `.pre-commit-config.yaml`:
|
|
@@ -210,7 +284,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
210
284
|
---
|
|
211
285
|
repos:
|
|
212
286
|
- repo: https://github.com/benner/commit-guard
|
|
213
|
-
rev: v0.
|
|
287
|
+
rev: v0.13.0
|
|
214
288
|
hooks:
|
|
215
289
|
- id: commit-guard
|
|
216
290
|
- id: commit-guard-signature
|
|
@@ -126,12 +126,33 @@ commit-guard --require-scope
|
|
|
126
126
|
commit-guard --scopes auth,api --require-scope
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### Required custom trailers
|
|
130
|
+
|
|
131
|
+
Require arbitrary trailers to be present in the commit message. Multiple
|
|
132
|
+
trailers can be specified as a comma-separated list:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
commit-guard --require-trailer Closes
|
|
136
|
+
commit-guard --require-trailer "Closes,Reviewed-by"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
In `.commit-guard.toml`:
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
require-trailers = ["Closes", "Reviewed-by"]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Trailer matching is case-sensitive and requires at least one non-space
|
|
146
|
+
character after the colon (e.g. `Closes: #42`). This check runs
|
|
147
|
+
independently of `--enable`/`--disable`.
|
|
148
|
+
|
|
129
149
|
### Configuration file
|
|
130
150
|
|
|
131
151
|
Place `.commit-guard.toml` in your project root (or any parent directory) to
|
|
132
152
|
set defaults for `enable`, `disable`, `scopes`, `require-scope`, `types`,
|
|
133
|
-
`max-subject-length`,
|
|
134
|
-
upward from the working directory and uses the first
|
|
153
|
+
`max-subject-length`, `min-description-length`, and `require-trailers`.
|
|
154
|
+
commit-guard searches upward from the working directory and uses the first
|
|
155
|
+
file found.
|
|
135
156
|
|
|
136
157
|
```toml
|
|
137
158
|
# .commit-guard.toml
|
|
@@ -141,6 +162,7 @@ require-scope = true
|
|
|
141
162
|
types = ["feat", "fix", "chore", "wip"]
|
|
142
163
|
max-subject-length = 100
|
|
143
164
|
min-description-length = 10
|
|
165
|
+
require-trailers = ["Closes", "Reviewed-by"]
|
|
144
166
|
```
|
|
145
167
|
|
|
146
168
|
```toml
|
|
@@ -149,8 +171,8 @@ enable = ["subject", "imperative"]
|
|
|
149
171
|
```
|
|
150
172
|
|
|
151
173
|
CLI flags (`--enable`, `--disable`, `--scopes`, `--require-scope`, `--types`,
|
|
152
|
-
`--max-subject-length`, `--min-description-length`) take
|
|
153
|
-
ignore config file values when provided.
|
|
174
|
+
`--max-subject-length`, `--min-description-length`, `--require-trailer`) take
|
|
175
|
+
full precedence and ignore config file values when provided.
|
|
154
176
|
|
|
155
177
|
### Checking a range of commits
|
|
156
178
|
|
|
@@ -181,6 +203,58 @@ misconfigured range specs in CI. Use `--allow-empty` to exit 0 instead:
|
|
|
181
203
|
commit-guard --range origin/main..HEAD --allow-empty
|
|
182
204
|
```
|
|
183
205
|
|
|
206
|
+
### GitHub Actions
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
steps:
|
|
210
|
+
- uses: actions/checkout@v4
|
|
211
|
+
with:
|
|
212
|
+
fetch-depth: 0
|
|
213
|
+
- uses: benner/commit-guard@v0.13.0
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Check all commits in a pull request:
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
jobs:
|
|
220
|
+
lint-commits:
|
|
221
|
+
runs-on: ubuntu-latest
|
|
222
|
+
env:
|
|
223
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
224
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
225
|
+
steps:
|
|
226
|
+
- uses: actions/checkout@v4
|
|
227
|
+
with:
|
|
228
|
+
fetch-depth: 0
|
|
229
|
+
- uses: benner/commit-guard@v0.13.0
|
|
230
|
+
with:
|
|
231
|
+
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
All inputs are optional and mirror the CLI flags:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
jobs:
|
|
238
|
+
lint-commits:
|
|
239
|
+
runs-on: ubuntu-latest
|
|
240
|
+
env:
|
|
241
|
+
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
|
242
|
+
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
|
243
|
+
steps:
|
|
244
|
+
- uses: actions/checkout@v4
|
|
245
|
+
with:
|
|
246
|
+
fetch-depth: 0
|
|
247
|
+
- uses: benner/commit-guard@v0.13.0
|
|
248
|
+
with:
|
|
249
|
+
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
|
|
250
|
+
disable: signed-off,signature
|
|
251
|
+
scopes: auth,api,db
|
|
252
|
+
require-scope: 'true'
|
|
253
|
+
require-trailer: 'Closes,Reviewed-by'
|
|
254
|
+
max-subject-length: '100'
|
|
255
|
+
min-description-length: '10'
|
|
256
|
+
```
|
|
257
|
+
|
|
184
258
|
### pre-commit
|
|
185
259
|
|
|
186
260
|
Add to your `.pre-commit-config.yaml`:
|
|
@@ -189,7 +263,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
189
263
|
---
|
|
190
264
|
repos:
|
|
191
265
|
- repo: https://github.com/benner/commit-guard
|
|
192
|
-
rev: v0.
|
|
266
|
+
rev: v0.13.0
|
|
193
267
|
hooks:
|
|
194
268
|
- id: commit-guard
|
|
195
269
|
- id: commit-guard-signature
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-guard
|
|
3
|
+
description: Conventional commit message linter with imperative mood detection
|
|
4
|
+
author: Nerijus Bendžiūnas
|
|
5
|
+
branding:
|
|
6
|
+
icon: shield
|
|
7
|
+
color: green
|
|
8
|
+
inputs:
|
|
9
|
+
rev:
|
|
10
|
+
description: Commit SHA to check (defaults to HEAD)
|
|
11
|
+
required: false
|
|
12
|
+
range:
|
|
13
|
+
description: Revision range to check (e.g. abc123..HEAD)
|
|
14
|
+
required: false
|
|
15
|
+
enable:
|
|
16
|
+
description: Comma-separated checks to enable
|
|
17
|
+
required: false
|
|
18
|
+
disable:
|
|
19
|
+
description: Comma-separated checks to disable
|
|
20
|
+
required: false
|
|
21
|
+
scopes:
|
|
22
|
+
description: Comma-separated list of allowed scopes
|
|
23
|
+
required: false
|
|
24
|
+
require-scope:
|
|
25
|
+
description: Require a scope in the subject line
|
|
26
|
+
required: false
|
|
27
|
+
default: 'false'
|
|
28
|
+
types:
|
|
29
|
+
description: Comma-separated list of allowed types (replaces defaults)
|
|
30
|
+
required: false
|
|
31
|
+
max-subject-length:
|
|
32
|
+
description: Maximum subject line length (default 72)
|
|
33
|
+
required: false
|
|
34
|
+
min-description-length:
|
|
35
|
+
description: Minimum description length in characters (default 0, off)
|
|
36
|
+
required: false
|
|
37
|
+
allow-empty:
|
|
38
|
+
description: Exit 0 when --range yields no commits
|
|
39
|
+
required: false
|
|
40
|
+
default: 'false'
|
|
41
|
+
include-merges:
|
|
42
|
+
description: Include merge commits when checking a range
|
|
43
|
+
required: false
|
|
44
|
+
default: 'false'
|
|
45
|
+
runs:
|
|
46
|
+
using: composite
|
|
47
|
+
steps:
|
|
48
|
+
- name: Set up uv
|
|
49
|
+
# yamllint disable-line rule:line-length
|
|
50
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
51
|
+
with:
|
|
52
|
+
enable-cache: false
|
|
53
|
+
- name: Install commit-guard
|
|
54
|
+
run: uv tool install git-commit-guard
|
|
55
|
+
shell: bash
|
|
56
|
+
- name: Run commit-guard
|
|
57
|
+
env:
|
|
58
|
+
CG_REV: ${{ inputs.rev }}
|
|
59
|
+
CG_RANGE: ${{ inputs.range }}
|
|
60
|
+
CG_ENABLE: ${{ inputs.enable }}
|
|
61
|
+
CG_DISABLE: ${{ inputs.disable }}
|
|
62
|
+
CG_SCOPES: ${{ inputs.scopes }}
|
|
63
|
+
CG_REQUIRE_SCOPE: ${{ inputs.require-scope }}
|
|
64
|
+
CG_TYPES: ${{ inputs.types }}
|
|
65
|
+
CG_MAX_SUBJECT_LENGTH: ${{ inputs.max-subject-length }}
|
|
66
|
+
CG_MIN_DESCRIPTION_LENGTH: ${{ inputs.min-description-length }}
|
|
67
|
+
CG_ALLOW_EMPTY: ${{ inputs.allow-empty }}
|
|
68
|
+
CG_INCLUDE_MERGES: ${{ inputs.include-merges }}
|
|
69
|
+
run: |
|
|
70
|
+
ARGS=()
|
|
71
|
+
[[ -n "$CG_REV" ]] && ARGS+=("$CG_REV")
|
|
72
|
+
[[ -n "$CG_RANGE" ]] && ARGS+=(--range "$CG_RANGE")
|
|
73
|
+
[[ -n "$CG_ENABLE" ]] && ARGS+=(--enable "$CG_ENABLE")
|
|
74
|
+
[[ -n "$CG_DISABLE" ]] && ARGS+=(--disable "$CG_DISABLE")
|
|
75
|
+
[[ -n "$CG_SCOPES" ]] && ARGS+=(--scopes "$CG_SCOPES")
|
|
76
|
+
[[ "$CG_REQUIRE_SCOPE" == "true" ]] && ARGS+=(--require-scope)
|
|
77
|
+
[[ -n "$CG_TYPES" ]] && ARGS+=(--types "$CG_TYPES")
|
|
78
|
+
[[ -n "$CG_MAX_SUBJECT_LENGTH" ]] && \
|
|
79
|
+
ARGS+=(--max-subject-length "$CG_MAX_SUBJECT_LENGTH")
|
|
80
|
+
[[ -n "$CG_MIN_DESCRIPTION_LENGTH" ]] && \
|
|
81
|
+
ARGS+=(--min-description-length "$CG_MIN_DESCRIPTION_LENGTH")
|
|
82
|
+
[[ "$CG_ALLOW_EMPTY" == "true" ]] && ARGS+=(--allow-empty)
|
|
83
|
+
[[ "$CG_INCLUDE_MERGES" == "true" ]] && ARGS+=(--include-merges)
|
|
84
|
+
commit-guard "${ARGS[@]}"
|
|
85
|
+
shell: bash
|
|
@@ -157,6 +157,7 @@ def check_subject( # noqa: PLR0913 Too many arguments in function definition (7
|
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
def check_imperative(desc, result):
|
|
160
|
+
_ensure_nltk_data()
|
|
160
161
|
tokens = nltk.word_tokenize(desc.lower())
|
|
161
162
|
if not tokens:
|
|
162
163
|
return
|
|
@@ -195,6 +196,13 @@ def check_signed_off(message, result):
|
|
|
195
196
|
result.error("missing 'Signed-off-by' trailer")
|
|
196
197
|
|
|
197
198
|
|
|
199
|
+
def check_required_trailers(message, required, result):
|
|
200
|
+
for trailer in required:
|
|
201
|
+
pattern = re.compile(rf"^{re.escape(trailer)}:\s+\S", re.MULTILINE)
|
|
202
|
+
if not pattern.search(message):
|
|
203
|
+
result.error(f"missing required trailer: {trailer}")
|
|
204
|
+
|
|
205
|
+
|
|
198
206
|
def check_signature(rev, result):
|
|
199
207
|
proc = subprocess.run( # noqa: S603
|
|
200
208
|
["git", "verify-commit", rev], # noqa: S607
|
|
@@ -257,6 +265,7 @@ class Args:
|
|
|
257
265
|
rev_range: str | None
|
|
258
266
|
allow_empty: bool
|
|
259
267
|
include_merges: bool
|
|
268
|
+
required_trailers: list
|
|
260
269
|
|
|
261
270
|
|
|
262
271
|
def _resolve_enabled(args, config, parser):
|
|
@@ -291,6 +300,14 @@ def _resolve_min_description_length(args, config):
|
|
|
291
300
|
return 0
|
|
292
301
|
|
|
293
302
|
|
|
303
|
+
def _resolve_required_trailers(args, config):
|
|
304
|
+
if args.require_trailer:
|
|
305
|
+
return [t.strip() for t in args.require_trailer.split(",")]
|
|
306
|
+
if config.get("require-trailers"):
|
|
307
|
+
return list(config["require-trailers"])
|
|
308
|
+
return []
|
|
309
|
+
|
|
310
|
+
|
|
294
311
|
def _resolve_types(args, config):
|
|
295
312
|
if args.types:
|
|
296
313
|
return frozenset(t.strip() for t in args.types.split(","))
|
|
@@ -381,6 +398,11 @@ def _parse_args():
|
|
|
381
398
|
default=False,
|
|
382
399
|
help="exit 0 when --range yields no commits (default: exit 1)",
|
|
383
400
|
)
|
|
401
|
+
parser.add_argument(
|
|
402
|
+
"--require-trailer",
|
|
403
|
+
metavar="TRAILER[,TRAILER,...]",
|
|
404
|
+
help="require these trailers in the commit message",
|
|
405
|
+
)
|
|
384
406
|
parser.add_argument(
|
|
385
407
|
"--include-merges",
|
|
386
408
|
action="store_true",
|
|
@@ -394,6 +416,7 @@ def _parse_args():
|
|
|
394
416
|
allowed_types = _resolve_types(args, config)
|
|
395
417
|
max_subject_length = _resolve_max_subject_length(args, config)
|
|
396
418
|
min_description_length = _resolve_min_description_length(args, config)
|
|
419
|
+
required_trailers = _resolve_required_trailers(args, config)
|
|
397
420
|
|
|
398
421
|
if args.allow_empty and not args.rev_range:
|
|
399
422
|
parser.error("--allow-empty requires --range")
|
|
@@ -430,6 +453,7 @@ def _parse_args():
|
|
|
430
453
|
rev_range=args.rev_range,
|
|
431
454
|
allow_empty=args.allow_empty,
|
|
432
455
|
include_merges=args.include_merges,
|
|
456
|
+
required_trailers=required_trailers,
|
|
433
457
|
)
|
|
434
458
|
|
|
435
459
|
|
|
@@ -466,6 +490,8 @@ def _run_checks(args, rev, message, result):
|
|
|
466
490
|
check_body(lines, result)
|
|
467
491
|
if Check.SIGNED_OFF in args.enabled:
|
|
468
492
|
check_signed_off(message, result)
|
|
493
|
+
if args.required_trailers:
|
|
494
|
+
check_required_trailers(message, args.required_trailers, result)
|
|
469
495
|
if Check.SIGNATURE in args.enabled and rev:
|
|
470
496
|
check_signature(rev, result)
|
|
471
497
|
|
|
@@ -473,9 +499,6 @@ def _run_checks(args, rev, message, result):
|
|
|
473
499
|
def main():
|
|
474
500
|
args = _parse_args()
|
|
475
501
|
|
|
476
|
-
if Check.IMPERATIVE in args.enabled:
|
|
477
|
-
_ensure_nltk_data()
|
|
478
|
-
|
|
479
502
|
if args.rev_range:
|
|
480
503
|
revs = _get_range_revs(args.rev_range, include_merges=args.include_merges)
|
|
481
504
|
if not revs:
|
|
@@ -18,10 +18,12 @@ from git_commit_guard import (
|
|
|
18
18
|
_report,
|
|
19
19
|
_resolve_max_subject_length,
|
|
20
20
|
_resolve_min_description_length,
|
|
21
|
+
_resolve_required_trailers,
|
|
21
22
|
_resolve_types,
|
|
22
23
|
_strip_comments,
|
|
23
24
|
check_body,
|
|
24
25
|
check_imperative,
|
|
26
|
+
check_required_trailers,
|
|
25
27
|
check_signature,
|
|
26
28
|
check_signed_off,
|
|
27
29
|
check_subject,
|
|
@@ -273,6 +275,83 @@ class TestCheckSignedOff:
|
|
|
273
275
|
assert not r.ok
|
|
274
276
|
|
|
275
277
|
|
|
278
|
+
class TestCheckRequiredTrailers:
|
|
279
|
+
def test_present_passes(self):
|
|
280
|
+
r = Result()
|
|
281
|
+
check_required_trailers("fix: add x\n\nbody\n\nCloses: #42", ["Closes"], r)
|
|
282
|
+
assert r.ok
|
|
283
|
+
|
|
284
|
+
def test_missing_fails(self):
|
|
285
|
+
r = Result()
|
|
286
|
+
check_required_trailers("fix: add x\n\nbody", ["Closes"], r)
|
|
287
|
+
assert not r.ok
|
|
288
|
+
assert "missing required trailer: Closes" in r.errors[0][1]
|
|
289
|
+
|
|
290
|
+
def test_multiple_all_present_passes(self):
|
|
291
|
+
r = Result()
|
|
292
|
+
check_required_trailers(
|
|
293
|
+
"fix: add x\n\nbody\n\nCloses: #42\nReviewed-by: Jane",
|
|
294
|
+
["Closes", "Reviewed-by"],
|
|
295
|
+
r,
|
|
296
|
+
)
|
|
297
|
+
assert r.ok
|
|
298
|
+
|
|
299
|
+
def test_multiple_one_missing_fails(self):
|
|
300
|
+
r = Result()
|
|
301
|
+
check_required_trailers(
|
|
302
|
+
"fix: add x\n\nbody\n\nCloses: #42",
|
|
303
|
+
["Closes", "Reviewed-by"],
|
|
304
|
+
r,
|
|
305
|
+
)
|
|
306
|
+
assert not r.ok
|
|
307
|
+
assert any("Reviewed-by" in msg for _, msg in r.errors)
|
|
308
|
+
|
|
309
|
+
def test_case_sensitive(self):
|
|
310
|
+
r = Result()
|
|
311
|
+
check_required_trailers("fix: add x\n\nbody\n\ncloses: #42", ["Closes"], r)
|
|
312
|
+
assert not r.ok
|
|
313
|
+
|
|
314
|
+
def test_empty_required_list_always_passes(self):
|
|
315
|
+
r = Result()
|
|
316
|
+
check_required_trailers("fix: add x", [], r)
|
|
317
|
+
assert r.ok
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class TestResolveRequiredTrailers:
|
|
321
|
+
def test_defaults_to_empty(self):
|
|
322
|
+
assert _resolve_required_trailers(Namespace(require_trailer=None), {}) == []
|
|
323
|
+
|
|
324
|
+
def test_cli_flag_single(self):
|
|
325
|
+
result = _resolve_required_trailers(Namespace(require_trailer="Closes"), {})
|
|
326
|
+
assert result == ["Closes"]
|
|
327
|
+
|
|
328
|
+
def test_cli_flag_multiple(self):
|
|
329
|
+
result = _resolve_required_trailers(
|
|
330
|
+
Namespace(require_trailer="Closes,Reviewed-by"), {}
|
|
331
|
+
)
|
|
332
|
+
assert result == ["Closes", "Reviewed-by"]
|
|
333
|
+
|
|
334
|
+
def test_cli_flag_strips_spaces(self):
|
|
335
|
+
result = _resolve_required_trailers(
|
|
336
|
+
Namespace(require_trailer="Closes, Reviewed-by"), {}
|
|
337
|
+
)
|
|
338
|
+
assert result == ["Closes", "Reviewed-by"]
|
|
339
|
+
|
|
340
|
+
def test_config(self):
|
|
341
|
+
result = _resolve_required_trailers(
|
|
342
|
+
Namespace(require_trailer=None),
|
|
343
|
+
{"require-trailers": ["Closes", "Reviewed-by"]},
|
|
344
|
+
)
|
|
345
|
+
assert result == ["Closes", "Reviewed-by"]
|
|
346
|
+
|
|
347
|
+
def test_cli_overrides_config(self):
|
|
348
|
+
result = _resolve_required_trailers(
|
|
349
|
+
Namespace(require_trailer="Fixes"),
|
|
350
|
+
{"require-trailers": ["Closes"]},
|
|
351
|
+
)
|
|
352
|
+
assert result == ["Fixes"]
|
|
353
|
+
|
|
354
|
+
|
|
276
355
|
class TestCheckImperative:
|
|
277
356
|
def test_imperative_verb_passes(self):
|
|
278
357
|
r = Result()
|
|
@@ -1103,3 +1182,89 @@ class TestGetRangeRevs:
|
|
|
1103
1182
|
pytest.raises(SystemExit, match="git error"),
|
|
1104
1183
|
):
|
|
1105
1184
|
_get_range_revs("bogus")
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
class TestRequireTrailerIntegration:
|
|
1188
|
+
def test_require_trailer_flag_passes(self, tmp_path):
|
|
1189
|
+
f = tmp_path / "msg"
|
|
1190
|
+
f.write_text(
|
|
1191
|
+
"fix: add thing\n\nbody\n\nCloses: #42\nSigned-off-by: A <a@b.com>"
|
|
1192
|
+
)
|
|
1193
|
+
argv = [
|
|
1194
|
+
"cg",
|
|
1195
|
+
"--message-file",
|
|
1196
|
+
str(f),
|
|
1197
|
+
"--disable",
|
|
1198
|
+
"signature,imperative",
|
|
1199
|
+
"--require-trailer",
|
|
1200
|
+
"Closes",
|
|
1201
|
+
]
|
|
1202
|
+
with patch("sys.argv", argv):
|
|
1203
|
+
assert main() == 0
|
|
1204
|
+
|
|
1205
|
+
def test_require_trailer_flag_fails(self, tmp_path):
|
|
1206
|
+
f = tmp_path / "msg"
|
|
1207
|
+
f.write_text("fix: add thing\n\nbody\n\nSigned-off-by: A <a@b.com>")
|
|
1208
|
+
argv = [
|
|
1209
|
+
"cg",
|
|
1210
|
+
"--message-file",
|
|
1211
|
+
str(f),
|
|
1212
|
+
"--disable",
|
|
1213
|
+
"signature,imperative",
|
|
1214
|
+
"--require-trailer",
|
|
1215
|
+
"Closes",
|
|
1216
|
+
]
|
|
1217
|
+
with patch("sys.argv", argv):
|
|
1218
|
+
assert main() == 1
|
|
1219
|
+
|
|
1220
|
+
def test_require_trailer_multiple_passes(self, tmp_path):
|
|
1221
|
+
f = tmp_path / "msg"
|
|
1222
|
+
f.write_text(
|
|
1223
|
+
"fix: add thing\n\nbody\n\n"
|
|
1224
|
+
"Closes: #42\nReviewed-by: Jane\nSigned-off-by: A <a@b.com>"
|
|
1225
|
+
)
|
|
1226
|
+
argv = [
|
|
1227
|
+
"cg",
|
|
1228
|
+
"--message-file",
|
|
1229
|
+
str(f),
|
|
1230
|
+
"--disable",
|
|
1231
|
+
"signature,imperative",
|
|
1232
|
+
"--require-trailer",
|
|
1233
|
+
"Closes,Reviewed-by",
|
|
1234
|
+
]
|
|
1235
|
+
with patch("sys.argv", argv):
|
|
1236
|
+
assert main() == 0
|
|
1237
|
+
|
|
1238
|
+
def test_require_trailer_from_config(self, tmp_path):
|
|
1239
|
+
f = tmp_path / "msg"
|
|
1240
|
+
f.write_text("fix: add thing\n\nbody\n\nSigned-off-by: A <a@b.com>")
|
|
1241
|
+
argv = ["cg", "--message-file", str(f), "--disable", "signature,imperative"]
|
|
1242
|
+
with (
|
|
1243
|
+
patch("sys.argv", argv),
|
|
1244
|
+
patch(
|
|
1245
|
+
"git_commit_guard._load_config",
|
|
1246
|
+
return_value={"require-trailers": ["Closes"]},
|
|
1247
|
+
),
|
|
1248
|
+
):
|
|
1249
|
+
assert main() == 1
|
|
1250
|
+
|
|
1251
|
+
def test_require_trailer_cli_overrides_config(self, tmp_path):
|
|
1252
|
+
f = tmp_path / "msg"
|
|
1253
|
+
f.write_text("fix: add thing\n\nbody\n\nFixes: #99\nSigned-off-by: A <a@b.com>")
|
|
1254
|
+
argv = [
|
|
1255
|
+
"cg",
|
|
1256
|
+
"--message-file",
|
|
1257
|
+
str(f),
|
|
1258
|
+
"--disable",
|
|
1259
|
+
"signature,imperative",
|
|
1260
|
+
"--require-trailer",
|
|
1261
|
+
"Fixes",
|
|
1262
|
+
]
|
|
1263
|
+
with (
|
|
1264
|
+
patch("sys.argv", argv),
|
|
1265
|
+
patch(
|
|
1266
|
+
"git_commit_guard._load_config",
|
|
1267
|
+
return_value={"require-trailers": ["Closes"]},
|
|
1268
|
+
),
|
|
1269
|
+
):
|
|
1270
|
+
assert main() == 0
|
|
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
|