conventional-pre-commit 3.4.0__tar.gz → 3.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.pre-commit-config.yaml +5 -4
  2. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/PKG-INFO +28 -16
  3. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/README.md +27 -15
  4. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit/format.py +43 -10
  5. conventional_pre_commit-3.6.0/conventional_pre_commit/hook.py +80 -0
  6. conventional_pre_commit-3.6.0/conventional_pre_commit/output.py +111 -0
  7. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/PKG-INFO +28 -16
  8. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/SOURCES.txt +2 -0
  9. conventional_pre_commit-3.6.0/tests/messages/bad_commit +1 -0
  10. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/test_format.py +29 -1
  11. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/test_hook.py +64 -0
  12. conventional_pre_commit-3.6.0/tests/test_output.py +121 -0
  13. conventional_pre_commit-3.4.0/conventional_pre_commit/hook.py +0 -106
  14. conventional_pre_commit-3.4.0/tests/messages/bad_commit +0 -1
  15. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.devcontainer/Dockerfile +0 -0
  16. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.devcontainer/compose.yml +0 -0
  17. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.devcontainer/devcontainer.json +0 -0
  18. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.dockerignore +0 -0
  19. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.flake8 +0 -0
  20. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.github/dependabot.yaml +0 -0
  21. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.github/workflows/release.yml +10 -10
  22. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.github/workflows/tests.yml +0 -0
  23. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.gitignore +0 -0
  24. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.pre-commit-hooks.yaml +0 -0
  25. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/.vscode/settings.json +0 -0
  26. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/LICENSE +0 -0
  27. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit/__init__.py +0 -0
  28. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/dependency_links.txt +0 -0
  29. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/entry_points.txt +0 -0
  30. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/requires.txt +0 -0
  31. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/conventional_pre_commit.egg-info/top_level.txt +0 -0
  32. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/pyproject.toml +0 -0
  33. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/setup.cfg +0 -0
  34. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/__init__.py +0 -0
  35. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/conftest.py +0 -0
  36. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit +0 -0
  37. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_bad_multi_line +0 -0
  38. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_gbk +0 -0
  39. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_multi_line +0 -0
  40. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_utf-8 +0 -0
  41. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_with_multiple_scopes +0 -0
  42. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/conventional_commit_with_scope +0 -0
  43. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/custom_commit +0 -0
  44. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/messages/fixup_commit +0 -0
  45. {conventional_pre_commit-3.4.0 → conventional_pre_commit-3.6.0}/tests/run.sh +0 -0
@@ -8,7 +8,7 @@ default_install_hook_types:
8
8
 
9
9
  repos:
10
10
  - repo: https://github.com/pre-commit/pre-commit-hooks
11
- rev: v4.6.0
11
+ rev: v5.0.0
12
12
  hooks:
13
13
  - id: trailing-whitespace
14
14
  - id: mixed-line-ending
@@ -18,21 +18,21 @@ repos:
18
18
  - id: check-added-large-files
19
19
 
20
20
  - repo: https://github.com/psf/black
21
- rev: 24.4.2
21
+ rev: 24.10.0
22
22
  hooks:
23
23
  - id: black
24
24
  types:
25
25
  - python
26
26
 
27
27
  - repo: https://github.com/PyCQA/flake8
28
- rev: 7.1.0
28
+ rev: 7.1.1
29
29
  hooks:
30
30
  - id: flake8
31
31
  types:
32
32
  - python
33
33
 
34
34
  - repo: https://github.com/pycqa/bandit
35
- rev: 1.7.9
35
+ rev: 1.7.10
36
36
  hooks:
37
37
  - id: bandit
38
38
  args: ["-ll"]
@@ -51,3 +51,4 @@ repos:
51
51
  entry: conventional-pre-commit
52
52
  language: python
53
53
  stages: [commit-msg]
54
+ args: [--verbose]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conventional_pre_commit
3
- Version: 3.4.0
3
+ Version: 3.6.0
4
4
  Summary: A pre-commit hook that checks commit messages for Conventional Commits formatting.
5
5
  Author-email: Compiler LLC <dev@compiler.la>
6
6
  License: Apache License
@@ -269,33 +269,43 @@ Conventional Commit......................................................Failed
269
269
  - duration: 0.07s
270
270
  - exit code: 1
271
271
 
272
- [Bad Commit message] >> add a new feature
273
-
272
+ [Bad commit message] >> add a new feature
274
273
  Your commit message does not follow Conventional Commits formatting
275
274
  https://www.conventionalcommits.org/
275
+ ```
276
+
277
+ And with the `--verbose` arg:
276
278
 
277
- Conventional Commits start with one of the below types, followed by a colon,
278
- followed by the commit message:
279
+ ```console
280
+ $ git commit -m "add a new feature"
279
281
 
280
- build chore ci docs feat fix perf refactor revert style test
282
+ [INFO] Initializing environment for ....
283
+ Conventional Commit......................................................Failed
284
+ - hook id: conventional-pre-commit
285
+ - duration: 0.07s
286
+ - exit code: 1
281
287
 
282
- Example commit message adding a feature:
288
+ [Bad commit message] >> add a new feature
289
+ Your commit message does not follow Conventional Commits formatting
290
+ https://www.conventionalcommits.org/
283
291
 
284
- feat: implement new API
292
+ Conventional Commit messages follow a pattern like:
285
293
 
286
- Example commit message fixing an issue:
294
+ type(scope): subject
287
295
 
288
- fix: remove infinite loop
296
+ extended body
289
297
 
290
- Example commit with scope in parentheses after the type for more context:
298
+ Please correct the following errors:
291
299
 
292
- fix(account): remove infinite loop
300
+ - Expected value for 'type' but found none.
301
+ - Expected value for 'delim' but found none.
302
+ - Expected value for 'subject' but found none.
293
303
 
294
- Example commit with a body:
304
+ Run:
295
305
 
296
- fix: remove infinite loop
306
+ git commit --edit --file=.git/COMMIT_EDITMSG
297
307
 
298
- Additional information on the issue caused by the infinite loop
308
+ to edit the commit message and retry the commit.
299
309
  ```
300
310
 
301
311
  Make a (conventional) commit :heavy_check_mark::
@@ -352,7 +362,7 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
352
362
 
353
363
  ```shell
354
364
  $ conventional-pre-commit -h
355
- usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
365
+ usage: conventional-pre-commit [-h] [--no-color] [--force-scope] [--scopes SCOPES] [--strict] [--verbose] [types ...] input
356
366
 
357
367
  Check a git commit message for Conventional Commits formatting.
358
368
 
@@ -362,9 +372,11 @@ positional arguments:
362
372
 
363
373
  options:
364
374
  -h, --help show this help message and exit
375
+ --no-color Disable color in output.
365
376
  --force-scope Force commit to have scope defined.
366
377
  --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
367
378
  --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
379
+ --verbose Print more verbose error output.
368
380
  ```
369
381
 
370
382
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -46,33 +46,43 @@ Conventional Commit......................................................Failed
46
46
  - duration: 0.07s
47
47
  - exit code: 1
48
48
 
49
- [Bad Commit message] >> add a new feature
50
-
49
+ [Bad commit message] >> add a new feature
51
50
  Your commit message does not follow Conventional Commits formatting
52
51
  https://www.conventionalcommits.org/
52
+ ```
53
+
54
+ And with the `--verbose` arg:
53
55
 
54
- Conventional Commits start with one of the below types, followed by a colon,
55
- followed by the commit message:
56
+ ```console
57
+ $ git commit -m "add a new feature"
56
58
 
57
- build chore ci docs feat fix perf refactor revert style test
59
+ [INFO] Initializing environment for ....
60
+ Conventional Commit......................................................Failed
61
+ - hook id: conventional-pre-commit
62
+ - duration: 0.07s
63
+ - exit code: 1
58
64
 
59
- Example commit message adding a feature:
65
+ [Bad commit message] >> add a new feature
66
+ Your commit message does not follow Conventional Commits formatting
67
+ https://www.conventionalcommits.org/
60
68
 
61
- feat: implement new API
69
+ Conventional Commit messages follow a pattern like:
62
70
 
63
- Example commit message fixing an issue:
71
+ type(scope): subject
64
72
 
65
- fix: remove infinite loop
73
+ extended body
66
74
 
67
- Example commit with scope in parentheses after the type for more context:
75
+ Please correct the following errors:
68
76
 
69
- fix(account): remove infinite loop
77
+ - Expected value for 'type' but found none.
78
+ - Expected value for 'delim' but found none.
79
+ - Expected value for 'subject' but found none.
70
80
 
71
- Example commit with a body:
81
+ Run:
72
82
 
73
- fix: remove infinite loop
83
+ git commit --edit --file=.git/COMMIT_EDITMSG
74
84
 
75
- Additional information on the issue caused by the infinite loop
85
+ to edit the commit message and retry the commit.
76
86
  ```
77
87
 
78
88
  Make a (conventional) commit :heavy_check_mark::
@@ -129,7 +139,7 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
129
139
 
130
140
  ```shell
131
141
  $ conventional-pre-commit -h
132
- usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
142
+ usage: conventional-pre-commit [-h] [--no-color] [--force-scope] [--scopes SCOPES] [--strict] [--verbose] [types ...] input
133
143
 
134
144
  Check a git commit message for Conventional Commits formatting.
135
145
 
@@ -139,9 +149,11 @@ positional arguments:
139
149
 
140
150
  options:
141
151
  -h, --help show this help message and exit
152
+ --no-color Disable color in output.
142
153
  --force-scope Force commit to have scope defined.
143
154
  --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
144
155
  --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
156
+ --verbose Print more verbose error output.
145
157
  ```
146
158
 
147
159
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -39,7 +39,10 @@ def r_scope(optional=True, scopes: Optional[List[str]] = None):
39
39
 
40
40
  if scopes:
41
41
  scopes_pattern = _get_scope_pattern(scopes)
42
- return scopes_pattern
42
+ if optional:
43
+ return f"(?:{scopes_pattern})?"
44
+ else:
45
+ return scopes_pattern
43
46
 
44
47
  if optional:
45
48
  return r"(\([\w \/:,-]+\))?"
@@ -93,22 +96,52 @@ def conventional_types(types=[]):
93
96
  return types
94
97
 
95
98
 
96
- def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
99
+ def conventional_regex(types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
100
+ types = conventional_types(types)
101
+
102
+ types_pattern = f"^(?P<type>{r_types(types)})?"
103
+ scope_pattern = f"(?P<scope>{r_scope(optional_scope, scopes=scopes)})?"
104
+ delim_pattern = f"(?P<delim>{r_delim()})?"
105
+ subject_pattern = f"(?P<subject>{r_subject()})?"
106
+ body_pattern = f"(?P<body>{r_body()})?"
107
+ pattern = types_pattern + scope_pattern + delim_pattern + subject_pattern + body_pattern
108
+
109
+ return re.compile(pattern, re.MULTILINE)
110
+
111
+
112
+ def clean_input(input: str):
113
+ """
114
+ Prepares an input message for conventional commits format check.
115
+ """
116
+ input = strip_verbose_commit_ignored(input)
117
+ input = strip_comments(input)
118
+ return input
119
+
120
+
121
+ def conventional_match(input: str, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
122
+ """
123
+ Returns an `re.Match` object for the input against the Conventional Commits format.
124
+ """
125
+ input = clean_input(input)
126
+ regex = conventional_regex(types, optional_scope, scopes)
127
+ return regex.match(input)
128
+
129
+
130
+ def is_conventional(input: str, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None) -> bool:
97
131
  """
98
132
  Returns True if input matches Conventional Commits formatting
99
133
  https://www.conventionalcommits.org
100
134
 
101
135
  Optionally provide a list of additional custom types.
102
136
  """
103
- input = strip_verbose_commit_ignored(input)
104
- input = strip_comments(input)
105
- types = conventional_types(types)
106
- pattern = f"^({r_types(types)}){r_scope(optional_scope, scopes=scopes)}{r_delim()}{r_subject()}{r_body()}"
107
- regex = re.compile(pattern, re.MULTILINE)
108
-
109
- result = regex.match(input)
137
+ result = conventional_match(input, types, optional_scope, scopes)
110
138
  is_valid = bool(result)
111
- if is_valid and result.group("multi") and not result.group("sep"):
139
+
140
+ if result and result.group("multi") and not result.group("sep"):
141
+ is_valid = False
142
+ if result and not all(
143
+ [result.group("type"), optional_scope or result.group("scope"), result.group("delim"), result.group("subject")]
144
+ ):
112
145
  is_valid = False
113
146
 
114
147
  return is_valid
@@ -0,0 +1,80 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from conventional_pre_commit import format, output
5
+
6
+ RESULT_SUCCESS = 0
7
+ RESULT_FAIL = 1
8
+
9
+
10
+ def main(argv=[]):
11
+ parser = argparse.ArgumentParser(
12
+ prog="conventional-pre-commit", description="Check a git commit message for Conventional Commits formatting."
13
+ )
14
+ parser.add_argument("types", type=str, nargs="*", default=format.DEFAULT_TYPES, help="Optional list of types to support")
15
+ parser.add_argument("input", type=str, help="A file containing a git commit message")
16
+ parser.add_argument("--no-color", action="store_false", default=True, dest="color", help="Disable color in output.")
17
+ parser.add_argument(
18
+ "--force-scope", action="store_false", default=True, dest="optional_scope", help="Force commit to have scope defined."
19
+ )
20
+ parser.add_argument(
21
+ "--scopes",
22
+ type=str,
23
+ default=None,
24
+ help="Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)",
25
+ )
26
+ parser.add_argument(
27
+ "--strict",
28
+ action="store_true",
29
+ help="Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.",
30
+ )
31
+ parser.add_argument(
32
+ "--verbose",
33
+ action="store_true",
34
+ dest="verbose",
35
+ default=False,
36
+ help="Print more verbose error output.",
37
+ )
38
+
39
+ if len(argv) < 1:
40
+ argv = sys.argv[1:]
41
+
42
+ try:
43
+ args = parser.parse_args(argv)
44
+ except SystemExit:
45
+ return RESULT_FAIL
46
+
47
+ try:
48
+ with open(args.input, encoding="utf-8") as f:
49
+ commit_msg = f.read()
50
+ except UnicodeDecodeError:
51
+ print(output.unicode_decode_error(args.color))
52
+ return RESULT_FAIL
53
+ if args.scopes:
54
+ scopes = args.scopes.split(",")
55
+ else:
56
+ scopes = args.scopes
57
+
58
+ if not args.strict:
59
+ if format.has_autosquash_prefix(commit_msg):
60
+ return RESULT_SUCCESS
61
+
62
+ if format.is_conventional(commit_msg, args.types, args.optional_scope, scopes):
63
+ return RESULT_SUCCESS
64
+
65
+ print(output.fail(commit_msg, use_color=args.color))
66
+
67
+ if not args.verbose:
68
+ print(output.verbose_arg(use_color=args.color))
69
+ else:
70
+ print(
71
+ output.fail_verbose(
72
+ commit_msg, types=args.types, optional_scope=args.optional_scope, scopes=scopes, use_color=args.color
73
+ )
74
+ )
75
+
76
+ return RESULT_FAIL
77
+
78
+
79
+ if __name__ == "__main__":
80
+ raise SystemExit(main())
@@ -0,0 +1,111 @@
1
+ import os
2
+ from typing import List, Optional
3
+
4
+ from conventional_pre_commit import format
5
+
6
+
7
+ class Colors:
8
+ LBLUE = "\033[00;34m"
9
+ LRED = "\033[01;31m"
10
+ RESTORE = "\033[0m"
11
+ YELLOW = "\033[00;33m"
12
+
13
+ def __init__(self, enabled=True):
14
+ self.enabled = enabled
15
+
16
+ @property
17
+ def blue(self):
18
+ return self.LBLUE if self.enabled else ""
19
+
20
+ @property
21
+ def red(self):
22
+ return self.LRED if self.enabled else ""
23
+
24
+ @property
25
+ def restore(self):
26
+ return self.RESTORE if self.enabled else ""
27
+
28
+ @property
29
+ def yellow(self):
30
+ return self.YELLOW if self.enabled else ""
31
+
32
+
33
+ def fail(commit_msg, use_color=True):
34
+ c = Colors(use_color)
35
+ lines = [
36
+ f"{c.red}[Bad commit message] >>{c.restore} {commit_msg}"
37
+ f"{c.yellow}Your commit message does not follow Conventional Commits formatting{c.restore}",
38
+ f"{c.blue}https://www.conventionalcommits.org/{c.restore}",
39
+ ]
40
+ return os.linesep.join(lines)
41
+
42
+
43
+ def verbose_arg(use_color=True):
44
+ c = Colors(use_color)
45
+ lines = [
46
+ "",
47
+ f"{c.yellow}Use the {c.restore}--verbose{c.yellow} arg for more information{c.restore}",
48
+ ]
49
+ return os.linesep.join(lines)
50
+
51
+
52
+ def fail_verbose(
53
+ commit_msg: str, types=format.DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None, use_color=True
54
+ ):
55
+ c = Colors(use_color)
56
+ match = format.conventional_match(commit_msg, types, optional_scope, scopes)
57
+ lines = [
58
+ "",
59
+ f"{c.yellow}Conventional Commit messages follow a pattern like:",
60
+ "",
61
+ f"{c.restore} type(scope): subject",
62
+ "",
63
+ " extended body",
64
+ "",
65
+ ]
66
+
67
+ groups = match.groupdict() if match else {}
68
+
69
+ if optional_scope:
70
+ groups.pop("scope", None)
71
+
72
+ if not groups.get("body"):
73
+ groups.pop("body", None)
74
+ groups.pop("multi", None)
75
+ groups.pop("sep", None)
76
+
77
+ if groups.keys():
78
+ lines.append(f"{c.yellow}Please correct the following errors:{c.restore}")
79
+ lines.append("")
80
+ for group in [g for g, v in groups.items() if not v]:
81
+ if group == "scope":
82
+ if scopes:
83
+ scopt_opts = f"{c.yellow},{c.restore}".join(scopes)
84
+ lines.append(f"{c.yellow} - Expected value for {c.restore}scope{c.yellow} from: {c.restore}{scopt_opts}")
85
+ else:
86
+ lines.append(f"{c.yellow} - Expected value for {c.restore}scope{c.yellow} but found none.{c.restore}")
87
+ else:
88
+ lines.append(f"{c.yellow} - Expected value for {c.restore}{group}{c.yellow} but found none.{c.restore}")
89
+
90
+ lines.extend(
91
+ [
92
+ "",
93
+ f"{c.yellow}Run:{c.restore}",
94
+ "",
95
+ " git commit --edit --file=.git/COMMIT_EDITMSG",
96
+ "",
97
+ f"{c.yellow}to edit the commit message and retry the commit.{c.restore}",
98
+ ]
99
+ )
100
+ return os.linesep.join(lines)
101
+
102
+
103
+ def unicode_decode_error(use_color=True):
104
+ c = Colors(use_color)
105
+ return f"""
106
+ {c.red}[Bad commit message encoding]{c.restore}
107
+
108
+ {c.yellow}conventional-pre-commit couldn't decode your commit message.
109
+ UTF-8 encoding is assumed, please configure git to write commit messages in UTF-8.
110
+ See {c.blue}https://git-scm.com/docs/git-commit/#_discussion{c.yellow} for more.{c.restore}
111
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conventional_pre_commit
3
- Version: 3.4.0
3
+ Version: 3.6.0
4
4
  Summary: A pre-commit hook that checks commit messages for Conventional Commits formatting.
5
5
  Author-email: Compiler LLC <dev@compiler.la>
6
6
  License: Apache License
@@ -269,33 +269,43 @@ Conventional Commit......................................................Failed
269
269
  - duration: 0.07s
270
270
  - exit code: 1
271
271
 
272
- [Bad Commit message] >> add a new feature
273
-
272
+ [Bad commit message] >> add a new feature
274
273
  Your commit message does not follow Conventional Commits formatting
275
274
  https://www.conventionalcommits.org/
275
+ ```
276
+
277
+ And with the `--verbose` arg:
276
278
 
277
- Conventional Commits start with one of the below types, followed by a colon,
278
- followed by the commit message:
279
+ ```console
280
+ $ git commit -m "add a new feature"
279
281
 
280
- build chore ci docs feat fix perf refactor revert style test
282
+ [INFO] Initializing environment for ....
283
+ Conventional Commit......................................................Failed
284
+ - hook id: conventional-pre-commit
285
+ - duration: 0.07s
286
+ - exit code: 1
281
287
 
282
- Example commit message adding a feature:
288
+ [Bad commit message] >> add a new feature
289
+ Your commit message does not follow Conventional Commits formatting
290
+ https://www.conventionalcommits.org/
283
291
 
284
- feat: implement new API
292
+ Conventional Commit messages follow a pattern like:
285
293
 
286
- Example commit message fixing an issue:
294
+ type(scope): subject
287
295
 
288
- fix: remove infinite loop
296
+ extended body
289
297
 
290
- Example commit with scope in parentheses after the type for more context:
298
+ Please correct the following errors:
291
299
 
292
- fix(account): remove infinite loop
300
+ - Expected value for 'type' but found none.
301
+ - Expected value for 'delim' but found none.
302
+ - Expected value for 'subject' but found none.
293
303
 
294
- Example commit with a body:
304
+ Run:
295
305
 
296
- fix: remove infinite loop
306
+ git commit --edit --file=.git/COMMIT_EDITMSG
297
307
 
298
- Additional information on the issue caused by the infinite loop
308
+ to edit the commit message and retry the commit.
299
309
  ```
300
310
 
301
311
  Make a (conventional) commit :heavy_check_mark::
@@ -352,7 +362,7 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
352
362
 
353
363
  ```shell
354
364
  $ conventional-pre-commit -h
355
- usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
365
+ usage: conventional-pre-commit [-h] [--no-color] [--force-scope] [--scopes SCOPES] [--strict] [--verbose] [types ...] input
356
366
 
357
367
  Check a git commit message for Conventional Commits formatting.
358
368
 
@@ -362,9 +372,11 @@ positional arguments:
362
372
 
363
373
  options:
364
374
  -h, --help show this help message and exit
375
+ --no-color Disable color in output.
365
376
  --force-scope Force commit to have scope defined.
366
377
  --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
367
378
  --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
379
+ --verbose Print more verbose error output.
368
380
  ```
369
381
 
370
382
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -16,6 +16,7 @@ pyproject.toml
16
16
  conventional_pre_commit/__init__.py
17
17
  conventional_pre_commit/format.py
18
18
  conventional_pre_commit/hook.py
19
+ conventional_pre_commit/output.py
19
20
  conventional_pre_commit.egg-info/PKG-INFO
20
21
  conventional_pre_commit.egg-info/SOURCES.txt
21
22
  conventional_pre_commit.egg-info/dependency_links.txt
@@ -27,6 +28,7 @@ tests/conftest.py
27
28
  tests/run.sh
28
29
  tests/test_format.py
29
30
  tests/test_hook.py
31
+ tests/test_output.py
30
32
  tests/messages/bad_commit
31
33
  tests/messages/conventional_commit
32
34
  tests/messages/conventional_commit_bad_multi_line
@@ -0,0 +1 @@
1
+ bad message
@@ -66,7 +66,7 @@ def test_r_scope__special_chars():
66
66
 
67
67
  def test_r_scope__scopes():
68
68
  scopes_input = ["api", "client"]
69
- result = format.r_scope(scopes=scopes_input)
69
+ result = format.r_scope(scopes=scopes_input, optional=False)
70
70
  regex = re.compile(result)
71
71
  assert regex.match("(api)")
72
72
  assert regex.match("(client)")
@@ -436,6 +436,34 @@ index fe8a527..1c00c14 100644
436
436
  assert result == expected
437
437
 
438
438
 
439
+ def test_conventional_regex():
440
+ regex = format.conventional_regex()
441
+
442
+ assert isinstance(regex, re.Pattern)
443
+ assert "type" in regex.groupindex
444
+ assert "scope" in regex.groupindex
445
+ assert "delim" in regex.groupindex
446
+ assert "subject" in regex.groupindex
447
+ assert "body" in regex.groupindex
448
+ assert "multi" in regex.groupindex
449
+ assert "sep" in regex.groupindex
450
+
451
+
452
+ def test_conventional_match():
453
+ match = format.conventional_match(
454
+ """test(scope): subject line
455
+
456
+ body copy
457
+ """
458
+ )
459
+ assert match
460
+ assert match.group("type") == "test"
461
+ assert match.group("scope") == "(scope)"
462
+ assert match.group("delim") == ":"
463
+ assert match.group("subject").strip() == "subject line"
464
+ assert match.group("body").strip() == "body copy"
465
+
466
+
439
467
  @pytest.mark.parametrize("type", format.DEFAULT_TYPES)
440
468
  def test_is_conventional__default_type(type):
441
469
  input = f"{type}: message"
@@ -1,8 +1,10 @@
1
+ import os
1
2
  import subprocess
2
3
 
3
4
  import pytest
4
5
 
5
6
  from conventional_pre_commit.hook import RESULT_FAIL, RESULT_SUCCESS, main
7
+ from conventional_pre_commit.output import Colors
6
8
 
7
9
 
8
10
  @pytest.fixture
@@ -94,6 +96,38 @@ def test_main_fail__conventional_commit_bad_multi_line(conventional_commit_bad_m
94
96
  assert result == RESULT_FAIL
95
97
 
96
98
 
99
+ def test_main_fail__verbose(bad_commit_path, capsys):
100
+ result = main(["--verbose", "--force-scope", bad_commit_path])
101
+
102
+ assert result == RESULT_FAIL
103
+
104
+ captured = capsys.readouterr()
105
+ output = captured.out
106
+
107
+ assert Colors.LBLUE in output
108
+ assert Colors.LRED in output
109
+ assert Colors.RESTORE in output
110
+ assert Colors.YELLOW in output
111
+ assert "Conventional Commit messages follow a pattern like" in output
112
+ assert f"type(scope): subject{os.linesep}{os.linesep} extended body" in output
113
+ assert "git commit --edit --file=.git/COMMIT_EDITMSG" in output
114
+ assert "edit the commit message and retry the commit" in output
115
+
116
+
117
+ def test_main_fail__no_color(bad_commit_path, capsys):
118
+ result = main(["--verbose", "--no-color", bad_commit_path])
119
+
120
+ assert result == RESULT_FAIL
121
+
122
+ captured = capsys.readouterr()
123
+ output = captured.out
124
+
125
+ assert Colors.LBLUE not in output
126
+ assert Colors.LRED not in output
127
+ assert Colors.RESTORE not in output
128
+ assert Colors.YELLOW not in output
129
+
130
+
97
131
  def test_subprocess_fail__missing_args(cmd):
98
132
  result = subprocess.call(cmd)
99
133
 
@@ -152,6 +186,36 @@ def test_subprocess_fail__conventional_with_multiple_scopes(cmd, conventional_co
152
186
  assert result == RESULT_FAIL
153
187
 
154
188
 
189
+ def test_main_success__custom_scopes_optional_scope(conventional_commit_path):
190
+ result = main(["--scopes", "api,client", conventional_commit_path])
191
+ assert result == RESULT_SUCCESS
192
+
193
+
194
+ def test_main_success__custom_scopes_with_allowed_scope(conventional_commit_with_multiple_scopes_path):
195
+ result = main(["--scopes", "chore,api,client", conventional_commit_with_multiple_scopes_path])
196
+ assert result == RESULT_SUCCESS
197
+
198
+
199
+ def test_main_fail__custom_scopes_with_disallowed_scope(conventional_commit_with_scope_path):
200
+ result = main(["--scopes", "api,client", conventional_commit_with_scope_path])
201
+ assert result == RESULT_FAIL
202
+
203
+
204
+ def test_main_fail__custom_scopes_require_scope_no_scope(conventional_commit_path):
205
+ result = main(["--scopes", "chore,feat,fix,custom", "--force-scope", conventional_commit_path])
206
+ assert result == RESULT_FAIL
207
+
208
+
209
+ def test_main_success__custom_scopes_require_scope_with_allowed_scope(conventional_commit_with_scope_path):
210
+ result = main(["--scopes", "api,client,scope", "--force-scope", conventional_commit_with_scope_path])
211
+ assert result == RESULT_SUCCESS
212
+
213
+
214
+ def test_main_fail__custom_scopes_require_scope_with_disallowed_scope(conventional_commit_with_scope_path):
215
+ result = main(["--scopes", "api,client", "--force-scope", conventional_commit_with_scope_path])
216
+ assert result == RESULT_FAIL
217
+
218
+
155
219
  def test_subprocess_success__fixup_commit(cmd, fixup_commit_path):
156
220
  result = subprocess.call((cmd, fixup_commit_path))
157
221
 
@@ -0,0 +1,121 @@
1
+ import os
2
+ from conventional_pre_commit.output import Colors, fail, fail_verbose, unicode_decode_error
3
+
4
+
5
+ def test_colors():
6
+ colors = Colors()
7
+
8
+ assert colors.blue == colors.LBLUE
9
+ assert colors.red == colors.LRED
10
+ assert colors.restore == colors.RESTORE
11
+ assert colors.yellow == colors.YELLOW
12
+
13
+ colors = Colors(enabled=False)
14
+
15
+ assert colors.blue == ""
16
+ assert colors.red == ""
17
+ assert colors.restore == ""
18
+ assert colors.yellow == ""
19
+
20
+
21
+ def test_fail():
22
+ output = fail("commit msg")
23
+
24
+ assert Colors.LRED in output
25
+ assert Colors.YELLOW in output
26
+ assert Colors.LBLUE in output
27
+ assert Colors.RESTORE in output
28
+
29
+ assert "Bad commit message" in output
30
+ assert "commit msg" in output
31
+ assert "Conventional Commits formatting" in output
32
+ assert "https://www.conventionalcommits.org/" in output
33
+
34
+
35
+ def test_fail__no_color():
36
+ output = fail("commit msg", use_color=False)
37
+
38
+ assert Colors.LRED not in output
39
+ assert Colors.YELLOW not in output
40
+ assert Colors.LBLUE not in output
41
+ assert Colors.RESTORE not in output
42
+
43
+
44
+ def test_fail_verbose():
45
+ output = fail_verbose("commit msg", optional_scope=False)
46
+
47
+ assert Colors.YELLOW in output
48
+ assert Colors.RESTORE in output
49
+
50
+ output = output.replace(Colors.YELLOW, Colors.RESTORE).replace(Colors.RESTORE, "")
51
+
52
+ assert "Conventional Commit messages follow a pattern like" in output
53
+ assert f"type(scope): subject{os.linesep}{os.linesep} extended body" in output
54
+ assert "Expected value for type but found none." in output
55
+ assert "Expected value for delim but found none." in output
56
+ assert "Expected value for scope but found none." in output
57
+ assert "Expected value for subject but found none." in output
58
+ assert "git commit --edit --file=.git/COMMIT_EDITMSG" in output
59
+ assert "edit the commit message and retry the commit" in output
60
+
61
+
62
+ def test_fail_verbose__no_color():
63
+ output = fail_verbose("commit msg", use_color=False)
64
+
65
+ assert Colors.LRED not in output
66
+ assert Colors.YELLOW not in output
67
+ assert Colors.LBLUE not in output
68
+ assert Colors.RESTORE not in output
69
+
70
+
71
+ def test_fail_verbose__optional_scope():
72
+ output = fail_verbose("commit msg", optional_scope=True, use_color=False)
73
+
74
+ assert "Expected value for scope but found none." not in output
75
+
76
+
77
+ def test_fail_verbose__missing_subject():
78
+ output = fail_verbose("feat(scope):", optional_scope=False, use_color=False)
79
+
80
+ assert "Expected value for subject but found none." in output
81
+ assert "Expected value for type but found none." not in output
82
+ assert "Expected value for scope but found none." not in output
83
+
84
+
85
+ def test_fail_verbose__no_body_sep():
86
+ output = fail_verbose(
87
+ """feat(scope): subject
88
+ body without blank line
89
+ """,
90
+ optional_scope=False,
91
+ use_color=False,
92
+ )
93
+
94
+ assert "Expected value for sep but found none." in output
95
+ assert "Expected value for multi but found none." not in output
96
+
97
+ assert "Expected value for subject but found none." not in output
98
+ assert "Expected value for type but found none." not in output
99
+ assert "Expected value for scope but found none." not in output
100
+
101
+
102
+ def test_unicode_decode_error():
103
+ output = unicode_decode_error()
104
+
105
+ assert Colors.LRED in output
106
+ assert Colors.YELLOW in output
107
+ assert Colors.LBLUE in output
108
+ assert Colors.RESTORE in output
109
+
110
+ assert "Bad commit message encoding" in output
111
+ assert "UTF-8 encoding is assumed" in output
112
+ assert "https://git-scm.com/docs/git-commit/#_discussion" in output
113
+
114
+
115
+ def test_unicode_decode_error__no_color():
116
+ output = unicode_decode_error(use_color=False)
117
+
118
+ assert Colors.LRED not in output
119
+ assert Colors.YELLOW not in output
120
+ assert Colors.LBLUE not in output
121
+ assert Colors.RESTORE not in output
@@ -1,106 +0,0 @@
1
- import argparse
2
- import sys
3
-
4
- from conventional_pre_commit import format
5
-
6
- RESULT_SUCCESS = 0
7
- RESULT_FAIL = 1
8
-
9
-
10
- class Colors:
11
- LBLUE = "\033[00;34m"
12
- LRED = "\033[01;31m"
13
- RESTORE = "\033[0m"
14
- YELLOW = "\033[00;33m"
15
-
16
-
17
- def main(argv=[]):
18
- parser = argparse.ArgumentParser(
19
- prog="conventional-pre-commit", description="Check a git commit message for Conventional Commits formatting."
20
- )
21
- parser.add_argument("types", type=str, nargs="*", default=format.DEFAULT_TYPES, help="Optional list of types to support")
22
- parser.add_argument("input", type=str, help="A file containing a git commit message")
23
- parser.add_argument(
24
- "--force-scope", action="store_false", default=True, dest="optional_scope", help="Force commit to have scope defined."
25
- )
26
- parser.add_argument(
27
- "--scopes",
28
- type=str,
29
- default=None,
30
- help="Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)",
31
- )
32
- parser.add_argument(
33
- "--strict",
34
- action="store_true",
35
- help="Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.",
36
- )
37
-
38
- if len(argv) < 1:
39
- argv = sys.argv[1:]
40
-
41
- try:
42
- args = parser.parse_args(argv)
43
- except SystemExit:
44
- return RESULT_FAIL
45
-
46
- try:
47
- with open(args.input, encoding="utf-8") as f:
48
- message = f.read()
49
- except UnicodeDecodeError:
50
- print(
51
- f"""
52
- {Colors.LRED}[Bad Commit message encoding] {Colors.RESTORE}
53
-
54
- {Colors.YELLOW}conventional-pre-commit couldn't decode your commit message.{Colors.RESTORE}
55
- {Colors.YELLOW}UTF-8{Colors.RESTORE} encoding is assumed, please configure git to write commit messages in UTF-8.
56
- See {Colors.LBLUE}https://git-scm.com/docs/git-commit/#_discussion{Colors.RESTORE} for more.
57
- """
58
- )
59
- return RESULT_FAIL
60
- if args.scopes:
61
- scopes = args.scopes.split(",")
62
- else:
63
- scopes = args.scopes
64
-
65
- if not args.strict:
66
- if format.has_autosquash_prefix(message):
67
- return RESULT_SUCCESS
68
-
69
- if format.is_conventional(message, args.types, args.optional_scope, scopes):
70
- return RESULT_SUCCESS
71
- else:
72
- print(
73
- f"""
74
- {Colors.LRED}[Bad Commit message] >>{Colors.RESTORE} {message}
75
- {Colors.YELLOW}Your commit message does not follow Conventional Commits formatting
76
- {Colors.LBLUE}https://www.conventionalcommits.org/{Colors.YELLOW}
77
-
78
- Conventional Commits start with one of the below types, followed by a colon,
79
- followed by the commit subject and an optional body seperated by a blank line:{Colors.RESTORE}
80
-
81
- {" ".join(format.conventional_types(args.types))}
82
-
83
- {Colors.YELLOW}Example commit message adding a feature:{Colors.RESTORE}
84
-
85
- feat: implement new API
86
-
87
- {Colors.YELLOW}Example commit message fixing an issue:{Colors.RESTORE}
88
-
89
- fix: remove infinite loop
90
-
91
- {Colors.YELLOW}Example commit with scope in parentheses after the type for more context:{Colors.RESTORE}
92
-
93
- fix(account): remove infinite loop
94
-
95
- {Colors.YELLOW}Example commit with a body:{Colors.RESTORE}
96
-
97
- fix: remove infinite loop
98
-
99
- Additional information on the issue caused by the infinite loop
100
- """
101
- )
102
- return RESULT_FAIL
103
-
104
-
105
- if __name__ == "__main__":
106
- raise SystemExit(main())
@@ -1 +0,0 @@
1
- bad: message
@@ -36,16 +36,6 @@ jobs:
36
36
  - name: Build package
37
37
  run: python -m build
38
38
 
39
- - name: Release
40
- id: release
41
- uses: softprops/action-gh-release@v2
42
- with:
43
- files: |
44
- ./dist/*.whl
45
- ./dist/*.tar.gz
46
- prerelease: ${{ contains(github.ref, '-pre') }}
47
- generate_release_notes: ${{ !contains(github.ref, '-pre') }}
48
-
49
39
  - name: Publish to Test PyPI
50
40
  uses: pypa/gh-action-pypi-publish@release/v1
51
41
  if: ${{ contains(github.ref, '-pre') }}
@@ -60,3 +50,13 @@ jobs:
60
50
  if: ${{ !contains(github.ref, '-pre') }}
61
51
  with:
62
52
  print-hash: true
53
+
54
+ - name: Release
55
+ id: release
56
+ uses: softprops/action-gh-release@v2
57
+ with:
58
+ files: |
59
+ ./dist/*.whl
60
+ ./dist/*.tar.gz
61
+ prerelease: ${{ contains(github.ref, '-pre') }}
62
+ generate_release_notes: ${{ !contains(github.ref, '-pre') }}