Skip to content

Commit 03899d1

Browse files
committed
feat(version-schemes): add monotonic version scheme implementation
1 parent fbee941 commit 03899d1

File tree

8 files changed

+125
-8
lines changed

8 files changed

+125
-8
lines changed

commitizen/tags.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,10 @@ def normalize_tag(
228228
version = self.scheme(version) if isinstance(version, str) else version
229229
tag_format = tag_format or self.tag_format
230230

231-
major, minor, patch = version.release
231+
release = list(version.release)
232+
while len(release) < 3:
233+
release.append(0)
234+
major, minor, patch = release[:3]
232235
prerelease = version.prerelease or ""
233236

234237
t = Template(tag_format)

commitizen/version_schemes.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,26 @@ def _get_increment_base(
298298
return f"{self.major}.{self.minor}.{self.micro}"
299299

300300

301+
class MonotonicVersion(BaseVersion):
302+
"""
303+
Monotonic versioning scheme
304+
305+
Any increment bump simply increases the single numeric component.
306+
"""
307+
308+
parser: ClassVar[re.Pattern] = re.compile(
309+
r"v?(?P<version>([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)"
310+
)
311+
312+
def increment_base(self, increment: Increment | None = None) -> str:
313+
return f"{self.major + 1}"
314+
315+
def _get_increment_base(
316+
self, increment: Increment | None, exact_increment: bool
317+
) -> str:
318+
return self.increment_base(increment)
319+
320+
301321
class Pep440(BaseVersion):
302322
"""
303323
PEP 440 Version Scheme

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ uv = "commitizen.providers:UvProvider"
8484
pep440 = "commitizen.version_schemes:Pep440"
8585
semver = "commitizen.version_schemes:SemVer"
8686
semver2 = "commitizen.version_schemes:SemVer2"
87+
monotonic = "commitizen.version_schemes:MonotonicVersion"
8788

8889
[dependency-groups]
8990
dev = ["ipython>=8.0", "tox>4", "poethepoet>=0.34.0"]

tests/commands/test_bump_command/test_bump_command_shows_description_when_use_help_option.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
88
[--changelog-to-stdout] [--git-output-to-stderr] [--retry]
99
[--major-version-zero] [--template TEMPLATE] [--extra EXTRA]
1010
[--file-name FILE_NAME] [--prerelease-offset PRERELEASE_OFFSET]
11-
[--version-scheme {pep440,semver,semver2}]
12-
[--version-type {pep440,semver,semver2}]
11+
[--version-scheme {monotonic,pep440,semver,semver2}]
12+
[--version-type {monotonic,pep440,semver,semver2}]
1313
[--build-metadata BUILD_METADATA] [--get-next]
1414
[--allow-no-commit]
1515
[MANUAL_VERSION]
@@ -71,9 +71,9 @@ options:
7171
file name of changelog (default: 'CHANGELOG.md')
7272
--prerelease-offset PRERELEASE_OFFSET
7373
start pre-releases with this offset
74-
--version-scheme {pep440,semver,semver2}
74+
--version-scheme {monotonic,pep440,semver,semver2}
7575
choose version scheme
76-
--version-type {pep440,semver,semver2}
76+
--version-type {monotonic,pep440,semver,semver2}
7777
Deprecated, use --version-scheme instead
7878
--build-metadata BUILD_METADATA
7979
Add additional build-metadata to the version-number

tests/commands/test_changelog_command/test_changelog_command_shows_description_when_use_help_option.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
usage: cz changelog [-h] [--dry-run] [--file-name FILE_NAME]
22
[--unreleased-version UNRELEASED_VERSION] [--incremental]
33
[--start-rev START_REV] [--merge-prerelease]
4-
[--version-scheme {pep440,semver,semver2}]
4+
[--version-scheme {monotonic,pep440,semver,semver2}]
55
[--export-template EXPORT_TEMPLATE] [--template TEMPLATE]
66
[--extra EXTRA] [--tag-format TAG_FORMAT]
77
[rev_range]
@@ -28,7 +28,7 @@ options:
2828
--merge-prerelease collect all changes from prereleases into next non-
2929
prerelease. If not set, it will include prereleases in
3030
the changelog
31-
--version-scheme {pep440,semver,semver2}
31+
--version-scheme {monotonic,pep440,semver,semver2}
3232
choose version scheme
3333
--export-template EXPORT_TEMPLATE
3434
Export the changelog template into this file instead

tests/test_bump_normalize_tag.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from commitizen.tags import TagRules
4+
from commitizen.version_schemes import MonotonicVersion
45

56
conversion = [
67
(("1.2.3", "v$version"), "v1.2.3"),
@@ -21,3 +22,8 @@ def test_create_tag(test_input, expected):
2122
rules = TagRules()
2223
new_tag = rules.normalize_tag(version, format)
2324
assert new_tag == expected
25+
26+
27+
def test_create_tag_monotonic_scheme():
28+
rules = TagRules(MonotonicVersion)
29+
assert rules.normalize_tag("4", "release-$version") == "release-4"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from commitizen.version_schemes import MonotonicVersion, VersionProtocol
6+
from tests.utils import VersionSchemeTestArgs
7+
8+
9+
@pytest.mark.parametrize(
10+
"version_args, expected_version",
11+
[
12+
(
13+
VersionSchemeTestArgs(
14+
current_version="1",
15+
increment="PATCH",
16+
prerelease=None,
17+
prerelease_offset=0,
18+
devrelease=None,
19+
),
20+
"2",
21+
),
22+
(
23+
VersionSchemeTestArgs(
24+
current_version="2",
25+
increment="MINOR",
26+
prerelease=None,
27+
prerelease_offset=0,
28+
devrelease=None,
29+
),
30+
"3",
31+
),
32+
(
33+
VersionSchemeTestArgs(
34+
current_version="3",
35+
increment="MAJOR",
36+
prerelease=None,
37+
prerelease_offset=0,
38+
devrelease=None,
39+
),
40+
"4",
41+
),
42+
(
43+
VersionSchemeTestArgs(
44+
current_version="10",
45+
increment="PATCH",
46+
prerelease=None,
47+
prerelease_offset=0,
48+
devrelease=None,
49+
),
50+
"11",
51+
),
52+
],
53+
)
54+
def test_bump_monotonic_version(
55+
version_args: VersionSchemeTestArgs, expected_version: str
56+
):
57+
assert (
58+
str(
59+
MonotonicVersion(version_args.current_version).bump(
60+
increment=version_args.increment,
61+
prerelease=version_args.prerelease,
62+
prerelease_offset=version_args.prerelease_offset,
63+
devrelease=version_args.devrelease,
64+
)
65+
)
66+
== expected_version
67+
)
68+
69+
70+
def test_monotonic_scheme_property():
71+
version = MonotonicVersion("1")
72+
assert version.scheme is MonotonicVersion
73+
74+
75+
def test_monotonic_implements_version_protocol():
76+
assert isinstance(MonotonicVersion("1"), VersionProtocol)

tests/test_version_schemes.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212

1313
from commitizen.config.base_config import BaseConfig
1414
from commitizen.exceptions import VersionSchemeUnknown
15-
from commitizen.version_schemes import Pep440, SemVer, get_version_scheme
15+
from commitizen.version_schemes import (
16+
MonotonicVersion,
17+
Pep440,
18+
SemVer,
19+
get_version_scheme,
20+
)
1621

1722

1823
def test_default_version_scheme_is_pep440(config: BaseConfig):
@@ -52,6 +57,12 @@ def test_version_scheme_from_config_priority(config: BaseConfig):
5257
assert scheme is Pep440
5358

5459

60+
def test_version_scheme_monotonic(config: BaseConfig):
61+
config.settings["version_scheme"] = "monotonic"
62+
scheme = get_version_scheme(config.settings)
63+
assert scheme is MonotonicVersion
64+
65+
5566
def test_warn_if_version_protocol_not_implemented(
5667
config: BaseConfig, mocker: MockerFixture
5768
):

0 commit comments

Comments
 (0)