commitizen 4.9.1__tar.gz → 4.10.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.
- {commitizen-4.9.1 → commitizen-4.10.0}/PKG-INFO +4 -3
- commitizen-4.10.0/commitizen/__version__.py +1 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/bump.py +33 -48
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog_formats/base.py +4 -4
- commitizen-4.10.0/commitizen/changelog_formats/restructuredtext.py +88 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cli.py +12 -2
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cmd.py +9 -7
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/bump.py +3 -4
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/changelog.py +8 -7
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/check.py +19 -8
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/commit.py +45 -33
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/init.py +87 -158
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/version.py +17 -1
- commitizen-4.10.0/commitizen/config/__init__.py +46 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/config/base_config.py +10 -7
- commitizen-4.10.0/commitizen/config/factory.py +22 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/config/json_config.py +10 -14
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/config/toml_config.py +17 -20
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/config/yaml_config.py +12 -14
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/base.py +10 -10
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/conventional_commits/conventional_commits.py +8 -7
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/utils.py +3 -3
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/defaults.py +5 -0
- commitizen-4.10.0/commitizen/project_info.py +47 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/uv_provider.py +1 -1
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/version_schemes.py +3 -1
- {commitizen-4.9.1 → commitizen-4.10.0}/pyproject.toml +3 -3
- commitizen-4.9.1/commitizen/__version__.py +0 -1
- commitizen-4.9.1/commitizen/changelog_formats/restructuredtext.py +0 -92
- commitizen-4.9.1/commitizen/config/__init__.py +0 -58
- {commitizen-4.9.1 → commitizen-4.10.0}/LICENSE +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/__main__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog_formats/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog_formats/asciidoc.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog_formats/markdown.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/changelog_formats/textile.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/example.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/info.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/list_cz.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/commands/schema.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/conventional_commits/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/conventional_commits/conventional_commits_info.txt +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/customize/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/customize/customize.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/customize/customize_info.txt +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/exceptions.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/jira/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/jira/jira.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/cz/jira/jira_info.txt +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/exceptions.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/factory.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/git.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/hooks.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/out.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/__init__.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/base_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/cargo_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/commitizen_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/composer_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/npm_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/pep621_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/poetry_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/providers/scm_provider.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/py.typed +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/question.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/tags.py +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/templates/CHANGELOG.adoc.j2 +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/templates/CHANGELOG.md.j2 +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/templates/CHANGELOG.rst.j2 +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/commitizen/templates/CHANGELOG.textile.j2 +0 -0
- {commitizen-4.9.1 → commitizen-4.10.0}/docs/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: commitizen
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.10.0
|
|
4
4
|
Summary: Python commitizen client tool
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -23,6 +23,7 @@ License: MIT License
|
|
|
23
23
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
24
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
25
|
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
26
27
|
Keywords: commitizen,conventional,commits,git
|
|
27
28
|
Author: Santiago Fraire
|
|
28
29
|
Author-email: santiwilly@gmail.com
|
|
@@ -55,7 +56,7 @@ Requires-Dist: prompt_toolkit (!=3.0.52)
|
|
|
55
56
|
Requires-Dist: pyyaml (>=3.08)
|
|
56
57
|
Requires-Dist: questionary (>=2.0,<3.0)
|
|
57
58
|
Requires-Dist: termcolor (>=1.1.0,<4.0.0)
|
|
58
|
-
Requires-Dist: tomlkit (>=0.
|
|
59
|
+
Requires-Dist: tomlkit (>=0.8.0,<1.0.0)
|
|
59
60
|
Requires-Dist: typing-extensions (>=4.0.1,<5.0.0) ; python_version < "3.11"
|
|
60
61
|
Project-URL: Changelog, https://github.com/commitizen-tools/commitizen/blob/master/CHANGELOG.md
|
|
61
62
|
Project-URL: Documentation, https://commitizen-tools.github.io/commitizen/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "4.10.0"
|
|
@@ -3,13 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
5
|
from collections import OrderedDict
|
|
6
|
-
from collections.abc import Iterable
|
|
6
|
+
from collections.abc import Generator, Iterable
|
|
7
7
|
from glob import iglob
|
|
8
8
|
from logging import getLogger
|
|
9
9
|
from string import Template
|
|
10
10
|
from typing import cast
|
|
11
11
|
|
|
12
|
-
from commitizen.defaults import BUMP_MESSAGE,
|
|
12
|
+
from commitizen.defaults import BUMP_MESSAGE, MAJOR, MINOR, PATCH
|
|
13
13
|
from commitizen.exceptions import CurrentVersionNotFoundError
|
|
14
14
|
from commitizen.git import GitCommit, smart_open
|
|
15
15
|
from commitizen.version_schemes import Increment, Version
|
|
@@ -64,8 +64,8 @@ def update_version_in_files(
|
|
|
64
64
|
new_version: str,
|
|
65
65
|
files: Iterable[str],
|
|
66
66
|
*,
|
|
67
|
-
check_consistency: bool
|
|
68
|
-
encoding: str
|
|
67
|
+
check_consistency: bool,
|
|
68
|
+
encoding: str,
|
|
69
69
|
) -> list[str]:
|
|
70
70
|
"""Change old version to the new one in every file given.
|
|
71
71
|
|
|
@@ -75,16 +75,22 @@ def update_version_in_files(
|
|
|
75
75
|
|
|
76
76
|
Returns the list of updated files.
|
|
77
77
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for path,
|
|
81
|
-
current_version_found
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
updated_files = []
|
|
79
|
+
|
|
80
|
+
for path, pattern in _resolve_files_and_regexes(files, current_version):
|
|
81
|
+
current_version_found = False
|
|
82
|
+
bumped_lines = []
|
|
83
|
+
|
|
84
|
+
with open(path, encoding=encoding) as version_file:
|
|
85
|
+
for line in version_file:
|
|
86
|
+
bumped_line = (
|
|
87
|
+
line.replace(current_version, new_version)
|
|
88
|
+
if pattern.search(line)
|
|
89
|
+
else line
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
current_version_found = current_version_found or bumped_line != line
|
|
93
|
+
bumped_lines.append(bumped_line)
|
|
88
94
|
|
|
89
95
|
if check_consistency and not current_version_found:
|
|
90
96
|
raise CurrentVersionNotFoundError(
|
|
@@ -93,53 +99,32 @@ def update_version_in_files(
|
|
|
93
99
|
"version_files are possibly inconsistent."
|
|
94
100
|
)
|
|
95
101
|
|
|
102
|
+
bumped_version_file_content = "".join(bumped_lines)
|
|
103
|
+
|
|
96
104
|
# Write the file out again
|
|
97
105
|
with smart_open(path, "w", encoding=encoding) as file:
|
|
98
|
-
file.write(
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
file.write(bumped_version_file_content)
|
|
107
|
+
updated_files.append(path)
|
|
108
|
+
|
|
109
|
+
return updated_files
|
|
101
110
|
|
|
102
111
|
|
|
103
|
-
def
|
|
112
|
+
def _resolve_files_and_regexes(
|
|
113
|
+
patterns: Iterable[str], version: str
|
|
114
|
+
) -> Generator[tuple[str, re.Pattern], None, None]:
|
|
104
115
|
"""
|
|
105
116
|
Resolve all distinct files with their regexp from a list of glob patterns with optional regexp
|
|
106
117
|
"""
|
|
107
|
-
|
|
118
|
+
filepath_set: set[tuple[str, str]] = set()
|
|
108
119
|
for pattern in patterns:
|
|
109
120
|
drive, tail = os.path.splitdrive(pattern)
|
|
110
121
|
path, _, regex = tail.partition(":")
|
|
111
122
|
filepath = drive + path
|
|
112
|
-
|
|
113
|
-
regex = re.escape(version)
|
|
123
|
+
regex = regex or re.escape(version)
|
|
114
124
|
|
|
115
|
-
for
|
|
116
|
-
out.add((file, regex))
|
|
125
|
+
filepath_set.update((path, regex) for path in iglob(filepath))
|
|
117
126
|
|
|
118
|
-
return sorted(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _bump_with_regex(
|
|
122
|
-
version_filepath: str,
|
|
123
|
-
current_version: str,
|
|
124
|
-
new_version: str,
|
|
125
|
-
regex: str,
|
|
126
|
-
encoding: str = ENCODING,
|
|
127
|
-
) -> tuple[bool, str]:
|
|
128
|
-
current_version_found = False
|
|
129
|
-
lines = []
|
|
130
|
-
pattern = re.compile(regex)
|
|
131
|
-
with open(version_filepath, encoding=encoding) as f:
|
|
132
|
-
for line in f:
|
|
133
|
-
if not pattern.search(line):
|
|
134
|
-
lines.append(line)
|
|
135
|
-
continue
|
|
136
|
-
|
|
137
|
-
bumped_line = line.replace(current_version, new_version)
|
|
138
|
-
if bumped_line != line:
|
|
139
|
-
current_version_found = True
|
|
140
|
-
lines.append(bumped_line)
|
|
141
|
-
|
|
142
|
-
return current_version_found, "".join(lines)
|
|
127
|
+
return ((path, re.compile(regex)) for path, regex in sorted(filepath_set))
|
|
143
128
|
|
|
144
129
|
|
|
145
130
|
def create_commit_message(
|
|
@@ -24,11 +24,9 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
|
|
|
24
24
|
# Constructor needs to be redefined because `Protocol` prevent instantiation by default
|
|
25
25
|
# See: https://bugs.python.org/issue44807
|
|
26
26
|
self.config = config
|
|
27
|
-
self.encoding = self.config.settings["encoding"]
|
|
28
|
-
self.tag_format = self.config.settings["tag_format"]
|
|
29
27
|
self.tag_rules = TagRules(
|
|
30
28
|
scheme=get_version_scheme(self.config.settings),
|
|
31
|
-
tag_format=self.tag_format,
|
|
29
|
+
tag_format=self.config.settings["tag_format"],
|
|
32
30
|
legacy_tag_formats=self.config.settings["legacy_tag_formats"],
|
|
33
31
|
ignored_tag_formats=self.config.settings["ignored_tag_formats"],
|
|
34
32
|
)
|
|
@@ -37,7 +35,9 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
|
|
|
37
35
|
if not os.path.isfile(filepath):
|
|
38
36
|
return Metadata()
|
|
39
37
|
|
|
40
|
-
with open(
|
|
38
|
+
with open(
|
|
39
|
+
filepath, encoding=self.config.settings["encoding"]
|
|
40
|
+
) as changelog_file:
|
|
41
41
|
return self.get_metadata_from_file(changelog_file)
|
|
42
42
|
|
|
43
43
|
def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from itertools import zip_longest
|
|
4
|
+
from typing import IO
|
|
5
|
+
|
|
6
|
+
from commitizen.changelog import Metadata
|
|
7
|
+
|
|
8
|
+
from .base import BaseFormat
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RestructuredText(BaseFormat):
|
|
12
|
+
extension = "rst"
|
|
13
|
+
|
|
14
|
+
def get_metadata_from_file(self, file: IO[str]) -> Metadata:
|
|
15
|
+
"""
|
|
16
|
+
RestructuredText section titles are not one-line-based,
|
|
17
|
+
they spread on 2 or 3 lines and levels are not predefined
|
|
18
|
+
but determined by their occurrence order.
|
|
19
|
+
|
|
20
|
+
It requires its own algorithm.
|
|
21
|
+
|
|
22
|
+
For a more generic approach, you need to rely on `docutils`.
|
|
23
|
+
"""
|
|
24
|
+
out_metadata = Metadata()
|
|
25
|
+
unreleased_title_kind: str | tuple[str, str] | None = None
|
|
26
|
+
is_overlined_title = False
|
|
27
|
+
lines = [line.strip().lower() for line in file.readlines()]
|
|
28
|
+
|
|
29
|
+
for index, (first, second, third) in enumerate(
|
|
30
|
+
zip_longest(lines, lines[1:], lines[2:], fillvalue="")
|
|
31
|
+
):
|
|
32
|
+
title: str | None = None
|
|
33
|
+
kind: str | tuple[str, str] | None = None
|
|
34
|
+
if _is_overlined_title(first, second, third):
|
|
35
|
+
title = second
|
|
36
|
+
kind = (first[0], third[0])
|
|
37
|
+
is_overlined_title = True
|
|
38
|
+
elif not is_overlined_title and _is_underlined_title(first, second):
|
|
39
|
+
title = first
|
|
40
|
+
kind = second[0]
|
|
41
|
+
else:
|
|
42
|
+
is_overlined_title = False
|
|
43
|
+
|
|
44
|
+
if not title:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
if "unreleased" in title:
|
|
48
|
+
unreleased_title_kind = kind
|
|
49
|
+
out_metadata.unreleased_start = index
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if unreleased_title_kind and unreleased_title_kind == kind:
|
|
53
|
+
out_metadata.unreleased_end = index
|
|
54
|
+
# Try to find the latest release done
|
|
55
|
+
if version := self.tag_rules.search_version(title):
|
|
56
|
+
out_metadata.latest_version = version[0]
|
|
57
|
+
out_metadata.latest_version_tag = version[1]
|
|
58
|
+
out_metadata.latest_version_position = index
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
out_metadata.unreleased_start is not None
|
|
63
|
+
and out_metadata.unreleased_end is None
|
|
64
|
+
):
|
|
65
|
+
out_metadata.unreleased_end = (
|
|
66
|
+
out_metadata.latest_version_position
|
|
67
|
+
if out_metadata.latest_version
|
|
68
|
+
else len(lines)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return out_metadata
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _is_overlined_title(first: str, second: str, third: str) -> bool:
|
|
75
|
+
return (
|
|
76
|
+
len(first) == len(third) >= len(second)
|
|
77
|
+
and first[0] == third[0]
|
|
78
|
+
and all(char == first[0] for char in first)
|
|
79
|
+
and _is_underlined_title(second, third)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_underlined_title(first: str, second: str) -> bool:
|
|
84
|
+
return (
|
|
85
|
+
len(second) >= len(first)
|
|
86
|
+
and not second.isalnum()
|
|
87
|
+
and all(char == second[0] for char in second)
|
|
88
|
+
)
|
|
@@ -160,7 +160,6 @@ data = {
|
|
|
160
160
|
{
|
|
161
161
|
"name": ["-l", "--message-length-limit"],
|
|
162
162
|
"type": int,
|
|
163
|
-
"default": 0,
|
|
164
163
|
"help": "length limit of the commit message; 0 for no limit",
|
|
165
164
|
},
|
|
166
165
|
{
|
|
@@ -499,7 +498,6 @@ data = {
|
|
|
499
498
|
{
|
|
500
499
|
"name": ["-l", "--message-length-limit"],
|
|
501
500
|
"type": int,
|
|
502
|
-
"default": 0,
|
|
503
501
|
"help": "length limit of the commit message; 0 for no limit",
|
|
504
502
|
},
|
|
505
503
|
],
|
|
@@ -543,6 +541,18 @@ data = {
|
|
|
543
541
|
"action": "store_true",
|
|
544
542
|
"exclusive_group": "group1",
|
|
545
543
|
},
|
|
544
|
+
{
|
|
545
|
+
"name": ["--major"],
|
|
546
|
+
"help": "get just the major version",
|
|
547
|
+
"action": "store_true",
|
|
548
|
+
"exclusive_group": "group2",
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"name": ["--minor"],
|
|
552
|
+
"help": "get just the minor version",
|
|
553
|
+
"action": "store_true",
|
|
554
|
+
"exclusive_group": "group2",
|
|
555
|
+
},
|
|
546
556
|
],
|
|
547
557
|
},
|
|
548
558
|
],
|
|
@@ -22,13 +22,15 @@ def _try_decode(bytes_: bytes) -> str:
|
|
|
22
22
|
try:
|
|
23
23
|
return bytes_.decode("utf-8")
|
|
24
24
|
except UnicodeDecodeError:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
charset_match = from_bytes(bytes_).best()
|
|
28
|
+
if charset_match is None:
|
|
29
|
+
raise CharacterSetDecodeError()
|
|
30
|
+
try:
|
|
31
|
+
return bytes_.decode(charset_match.encoding)
|
|
32
|
+
except UnicodeDecodeError as e:
|
|
33
|
+
raise CharacterSetDecodeError() from e
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
def run(cmd: str, env: Mapping[str, str] | None = None) -> Command:
|
|
@@ -67,7 +67,6 @@ class Bump:
|
|
|
67
67
|
raise NotAGitProjectError()
|
|
68
68
|
|
|
69
69
|
self.config: BaseConfig = config
|
|
70
|
-
self.encoding = config.settings["encoding"]
|
|
71
70
|
self.arguments = arguments
|
|
72
71
|
self.bump_settings = cast(
|
|
73
72
|
BumpArgs,
|
|
@@ -216,8 +215,8 @@ class Bump:
|
|
|
216
215
|
|
|
217
216
|
rules = TagRules.from_settings(cast(Settings, self.bump_settings))
|
|
218
217
|
current_tag = rules.find_tag_for(git.get_tags(), current_version)
|
|
219
|
-
current_tag_version =
|
|
220
|
-
current_tag
|
|
218
|
+
current_tag_version = (
|
|
219
|
+
current_tag.name if current_tag else rules.normalize_tag(current_version)
|
|
221
220
|
)
|
|
222
221
|
|
|
223
222
|
is_initial = self._is_initial_tag(current_tag, self.arguments["yes"])
|
|
@@ -335,7 +334,7 @@ class Bump:
|
|
|
335
334
|
str(new_version),
|
|
336
335
|
self.bump_settings["version_files"],
|
|
337
336
|
check_consistency=self.check_consistency,
|
|
338
|
-
encoding=self.encoding,
|
|
337
|
+
encoding=self.config.settings["encoding"],
|
|
339
338
|
)
|
|
340
339
|
)
|
|
341
340
|
|
|
@@ -67,7 +67,6 @@ class Changelog:
|
|
|
67
67
|
else changelog_file_name
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
-
self.encoding = self.config.settings["encoding"]
|
|
71
70
|
self.cz = factory.committer_factory(self.config)
|
|
72
71
|
|
|
73
72
|
self.start_rev = arguments.get("start_rev") or self.config.settings.get(
|
|
@@ -104,12 +103,10 @@ class Changelog:
|
|
|
104
103
|
or defaults.CHANGE_TYPE_ORDER,
|
|
105
104
|
)
|
|
106
105
|
self.rev_range = arguments.get("rev_range")
|
|
107
|
-
self.tag_format = (
|
|
108
|
-
arguments.get("tag_format") or self.config.settings["tag_format"]
|
|
109
|
-
)
|
|
110
106
|
self.tag_rules = TagRules(
|
|
111
107
|
scheme=self.scheme,
|
|
112
|
-
tag_format=
|
|
108
|
+
tag_format=arguments.get("tag_format")
|
|
109
|
+
or self.config.settings["tag_format"],
|
|
113
110
|
legacy_tag_formats=self.config.settings["legacy_tag_formats"],
|
|
114
111
|
ignored_tag_formats=self.config.settings["ignored_tag_formats"],
|
|
115
112
|
merge_prereleases=arguments.get("merge_prerelease")
|
|
@@ -159,7 +156,9 @@ class Changelog:
|
|
|
159
156
|
def _write_changelog(
|
|
160
157
|
self, changelog_out: str, lines: list[str], changelog_meta: changelog.Metadata
|
|
161
158
|
) -> None:
|
|
162
|
-
with smart_open(
|
|
159
|
+
with smart_open(
|
|
160
|
+
self.file_name, "w", encoding=self.config.settings["encoding"]
|
|
161
|
+
) as changelog_file:
|
|
163
162
|
partial_changelog: str | None = None
|
|
164
163
|
if self.incremental:
|
|
165
164
|
new_lines = changelog.incremental_build(
|
|
@@ -261,7 +260,9 @@ class Changelog:
|
|
|
261
260
|
|
|
262
261
|
lines = []
|
|
263
262
|
if self.incremental and os.path.isfile(self.file_name):
|
|
264
|
-
with open(
|
|
263
|
+
with open(
|
|
264
|
+
self.file_name, encoding=self.config.settings["encoding"]
|
|
265
|
+
) as changelog_file:
|
|
265
266
|
lines = changelog_file.readlines()
|
|
266
267
|
|
|
267
268
|
self._write_changelog(changelog_out, lines, changelog_meta)
|
|
@@ -7,6 +7,7 @@ from typing import TypedDict
|
|
|
7
7
|
from commitizen import factory, git, out
|
|
8
8
|
from commitizen.config import BaseConfig
|
|
9
9
|
from commitizen.exceptions import (
|
|
10
|
+
CommitMessageLengthExceededError,
|
|
10
11
|
InvalidCommandArgumentError,
|
|
11
12
|
InvalidCommitMessageError,
|
|
12
13
|
NoCommitsFoundError,
|
|
@@ -18,7 +19,7 @@ class CheckArgs(TypedDict, total=False):
|
|
|
18
19
|
commit_msg: str
|
|
19
20
|
rev_range: str
|
|
20
21
|
allow_abort: bool
|
|
21
|
-
message_length_limit: int
|
|
22
|
+
message_length_limit: int | None
|
|
22
23
|
allowed_prefixes: list[str]
|
|
23
24
|
message: str
|
|
24
25
|
use_default_range: bool
|
|
@@ -41,8 +42,11 @@ class Check:
|
|
|
41
42
|
self.allow_abort = bool(
|
|
42
43
|
arguments.get("allow_abort", config.settings["allow_abort"])
|
|
43
44
|
)
|
|
45
|
+
|
|
44
46
|
self.use_default_range = bool(arguments.get("use_default_range"))
|
|
45
|
-
self.max_msg_length = arguments.get(
|
|
47
|
+
self.max_msg_length = arguments.get(
|
|
48
|
+
"message_length_limit", config.settings.get("message_length_limit", None)
|
|
49
|
+
)
|
|
46
50
|
|
|
47
51
|
# we need to distinguish between None and [], which is a valid value
|
|
48
52
|
allowed_prefixes = arguments.get("allowed_prefixes")
|
|
@@ -71,7 +75,6 @@ class Check:
|
|
|
71
75
|
self.commit_msg = sys.stdin.read()
|
|
72
76
|
|
|
73
77
|
self.config: BaseConfig = config
|
|
74
|
-
self.encoding = config.settings["encoding"]
|
|
75
78
|
self.cz = factory.committer_factory(self.config)
|
|
76
79
|
|
|
77
80
|
def __call__(self) -> None:
|
|
@@ -79,6 +82,7 @@ class Check:
|
|
|
79
82
|
|
|
80
83
|
Raises:
|
|
81
84
|
InvalidCommitMessageError: if the commit provided not follows the conventional pattern
|
|
85
|
+
NoCommitsFoundError: if no commit is found with the given range
|
|
82
86
|
"""
|
|
83
87
|
commits = self._get_commits()
|
|
84
88
|
if not commits:
|
|
@@ -88,7 +92,7 @@ class Check:
|
|
|
88
92
|
invalid_msgs_content = "\n".join(
|
|
89
93
|
f'commit "{commit.rev}": "{commit.message}"'
|
|
90
94
|
for commit in commits
|
|
91
|
-
if not self._validate_commit_message(commit.message, pattern)
|
|
95
|
+
if not self._validate_commit_message(commit.message, pattern, commit.rev)
|
|
92
96
|
)
|
|
93
97
|
if invalid_msgs_content:
|
|
94
98
|
# TODO: capitalize the first letter of the error message for consistency in v5
|
|
@@ -105,7 +109,9 @@ class Check:
|
|
|
105
109
|
# Get commit message from command line (--message)
|
|
106
110
|
return self.commit_msg
|
|
107
111
|
|
|
108
|
-
with open(
|
|
112
|
+
with open(
|
|
113
|
+
self.commit_msg_file, encoding=self.config.settings["encoding"]
|
|
114
|
+
) as commit_file:
|
|
109
115
|
# Get commit message from file (--commit-msg-file)
|
|
110
116
|
return commit_file.read()
|
|
111
117
|
|
|
@@ -151,7 +157,7 @@ class Check:
|
|
|
151
157
|
return "\n".join(lines)
|
|
152
158
|
|
|
153
159
|
def _validate_commit_message(
|
|
154
|
-
self, commit_msg: str, pattern: re.Pattern[str]
|
|
160
|
+
self, commit_msg: str, pattern: re.Pattern[str], commit_hash: str
|
|
155
161
|
) -> bool:
|
|
156
162
|
if not commit_msg:
|
|
157
163
|
return self.allow_abort
|
|
@@ -159,9 +165,14 @@ class Check:
|
|
|
159
165
|
if any(map(commit_msg.startswith, self.allowed_prefixes)):
|
|
160
166
|
return True
|
|
161
167
|
|
|
162
|
-
if self.max_msg_length:
|
|
168
|
+
if self.max_msg_length is not None:
|
|
163
169
|
msg_len = len(commit_msg.partition("\n")[0].strip())
|
|
164
170
|
if msg_len > self.max_msg_length:
|
|
165
|
-
|
|
171
|
+
raise CommitMessageLengthExceededError(
|
|
172
|
+
f"commit validation: failed!\n"
|
|
173
|
+
f"commit message length exceeds the limit.\n"
|
|
174
|
+
f'commit "{commit_hash}": "{commit_msg}"\n'
|
|
175
|
+
f"message length limit: {self.max_msg_length} (actual: {msg_len})"
|
|
176
|
+
)
|
|
166
177
|
|
|
167
178
|
return bool(pattern.match(commit_msg))
|
|
@@ -33,7 +33,7 @@ class CommitArgs(TypedDict, total=False):
|
|
|
33
33
|
dry_run: bool
|
|
34
34
|
edit: bool
|
|
35
35
|
extra_cli_args: str
|
|
36
|
-
message_length_limit: int
|
|
36
|
+
message_length_limit: int | None
|
|
37
37
|
no_retry: bool
|
|
38
38
|
signoff: bool
|
|
39
39
|
write_message_to_file: Path | None
|
|
@@ -48,47 +48,53 @@ class Commit:
|
|
|
48
48
|
raise NotAGitProjectError()
|
|
49
49
|
|
|
50
50
|
self.config: BaseConfig = config
|
|
51
|
-
self.encoding = config.settings["encoding"]
|
|
52
51
|
self.cz = factory.committer_factory(self.config)
|
|
53
52
|
self.arguments = arguments
|
|
54
|
-
self.
|
|
53
|
+
self.backup_file_path = get_backup_file_path()
|
|
55
54
|
|
|
56
55
|
def _read_backup_message(self) -> str | None:
|
|
57
56
|
# Check the commit backup file exists
|
|
58
|
-
if not
|
|
57
|
+
if not self.backup_file_path.is_file():
|
|
59
58
|
return None
|
|
60
59
|
|
|
61
60
|
# Read commit message from backup
|
|
62
|
-
with open(
|
|
61
|
+
with open(
|
|
62
|
+
self.backup_file_path, encoding=self.config.settings["encoding"]
|
|
63
|
+
) as f:
|
|
63
64
|
return f.read().strip()
|
|
64
65
|
|
|
65
|
-
def
|
|
66
|
+
def _get_message_by_prompt_commit_questions(self) -> str:
|
|
66
67
|
# Prompt user for the commit message
|
|
67
|
-
|
|
68
|
-
questions = cz.questions()
|
|
68
|
+
questions = self.cz.questions()
|
|
69
69
|
for question in (q for q in questions if q["type"] == "list"):
|
|
70
70
|
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
|
|
71
71
|
try:
|
|
72
|
-
answers = questionary.prompt(questions, style=cz.style)
|
|
72
|
+
answers = questionary.prompt(questions, style=self.cz.style)
|
|
73
73
|
except ValueError as err:
|
|
74
74
|
root_err = err.__context__
|
|
75
75
|
if isinstance(root_err, CzException):
|
|
76
|
-
raise CustomError(root_err
|
|
76
|
+
raise CustomError(str(root_err))
|
|
77
77
|
raise err
|
|
78
78
|
|
|
79
79
|
if not answers:
|
|
80
80
|
raise NoAnswersError()
|
|
81
81
|
|
|
82
|
-
message = cz.message(answers)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
f"Length of commit message exceeds limit ({message_len}/{message_length_limit})"
|
|
88
|
-
)
|
|
82
|
+
message = self.cz.message(answers)
|
|
83
|
+
if limit := self.arguments.get(
|
|
84
|
+
"message_length_limit", self.config.settings.get("message_length_limit", 0)
|
|
85
|
+
):
|
|
86
|
+
self._validate_subject_length(message=message, length_limit=limit)
|
|
89
87
|
|
|
90
88
|
return message
|
|
91
89
|
|
|
90
|
+
def _validate_subject_length(self, *, message: str, length_limit: int) -> None:
|
|
91
|
+
# By the contract, message_length_limit is set to 0 for no limit
|
|
92
|
+
subject = message.partition("\n")[0].strip()
|
|
93
|
+
if len(subject) > length_limit:
|
|
94
|
+
raise CommitMessageLengthExceededError(
|
|
95
|
+
f"Length of commit message exceeds limit ({len(subject)}/{length_limit}), subject: '{subject}'"
|
|
96
|
+
)
|
|
97
|
+
|
|
92
98
|
def manual_edit(self, message: str) -> str:
|
|
93
99
|
editor = git.get_core_editor()
|
|
94
100
|
if editor is None:
|
|
@@ -108,16 +114,18 @@ class Commit:
|
|
|
108
114
|
|
|
109
115
|
def _get_message(self) -> str:
|
|
110
116
|
if self.arguments.get("retry"):
|
|
111
|
-
|
|
112
|
-
if
|
|
117
|
+
commit_message = self._read_backup_message()
|
|
118
|
+
if commit_message is None:
|
|
113
119
|
raise NoCommitBackupError()
|
|
114
|
-
return
|
|
120
|
+
return commit_message
|
|
115
121
|
|
|
116
|
-
if
|
|
117
|
-
"
|
|
122
|
+
if (
|
|
123
|
+
self.config.settings.get("retry_after_failure")
|
|
124
|
+
and not self.arguments.get("no_retry")
|
|
125
|
+
and (backup_message := self._read_backup_message())
|
|
118
126
|
):
|
|
119
|
-
return
|
|
120
|
-
return self.
|
|
127
|
+
return backup_message
|
|
128
|
+
return self._get_message_by_prompt_commit_questions()
|
|
121
129
|
|
|
122
130
|
def __call__(self) -> None:
|
|
123
131
|
extra_args = self.arguments.get("extra_cli_args", "")
|
|
@@ -139,15 +147,17 @@ class Commit:
|
|
|
139
147
|
if write_message_to_file is not None and write_message_to_file.is_dir():
|
|
140
148
|
raise NotAllowed(f"{write_message_to_file} is a directory")
|
|
141
149
|
|
|
142
|
-
|
|
150
|
+
commit_message = self._get_message()
|
|
143
151
|
if self.arguments.get("edit"):
|
|
144
|
-
|
|
152
|
+
commit_message = self.manual_edit(commit_message)
|
|
145
153
|
|
|
146
|
-
out.info(f"\n{
|
|
154
|
+
out.info(f"\n{commit_message}\n")
|
|
147
155
|
|
|
148
156
|
if write_message_to_file:
|
|
149
|
-
with smart_open(
|
|
150
|
-
|
|
157
|
+
with smart_open(
|
|
158
|
+
write_message_to_file, "w", encoding=self.config.settings["encoding"]
|
|
159
|
+
) as file:
|
|
160
|
+
file.write(commit_message)
|
|
151
161
|
|
|
152
162
|
if dry_run:
|
|
153
163
|
raise DryRunExit()
|
|
@@ -155,13 +165,15 @@ class Commit:
|
|
|
155
165
|
if self.config.settings["always_signoff"] or signoff:
|
|
156
166
|
extra_args = f"{extra_args} -s".strip()
|
|
157
167
|
|
|
158
|
-
c = git.commit(
|
|
168
|
+
c = git.commit(commit_message, args=extra_args)
|
|
159
169
|
if c.return_code != 0:
|
|
160
170
|
out.error(c.err)
|
|
161
171
|
|
|
162
172
|
# Create commit backup
|
|
163
|
-
with smart_open(
|
|
164
|
-
|
|
173
|
+
with smart_open(
|
|
174
|
+
self.backup_file_path, "w", encoding=self.config.settings["encoding"]
|
|
175
|
+
) as f:
|
|
176
|
+
f.write(commit_message)
|
|
165
177
|
|
|
166
178
|
raise CommitError()
|
|
167
179
|
|
|
@@ -170,7 +182,7 @@ class Commit:
|
|
|
170
182
|
return
|
|
171
183
|
|
|
172
184
|
with contextlib.suppress(FileNotFoundError):
|
|
173
|
-
|
|
185
|
+
self.backup_file_path.unlink()
|
|
174
186
|
out.write(c.err)
|
|
175
187
|
out.write(c.out)
|
|
176
188
|
out.success("Commit successful!")
|