Skip to content

Commit 72fbe95

Browse files
fix(version_schemes): support arbitrary semver pre-release labels
Extend BaseVersion with a custom _VERSION_PATTERN regex that accepts arbitrary pre-release identifiers (e.g., -release, -SNAPSHOT, -reallyweird) instead of only PEP 440's alpha/beta/rc. This fixes InvalidVersion errors when using tags like v0.7.1-release or v0.0.1-SNAPSHOT with commitizen's changelog and bump commands. Closes #950 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 35ffe03 commit 72fbe95

4 files changed

Lines changed: 92 additions & 4 deletions

File tree

commitizen/version_schemes.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def __ne__(self, other: object) -> bool:
121121
def bump(
122122
self,
123123
increment: Increment | None,
124-
prerelease: Prerelease | None = None,
124+
prerelease: str | None = None,
125125
prerelease_offset: int = 0,
126126
devrelease: int | None = None,
127127
is_local_version: bool = False,
@@ -145,11 +145,57 @@ def bump(
145145
VersionScheme: TypeAlias = type[VersionProtocol]
146146

147147

148+
# Custom version pattern that extends packaging's PEP 440 regex to support
149+
# arbitrary semver pre-release labels (e.g., -release, -SNAPSHOT, -reallyweird).
150+
# Python's packaging library does not use semver; it predates it. We cannot fully
151+
# rely on packaging.version for semver-compatible parsing.
152+
# See: https://github.com/pypa/packaging/blob/14b83e15dbb9caa87c63646ba7808b2b5e460ce6/src/packaging/version.py#L117
153+
_VERSION_PATTERN = r"""^\s*
154+
v?
155+
(?:
156+
(?:(?P<epoch>[0-9]+)!)? # epoch
157+
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
158+
(?P<pre> # pre-release
159+
[-_\.]?
160+
(?P<pre_l>
161+
(?! # negative lookahead to prevent
162+
[-_\.]? # matching post, rev, r, dev
163+
(post|rev|r|dev)
164+
[-_\.]?
165+
([0-9]+)?
166+
$)
167+
[a-z]+? # match any letters (semver support)
168+
)
169+
[-_\.]?
170+
(?P<pre_n>[0-9]+)?
171+
)?
172+
(?P<post> # post release
173+
(?:-(?P<post_n1>[0-9]+))
174+
|
175+
(?:
176+
[-_\.]?
177+
(?P<post_l>post|rev|r)
178+
[-_\.]?
179+
(?P<post_n2>[0-9]+)?
180+
)
181+
)?
182+
(?P<dev> # dev release
183+
[-_\.]?
184+
(?P<dev_l>dev)
185+
[-_\.]?
186+
(?P<dev_n>[0-9]+)?
187+
)?
188+
)
189+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
190+
\s*$"""
191+
192+
148193
class BaseVersion(_BaseVersion):
149194
"""
150195
A base class implementing the `VersionProtocol` for PEP440-like versions.
151196
"""
152197

198+
_regex: re.Pattern = re.compile(_VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
153199
parser: ClassVar[re.Pattern] = _DEFAULT_VERSION_PARSER
154200
"""Regex capturing this version scheme into a `version` group"""
155201

@@ -232,7 +278,7 @@ def increment_base(self, increment: Increment | None = None) -> str:
232278
def bump(
233279
self,
234280
increment: Increment | None,
235-
prerelease: Prerelease | None = None,
281+
prerelease: str | None = None,
236282
prerelease_offset: int = 0,
237283
devrelease: int | None = None,
238284
is_local_version: bool = False,

tests/test_version_scheme_semver.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,37 @@
250250
),
251251
"1.0.0",
252252
),
253+
# arbitrary semver pre-release labels (issue #950)
254+
(
255+
VersionSchemeTestArgs(
256+
current_version="1.0.0-reallyweird",
257+
increment="PATCH",
258+
prerelease="reallyweird",
259+
prerelease_offset=0,
260+
devrelease=None,
261+
),
262+
"1.0.0-reallyweird1",
263+
),
264+
(
265+
VersionSchemeTestArgs(
266+
current_version="v0.7.1-release",
267+
increment="PATCH",
268+
prerelease="release",
269+
prerelease_offset=0,
270+
devrelease=None,
271+
),
272+
"0.7.1-release1",
273+
),
274+
(
275+
VersionSchemeTestArgs(
276+
current_version="v0.0.1-SNAPSHOT",
277+
increment="PATCH",
278+
prerelease="SNAPSHOT",
279+
prerelease_offset=0,
280+
devrelease=None,
281+
),
282+
"0.0.1-snapshot1",
283+
),
253284
# simple flow
254285
(
255286
VersionSchemeTestArgs(

tests/test_version_scheme_semver2.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,17 @@
240240
),
241241
"1.0.0",
242242
),
243+
# arbitrary semver pre-release labels (issue #950)
244+
(
245+
VersionSchemeTestArgs(
246+
current_version="1.0.0-reallyweird",
247+
increment="PATCH",
248+
prerelease="reallyweird",
249+
prerelease_offset=0,
250+
devrelease=None,
251+
),
252+
"1.0.0-reallyweird.1",
253+
),
243254
# simple_flow
244255
(
245256
VersionSchemeTestArgs(

tests/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
from freezegun.api import FrozenDateTimeFactory
1919
from pytest_mock import MockerFixture
2020

21-
from commitizen.version_schemes import Increment, Prerelease
21+
from commitizen.version_schemes import Increment
2222

2323

2424
class VersionSchemeTestArgs(NamedTuple):
2525
current_version: str
2626
increment: Increment | None
27-
prerelease: Prerelease | None
27+
prerelease: str | None
2828
prerelease_offset: int
2929
devrelease: int | None
3030

0 commit comments

Comments
 (0)