conventional-pre-commit 3.2.0__tar.gz → 3.4.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 (42) hide show
  1. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.pre-commit-config.yaml +4 -4
  2. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/PKG-INFO +8 -7
  3. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/README.md +7 -6
  4. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit/format.py +23 -9
  5. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit/hook.py +11 -1
  6. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/PKG-INFO +8 -7
  7. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/SOURCES.txt +1 -0
  8. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/conftest.py +5 -0
  9. conventional_pre_commit-3.4.0/tests/messages/conventional_commit_with_multiple_scopes +1 -0
  10. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/run.sh +1 -1
  11. conventional_pre_commit-3.4.0/tests/test_format.py +586 -0
  12. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/test_hook.py +10 -0
  13. conventional_pre_commit-3.2.0/tests/test_format.py +0 -368
  14. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.devcontainer/Dockerfile +0 -0
  15. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.devcontainer/compose.yml +0 -0
  16. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.devcontainer/devcontainer.json +0 -0
  17. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.dockerignore +0 -0
  18. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.flake8 +0 -0
  19. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.github/dependabot.yaml +0 -0
  20. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.github/workflows/release.yml +0 -0
  21. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.github/workflows/tests.yml +0 -0
  22. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.gitignore +0 -0
  23. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.pre-commit-hooks.yaml +0 -0
  24. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/.vscode/settings.json +0 -0
  25. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/LICENSE +0 -0
  26. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit/__init__.py +0 -0
  27. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/dependency_links.txt +0 -0
  28. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/entry_points.txt +0 -0
  29. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/requires.txt +0 -0
  30. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/conventional_pre_commit.egg-info/top_level.txt +0 -0
  31. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/pyproject.toml +0 -0
  32. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/setup.cfg +0 -0
  33. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/__init__.py +0 -0
  34. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/bad_commit +0 -0
  35. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit +0 -0
  36. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit_bad_multi_line +0 -0
  37. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit_gbk +0 -0
  38. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit_multi_line +0 -0
  39. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit_utf-8 +0 -0
  40. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/conventional_commit_with_scope +0 -0
  41. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/custom_commit +0 -0
  42. {conventional_pre_commit-3.2.0 → conventional_pre_commit-3.4.0}/tests/messages/fixup_commit +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.5.0
11
+ rev: v4.6.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.3.0
21
+ rev: 24.4.2
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.0.0
28
+ rev: 7.1.0
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.8
35
+ rev: 1.7.9
36
36
  hooks:
37
37
  - id: bandit
38
38
  args: ["-ll"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conventional_pre_commit
3
- Version: 3.2.0
3
+ Version: 3.4.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
@@ -352,18 +352,19 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
352
352
 
353
353
  ```shell
354
354
  $ conventional-pre-commit -h
355
- usage: conventional-pre-commit [-h] [--force-scope] [--strict] [types ...] input
355
+ usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
356
356
 
357
357
  Check a git commit message for Conventional Commits formatting.
358
358
 
359
359
  positional arguments:
360
- types Optional list of types to support
361
- input A file containing a git commit message
360
+ types Optional list of types to support
361
+ input A file containing a git commit message
362
362
 
363
363
  options:
364
- -h, --help show this help message and exit
365
- --force-scope Force commit to have scope defined.
366
- --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
364
+ -h, --help show this help message and exit
365
+ --force-scope Force commit to have scope defined.
366
+ --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
367
+ --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
367
368
  ```
368
369
 
369
370
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -129,18 +129,19 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
129
129
 
130
130
  ```shell
131
131
  $ conventional-pre-commit -h
132
- usage: conventional-pre-commit [-h] [--force-scope] [--strict] [types ...] input
132
+ usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
133
133
 
134
134
  Check a git commit message for Conventional Commits formatting.
135
135
 
136
136
  positional arguments:
137
- types Optional list of types to support
138
- input A file containing a git commit message
137
+ types Optional list of types to support
138
+ input A file containing a git commit message
139
139
 
140
140
  options:
141
- -h, --help show this help message and exit
142
- --force-scope Force commit to have scope defined.
143
- --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
141
+ -h, --help show this help message and exit
142
+ --force-scope Force commit to have scope defined.
143
+ --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
144
+ --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
144
145
  ```
145
146
 
146
147
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -1,4 +1,5 @@
1
1
  import re
2
+ from typing import List, Optional
2
3
 
3
4
  CONVENTIONAL_TYPES = ["feat", "fix"]
4
5
  DEFAULT_TYPES = [
@@ -26,8 +27,20 @@ def r_types(types):
26
27
  return "|".join(types)
27
28
 
28
29
 
29
- def r_scope(optional=True):
30
+ def _get_scope_pattern(scopes: Optional[List[str]] = None):
31
+ scopes_str = r_types(scopes)
32
+ escaped_delimiters = list(map(re.escape, [":", ",", "-", "/"])) # type: ignore
33
+ delimiters_pattern = r_types(escaped_delimiters)
34
+ return rf"\(\s*(?:{scopes_str})(?:\s*(?:{delimiters_pattern})\s*(?:{scopes_str}))*\s*\)"
35
+
36
+
37
+ def r_scope(optional=True, scopes: Optional[List[str]] = None):
30
38
  """Regex str for an optional (scope)."""
39
+
40
+ if scopes:
41
+ scopes_pattern = _get_scope_pattern(scopes)
42
+ return scopes_pattern
43
+
31
44
  if optional:
32
45
  return r"(\([\w \/:,-]+\))?"
33
46
  else:
@@ -54,13 +67,14 @@ def r_autosquash_prefixes():
54
67
  return "|".join(AUTOSQUASH_PREFIXES)
55
68
 
56
69
 
57
- def r_verbose_diff():
58
- """Regex str for verbose diff"""
59
- return r"(?P<diff>(^# -* >8 -*$\r?\n)(^# .*$\r?\n)+(diff ){1}(.*\r?\n)*)"
70
+ def r_verbose_commit_ignored():
71
+ """Regex str for the ignored part of verbose commit message templates"""
72
+ return r"^# -{24} >8 -{24}\r?\n.*\Z"
60
73
 
61
74
 
62
- def strip_verbose_diff(input):
63
- return re.sub(r_verbose_diff(), "", input, flags=re.MULTILINE)
75
+ def strip_verbose_commit_ignored(input):
76
+ """Strip the ignored part of verbose commit message templates."""
77
+ return re.sub(r_verbose_commit_ignored(), "", input, flags=re.DOTALL | re.MULTILINE)
64
78
 
65
79
 
66
80
  def r_comment():
@@ -79,17 +93,17 @@ def conventional_types(types=[]):
79
93
  return types
80
94
 
81
95
 
82
- def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
96
+ def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
83
97
  """
84
98
  Returns True if input matches Conventional Commits formatting
85
99
  https://www.conventionalcommits.org
86
100
 
87
101
  Optionally provide a list of additional custom types.
88
102
  """
89
- input = strip_verbose_diff(input)
103
+ input = strip_verbose_commit_ignored(input)
90
104
  input = strip_comments(input)
91
105
  types = conventional_types(types)
92
- pattern = f"^({r_types(types)}){r_scope(optional_scope)}{r_delim()}{r_subject()}{r_body()}"
106
+ pattern = f"^({r_types(types)}){r_scope(optional_scope, scopes=scopes)}{r_delim()}{r_subject()}{r_body()}"
93
107
  regex = re.compile(pattern, re.MULTILINE)
94
108
 
95
109
  result = regex.match(input)
@@ -23,6 +23,12 @@ def main(argv=[]):
23
23
  parser.add_argument(
24
24
  "--force-scope", action="store_false", default=True, dest="optional_scope", help="Force commit to have scope defined."
25
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
+ )
26
32
  parser.add_argument(
27
33
  "--strict",
28
34
  action="store_true",
@@ -51,12 +57,16 @@ See {Colors.LBLUE}https://git-scm.com/docs/git-commit/#_discussion{Colors.RESTOR
51
57
  """
52
58
  )
53
59
  return RESULT_FAIL
60
+ if args.scopes:
61
+ scopes = args.scopes.split(",")
62
+ else:
63
+ scopes = args.scopes
54
64
 
55
65
  if not args.strict:
56
66
  if format.has_autosquash_prefix(message):
57
67
  return RESULT_SUCCESS
58
68
 
59
- if format.is_conventional(message, args.types, args.optional_scope):
69
+ if format.is_conventional(message, args.types, args.optional_scope, scopes):
60
70
  return RESULT_SUCCESS
61
71
  else:
62
72
  print(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conventional_pre_commit
3
- Version: 3.2.0
3
+ Version: 3.4.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
@@ -352,18 +352,19 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
352
352
 
353
353
  ```shell
354
354
  $ conventional-pre-commit -h
355
- usage: conventional-pre-commit [-h] [--force-scope] [--strict] [types ...] input
355
+ usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
356
356
 
357
357
  Check a git commit message for Conventional Commits formatting.
358
358
 
359
359
  positional arguments:
360
- types Optional list of types to support
361
- input A file containing a git commit message
360
+ types Optional list of types to support
361
+ input A file containing a git commit message
362
362
 
363
363
  options:
364
- -h, --help show this help message and exit
365
- --force-scope Force commit to have scope defined.
366
- --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
364
+ -h, --help show this help message and exit
365
+ --force-scope Force commit to have scope defined.
366
+ --scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
367
+ --strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
367
368
  ```
368
369
 
369
370
  Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
@@ -33,6 +33,7 @@ tests/messages/conventional_commit_bad_multi_line
33
33
  tests/messages/conventional_commit_gbk
34
34
  tests/messages/conventional_commit_multi_line
35
35
  tests/messages/conventional_commit_utf-8
36
+ tests/messages/conventional_commit_with_multiple_scopes
36
37
  tests/messages/conventional_commit_with_scope
37
38
  tests/messages/custom_commit
38
39
  tests/messages/fixup_commit
@@ -52,3 +52,8 @@ def conventional_commit_bad_multi_line_path():
52
52
  @pytest.fixture
53
53
  def conventional_commit_multi_line_path():
54
54
  return get_message_path("conventional_commit_multi_line")
55
+
56
+
57
+ @pytest.fixture
58
+ def conventional_commit_with_multiple_scopes_path():
59
+ return get_message_path("conventional_commit_with_multiple_scopes")
@@ -0,0 +1 @@
1
+ feat(api,client): added new endpoint with client support
@@ -7,5 +7,5 @@ coverage run -m pytest
7
7
  # clean out old coverage results
8
8
  rm -rf ./tests/coverage
9
9
 
10
- # regenerate coverate report
10
+ # regenerate coverage report
11
11
  coverage html --directory ./tests/coverage
@@ -0,0 +1,586 @@
1
+ import re
2
+
3
+ import pytest
4
+
5
+ from conventional_pre_commit import format
6
+
7
+ CUSTOM_TYPES = ["one", "two"]
8
+
9
+
10
+ def test_r_types():
11
+ result = format.r_types(CUSTOM_TYPES)
12
+ regex = re.compile(result)
13
+
14
+ assert regex.match("one")
15
+ assert regex.match("two")
16
+
17
+
18
+ def test_r_scope__optional():
19
+ result = format.r_scope()
20
+ regex = re.compile(result)
21
+
22
+ assert regex.match("")
23
+
24
+
25
+ def test_r_scope__not_optional():
26
+ result = format.r_scope(optional=False)
27
+ regex = re.compile(result)
28
+
29
+ # Assert not optional anymore
30
+ assert not regex.match("")
31
+
32
+
33
+ def test_r_scope__parenthesis_required():
34
+ result = format.r_scope()
35
+ regex = re.compile(result)
36
+
37
+ # without parens produces a match object with a 0 span
38
+ # since the (scope) is optional
39
+ without_parens = regex.match("something")
40
+ assert without_parens.span() == (0, 0)
41
+
42
+ # with parens produces a match object with a span
43
+ # that covers the input string
44
+ with_parens = regex.match("(something)")
45
+ assert with_parens.span() == (0, 11)
46
+
47
+
48
+ def test_r_scope__alphanumeric():
49
+ result = format.r_scope()
50
+ regex = re.compile(result)
51
+
52
+ assert regex.match("(50m3t41N6)")
53
+
54
+
55
+ def test_r_scope__special_chars():
56
+ result = format.r_scope()
57
+ regex = re.compile(result)
58
+
59
+ assert regex.match("(some-thing)")
60
+ assert regex.match("(some_thing)")
61
+ assert regex.match("(some/thing)")
62
+ assert regex.match("(some thing)")
63
+ assert regex.match("(some:thing)")
64
+ assert regex.match("(some,thing)")
65
+
66
+
67
+ def test_r_scope__scopes():
68
+ scopes_input = ["api", "client"]
69
+ result = format.r_scope(scopes=scopes_input)
70
+ regex = re.compile(result)
71
+ assert regex.match("(api)")
72
+ assert regex.match("(client)")
73
+ assert regex.match("(api, client)")
74
+ assert regex.match("(api: client)")
75
+ assert regex.match("(api/client)")
76
+ assert regex.match("(api-client)")
77
+ assert not regex.match("(test)")
78
+ assert not regex.match("(api; client)")
79
+
80
+
81
+ def test_r_delim():
82
+ result = format.r_delim()
83
+ regex = re.compile(result)
84
+
85
+ assert regex.match(":")
86
+
87
+
88
+ def test_r_delim__optional_breaking_indicator():
89
+ result = format.r_delim()
90
+ regex = re.compile(result)
91
+
92
+ assert regex.match("!:")
93
+
94
+
95
+ def test_r_subject__starts_with_space():
96
+ result = format.r_subject()
97
+ regex = re.compile(result)
98
+
99
+ assert not regex.match("something")
100
+ assert regex.match(" something")
101
+
102
+
103
+ def test_r_subject__alphanumeric():
104
+ result = format.r_subject()
105
+ regex = re.compile(result)
106
+
107
+ assert regex.match(" 50m3t41N6")
108
+
109
+
110
+ def test_r_subject__special_chars():
111
+ result = format.r_subject()
112
+ regex = re.compile(result)
113
+
114
+ assert regex.match(" some-thing")
115
+ assert regex.match(" some_thing")
116
+ assert regex.match(" some/thing")
117
+ assert regex.match(" some thing")
118
+
119
+
120
+ def test_r_autosquash_prefixes():
121
+ result = format.r_autosquash_prefixes()
122
+ regex = re.compile(result)
123
+
124
+ for prefix in format.AUTOSQUASH_PREFIXES:
125
+ assert regex.match(prefix)
126
+
127
+
128
+ def test_conventional_types__default():
129
+ result = format.conventional_types()
130
+
131
+ assert result == format.CONVENTIONAL_TYPES
132
+
133
+
134
+ def test_conventional_types__custom():
135
+ result = format.conventional_types(["custom"])
136
+
137
+ assert set(["custom", *format.CONVENTIONAL_TYPES]) == set(result)
138
+
139
+
140
+ def test_r_comment_single():
141
+ regex = re.compile(format.r_comment())
142
+ assert regex.match("# Some comment")
143
+ assert not regex.match("Some comment")
144
+ assert not regex.match(" # Some comment")
145
+
146
+
147
+ def test_strip_comments__consecutive():
148
+ input = """feat(scope): message
149
+ # Please enter the commit message for your changes.
150
+ # These are comments usually added by editors, f.ex. with export EDITOR=vim
151
+ """
152
+ result = format.strip_comments(input)
153
+ assert result.count("\n") == 1
154
+ assert result.strip() == "feat(scope): message"
155
+
156
+
157
+ def test_strip_comments__spaced():
158
+ input = """feat(scope): message
159
+ # Please enter the commit message for your changes.
160
+
161
+ # These are comments usually added by editors, f.ex. with export EDITOR=vim
162
+ """
163
+ result = format.strip_comments(input)
164
+ assert result.count("\n") == 2
165
+ assert result.strip() == "feat(scope): message"
166
+
167
+
168
+ def test_r_verbose_commit_ignored__does_not_match_no_verbose():
169
+ regex = re.compile(format.r_verbose_commit_ignored(), re.DOTALL | re.MULTILINE)
170
+ input = """feat: some commit message
171
+ # Please enter the commit message for your changes. Lines starting
172
+ # with '#' will be ignored, and an empty message aborts the commit.
173
+ #
174
+ # On branch main
175
+ # Your branch is up to date with 'origin/main'.
176
+ #
177
+ # Changes to be committed:
178
+ # modified: README.md
179
+ #
180
+ # Changes not staged for commit:
181
+ # modified: README.md
182
+ #
183
+ """
184
+
185
+ assert not regex.search(input)
186
+
187
+
188
+ def test_r_verbose_commit_ignored__matches_single_verbose_ignored():
189
+ regex = re.compile(format.r_verbose_commit_ignored(), re.DOTALL | re.MULTILINE)
190
+ input = (
191
+ """feat: some commit message
192
+ # Please enter the commit message for your changes. Lines starting
193
+ # with '#' will be ignored, and an empty message aborts the commit.
194
+ #
195
+ # On branch main
196
+ # Your branch is up to date with 'origin/main'.
197
+ #
198
+ # Changes to be committed:
199
+ # modified: README.md
200
+ #
201
+ # Changes not staged for commit:
202
+ # modified: README.md
203
+ #
204
+ # ------------------------ >8 ------------------------
205
+ # Do not modify or remove the line above.
206
+ # Everything below it will be ignored.
207
+ diff --git c/README.md i/README.md
208
+ index ea80a93..fe8a527 100644
209
+ --- c/README.md
210
+ +++ i/README.md
211
+ @@ -20,3 +20,4 @@ Some hunk header
212
+ Context 1
213
+ """
214
+ + " " # This is on purpose to preserve the space from overly eager stripping.
215
+ + """
216
+ Context 2
217
+ +Added line
218
+ """
219
+ )
220
+
221
+ assert regex.search(input)
222
+
223
+
224
+ def test_r_verbose_commit_ignored__matches_double_verbose_ignored():
225
+ regex = re.compile(format.r_verbose_commit_ignored(), re.DOTALL | re.MULTILINE)
226
+ input = (
227
+ """feat: some commit message
228
+ # Please enter the commit message for your changes. Lines starting
229
+ # with '#' will be ignored, and an empty message aborts the commit.
230
+ #
231
+ # On branch main
232
+ # Your branch is up to date with 'origin/main'.
233
+ #
234
+ # Changes to be committed:
235
+ # modified: README.md
236
+ #
237
+ # Changes not staged for commit:
238
+ # modified: README.md
239
+ #
240
+ # ------------------------ >8 ------------------------
241
+ # Do not modify or remove the line above.
242
+ # Everything below it will be ignored.
243
+ #
244
+ # Changes to be committed:
245
+ diff --git c/README.md i/README.md
246
+ index ea80a93..fe8a527 100644
247
+ --- c/README.md
248
+ +++ i/README.md
249
+ @@ -20,3 +20,4 @@ Some staged hunk header
250
+ Staged Context 1
251
+ """
252
+ + " " # This is on purpose to preserve the space from overly eager stripping.
253
+ + """
254
+ Staged Context 2
255
+ +Staged added line
256
+ # --------------------------------------------------
257
+ # Changes not staged for commit:
258
+ diff --git i/README.md w/README.md
259
+ index fe8a527..1c00c14 100644
260
+ --- i/README.md
261
+ +++ w/README.md
262
+ @@ -10,6 +10,7 @@ Some unstaged hunk header
263
+ Context 1
264
+ Context 2
265
+ Context 3
266
+ -Removed line
267
+ +Added line
268
+ """
269
+ + " " # This is on purpose to preserve the space from overly eager stripping.
270
+ + """
271
+ Context 4
272
+ """
273
+ + " " # This is on purpose to preserve the space from overly eager stripping.
274
+ + """
275
+ """
276
+ )
277
+
278
+ assert regex.search(input)
279
+
280
+
281
+ def test_strip_verbose_commit_ignored__does_not_strip_no_verbose():
282
+ input = """feat: some commit message
283
+ # Please enter the commit message for your changes. Lines starting
284
+ # with '#' will be ignored, and an empty message aborts the commit.
285
+ #
286
+ # On branch main
287
+ # Your branch is up to date with 'origin/main'.
288
+ #
289
+ # Changes to be committed:
290
+ # modified: README.md
291
+ #
292
+ # Changes not staged for commit:
293
+ # modified: README.md
294
+ #
295
+ """
296
+
297
+ expected = """feat: some commit message
298
+ # Please enter the commit message for your changes. Lines starting
299
+ # with '#' will be ignored, and an empty message aborts the commit.
300
+ #
301
+ # On branch main
302
+ # Your branch is up to date with 'origin/main'.
303
+ #
304
+ # Changes to be committed:
305
+ # modified: README.md
306
+ #
307
+ # Changes not staged for commit:
308
+ # modified: README.md
309
+ #
310
+ """
311
+
312
+ result = format.strip_verbose_commit_ignored(input)
313
+ assert result == expected
314
+
315
+
316
+ def test_strip_verbose_commit_ignored__strips_single_verbose_ignored():
317
+ input = (
318
+ """feat: some commit message
319
+ # Please enter the commit message for your changes. Lines starting
320
+ # with '#' will be ignored, and an empty message aborts the commit.
321
+ #
322
+ # On branch main
323
+ # Your branch is up to date with 'origin/main'.
324
+ #
325
+ # Changes to be committed:
326
+ # modified: README.md
327
+ #
328
+ # Changes not staged for commit:
329
+ # modified: README.md
330
+ #
331
+ # ------------------------ >8 ------------------------
332
+ # Do not modify or remove the line above.
333
+ # Everything below it will be ignored.
334
+ diff --git c/README.md i/README.md
335
+ index ea80a93..fe8a527 100644
336
+ --- c/README.md
337
+ +++ i/README.md
338
+ @@ -20,3 +20,4 @@ Some hunk header
339
+ Context 1
340
+ """
341
+ + " " # This is on purpose to preserve the space from overly eager stripping.
342
+ + """
343
+ Context 2
344
+ +Added line
345
+ """
346
+ )
347
+
348
+ expected = """feat: some commit message
349
+ # Please enter the commit message for your changes. Lines starting
350
+ # with '#' will be ignored, and an empty message aborts the commit.
351
+ #
352
+ # On branch main
353
+ # Your branch is up to date with 'origin/main'.
354
+ #
355
+ # Changes to be committed:
356
+ # modified: README.md
357
+ #
358
+ # Changes not staged for commit:
359
+ # modified: README.md
360
+ #
361
+ """
362
+
363
+ result = format.strip_verbose_commit_ignored(input)
364
+ assert result == expected
365
+
366
+
367
+ def test_strip_verbose_commit_ignored__strips_double_verbose_ignored():
368
+ input = (
369
+ """feat: some commit message
370
+ # Please enter the commit message for your changes. Lines starting
371
+ # with '#' will be ignored, and an empty message aborts the commit.
372
+ #
373
+ # On branch main
374
+ # Your branch is up to date with 'origin/main'.
375
+ #
376
+ # Changes to be committed:
377
+ # modified: README.md
378
+ #
379
+ # Changes not staged for commit:
380
+ # modified: README.md
381
+ #
382
+ # ------------------------ >8 ------------------------
383
+ # Do not modify or remove the line above.
384
+ # Everything below it will be ignored.
385
+ #
386
+ # Changes to be committed:
387
+ diff --git c/README.md i/README.md
388
+ index ea80a93..fe8a527 100644
389
+ --- c/README.md
390
+ +++ i/README.md
391
+ @@ -20,3 +20,4 @@ Some staged hunk header
392
+ Staged Context 1
393
+ """
394
+ + " " # This is on purpose to preserve the space from overly eager stripping.
395
+ + """
396
+ Staged Context 2
397
+ +Staged added line
398
+ # --------------------------------------------------
399
+ # Changes not staged for commit:
400
+ diff --git i/README.md w/README.md
401
+ index fe8a527..1c00c14 100644
402
+ --- i/README.md
403
+ +++ w/README.md
404
+ @@ -10,6 +10,7 @@ Some unstaged hunk header
405
+ Context 1
406
+ Context 2
407
+ Context 3
408
+ -Removed line
409
+ +Added line
410
+ """
411
+ + " " # This is on purpose to preserve the space from overly eager stripping.
412
+ + """
413
+ Context 4
414
+ """
415
+ + " " # This is on purpose to preserve the space from overly eager stripping.
416
+ + """
417
+ """
418
+ )
419
+
420
+ expected = """feat: some commit message
421
+ # Please enter the commit message for your changes. Lines starting
422
+ # with '#' will be ignored, and an empty message aborts the commit.
423
+ #
424
+ # On branch main
425
+ # Your branch is up to date with 'origin/main'.
426
+ #
427
+ # Changes to be committed:
428
+ # modified: README.md
429
+ #
430
+ # Changes not staged for commit:
431
+ # modified: README.md
432
+ #
433
+ """
434
+
435
+ result = format.strip_verbose_commit_ignored(input)
436
+ assert result == expected
437
+
438
+
439
+ @pytest.mark.parametrize("type", format.DEFAULT_TYPES)
440
+ def test_is_conventional__default_type(type):
441
+ input = f"{type}: message"
442
+
443
+ assert format.is_conventional(input)
444
+
445
+
446
+ @pytest.mark.parametrize("type", format.CONVENTIONAL_TYPES)
447
+ def test_is_conventional__conventional_type(type):
448
+ input = f"{type}: message"
449
+
450
+ assert format.is_conventional(input)
451
+
452
+
453
+ @pytest.mark.parametrize("type", CUSTOM_TYPES)
454
+ def test_is_conventional__custom_type(type):
455
+ input = f"{type}: message"
456
+
457
+ assert format.is_conventional(input, CUSTOM_TYPES)
458
+
459
+
460
+ @pytest.mark.parametrize("type", format.CONVENTIONAL_TYPES)
461
+ def test_is_conventional__conventional_custom_type(type):
462
+ input = f"{type}: message"
463
+
464
+ assert format.is_conventional(input, CUSTOM_TYPES)
465
+
466
+
467
+ def test_is_conventional__breaking_change():
468
+ input = "fix!: message"
469
+
470
+ assert format.is_conventional(input)
471
+
472
+
473
+ def test_is_conventional__with_scope():
474
+ input = "feat(scope): message"
475
+
476
+ assert format.is_conventional(input)
477
+
478
+
479
+ def test_is_conventional__body_multiline_body_bad_type():
480
+ input = """wrong: message
481
+
482
+ more_message
483
+ """
484
+
485
+ assert not format.is_conventional(input)
486
+
487
+
488
+ def test_is_conventional__bad_body_multiline():
489
+ input = """feat(scope): message
490
+ more message
491
+ """
492
+
493
+ assert not format.is_conventional(input)
494
+
495
+
496
+ def test_is_conventional__body_multiline():
497
+ input = """feat(scope): message
498
+
499
+ more message
500
+ """
501
+
502
+ assert format.is_conventional(input)
503
+
504
+
505
+ def test_is_conventional__bad_body_multiline_paragraphs():
506
+ input = """feat(scope): message
507
+ more message
508
+
509
+ more body message
510
+ """
511
+
512
+ assert not format.is_conventional(input)
513
+
514
+
515
+ def test_is_conventional__comment():
516
+ input = """feat(scope): message
517
+ # Please enter the commit message for your changes.
518
+ # These are comments usually added by editors, f.ex. with export EDITOR=vim
519
+ """
520
+ assert format.is_conventional(input)
521
+
522
+
523
+ @pytest.mark.parametrize("char", ['"', "'", "`", "#", "&"])
524
+ def test_is_conventional__body_special_char(char):
525
+ input = f"feat: message with {char}"
526
+
527
+ assert format.is_conventional(input)
528
+
529
+
530
+ def test_is_conventional__wrong_type():
531
+ input = "wrong: message"
532
+
533
+ assert not format.is_conventional(input)
534
+
535
+
536
+ def test_is_conventional__scope_special_chars():
537
+ input = "feat(%&*@()): message"
538
+
539
+ assert not format.is_conventional(input)
540
+
541
+
542
+ def test_is_conventional__space_scope():
543
+ input = "feat (scope): message"
544
+
545
+ assert not format.is_conventional(input)
546
+
547
+
548
+ def test_is_conventional__scope_space():
549
+ input = "feat(scope) : message"
550
+
551
+ assert not format.is_conventional(input)
552
+
553
+
554
+ def test_is_conventional__scope_not_optional():
555
+ input = "feat: message"
556
+
557
+ assert not format.is_conventional(input, optional_scope=False)
558
+
559
+
560
+ def test_is_conventional__scope_not_optional_empty_parenthesis():
561
+ input = "feat(): message"
562
+
563
+ assert not format.is_conventional(input, optional_scope=False)
564
+
565
+
566
+ def test_is_conventional__missing_delimiter():
567
+ input = "feat message"
568
+
569
+ assert not format.is_conventional(input)
570
+
571
+
572
+ @pytest.mark.parametrize(
573
+ "input,has_prefix",
574
+ [
575
+ ("amend! ", True),
576
+ ("fixup! ", True),
577
+ ("squash! ", True),
578
+ ("squash! whatever .. $12 #", True),
579
+ ("squash!", False),
580
+ (" squash! ", False),
581
+ ("squash!:", False),
582
+ ("feat(foo):", False),
583
+ ],
584
+ )
585
+ def test_has_autosquash_prefix(input, has_prefix):
586
+ assert format.has_autosquash_prefix(input) == has_prefix
@@ -142,6 +142,16 @@ def test_subprocess_success__conventional_with_scope(cmd, conventional_commit_wi
142
142
  assert result == RESULT_SUCCESS
143
143
 
144
144
 
145
+ def test_subprocess_success__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
146
+ result = subprocess.call((cmd, "--scopes", "api,client", conventional_commit_with_multiple_scopes_path))
147
+ assert result == RESULT_SUCCESS
148
+
149
+
150
+ def test_subprocess_fail__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
151
+ result = subprocess.call((cmd, "--scopes", "api", conventional_commit_with_multiple_scopes_path))
152
+ assert result == RESULT_FAIL
153
+
154
+
145
155
  def test_subprocess_success__fixup_commit(cmd, fixup_commit_path):
146
156
  result = subprocess.call((cmd, fixup_commit_path))
147
157
 
@@ -1,368 +0,0 @@
1
- import re
2
-
3
- import pytest
4
-
5
- from conventional_pre_commit import format
6
-
7
- CUSTOM_TYPES = ["one", "two"]
8
-
9
-
10
- def test_r_types():
11
- result = format.r_types(CUSTOM_TYPES)
12
- regex = re.compile(result)
13
-
14
- assert regex.match("one")
15
- assert regex.match("two")
16
-
17
-
18
- def test_r_scope__optional():
19
- result = format.r_scope()
20
- regex = re.compile(result)
21
-
22
- assert regex.match("")
23
-
24
-
25
- def test_r_scope__not_optional():
26
- result = format.r_scope(optional=False)
27
- regex = re.compile(result)
28
-
29
- # Assert not optional anymore
30
- assert not regex.match("")
31
-
32
-
33
- def test_r_scope__parenthesis_required():
34
- result = format.r_scope()
35
- regex = re.compile(result)
36
-
37
- # without parens produces a match object with a 0 span
38
- # since the (scope) is optional
39
- without_parens = regex.match("something")
40
- assert without_parens.span() == (0, 0)
41
-
42
- # with parens produces a match object with a span
43
- # that covers the input string
44
- with_parens = regex.match("(something)")
45
- assert with_parens.span() == (0, 11)
46
-
47
-
48
- def test_r_scope__alphanumeric():
49
- result = format.r_scope()
50
- regex = re.compile(result)
51
-
52
- assert regex.match("(50m3t41N6)")
53
-
54
-
55
- def test_r_scope__special_chars():
56
- result = format.r_scope()
57
- regex = re.compile(result)
58
-
59
- assert regex.match("(some-thing)")
60
- assert regex.match("(some_thing)")
61
- assert regex.match("(some/thing)")
62
- assert regex.match("(some thing)")
63
- assert regex.match("(some:thing)")
64
- assert regex.match("(some,thing)")
65
-
66
-
67
- def test_r_delim():
68
- result = format.r_delim()
69
- regex = re.compile(result)
70
-
71
- assert regex.match(":")
72
-
73
-
74
- def test_r_delim__optional_breaking_indicator():
75
- result = format.r_delim()
76
- regex = re.compile(result)
77
-
78
- assert regex.match("!:")
79
-
80
-
81
- def test_r_subject__starts_with_space():
82
- result = format.r_subject()
83
- regex = re.compile(result)
84
-
85
- assert not regex.match("something")
86
- assert regex.match(" something")
87
-
88
-
89
- def test_r_subject__alphanumeric():
90
- result = format.r_subject()
91
- regex = re.compile(result)
92
-
93
- assert regex.match(" 50m3t41N6")
94
-
95
-
96
- def test_r_subject__special_chars():
97
- result = format.r_subject()
98
- regex = re.compile(result)
99
-
100
- assert regex.match(" some-thing")
101
- assert regex.match(" some_thing")
102
- assert regex.match(" some/thing")
103
- assert regex.match(" some thing")
104
-
105
-
106
- def test_r_autosquash_prefixes():
107
- result = format.r_autosquash_prefixes()
108
- regex = re.compile(result)
109
-
110
- for prefix in format.AUTOSQUASH_PREFIXES:
111
- assert regex.match(prefix)
112
-
113
-
114
- def test_conventional_types__default():
115
- result = format.conventional_types()
116
-
117
- assert result == format.CONVENTIONAL_TYPES
118
-
119
-
120
- def test_conventional_types__custom():
121
- result = format.conventional_types(["custom"])
122
-
123
- assert set(["custom", *format.CONVENTIONAL_TYPES]) == set(result)
124
-
125
-
126
- def test_r_comment_single():
127
- regex = re.compile(format.r_comment())
128
- assert regex.match("# Some comment")
129
- assert not regex.match("Some comment")
130
- assert not regex.match(" # Some comment")
131
-
132
-
133
- def test_strip_comments__consecutive():
134
- input = """feat(scope): message
135
- # Please enter the commit message for your changes.
136
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
137
- """
138
- result = format.strip_comments(input)
139
- assert result.count("\n") == 1
140
- assert result.strip() == "feat(scope): message"
141
-
142
-
143
- def test_strip_comments__spaced():
144
- input = """feat(scope): message
145
- # Please enter the commit message for your changes.
146
-
147
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
148
- """
149
- result = format.strip_comments(input)
150
- assert result.count("\n") == 2
151
- assert result.strip() == "feat(scope): message"
152
-
153
-
154
- def test_r_verbose_diff__has_diff():
155
- regex = re.compile(format.r_verbose_diff(), re.MULTILINE)
156
- input = """# ----------- >8 -----------
157
- # Some comment
158
- # Some comment
159
- diff --git a/file b/file
160
- """
161
-
162
- assert regex.match(input)
163
-
164
-
165
- def test_r_verbose_diff__no_diff():
166
- regex = re.compile(format.r_verbose_diff(), re.MULTILINE)
167
- input = """# ----------- >8 -----------
168
- # Some comment
169
- # Some comment
170
- """
171
-
172
- assert not regex.match(input)
173
-
174
-
175
- def test_r_verbose_diff__no_extra_comments():
176
- regex = re.compile(format.r_verbose_diff(), re.MULTILINE)
177
- input = """# ----------- >8 -----------
178
- diff --git a/file b/file
179
- """
180
-
181
- assert not regex.match(input)
182
-
183
-
184
- def test_strip_verbose_diff__has_diff():
185
- input = """feat(scope): message
186
- # Please enter the commit message for your changes.
187
-
188
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
189
- # ----------- >8 -----------
190
- # Some comment
191
- # Some comment
192
- diff --git a/file b/file
193
- """
194
-
195
- result = format.strip_verbose_diff(input)
196
- assert result.count("\n") == 4
197
- assert (
198
- result
199
- == """feat(scope): message
200
- # Please enter the commit message for your changes.
201
-
202
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
203
- """
204
- )
205
-
206
-
207
- def test_strip_verbose_diff__no_diff():
208
- input = """feat(scope): message
209
- # Please enter the commit message for your changes.
210
-
211
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
212
- # ----------- >8 -----------
213
- # Some comment
214
- # Some comment
215
- """
216
-
217
- result = format.strip_verbose_diff(input)
218
- assert result == input
219
-
220
-
221
- @pytest.mark.parametrize("type", format.DEFAULT_TYPES)
222
- def test_is_conventional__default_type(type):
223
- input = f"{type}: message"
224
-
225
- assert format.is_conventional(input)
226
-
227
-
228
- @pytest.mark.parametrize("type", format.CONVENTIONAL_TYPES)
229
- def test_is_conventional__conventional_type(type):
230
- input = f"{type}: message"
231
-
232
- assert format.is_conventional(input)
233
-
234
-
235
- @pytest.mark.parametrize("type", CUSTOM_TYPES)
236
- def test_is_conventional__custom_type(type):
237
- input = f"{type}: message"
238
-
239
- assert format.is_conventional(input, CUSTOM_TYPES)
240
-
241
-
242
- @pytest.mark.parametrize("type", format.CONVENTIONAL_TYPES)
243
- def test_is_conventional__conventional_custom_type(type):
244
- input = f"{type}: message"
245
-
246
- assert format.is_conventional(input, CUSTOM_TYPES)
247
-
248
-
249
- def test_is_conventional__breaking_change():
250
- input = "fix!: message"
251
-
252
- assert format.is_conventional(input)
253
-
254
-
255
- def test_is_conventional__with_scope():
256
- input = "feat(scope): message"
257
-
258
- assert format.is_conventional(input)
259
-
260
-
261
- def test_is_conventional__body_multiline_body_bad_type():
262
- input = """wrong: message
263
-
264
- more_message
265
- """
266
-
267
- assert not format.is_conventional(input)
268
-
269
-
270
- def test_is_conventional__bad_body_multiline():
271
- input = """feat(scope): message
272
- more message
273
- """
274
-
275
- assert not format.is_conventional(input)
276
-
277
-
278
- def test_is_conventional__body_multiline():
279
- input = """feat(scope): message
280
-
281
- more message
282
- """
283
-
284
- assert format.is_conventional(input)
285
-
286
-
287
- def test_is_conventional__bad_body_multiline_paragraphs():
288
- input = """feat(scope): message
289
- more message
290
-
291
- more body message
292
- """
293
-
294
- assert not format.is_conventional(input)
295
-
296
-
297
- def test_is_conventional__comment():
298
- input = """feat(scope): message
299
- # Please enter the commit message for your changes.
300
- # These are comments usually added by editors, f.ex. with export EDITOR=vim
301
- """
302
- assert format.is_conventional(input)
303
-
304
-
305
- @pytest.mark.parametrize("char", ['"', "'", "`", "#", "&"])
306
- def test_is_conventional__body_special_char(char):
307
- input = f"feat: message with {char}"
308
-
309
- assert format.is_conventional(input)
310
-
311
-
312
- def test_is_conventional__wrong_type():
313
- input = "wrong: message"
314
-
315
- assert not format.is_conventional(input)
316
-
317
-
318
- def test_is_conventional__scope_special_chars():
319
- input = "feat(%&*@()): message"
320
-
321
- assert not format.is_conventional(input)
322
-
323
-
324
- def test_is_conventional__space_scope():
325
- input = "feat (scope): message"
326
-
327
- assert not format.is_conventional(input)
328
-
329
-
330
- def test_is_conventional__scope_space():
331
- input = "feat(scope) : message"
332
-
333
- assert not format.is_conventional(input)
334
-
335
-
336
- def test_is_conventional__scope_not_optional():
337
- input = "feat: message"
338
-
339
- assert not format.is_conventional(input, optional_scope=False)
340
-
341
-
342
- def test_is_conventional__scope_not_optional_empty_parenthesis():
343
- input = "feat(): message"
344
-
345
- assert not format.is_conventional(input, optional_scope=False)
346
-
347
-
348
- def test_is_conventional__missing_delimiter():
349
- input = "feat message"
350
-
351
- assert not format.is_conventional(input)
352
-
353
-
354
- @pytest.mark.parametrize(
355
- "input,has_prefix",
356
- [
357
- ("amend! ", True),
358
- ("fixup! ", True),
359
- ("squash! ", True),
360
- ("squash! whatever .. $12 #", True),
361
- ("squash!", False),
362
- (" squash! ", False),
363
- ("squash!:", False),
364
- ("feat(foo):", False),
365
- ],
366
- )
367
- def test_has_autosquash_prefix(input, has_prefix):
368
- assert format.has_autosquash_prefix(input) == has_prefix