Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0cc5cef

Browse files
fix(changelog): Handle tag format without version pattern
1 parent 0a89510 commit 0cc5cef

10 files changed

+328
-30
lines changed

‎commitizen/changelog.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ def get_version_tags(
9898
) -> list[GitTag]:
9999
valid_tags: list[GitTag] = []
100100
TAG_FORMAT_REGEXS = {
101-
"$version": str(scheme.parser.pattern),
101+
"$version": scheme.parser.pattern,
102102
"$major": r"(?P<major>\d+)",
103103
"$minor": r"(?P<minor>\d+)",
104104
"$patch": r"(?P<patch>\d+)",
105105
"$prerelease": r"(?P<prerelease>\w+\d+)?",
106106
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
107-
"${version}": str(scheme.parser.pattern),
107+
"${version}": scheme.parser.pattern,
108108
"${major}": r"(?P<major>\d+)",
109109
"${minor}": r"(?P<minor>\d+)",
110110
"${patch}": r"(?P<patch>\d+)",

‎commitizen/changelog_formats/asciidoc.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,19 @@ def parse_version_from_title(self, line: str) -> str | None:
1818
matches = list(re.finditer(self.version_parser, m.group("title")))
1919
if not matches:
2020
return None
21-
return matches[-1].group("version")
21+
if "version" in matches[-1].groupdict():
22+
return matches[-1].group("version")
23+
partial_matches = matches[-1].groupdict()
24+
try:
25+
partial_version = f"{partial_matches['major']}.{partial_matches['minor']}.{partial_matches['patch']}"
26+
except KeyError:
27+
return None
28+
29+
if partial_matches.get("prerelease"):
30+
partial_version += f"-{partial_matches['prerelease']}"
31+
if partial_matches.get("devrelease"):
32+
partial_version += f"+{partial_matches['devrelease']}"
33+
return partial_version
2234

2335
def parse_title_level(self, line: str) -> int | None:
2436
m = self.RE_TITLE.match(line)

‎commitizen/changelog_formats/base.py‎

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import os
4+
import re
45
from abc import ABCMeta
56
from re import Pattern
67
from typing import IO, Any, ClassVar
@@ -24,30 +25,29 @@ def __init__(self, config: BaseConfig):
2425
# Constructor needs to be redefined because `Protocol` prevent instantiation by default
2526
# See: https://bugs.python.org/issue44807
2627
self.config = config
27-
self.tag_format = self.config.settings.get("tag_format")
28+
self.tag_format = self.config.settings["tag_format"]
2829

2930
@property
3031
def version_parser(self) -> Pattern:
32+
tag_regex: str = self.tag_format
3133
version_regex = get_version_scheme(self.config).parser.pattern
32-
if self.tag_format != "$version":
33-
TAG_FORMAT_REGEXS = {
34-
"$version": version_regex,
35-
"$major": "(?P<major>\d+)",
36-
"$minor": "(?P<minor>\d+)",
37-
"$patch": "(?P<patch>\d+)",
38-
"$prerelease": "(?P<prerelease>\w+\d+)?",
39-
"$devrelease": "(?P<devrelease>\.dev\d+)?",
40-
"${version}": version_regex,
41-
"${major}": "(?P<major>\d+)",
42-
"${minor}": "(?P<minor>\d+)",
43-
"${patch}": "(?P<patch>\d+)",
44-
"${prerelease}": "(?P<prerelease>\w+\d+)?",
45-
"${devrelease}": "(?P<devrelease>\.dev\d+)?",
46-
}
47-
version_regex = self.tag_format
48-
for pattern, regex in TAG_FORMAT_REGEXS.items():
49-
version_regex = version_regex.replace(pattern, regex)
50-
return rf"{version_regex}"
34+
TAG_FORMAT_REGEXS = {
35+
"$version": version_regex,
36+
"$major": r"(?P<major>\d+)",
37+
"$minor": r"(?P<minor>\d+)",
38+
"$patch": r"(?P<patch>\d+)",
39+
"$prerelease": r"(?P<prerelease>\w+\d+)?",
40+
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
41+
"${version}": version_regex,
42+
"${major}": r"(?P<major>\d+)",
43+
"${minor}": r"(?P<minor>\d+)",
44+
"${patch}": r"(?P<patch>\d+)",
45+
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
46+
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
47+
}
48+
for pattern, regex in TAG_FORMAT_REGEXS.items():
49+
tag_regex = tag_regex.replace(pattern, regex)
50+
return re.compile(tag_regex)
5151

5252
def get_metadata(self, filepath: str) -> Metadata:
5353
if not os.path.isfile(filepath):

‎commitizen/changelog_formats/markdown.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@ def parse_version_from_title(self, line: str) -> str | None:
1919
m = re.search(self.version_parser, m.group("title"))
2020
if not m:
2121
return None
22-
return m.group("version")
22+
if "version" in m.groupdict():
23+
return m.group("version")
24+
matches = m.groupdict()
25+
try:
26+
partial_version = (
27+
f"{matches['major']}.{matches['minor']}.{matches['patch']}"
28+
)
29+
except KeyError:
30+
return None
31+
32+
if matches.get("prerelease"):
33+
partial_version += f"-{matches['prerelease']}"
34+
if matches.get("devrelease"):
35+
partial_version += f"+{matches['devrelease']}"
36+
return partial_version
2337

2438
def parse_title_level(self, line: str) -> int | None:
2539
m = self.RE_TITLE.match(line)

‎commitizen/changelog_formats/restructuredtext.py‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
4646
third = third.strip().lower()
4747
title: str | None = None
4848
kind: TitleKind | None = None
49-
5049
if self.is_overlined_title(first, second, third):
5150
title = second
5251
kind = (first[0], third[0])
@@ -67,10 +66,25 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
6766
# Try to find the latest release done
6867
m = re.search(self.version_parser, title)
6968
if m:
70-
version = m.group("version")
71-
meta.latest_version = version
72-
meta.latest_version_position = index
73-
break # there's no need for more info
69+
matches = m.groupdict()
70+
if "version" in matches:
71+
version = m.group("version")
72+
meta.latest_version = version
73+
meta.latest_version_position = index
74+
break # there's no need for more info
75+
try:
76+
partial_version = (
77+
f"{matches['major']}.{matches['minor']}.{matches['patch']}"
78+
)
79+
if matches.get("prerelease"):
80+
partial_version += f"-{matches['prerelease']}"
81+
if matches.get("devrelease"):
82+
partial_version += f"+{matches['devrelease']}"
83+
meta.latest_version = partial_version
84+
meta.latest_version_position = index
85+
break
86+
except KeyError:
87+
pass
7488
if meta.unreleased_start is not None and meta.unreleased_end is None:
7589
meta.unreleased_end = (
7690
meta.latest_version_position if meta.latest_version else index + 1

‎commitizen/changelog_formats/textile.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,21 @@ def parse_version_from_title(self, line: str) -> str | None:
1616
m = re.search(self.version_parser, line)
1717
if not m:
1818
return None
19-
return m.group("version")
19+
if "version" in m.groupdict():
20+
return m.group("version")
21+
matches = m.groupdict()
22+
try:
23+
partial_version = (
24+
f"{matches['major']}.{matches['minor']}.{matches['patch']}"
25+
)
26+
except KeyError:
27+
return None
28+
29+
if matches.get("prerelease"):
30+
partial_version += f"-{matches['prerelease']}"
31+
if matches.get("devrelease"):
32+
partial_version += f"+{matches['devrelease']}"
33+
return partial_version
2034

2135
def parse_title_level(self, line: str) -> int | None:
2236
m = self.RE_TITLE.match(line)

‎tests/test_changelog_format_asciidoc.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,42 @@
7373
unreleased_start=1,
7474
)
7575

76+
CHANGELOG_E = """
77+
= Changelog
78+
79+
All notable changes to this project will be documented in this file.
80+
81+
The format is based on https://keepachangelog.com/en/1.0.0/[Keep a Changelog],
82+
and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning].
83+
84+
== [Unreleased]
85+
* Start using "changelog" over "change log" since it's the common usage.
86+
87+
== [{tag_formatted_version}] - 2017年06月20日
88+
=== Added
89+
* New visual identity by https://github.com/tylerfortune8[@tylerfortune8].
90+
* Version navigation.
91+
""".strip()
92+
93+
EXPECTED_E = Metadata(
94+
latest_version="1.0.0",
95+
latest_version_position=10,
96+
unreleased_end=10,
97+
unreleased_start=7,
98+
)
99+
76100

77101
@pytest.fixture
78102
def format(config: BaseConfig) -> AsciiDoc:
79103
return AsciiDoc(config)
80104

81105

106+
@pytest.fixture
107+
def format_with_tags(config: BaseConfig, request) -> AsciiDoc:
108+
config.settings["tag_format"] = request.param
109+
return AsciiDoc(config)
110+
111+
82112
VERSIONS_EXAMPLES = [
83113
("== [1.0.0] - 2017年06月20日", "1.0.0"),
84114
(
@@ -136,3 +166,34 @@ def test_get_matadata(
136166
changelog.write_text(content)
137167

138168
assert format.get_metadata(str(changelog)) == expected
169+
170+
171+
@pytest.mark.parametrize(
172+
"format_with_tags, tag_string, expected, ",
173+
(
174+
pytest.param("${version}-example", "1.0.0-example", "1.0.0"),
175+
pytest.param("${version}example", "1.0.0example", "1.0.0"),
176+
pytest.param("example${version}", "example1.0.0", "1.0.0"),
177+
pytest.param("example-${version}", "example-1.0.0", "1.0.0"),
178+
pytest.param("example-${major}-${minor}-${patch}", "example-1-0-0", "1.0.0"),
179+
pytest.param("example-${major}-${minor}", "example-1-0-0", None),
180+
pytest.param(
181+
"${major}-${minor}-${patch}-${prerelease}-example",
182+
"1-0-0-rc1-example",
183+
"1.0.0-rc1",
184+
),
185+
pytest.param(
186+
"${major}-${minor}-${patch}-${prerelease}-example",
187+
"1-0-0-a1-example",
188+
"1.0.0-a1",
189+
),
190+
),
191+
indirect=["format_with_tags"],
192+
)
193+
def test_get_metadata_custom_tag_format(
194+
tmp_path: Path, format_with_tags: AsciiDoc, tag_string: str, expected: Metadata
195+
):
196+
content = CHANGELOG_E.format(tag_formatted_version=tag_string)
197+
changelog = tmp_path / format_with_tags.default_changelog_file
198+
changelog.write_text(content)
199+
assert format_with_tags.get_metadata(str(changelog)).latest_version == expected

‎tests/test_changelog_format_markdown.py‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,42 @@
7373
unreleased_start=1,
7474
)
7575

76+
CHANGELOG_E = """
77+
# Changelog
78+
79+
All notable changes to this project will be documented in this file.
80+
81+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
82+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
83+
84+
## [Unreleased]
85+
- Start using "changelog" over "change log" since it's the common usage.
86+
87+
## {tag_formatted_version} - 2017年06月20日
88+
### Added
89+
- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8).
90+
- Version navigation.
91+
""".strip()
92+
93+
EXPECTED_E = Metadata(
94+
latest_version="1.0.0",
95+
latest_version_position=10,
96+
unreleased_end=10,
97+
unreleased_start=7,
98+
)
99+
76100

77101
@pytest.fixture
78102
def format(config: BaseConfig) -> Markdown:
79103
return Markdown(config)
80104

81105

106+
@pytest.fixture
107+
def format_with_tags(config: BaseConfig, request) -> Markdown:
108+
config.settings["tag_format"] = request.param
109+
return Markdown(config)
110+
111+
82112
VERSIONS_EXAMPLES = [
83113
("## [1.0.0] - 2017年06月20日", "1.0.0"),
84114
(
@@ -136,3 +166,35 @@ def test_get_matadata(
136166
changelog.write_text(content)
137167

138168
assert format.get_metadata(str(changelog)) == expected
169+
170+
171+
@pytest.mark.parametrize(
172+
"format_with_tags, tag_string, expected, ",
173+
(
174+
pytest.param("${version}-example", "1.0.0-example", "1.0.0"),
175+
pytest.param("${version}example", "1.0.0example", "1.0.0"),
176+
pytest.param("example${version}", "example1.0.0", "1.0.0"),
177+
pytest.param("example-${version}", "example-1.0.0", "1.0.0"),
178+
pytest.param("example-${major}-${minor}-${patch}", "example-1-0-0", "1.0.0"),
179+
pytest.param("example-${major}-${minor}", "example-1-0-0", None),
180+
pytest.param(
181+
"${major}-${minor}-${patch}-${prerelease}-example",
182+
"1-0-0-rc1-example",
183+
"1.0.0-rc1",
184+
),
185+
pytest.param(
186+
"${major}-${minor}-${patch}-${prerelease}-example",
187+
"1-0-0-a1-example",
188+
"1.0.0-a1",
189+
),
190+
),
191+
indirect=["format_with_tags"],
192+
)
193+
def test_get_metadata_custom_tag_format(
194+
tmp_path: Path, format_with_tags: Markdown, tag_string: str, expected: Metadata
195+
):
196+
content = CHANGELOG_E.format(tag_formatted_version=tag_string)
197+
changelog = tmp_path / format_with_tags.default_changelog_file
198+
changelog.write_text(content)
199+
200+
assert format_with_tags.get_metadata(str(changelog)).latest_version == expected

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /