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 2ad26e0

Browse files
feat(schemes): adds support for SemVer 2.0 (dot in pre-releases) (fix #1025) (#1072)
1 parent aad0602 commit 2ad26e0

File tree

5 files changed

+281
-15
lines changed

5 files changed

+281
-15
lines changed

‎commitizen/version_schemes.py‎

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ class SemVer(BaseVersion):
310310
"""
311311
Semantic Versioning (SemVer) scheme
312312
313-
See: https://semver.org/
313+
See: https://semver.org/spec/v1.0.0.html
314314
"""
315315

316316
def __str__(self) -> str:
@@ -324,9 +324,8 @@ def __str__(self) -> str:
324324
parts.append(".".join(str(x) for x in self.release))
325325

326326
# Pre-release
327-
if self.pre:
328-
pre = "".join(str(x) for x in self.pre)
329-
parts.append(f"-{pre}")
327+
if self.prerelease:
328+
parts.append(f"-{self.prerelease}")
330329

331330
# Post-release
332331
if self.post is not None:
@@ -343,6 +342,60 @@ def __str__(self) -> str:
343342
return "".join(parts)
344343

345344

345+
class SemVer2(SemVer):
346+
"""
347+
Semantic Versioning 2.0 (SemVer2) schema
348+
349+
See: https://semver.org/spec/v2.0.0.html
350+
"""
351+
352+
_STD_PRELEASES = {
353+
"a": "alpha",
354+
"b": "beta",
355+
}
356+
357+
@property
358+
def prerelease(self) -> str | None:
359+
if self.is_prerelease and self.pre:
360+
prerelease_type = self._STD_PRELEASES.get(self.pre[0], self.pre[0])
361+
return f"{prerelease_type}.{self.pre[1]}"
362+
return None
363+
364+
def __str__(self) -> str:
365+
parts = []
366+
367+
# Epoch
368+
if self.epoch != 0:
369+
parts.append(f"{self.epoch}!")
370+
371+
# Release segment
372+
parts.append(".".join(str(x) for x in self.release))
373+
374+
# Pre-release identifiers
375+
# See: https://semver.org/spec/v2.0.0.html#spec-item-9
376+
prerelease_parts = []
377+
if self.prerelease:
378+
prerelease_parts.append(f"{self.prerelease}")
379+
380+
# Post-release
381+
if self.post is not None:
382+
prerelease_parts.append(f"post.{self.post}")
383+
384+
# Development release
385+
if self.dev is not None:
386+
prerelease_parts.append(f"dev.{self.dev}")
387+
388+
if prerelease_parts:
389+
parts.append("-")
390+
parts.append(".".join(prerelease_parts))
391+
392+
# Local version segment
393+
if self.local:
394+
parts.append(f"+{self.local}")
395+
396+
return "".join(parts)
397+
398+
346399
DEFAULT_SCHEME: VersionScheme = Pep440
347400

348401
SCHEMES_ENTRYPOINT = "commitizen.scheme"

‎docs/bump.md‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ $ cz bump --help
5555
usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog] [--no-verify] [--yes] [--tag-format TAG_FORMAT]
5656
[--bump-message BUMP_MESSAGE] [--prerelease {alpha,beta,rc}] [--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
5757
[--check-consistency] [--annotated-tag] [--gpg-sign] [--changelog-to-stdout] [--git-output-to-stderr] [--retry] [--major-version-zero]
58-
[--prerelease-offset PRERELEASE_OFFSET] [--version-scheme {semver,pep440}] [--version-type {semver,pep440}] [--build-metadata BUILD_METADATA]
58+
[--prerelease-offset PRERELEASE_OFFSET] [--version-scheme {pep440,semver,semver2}] [--version-type {pep440,semver,semver2}] [--build-metadata BUILD_METADATA]
5959
[MANUAL_VERSION]
6060

6161
positional arguments:
@@ -97,9 +97,9 @@ options:
9797
--major-version-zero keep major version at zero, even for breaking changes
9898
--prerelease-offset PRERELEASE_OFFSET
9999
start pre-releases with this offset
100-
--version-scheme {semver,pep440}
100+
--version-scheme {pep440,semver,semver2}
101101
choose version scheme
102-
--version-type {semver,pep440}
102+
--version-type {pep440,semver,semver2}
103103
Deprecated, use --version-scheme
104104
--build-metadata {BUILD_METADATA}
105105
additional metadata in the version string
@@ -619,14 +619,14 @@ prerelease_offset = 1
619619
620620
Choose version scheme
621621
622-
| schemes | pep440 | semver |
623-
| -------------- | -------------- | --------------- |
624-
| non-prerelease | `0.1.0` | `0.1.0` |
625-
| prerelease | `0.3.1a0` | `0.3.1-a0` |
626-
| devrelease | `0.1.1.dev1` | `0.1.1-dev1` |
627-
| dev and pre | `1.0.0a3.dev1` | `1.0.0-a3-dev1` |
622+
| schemes | pep440 | semver | semver2 |
623+
| -------------- | -------------- | --------------- | --------------------- |
624+
| non-prerelease | `0.1.0` | `0.1.0` | `0.1.0` |
625+
| prerelease | `0.3.1a0` | `0.3.1-a0` | `0.3.1-alpha.0` |
626+
| devrelease | `0.1.1.dev1` | `0.1.1-dev1` | `0.1.1-dev.1` |
627+
| dev and pre | `1.0.0a3.dev1` | `1.0.0-a3-dev1` | `1.0.0-alpha.3.dev.1` |
628628
629-
Options: `semver`, `pep440`
629+
Options: `pep440`, `semver`, `semver2`
630630
631631
Defaults to: `pep440`
632632

‎docs/config.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ Type: `str`
4040

4141
Default: `pep440`
4242

43-
Select a version scheme from the following options [`pep440`, `semver`]. Useful for non-python projects. [Read more][version-scheme]
43+
Select a version scheme from the following options [`pep440`, `semver`, `semver2`].
44+
Useful for non-python projects. [Read more][version-scheme]
4445

4546
### `tag_format`
4647

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ scm = "commitizen.providers:ScmProvider"
103103
[tool.poetry.plugins."commitizen.scheme"]
104104
pep440 = "commitizen.version_schemes:Pep440"
105105
semver = "commitizen.version_schemes:SemVer"
106+
semver2 = "commitizen.version_schemes:SemVer2"
106107

107108
[tool.coverage]
108109
[tool.coverage.report]

‎tests/test_version_scheme_semver2.py‎

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import itertools
2+
import random
3+
4+
import pytest
5+
6+
from commitizen.version_schemes import SemVer2, VersionProtocol
7+
8+
simple_flow = [
9+
(("0.1.0", "PATCH", None, 0, None), "0.1.1"),
10+
(("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev.1"),
11+
(("0.1.1", "MINOR", None, 0, None), "0.2.0"),
12+
(("0.2.0", "MINOR", None, 0, None), "0.3.0"),
13+
(("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev.1"),
14+
(("0.3.0", "PATCH", None, 0, None), "0.3.1"),
15+
(("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-alpha.0"),
16+
(("0.3.1-alpha.0", None, "alpha", 0, None), "0.3.1-alpha.1"),
17+
(("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-alpha.1"),
18+
(("0.3.1-alpha.0", None, "alpha", 1, None), "0.3.1-alpha.1"),
19+
(("0.3.1-alpha.0", None, None, 0, None), "0.3.1"),
20+
(("0.3.1", "PATCH", None, 0, None), "0.3.2"),
21+
(("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"),
22+
(("1.0.0-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"),
23+
(("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"),
24+
(("1.0.0-alpha.1", None, "alpha", 0, 1), "1.0.0-alpha.2.dev.1"),
25+
(("1.0.0-alpha.2.dev.0", None, "alpha", 0, 1), "1.0.0-alpha.3.dev.1"),
26+
(("1.0.0-alpha.2.dev.0", None, "alpha", 0, 0), "1.0.0-alpha.3.dev.0"),
27+
(("1.0.0-alpha.1", None, "beta", 0, None), "1.0.0-beta.0"),
28+
(("1.0.0-beta.0", None, "beta", 0, None), "1.0.0-beta.1"),
29+
(("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"),
30+
(("1.0.0-rc.0", None, "rc", 0, None), "1.0.0-rc.1"),
31+
(("1.0.0-rc.0", None, "rc", 0, 1), "1.0.0-rc.1.dev.1"),
32+
(("1.0.0-rc.0", "PATCH", None, 0, None), "1.0.0"),
33+
(("1.0.0-alpha.3.dev.0", None, "beta", 0, None), "1.0.0-beta.0"),
34+
(("1.0.0", "PATCH", None, 0, None), "1.0.1"),
35+
(("1.0.1", "PATCH", None, 0, None), "1.0.2"),
36+
(("1.0.2", "MINOR", None, 0, None), "1.1.0"),
37+
(("1.1.0", "MINOR", None, 0, None), "1.2.0"),
38+
(("1.2.0", "PATCH", None, 0, None), "1.2.1"),
39+
(("1.2.1", "MAJOR", None, 0, None), "2.0.0"),
40+
]
41+
42+
local_versions = [
43+
(("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"),
44+
(("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"),
45+
(("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"),
46+
]
47+
48+
# never bump backwards on pre-releases
49+
linear_prerelease_cases = [
50+
(("0.1.1-beta.1", None, "alpha", 0, None), "0.1.1-beta.2"),
51+
(("0.1.1-rc.0", None, "alpha", 0, None), "0.1.1-rc.1"),
52+
(("0.1.1-rc.0", None, "beta", 0, None), "0.1.1-rc.1"),
53+
]
54+
55+
weird_cases = [
56+
(("1.1", "PATCH", None, 0, None), "1.1.1"),
57+
(("1", "MINOR", None, 0, None), "1.1.0"),
58+
(("1", "MAJOR", None, 0, None), "2.0.0"),
59+
(("1-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"),
60+
(("1-alpha.0", None, "alpha", 1, None), "1.0.0-alpha.1"),
61+
(("1", None, "beta", 0, None), "1.0.0-beta.0"),
62+
(("1", None, "beta", 1, None), "1.0.0-beta.1"),
63+
(("1-beta", None, "beta", 0, None), "1.0.0-beta.1"),
64+
(("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"),
65+
(("1", None, "rc", 0, None), "1.0.0-rc.0"),
66+
(("1.0.0-rc.1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"),
67+
]
68+
69+
# test driven development
70+
tdd_cases = [
71+
(("0.1.1", "PATCH", None, 0, None), "0.1.2"),
72+
(("0.1.1", "MINOR", None, 0, None), "0.2.0"),
73+
(("2.1.1", "MAJOR", None, 0, None), "3.0.0"),
74+
(("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-alpha.0"),
75+
(("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-alpha.0"),
76+
(("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"),
77+
(("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-alpha.1"),
78+
(("1.0.0-alpha.2", None, "beta", 0, None), "1.0.0-beta.0"),
79+
(("1.0.0-alpha.2", None, "beta", 1, None), "1.0.0-beta.1"),
80+
(("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"),
81+
(("1.0.0-rc.1", None, "rc", 0, None), "1.0.0-rc.2"),
82+
(("1.0.0-alpha.0", None, "rc", 0, None), "1.0.0-rc.0"),
83+
(("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"),
84+
]
85+
86+
87+
@pytest.mark.parametrize(
88+
"test_input, expected",
89+
itertools.chain(tdd_cases, weird_cases, simple_flow, linear_prerelease_cases),
90+
)
91+
def test_bump_semver_version(test_input, expected):
92+
current_version = test_input[0]
93+
increment = test_input[1]
94+
prerelease = test_input[2]
95+
prerelease_offset = test_input[3]
96+
devrelease = test_input[4]
97+
assert (
98+
str(
99+
SemVer2(current_version).bump(
100+
increment=increment,
101+
prerelease=prerelease,
102+
prerelease_offset=prerelease_offset,
103+
devrelease=devrelease,
104+
)
105+
)
106+
== expected
107+
)
108+
109+
110+
@pytest.mark.parametrize("test_input,expected", local_versions)
111+
def test_bump_semver_version_local(test_input, expected):
112+
current_version = test_input[0]
113+
increment = test_input[1]
114+
prerelease = test_input[2]
115+
prerelease_offset = test_input[3]
116+
devrelease = test_input[4]
117+
is_local_version = True
118+
assert (
119+
str(
120+
SemVer2(current_version).bump(
121+
increment=increment,
122+
prerelease=prerelease,
123+
prerelease_offset=prerelease_offset,
124+
devrelease=devrelease,
125+
is_local_version=is_local_version,
126+
)
127+
)
128+
== expected
129+
)
130+
131+
132+
def test_semver_scheme_property():
133+
version = SemVer2("0.0.1")
134+
assert version.scheme is SemVer2
135+
136+
137+
def test_semver_implement_version_protocol():
138+
assert isinstance(SemVer2("0.0.1"), VersionProtocol)
139+
140+
141+
def test_semver_sortable():
142+
test_input = [x[0][0] for x in simple_flow]
143+
test_input.extend([x[1] for x in simple_flow])
144+
# randomize
145+
random_input = [SemVer2(x) for x in random.sample(test_input, len(test_input))]
146+
assert len(random_input) == len(test_input)
147+
sorted_result = [str(x) for x in sorted(random_input)]
148+
assert sorted_result == [
149+
"0.1.0",
150+
"0.1.0",
151+
"0.1.1-dev.1",
152+
"0.1.1",
153+
"0.1.1",
154+
"0.2.0",
155+
"0.2.0",
156+
"0.2.0",
157+
"0.3.0-dev.1",
158+
"0.3.0",
159+
"0.3.0",
160+
"0.3.0",
161+
"0.3.0",
162+
"0.3.1-alpha.0",
163+
"0.3.1-alpha.0",
164+
"0.3.1-alpha.0",
165+
"0.3.1-alpha.0",
166+
"0.3.1-alpha.1",
167+
"0.3.1-alpha.1",
168+
"0.3.1-alpha.1",
169+
"0.3.1",
170+
"0.3.1",
171+
"0.3.1",
172+
"0.3.2",
173+
"0.4.2",
174+
"1.0.0-alpha.0",
175+
"1.0.0-alpha.0",
176+
"1.0.0-alpha.1",
177+
"1.0.0-alpha.1",
178+
"1.0.0-alpha.1",
179+
"1.0.0-alpha.1",
180+
"1.0.0-alpha.2.dev.0",
181+
"1.0.0-alpha.2.dev.0",
182+
"1.0.0-alpha.2.dev.1",
183+
"1.0.0-alpha.2",
184+
"1.0.0-alpha.3.dev.0",
185+
"1.0.0-alpha.3.dev.0",
186+
"1.0.0-alpha.3.dev.1",
187+
"1.0.0-beta.0",
188+
"1.0.0-beta.0",
189+
"1.0.0-beta.0",
190+
"1.0.0-beta.1",
191+
"1.0.0-beta.1",
192+
"1.0.0-rc.0",
193+
"1.0.0-rc.0",
194+
"1.0.0-rc.0",
195+
"1.0.0-rc.0",
196+
"1.0.0-rc.1.dev.1",
197+
"1.0.0-rc.1",
198+
"1.0.0",
199+
"1.0.0",
200+
"1.0.1",
201+
"1.0.1",
202+
"1.0.2",
203+
"1.0.2",
204+
"1.1.0",
205+
"1.1.0",
206+
"1.2.0",
207+
"1.2.0",
208+
"1.2.1",
209+
"1.2.1",
210+
"2.0.0",
211+
]

0 commit comments

Comments
(0)

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