From e19c7290436eb95c1e09e3e42301bbb45f3e743d Mon Sep 17 00:00:00 2001 From: apkawa Date: Sun, 12 Mar 2023 20:15:28 +0300 Subject: [PATCH 1/8] feat(bump): version_provider=semver optional option to make version compatible with semver Signed-off-by: apkawa --- commitizen/bump.py | 19 ++- commitizen/cli.py | 8 +- commitizen/commands/bump.py | 19 ++- commitizen/defaults.py | 2 + commitizen/version_providers.py | 38 ++++++ docs/bump.md | 23 ++++ tests/commands/test_bump_command.py | 165 +++++++++++++++++++++++ tests/test_bump_find_version_provider.py | 102 ++++++++++++++ tests/test_conf.py | 2 + 9 files changed, 368 insertions(+), 10 deletions(-) create mode 100644 commitizen/version_providers.py create mode 100644 tests/test_bump_find_version_provider.py diff --git a/commitizen/bump.py b/commitizen/bump.py index 766ce1fbd2..805ba124e6 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -3,7 +3,7 @@ from collections import OrderedDict from itertools import zip_longest from string import Template -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Tuple, Type, Union from packaging.version import Version @@ -120,6 +120,7 @@ def generate_version( prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, + version_provider_cls: Optional[Type[Version]] = None, ) -> Version: """Based on the given increment a proper semver will be generated. @@ -132,15 +133,18 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ + version_provider_cls = version_provider_cls or Version if is_local_version: - version = Version(current_version) + version = version_provider_cls(current_version) dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( str(version.local), prerelease=prerelease, offset=prerelease_offset ) semver = semver_generator(str(version.local), increment=increment) - return Version(f"{version.public}+{semver}{pre_version}{dev_version}") + return version_provider_cls( + f"{version.public}+{semver}{pre_version}{dev_version}" + ) else: dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( @@ -149,7 +153,7 @@ def generate_version( semver = semver_generator(current_version, increment=increment) # TODO: post version - return Version(f"{semver}{pre_version}{dev_version}") + return version_provider_cls(f"{semver}{pre_version}{dev_version}") def update_version_in_files( @@ -208,7 +212,9 @@ def _version_to_regex(version: str) -> str: def normalize_tag( - version: Union[Version, str], tag_format: Optional[str] = None + version: Union[Version, str], + tag_format: Optional[str] = None, + version_provider_cls: Optional[Type[Version]] = None, ) -> str: """The tag and the software version might be different. @@ -221,8 +227,9 @@ def normalize_tag( | ver1.0.0 | 1.0.0 | | ver1.0.0.a0 | 1.0.0a0 | """ + version_provider_cls = version_provider_cls or Version if isinstance(version, str): - version = Version(version) + version = version_provider_cls(version) if not tag_format: return str(version) diff --git a/commitizen/cli.py b/commitizen/cli.py index d6b2d5aae7..5a27d3fd21 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -8,7 +8,7 @@ import argcomplete from decli import cli -from commitizen import commands, config, out +from commitizen import commands, config, out, version_providers from commitizen.exceptions import ( CommitizenException, ExitCode, @@ -203,6 +203,12 @@ "help": "bump to the given version (e.g: 1.5.3)", "metavar": "MANUAL_VERSION", }, + { + "name": ["--version-provider"], + "help": "choose version provider", + "default": None, + "choices": version_providers.providers, + }, ], }, { diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 00d90b48c8..eb9ab16c41 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -5,7 +5,7 @@ import questionary from packaging.version import InvalidVersion, Version -from commitizen import bump, cmd, defaults, factory, git, hooks, out +from commitizen import bump, cmd, defaults, factory, git, hooks, out, version_providers from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig from commitizen.exceptions import ( @@ -62,6 +62,12 @@ def __init__(self, config: BaseConfig, arguments: dict): self.retry = arguments["retry"] self.pre_bump_hooks = self.config.settings["pre_bump_hooks"] self.post_bump_hooks = self.config.settings["post_bump_hooks"] + version_provider = arguments["version_provider"] or self.config.settings.get( + "version_provider" + ) + self.version_provider = ( + version_provider and version_providers.providers[version_provider] + ) def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool: """Check if reading the whole git tree up to HEAD is needed.""" @@ -153,7 +159,9 @@ def __call__(self): # noqa: C901 self.cz.bump_map = defaults.bump_map_major_version_zero current_tag_version: str = bump.normalize_tag( - current_version, tag_format=tag_format + current_version, + tag_format=tag_format, + version_provider_cls=self.version_provider, ) is_initial = self.is_initial_tag(current_tag_version, is_yes) @@ -209,9 +217,14 @@ def __call__(self): # noqa: C901 prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, + version_provider_cls=self.version_provider, ) - new_tag_version = bump.normalize_tag(new_version, tag_format=tag_format) + new_tag_version = bump.normalize_tag( + new_version, + tag_format=tag_format, + version_provider_cls=self.version_provider, + ) message = bump.create_commit_message( current_version, new_version, bump_commit_message ) diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 2ab349367d..c542e616a8 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -45,6 +45,7 @@ class Settings(TypedDict, total=False): pre_bump_hooks: Optional[List[str]] post_bump_hooks: Optional[List[str]] prerelease_offset: int + version_provider: Optional[str] name: str = "cz_conventional_commits" @@ -75,6 +76,7 @@ class Settings(TypedDict, total=False): "pre_bump_hooks": [], "post_bump_hooks": [], "prerelease_offset": 0, + "version_provider": None, } MAJOR = "MAJOR" diff --git a/commitizen/version_providers.py b/commitizen/version_providers.py new file mode 100644 index 0000000000..577dcd3bfe --- /dev/null +++ b/commitizen/version_providers.py @@ -0,0 +1,38 @@ +from packaging.version import Version + + +class SemVerVersion(Version): + def __str__(self) -> str: + parts = [] + + # Epoch + if self.epoch != 0: + parts.append(f"{self.epoch}!") + + # Release segment + parts.append(".".join(str(x) for x in self.release)) + + # Pre-release + if self.pre is not None: + pre = "".join(str(x) for x in self.pre) + parts.append(f"-{pre}") + + # Post-release + if self.post is not None: + parts.append(f"-post{self.post}") + + # Development release + if self.dev is not None: + parts.append(f"-dev{self.dev}") + + # Local version segment + if self.local is not None: + parts.append(f"+{self.local}") + + return "".join(parts) + + +providers = { + "pep": Version, + "semver": SemVerVersion, +} diff --git a/docs/bump.md b/docs/bump.md index ca5bdf7fe2..fa1bd36883 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -96,6 +96,9 @@ options: --retry retry commit if it fails the 1st time --major-version-zero keep major version at zero, even for breaking changes --prerelease-offset start pre-releases with this offset + --version-provider {pep,semver} + choose version provider + ``` ### `--files-only` @@ -476,6 +479,26 @@ Defaults to: `0` prerelease_offset = 1 ``` +### `version_provider` + +Choose version provider + +* `pep` - default version provider. + - prerelease - `1.0.1a0` + - devrelease - `1.0.1dev0` + - dev and pre - `1.0.1a0.dev0` +* `semver` - semver compatibly provider. Added "-" delimiter + - prerelease - `1.0.1-a0` + - devrelease - `1.0.1-dev0` + - dev and pre - `1.0.1-a0-dev0` + +Defaults to: `pep` + +```toml +[tool.commitizen] +version_provider = "semver" +``` + ## Custom bump Read the [customizing section](./customization.md). diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 2a117aa5a1..25a6e0f3b6 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -853,3 +853,168 @@ def test_bump_use_version_provider(mocker: MockFixture): get_provider.assert_called_once() mock.get_version.assert_called_once() mock.set_version.assert_called_once_with("0.0.1") + + +def test_bump_command_prelease_version_provider_via_cli( + tmp_commitizen_project, mocker: MockFixture +): + # PRERELEASE + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_version_file.write("0.1.0") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + tmp_version_file_string = str(tmp_version_file).replace("\\", "/") + tmp_commitizen_cfg_file.write( + f"{tmp_commitizen_cfg_file.read()}\n" + f'version_files = ["{tmp_version_file_string}"]' + ) + + create_file_and_commit("feat: new user interface") + + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--version-provider", + "semver", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0-a0" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0" in f.read() + + +def test_bump_command_prelease_version_provider_via_config( + tmp_commitizen_project, mocker: MockFixture +): + # PRERELEASE + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_version_file.write("0.1.0") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + tmp_version_file_string = str(tmp_version_file).replace("\\", "/") + tmp_commitizen_cfg_file.write( + f"{tmp_commitizen_cfg_file.read()}\n" + f'version_files = ["{tmp_version_file_string}"]\n' + f'version_provider = "semver"' + ) + + create_file_and_commit("feat: new user interface") + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0-a0" in f.read() + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a1") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0-a1" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0-a1" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0" in f.read() + + +def test_bump_command_prelease_version_provider_check_old_tags( + tmp_commitizen_project, mocker: MockFixture +): + # PRERELEASE + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_version_file.write("0.1.0") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + tmp_version_file_string = str(tmp_version_file).replace("\\", "/") + tmp_commitizen_cfg_file.write( + f"{tmp_commitizen_cfg_file.read()}\n" + f'version_files = ["{tmp_version_file_string}"]\n' + f'tag_format = "v$version"\n' + f'version_provider = "semver"\n' + ) + create_file_and_commit("feat: new user interface") + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0-a0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0-a0" in f.read() + + testargs = ["cz", "bump", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0-a1") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0-a1" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0-a1" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0") + assert tag_exists is True + + with open(tmp_version_file, "r") as f: + assert "0.2.0" in f.read() + + with open(tmp_commitizen_cfg_file, "r") as f: + assert "0.2.0" in f.read() diff --git a/tests/test_bump_find_version_provider.py b/tests/test_bump_find_version_provider.py new file mode 100644 index 0000000000..bfe83e3763 --- /dev/null +++ b/tests/test_bump_find_version_provider.py @@ -0,0 +1,102 @@ +import itertools + +import pytest + +from commitizen.bump import generate_version +from commitizen.version_providers import SemVerVersion + +simple_flow = [ + (("0.1.0", "PATCH", None, 0, None), "0.1.1"), + (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev1"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("0.2.0", "MINOR", None, 0, None), "0.3.0"), + (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev1"), + (("0.3.0", "PATCH", None, 0, None), "0.3.1"), + (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-a0"), + (("0.3.1a0", None, "alpha", 0, None), "0.3.1-a1"), + (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-a1"), + (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.3.1a0", None, None, 0, None), "0.3.1"), + (("0.3.1", "PATCH", None, 0, None), "0.3.2"), + (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-a0"), + (("1.0.0a0", None, "alpha", 0, None), "1.0.0-a1"), + (("1.0.0a1", None, "alpha", 0, None), "1.0.0-a2"), + (("1.0.0a1", None, "alpha", 0, 1), "1.0.0-a2-dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), + (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), + (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), + (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), + (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0", "PATCH", None, 0, None), "1.0.1"), + (("1.0.1", "PATCH", None, 0, None), "1.0.2"), + (("1.0.2", "MINOR", None, 0, None), "1.1.0"), + (("1.1.0", "MINOR", None, 0, None), "1.2.0"), + (("1.2.0", "PATCH", None, 0, None), "1.2.1"), + (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), +] + +# this cases should be handled gracefully +unexpected_cases = [ + (("0.1.1rc0", None, "alpha", 0, None), "0.1.1-a0"), + (("0.1.1b1", None, "alpha", 0, None), "0.1.1-a0"), +] + +weird_cases = [ + (("1.1", "PATCH", None, 0, None), "1.1.1"), + (("1", "MINOR", None, 0, None), "1.1.0"), + (("1", "MAJOR", None, 0, None), "2.0.0"), + (("1a0", None, "alpha", 0, None), "1.0.0-a1"), + (("1a0", None, "alpha", 1, None), "1.0.0-a1"), + (("1", None, "beta", 0, None), "1.0.0-b0"), + (("1", None, "beta", 1, None), "1.0.0-b1"), + (("1beta", None, "beta", 0, None), "1.0.0-b1"), + (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), +] + +# test driven development +tdd_cases = [ + (("0.1.1", "PATCH", None, 0, None), "0.1.2"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), + (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-a0"), + (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-a0"), + (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-a0"), + (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-a1"), + (("1.0.0a2", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0a2", None, "beta", 1, None), "1.0.0-b1"), + (("1.0.0beta1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), + (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), +] + + +@pytest.mark.parametrize( + "test_input,expected", + itertools.chain(tdd_cases, weird_cases, simple_flow, unexpected_cases), +) +def test_generate_version_provider(test_input, expected): + current_version = test_input[0] + increment = test_input[1] + prerelease = test_input[2] + prerelease_offset = test_input[3] + devrelease = test_input[4] + assert ( + str( + generate_version( + current_version, + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + version_provider_cls=SemVerVersion, + ) + ) + == expected + ) diff --git a/tests/test_conf.py b/tests/test_conf.py index df17a108ef..2674a033e1 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -60,6 +60,7 @@ "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, + "version_provider": None, } _new_settings = { @@ -81,6 +82,7 @@ "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, + "version_provider": None, } _read_settings = { From c2a2a03ed583fd80687c1a5169570510b6cb3377 Mon Sep 17 00:00:00 2001 From: apkawa Date: Mon, 13 Mar 2023 10:49:28 +0300 Subject: [PATCH 2/8] refactor(bump): version_provider rename to version_type; `pep` option rename to `pep440` Signed-off-by: apkawa --- commitizen/bump.py | 18 ++++++++---------- commitizen/cli.py | 8 ++++---- commitizen/commands/bump.py | 16 +++++++--------- commitizen/defaults.py | 4 ++-- .../{version_providers.py => version_types.py} | 4 ++-- docs/bump.md | 16 ++++++++-------- tests/commands/test_bump_command.py | 10 +++++----- ...y => test_bump_find_version_type_semver.py} | 6 +++--- tests/test_conf.py | 4 ++-- 9 files changed, 41 insertions(+), 45 deletions(-) rename commitizen/{version_providers.py => version_types.py} (96%) rename tests/{test_bump_find_version_provider.py => test_bump_find_version_type_semver.py} (95%) diff --git a/commitizen/bump.py b/commitizen/bump.py index 805ba124e6..df9d549f06 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -120,7 +120,7 @@ def generate_version( prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, - version_provider_cls: Optional[Type[Version]] = None, + version_type_cls: Optional[Type[Version]] = None, ) -> Version: """Based on the given increment a proper semver will be generated. @@ -133,18 +133,16 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ - version_provider_cls = version_provider_cls or Version + version_type_cls = version_type_cls or Version if is_local_version: - version = version_provider_cls(current_version) + version = version_type_cls(current_version) dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( str(version.local), prerelease=prerelease, offset=prerelease_offset ) semver = semver_generator(str(version.local), increment=increment) - return version_provider_cls( - f"{version.public}+{semver}{pre_version}{dev_version}" - ) + return version_type_cls(f"{version.public}+{semver}{pre_version}{dev_version}") else: dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( @@ -153,7 +151,7 @@ def generate_version( semver = semver_generator(current_version, increment=increment) # TODO: post version - return version_provider_cls(f"{semver}{pre_version}{dev_version}") + return version_type_cls(f"{semver}{pre_version}{dev_version}") def update_version_in_files( @@ -214,7 +212,7 @@ def _version_to_regex(version: str) -> str: def normalize_tag( version: Union[Version, str], tag_format: Optional[str] = None, - version_provider_cls: Optional[Type[Version]] = None, + version_type_cls: Optional[Type[Version]] = None, ) -> str: """The tag and the software version might be different. @@ -227,9 +225,9 @@ def normalize_tag( | ver1.0.0 | 1.0.0 | | ver1.0.0.a0 | 1.0.0a0 | """ - version_provider_cls = version_provider_cls or Version + version_type_cls = version_type_cls or Version if isinstance(version, str): - version = version_provider_cls(version) + version = version_type_cls(version) if not tag_format: return str(version) diff --git a/commitizen/cli.py b/commitizen/cli.py index 5a27d3fd21..f85c8c3b9a 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -8,7 +8,7 @@ import argcomplete from decli import cli -from commitizen import commands, config, out, version_providers +from commitizen import commands, config, out, version_types from commitizen.exceptions import ( CommitizenException, ExitCode, @@ -204,10 +204,10 @@ "metavar": "MANUAL_VERSION", }, { - "name": ["--version-provider"], - "help": "choose version provider", + "name": ["--version-type"], + "help": "choose version type", "default": None, - "choices": version_providers.providers, + "choices": version_types.types, }, ], }, diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index eb9ab16c41..8db134e3b1 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -5,7 +5,7 @@ import questionary from packaging.version import InvalidVersion, Version -from commitizen import bump, cmd, defaults, factory, git, hooks, out, version_providers +from commitizen import bump, cmd, defaults, factory, git, hooks, out, version_types from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig from commitizen.exceptions import ( @@ -62,12 +62,10 @@ def __init__(self, config: BaseConfig, arguments: dict): self.retry = arguments["retry"] self.pre_bump_hooks = self.config.settings["pre_bump_hooks"] self.post_bump_hooks = self.config.settings["post_bump_hooks"] - version_provider = arguments["version_provider"] or self.config.settings.get( - "version_provider" - ) - self.version_provider = ( - version_provider and version_providers.providers[version_provider] + version_type = arguments["version_type"] or self.config.settings.get( + "version_type" ) + self.version_type = version_type and version_types.types[version_type] def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool: """Check if reading the whole git tree up to HEAD is needed.""" @@ -161,7 +159,7 @@ def __call__(self): # noqa: C901 current_tag_version: str = bump.normalize_tag( current_version, tag_format=tag_format, - version_provider_cls=self.version_provider, + version_type_cls=self.version_type, ) is_initial = self.is_initial_tag(current_tag_version, is_yes) @@ -217,13 +215,13 @@ def __call__(self): # noqa: C901 prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, - version_provider_cls=self.version_provider, + version_type_cls=self.version_type, ) new_tag_version = bump.normalize_tag( new_version, tag_format=tag_format, - version_provider_cls=self.version_provider, + version_type_cls=self.version_type, ) message = bump.create_commit_message( current_version, new_version, bump_commit_message diff --git a/commitizen/defaults.py b/commitizen/defaults.py index c542e616a8..cae1d493fd 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -45,7 +45,7 @@ class Settings(TypedDict, total=False): pre_bump_hooks: Optional[List[str]] post_bump_hooks: Optional[List[str]] prerelease_offset: int - version_provider: Optional[str] + version_type: Optional[str] name: str = "cz_conventional_commits" @@ -76,7 +76,7 @@ class Settings(TypedDict, total=False): "pre_bump_hooks": [], "post_bump_hooks": [], "prerelease_offset": 0, - "version_provider": None, + "version_type": None, } MAJOR = "MAJOR" diff --git a/commitizen/version_providers.py b/commitizen/version_types.py similarity index 96% rename from commitizen/version_providers.py rename to commitizen/version_types.py index 577dcd3bfe..52af6ed3b8 100644 --- a/commitizen/version_providers.py +++ b/commitizen/version_types.py @@ -32,7 +32,7 @@ def __str__(self) -> str: return "".join(parts) -providers = { - "pep": Version, +types = { + "pep440": Version, "semver": SemVerVersion, } diff --git a/docs/bump.md b/docs/bump.md index fa1bd36883..4a686b77ef 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -96,8 +96,8 @@ options: --retry retry commit if it fails the 1st time --major-version-zero keep major version at zero, even for breaking changes --prerelease-offset start pre-releases with this offset - --version-provider {pep,semver} - choose version provider + --version-type {pep440,semver} + choose version type ``` @@ -479,24 +479,24 @@ Defaults to: `0` prerelease_offset = 1 ``` -### `version_provider` +### `version_type` -Choose version provider +Choose version type -* `pep` - default version provider. +* `pep440` - default version type. - prerelease - `1.0.1a0` - devrelease - `1.0.1dev0` - dev and pre - `1.0.1a0.dev0` -* `semver` - semver compatibly provider. Added "-" delimiter +* `semver` - semver compatibly type. Added "-" delimiter - prerelease - `1.0.1-a0` - devrelease - `1.0.1-dev0` - dev and pre - `1.0.1-a0-dev0` -Defaults to: `pep` +Defaults to: `pep440` ```toml [tool.commitizen] -version_provider = "semver" +version_type = "semver" ``` ## Custom bump diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 25a6e0f3b6..fb08e551ea 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -876,7 +876,7 @@ def test_bump_command_prelease_version_provider_via_cli( "--prerelease", "alpha", "--yes", - "--version-provider", + "--version-type", "semver", ] mocker.patch.object(sys, "argv", testargs) @@ -906,7 +906,7 @@ def test_bump_command_prelease_version_provider_via_cli( assert "0.2.0" in f.read() -def test_bump_command_prelease_version_provider_via_config( +def test_bump_command_prelease_version_type_via_config( tmp_commitizen_project, mocker: MockFixture ): # PRERELEASE @@ -917,7 +917,7 @@ def test_bump_command_prelease_version_provider_via_config( tmp_commitizen_cfg_file.write( f"{tmp_commitizen_cfg_file.read()}\n" f'version_files = ["{tmp_version_file_string}"]\n' - f'version_provider = "semver"' + f'version_type = "semver"' ) create_file_and_commit("feat: new user interface") @@ -963,7 +963,7 @@ def test_bump_command_prelease_version_provider_via_config( assert "0.2.0" in f.read() -def test_bump_command_prelease_version_provider_check_old_tags( +def test_bump_command_prelease_version_type_check_old_tags( tmp_commitizen_project, mocker: MockFixture ): # PRERELEASE @@ -975,7 +975,7 @@ def test_bump_command_prelease_version_provider_check_old_tags( f"{tmp_commitizen_cfg_file.read()}\n" f'version_files = ["{tmp_version_file_string}"]\n' f'tag_format = "v$version"\n' - f'version_provider = "semver"\n' + f'version_type = "semver"\n' ) create_file_and_commit("feat: new user interface") diff --git a/tests/test_bump_find_version_provider.py b/tests/test_bump_find_version_type_semver.py similarity index 95% rename from tests/test_bump_find_version_provider.py rename to tests/test_bump_find_version_type_semver.py index bfe83e3763..6c59128738 100644 --- a/tests/test_bump_find_version_provider.py +++ b/tests/test_bump_find_version_type_semver.py @@ -3,7 +3,7 @@ import pytest from commitizen.bump import generate_version -from commitizen.version_providers import SemVerVersion +from commitizen.version_types import SemVerVersion simple_flow = [ (("0.1.0", "PATCH", None, 0, None), "0.1.1"), @@ -81,7 +81,7 @@ "test_input,expected", itertools.chain(tdd_cases, weird_cases, simple_flow, unexpected_cases), ) -def test_generate_version_provider(test_input, expected): +def test_generate_version_type(test_input, expected): current_version = test_input[0] increment = test_input[1] prerelease = test_input[2] @@ -95,7 +95,7 @@ def test_generate_version_provider(test_input, expected): prerelease=prerelease, prerelease_offset=prerelease_offset, devrelease=devrelease, - version_provider_cls=SemVerVersion, + version_type_cls=SemVerVersion, ) ) == expected diff --git a/tests/test_conf.py b/tests/test_conf.py index 2674a033e1..4226096371 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -60,7 +60,7 @@ "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, - "version_provider": None, + "version_type": None, } _new_settings = { @@ -82,7 +82,7 @@ "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, - "version_provider": None, + "version_type": None, } _read_settings = { From 14d6ca9e905130823b44cfbbe86c4ce49e85a85c Mon Sep 17 00:00:00 2001 From: apkawa Date: Mon, 13 Mar 2023 10:52:03 +0300 Subject: [PATCH 3/8] docs(bump): add `version_type` info to config.md Signed-off-by: apkawa --- docs/config.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index d752c1210e..a3d79a3f39 100644 --- a/docs/config.md +++ b/docs/config.md @@ -3,7 +3,7 @@ ## Settings | Variable | Type | Default | Description | -| -------------------------- | ------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|----------------------------|--------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `name` | `str` | `"cz_conventional_commits"` | Name of the committing rules to use | | `version` | `str` | `None` | Current version. Example: "0.1.2" | | `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more][version_files] | @@ -23,6 +23,7 @@ | `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] | | `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] | | `prerelease_offset` | `int` | `0` | In special cases it may be necessary that a prerelease cannot start with a 0, e.g. in an embedded project the individual characters are encoded in bytes. This can be done by specifying an offset from which to start counting. [prerelease-offset] | +| `version_type` | `str` | `pep440` | Select a version type from the following options [`pep440`, `semver`]. Useful for non python projects. [See more][version_type] | ## pyproject.toml or .cz.toml @@ -179,6 +180,7 @@ setup( [major-version-zero]: bump.md#-major-version-zero [prerelease-offset]: bump.md#-prerelease_offset [allow_abort]: check.md#allow-abort +[version_type]: bump.md#version_type [additional-features]: https://github.com/tmbo/questionary#additional-features [customization]: customization.md [shortcuts]: customization.md#shortcut-keys From 54a91916f05d5eb18d445aa19f8758ba6eb46725 Mon Sep 17 00:00:00 2001 From: apkawa Date: Mon, 13 Mar 2023 11:45:41 +0300 Subject: [PATCH 4/8] refactor(bump): to use VersionProtocol interface instead packaging.Version subclass Signed-off-by: apkawa --- commitizen/bump.py | 9 ++-- commitizen/version_types.py | 85 +++++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index df9d549f06..68097d1182 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -10,6 +10,7 @@ from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import GitCommit, smart_open +from commitizen.version_types import VersionProtocol def find_increment( @@ -120,8 +121,8 @@ def generate_version( prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, - version_type_cls: Optional[Type[Version]] = None, -) -> Version: + version_type_cls: Optional[Type[VersionProtocol]] = None, +) -> VersionProtocol: """Based on the given increment a proper semver will be generated. For now the rules and versioning scheme is based on @@ -210,9 +211,9 @@ def _version_to_regex(version: str) -> str: def normalize_tag( - version: Union[Version, str], + version: Union[VersionProtocol, str], tag_format: Optional[str] = None, - version_type_cls: Optional[Type[Version]] = None, + version_type_cls: Optional[Type[VersionProtocol]] = None, ) -> str: """The tag and the software version might be different. diff --git a/commitizen/version_types.py b/commitizen/version_types.py index 52af6ed3b8..82b4def07b 100644 --- a/commitizen/version_types.py +++ b/commitizen/version_types.py @@ -1,33 +1,94 @@ +import sys +import typing + +if sys.version_info >= (3, 8): + from typing import Protocol as _Protocol +else: + _Protocol = object + from packaging.version import Version -class SemVerVersion(Version): +class VersionProtocol(_Protocol): + def __init__(self, _version: typing.Union[Version, str]): + raise NotImplementedError("must be implemented") + + def __str__(self) -> str: + raise NotImplementedError("must be implemented") + + @property + def release(self) -> typing.Tuple[int, ...]: + raise NotImplementedError("must be implemented") + + @property + def is_prerelease(self) -> bool: + raise NotImplementedError("must be implemented") + + @property + def pre(self) -> typing.Optional[typing.Tuple[str, int]]: + raise NotImplementedError("must be implemented") + + @property + def local(self) -> typing.Optional[str]: + raise NotImplementedError("must be implemented") + + @property + def public(self) -> str: + raise NotImplementedError("must be implemented") + + +class SemVerVersion(VersionProtocol): + def __init__(self, version: str): + self._version = Version(version) + + @property + def release(self) -> typing.Tuple[int, ...]: + return self._version.release + + @property + def is_prerelease(self) -> bool: + return self._version.is_prerelease + + @property + def pre(self) -> typing.Optional[typing.Tuple[str, int]]: + return self._version.pre + + @property + def local(self) -> typing.Optional[str]: + return self._version.local + + @property + def public(self) -> str: + return self._version.public + def __str__(self) -> str: parts = [] + version = self._version + # Epoch - if self.epoch != 0: - parts.append(f"{self.epoch}!") + if version.epoch != 0: + parts.append(f"{version.epoch}!") # Release segment - parts.append(".".join(str(x) for x in self.release)) + parts.append(".".join(str(x) for x in version.release)) # Pre-release - if self.pre is not None: - pre = "".join(str(x) for x in self.pre) + if version.pre is not None: + pre = "".join(str(x) for x in version.pre) parts.append(f"-{pre}") # Post-release - if self.post is not None: - parts.append(f"-post{self.post}") + if version.post is not None: + parts.append(f"-post{version.post}") # Development release - if self.dev is not None: - parts.append(f"-dev{self.dev}") + if version.dev is not None: + parts.append(f"-dev{version.dev}") # Local version segment - if self.local is not None: - parts.append(f"+{self.local}") + if version.local is not None: + parts.append(f"+{version.local}") return "".join(parts) From 389b85bf5f5f628dbe28424451dc59bb7a155e66 Mon Sep 17 00:00:00 2001 From: apkawa Date: Mon, 13 Mar 2023 12:11:51 +0300 Subject: [PATCH 5/8] test(bump): `VersionProtocol` workaround mypy for py==3.7 Signed-off-by: apkawa --- commitizen/bump.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index 68097d1182..c405414e87 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -1,5 +1,7 @@ import os import re +import sys +import typing from collections import OrderedDict from itertools import zip_longest from string import Template @@ -10,7 +12,12 @@ from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import GitCommit, smart_open -from commitizen.version_types import VersionProtocol + +if sys.version_info >= (3, 8): + from commitizen.version_types import VersionProtocol +else: + # workaround mypy issue for 3.7 python + VersionProtocol = typing.Any def find_increment( @@ -134,7 +141,8 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ - version_type_cls = version_type_cls or Version + if version_type_cls is None: + version_type_cls = Version if is_local_version: version = version_type_cls(current_version) dev_version = devrelease_generator(devrelease=devrelease) @@ -226,7 +234,8 @@ def normalize_tag( | ver1.0.0 | 1.0.0 | | ver1.0.0.a0 | 1.0.0a0 | """ - version_type_cls = version_type_cls or Version + if version_type_cls is None: + version_type_cls = Version if isinstance(version, str): version = version_type_cls(version) From af176ced8f7a076e1377d24aec898470995d6e45 Mon Sep 17 00:00:00 2001 From: apkawa Date: Mon, 13 Mar 2023 13:10:42 +0300 Subject: [PATCH 6/8] fix(changelog): `changelog` command does not find version tag with `version_type=semver` option Signed-off-by: apkawa --- commitizen/changelog.py | 23 ++++++-- commitizen/commands/changelog.py | 10 +++- tests/commands/test_changelog_command.py | 57 +++++++++++++++++++ ...elease_rev_with_use_version_type_semver.md | 7 +++ 4 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md diff --git a/commitizen/changelog.py b/commitizen/changelog.py index dcbc9a1dea..1a6ca04cc1 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -27,9 +27,11 @@ import os import re +import sys +import typing from collections import OrderedDict, defaultdict from datetime import date -from typing import Callable, Dict, Iterable, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type from jinja2 import Environment, PackageLoader from packaging.version import InvalidVersion, Version @@ -39,6 +41,12 @@ from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError from commitizen.git import GitCommit, GitTag +if sys.version_info >= (3, 8): + from commitizen.version_types import VersionProtocol +else: + # workaround mypy issue for 3.7 python + VersionProtocol = typing.Any + def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]: return next((tag for tag in tags if tag.rev == commit.rev), None) @@ -313,7 +321,10 @@ def get_smart_tag_range( def get_oldest_and_newest_rev( - tags: List[GitTag], version: str, tag_format: str + tags: List[GitTag], + version: str, + tag_format: str, + version_type_cls: Optional[Type[VersionProtocol]] = None, ) -> Tuple[Optional[str], Optional[str]]: """Find the tags for the given version. @@ -328,11 +339,15 @@ def get_oldest_and_newest_rev( except ValueError: newest = version - newest_tag = normalize_tag(newest, tag_format=tag_format) + newest_tag = normalize_tag( + newest, tag_format=tag_format, version_type_cls=version_type_cls + ) oldest_tag = None if oldest: - oldest_tag = normalize_tag(oldest, tag_format=tag_format) + oldest_tag = normalize_tag( + oldest, tag_format=tag_format, version_type_cls=version_type_cls + ) tags_range = get_smart_tag_range(tags, newest=newest_tag, oldest=oldest_tag) if not tags_range: diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 10fc890e9d..02f1f01dca 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -3,7 +3,7 @@ from operator import itemgetter from typing import Callable, Dict, List, Optional -from commitizen import bump, changelog, defaults, factory, git, out +from commitizen import bump, changelog, defaults, factory, git, out, version_types from commitizen.config import BaseConfig from commitizen.exceptions import ( DryRunExit, @@ -53,6 +53,9 @@ def __init__(self, config: BaseConfig, args): "merge_prerelease" ) or self.config.settings.get("changelog_merge_prerelease") + version_type = self.config.settings.get("version_type") + self.version_type = version_type and version_types.types[version_type] + def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str: """Try to find the 'start_rev'. @@ -137,7 +140,9 @@ def __call__(self): latest_version = changelog_meta.get("latest_version") if latest_version: latest_tag_version: str = bump.normalize_tag( - latest_version, tag_format=self.tag_format + latest_version, + tag_format=self.tag_format, + version_type_cls=self.version_type, ) start_rev = self._find_incremental_rev(latest_tag_version, tags) @@ -146,6 +151,7 @@ def __call__(self): tags, version=self.rev_range, tag_format=self.tag_format, + version_type_cls=self.version_type, ) commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order") diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index 01290c1d0a..4f55ee08cf 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -1152,3 +1152,60 @@ def test_empty_commit_list(mocker): mocker.patch.object(sys, "argv", testargs) with pytest.raises(NoCommitsFoundError): cli.main() + + +@pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2022-02-13") +def test_changelog_prerelease_rev_with_use_version_type_semver( + mocker: MockFixture, capsys, config_path, changelog_path, file_regression +): + mocker.patch("commitizen.git.GitTag.date", "2022-02-13") + + with open(config_path, "a") as f: + f.write('tag_format = "$version"\n' 'version_type = "semver"') + + # create commit and tag + create_file_and_commit("feat: new file") + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + wait_for_tag() + + create_file_and_commit("feat: after 0.2.0") + create_file_and_commit("feat: another feature") + + testargs = ["cz", "bump", "--yes", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + capsys.readouterr() + wait_for_tag() + + tag_exists = git.tag_exist("0.3.0-a0") + assert tag_exists is True + + testargs = ["cz", "changelog", "0.3.0-a0", "--dry-run"] + mocker.patch.object(sys, "argv", testargs) + with pytest.raises(DryRunExit): + cli.main() + + out, _ = capsys.readouterr() + + file_regression.check(out, extension=".md") + + testargs = ["cz", "bump", "--yes", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + capsys.readouterr() + wait_for_tag() + + tag_exists = git.tag_exist("0.3.0-a1") + assert tag_exists is True + + testargs = ["cz", "changelog", "0.3.0-a1", "--dry-run"] + mocker.patch.object(sys, "argv", testargs) + with pytest.raises(DryRunExit): + cli.main() + + out, _ = capsys.readouterr() + + assert out == "## 0.3.0-a1 (2022-02-13)\n\n" diff --git a/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md new file mode 100644 index 0000000000..9a66210a74 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md @@ -0,0 +1,7 @@ +## 0.3.0-a0 (2022-02-13) + +### Feat + +- another feature +- after 0.2.0 + From 59d1432b61b7c7b4910b3f609c59d8bafab79668 Mon Sep 17 00:00:00 2001 From: apkawa Date: Sun, 16 Apr 2023 17:24:48 +0300 Subject: [PATCH 7/8] refactor: minor review fixes --- commitizen/cli.py | 2 +- commitizen/commands/bump.py | 2 +- commitizen/commands/changelog.py | 2 +- commitizen/version_types.py | 22 ++-- docs/config.md | 2 +- tests/commands/test_bump_command.py | 108 ++++++------------ tests/commands/test_changelog_command.py | 2 +- ...e_version_type_semver.second-prerelease.md | 2 + tests/conftest.py | 31 +++++ tests/test_bump_find_version_type_semver.py | 2 +- 10 files changed, 85 insertions(+), 90 deletions(-) create mode 100644 tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md diff --git a/commitizen/cli.py b/commitizen/cli.py index f85c8c3b9a..b59c257db3 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -207,7 +207,7 @@ "name": ["--version-type"], "help": "choose version type", "default": None, - "choices": version_types.types, + "choices": version_types.VERSION_TYPES, }, ], }, diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 8db134e3b1..1477f79449 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -65,7 +65,7 @@ def __init__(self, config: BaseConfig, arguments: dict): version_type = arguments["version_type"] or self.config.settings.get( "version_type" ) - self.version_type = version_type and version_types.types[version_type] + self.version_type = version_type and version_types.VERSION_TYPES[version_type] def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool: """Check if reading the whole git tree up to HEAD is needed.""" diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 02f1f01dca..4c291d3ac2 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -54,7 +54,7 @@ def __init__(self, config: BaseConfig, args): ) or self.config.settings.get("changelog_merge_prerelease") version_type = self.config.settings.get("version_type") - self.version_type = version_type and version_types.types[version_type] + self.version_type = version_type and version_types.VERSION_TYPES[version_type] def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str: """Try to find the 'start_rev'. diff --git a/commitizen/version_types.py b/commitizen/version_types.py index 82b4def07b..d896d809b1 100644 --- a/commitizen/version_types.py +++ b/commitizen/version_types.py @@ -1,5 +1,5 @@ import sys -import typing +from typing import Optional, Tuple, Union if sys.version_info >= (3, 8): from typing import Protocol as _Protocol @@ -10,14 +10,14 @@ class VersionProtocol(_Protocol): - def __init__(self, _version: typing.Union[Version, str]): + def __init__(self, _version: Union[Version, str]): raise NotImplementedError("must be implemented") def __str__(self) -> str: raise NotImplementedError("must be implemented") @property - def release(self) -> typing.Tuple[int, ...]: + def release(self) -> Tuple[int, ...]: raise NotImplementedError("must be implemented") @property @@ -25,11 +25,11 @@ def is_prerelease(self) -> bool: raise NotImplementedError("must be implemented") @property - def pre(self) -> typing.Optional[typing.Tuple[str, int]]: + def pre(self) -> Optional[Tuple[str, int]]: raise NotImplementedError("must be implemented") @property - def local(self) -> typing.Optional[str]: + def local(self) -> Optional[str]: raise NotImplementedError("must be implemented") @property @@ -42,7 +42,7 @@ def __init__(self, version: str): self._version = Version(version) @property - def release(self) -> typing.Tuple[int, ...]: + def release(self) -> Tuple[int, ...]: return self._version.release @property @@ -50,11 +50,11 @@ def is_prerelease(self) -> bool: return self._version.is_prerelease @property - def pre(self) -> typing.Optional[typing.Tuple[str, int]]: + def pre(self) -> Optional[Tuple[str, int]]: return self._version.pre @property - def local(self) -> typing.Optional[str]: + def local(self) -> Optional[str]: return self._version.local @property @@ -74,7 +74,7 @@ def __str__(self) -> str: parts.append(".".join(str(x) for x in version.release)) # Pre-release - if version.pre is not None: + if version.pre: pre = "".join(str(x) for x in version.pre) parts.append(f"-{pre}") @@ -87,13 +87,13 @@ def __str__(self) -> str: parts.append(f"-dev{version.dev}") # Local version segment - if version.local is not None: + if version.local: parts.append(f"+{version.local}") return "".join(parts) -types = { +VERSION_TYPES = { "pep440": Version, "semver": SemVerVersion, } diff --git a/docs/config.md b/docs/config.md index a3d79a3f39..e30d8beaa0 100644 --- a/docs/config.md +++ b/docs/config.md @@ -23,7 +23,7 @@ | `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] | | `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] | | `prerelease_offset` | `int` | `0` | In special cases it may be necessary that a prerelease cannot start with a 0, e.g. in an embedded project the individual characters are encoded in bytes. This can be done by specifying an offset from which to start counting. [prerelease-offset] | -| `version_type` | `str` | `pep440` | Select a version type from the following options [`pep440`, `semver`]. Useful for non python projects. [See more][version_type] | +| `version_type` | `str` | `pep440` | Select a version type from the following options [`pep440`, `semver`]. Useful for non-python projects. [See more][version_type] | ## pyproject.toml or .cz.toml diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index fb08e551ea..59a0e01ef3 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -855,20 +855,12 @@ def test_bump_use_version_provider(mocker: MockFixture): mock.set_version.assert_called_once_with("0.0.1") -def test_bump_command_prelease_version_provider_via_cli( - tmp_commitizen_project, mocker: MockFixture +def test_bump_command_prelease_version_type_via_cli( + tmp_commitizen_project_initial, mocker: MockFixture ): - # PRERELEASE + tmp_commitizen_project = tmp_commitizen_project_initial() tmp_version_file = tmp_commitizen_project.join("__version__.py") - tmp_version_file.write("0.1.0") tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") - tmp_version_file_string = str(tmp_version_file).replace("\\", "/") - tmp_commitizen_cfg_file.write( - f"{tmp_commitizen_cfg_file.read()}\n" - f'version_files = ["{tmp_version_file_string}"]' - ) - - create_file_and_commit("feat: new user interface") testargs = [ "cz", @@ -885,11 +877,9 @@ def test_bump_command_prelease_version_provider_via_cli( tag_exists = git.tag_exist("0.2.0-a0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0-a0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0-a0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE testargs = ["cz", "bump", "--yes"] @@ -899,28 +889,19 @@ def test_bump_command_prelease_version_provider_via_cli( tag_exists = git.tag_exist("0.2.0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() def test_bump_command_prelease_version_type_via_config( - tmp_commitizen_project, mocker: MockFixture + tmp_commitizen_project_initial, mocker: MockFixture ): - # PRERELEASE + tmp_commitizen_project = tmp_commitizen_project_initial( + config_extra='version_type = "semver"\n', + ) tmp_version_file = tmp_commitizen_project.join("__version__.py") - tmp_version_file.write("0.1.0") tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") - tmp_version_file_string = str(tmp_version_file).replace("\\", "/") - tmp_commitizen_cfg_file.write( - f"{tmp_commitizen_cfg_file.read()}\n" - f'version_files = ["{tmp_version_file_string}"]\n' - f'version_type = "semver"' - ) - - create_file_and_commit("feat: new user interface") testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] mocker.patch.object(sys, "argv", testargs) @@ -929,11 +910,9 @@ def test_bump_command_prelease_version_type_via_config( tag_exists = git.tag_exist("0.2.0-a0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0-a0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0-a0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] mocker.patch.object(sys, "argv", testargs) @@ -942,11 +921,9 @@ def test_bump_command_prelease_version_type_via_config( tag_exists = git.tag_exist("0.2.0-a1") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0-a1" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0-a1" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a1" in f.read() # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE testargs = ["cz", "bump", "--yes"] @@ -956,28 +933,19 @@ def test_bump_command_prelease_version_type_via_config( tag_exists = git.tag_exist("0.2.0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() def test_bump_command_prelease_version_type_check_old_tags( - tmp_commitizen_project, mocker: MockFixture + tmp_commitizen_project_initial, mocker: MockFixture ): - # PRERELEASE + tmp_commitizen_project = tmp_commitizen_project_initial( + config_extra=('tag_format = "v$version"\nversion_type = "semver"\n'), + ) tmp_version_file = tmp_commitizen_project.join("__version__.py") - tmp_version_file.write("0.1.0") tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") - tmp_version_file_string = str(tmp_version_file).replace("\\", "/") - tmp_commitizen_cfg_file.write( - f"{tmp_commitizen_cfg_file.read()}\n" - f'version_files = ["{tmp_version_file_string}"]\n' - f'tag_format = "v$version"\n' - f'version_type = "semver"\n' - ) - create_file_and_commit("feat: new user interface") testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] mocker.patch.object(sys, "argv", testargs) @@ -986,11 +954,9 @@ def test_bump_command_prelease_version_type_check_old_tags( tag_exists = git.tag_exist("v0.2.0-a0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0-a0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0-a0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() testargs = ["cz", "bump", "--prerelease", "alpha"] mocker.patch.object(sys, "argv", testargs) @@ -999,11 +965,9 @@ def test_bump_command_prelease_version_type_check_old_tags( tag_exists = git.tag_exist("v0.2.0-a1") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0-a1" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0-a1" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a1" in f.read() # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE testargs = ["cz", "bump"] @@ -1013,8 +977,6 @@ def test_bump_command_prelease_version_type_check_old_tags( tag_exists = git.tag_exist("v0.2.0") assert tag_exists is True - with open(tmp_version_file, "r") as f: - assert "0.2.0" in f.read() - - with open(tmp_commitizen_cfg_file, "r") as f: - assert "0.2.0" in f.read() + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index 4f55ee08cf..c8af897a20 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -1208,4 +1208,4 @@ def test_changelog_prerelease_rev_with_use_version_type_semver( out, _ = capsys.readouterr() - assert out == "## 0.3.0-a1 (2022-02-13)\n\n" + file_regression.check(out, extension=".second-prerelease.md") diff --git a/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md new file mode 100644 index 0000000000..09fd10ee8d --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md @@ -0,0 +1,2 @@ +## 0.3.0-a1 (2022-02-13) + diff --git a/tests/conftest.py b/tests/conftest.py index 42a4606e1b..25cbcbf27c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,11 +2,13 @@ import re import tempfile from pathlib import Path +from typing import Optional import pytest from commitizen import cmd, defaults from commitizen.config import BaseConfig +from tests.utils import create_file_and_commit SIGNER = "GitHub Action" SIGNER_MAIL = "action@github.com" @@ -42,6 +44,35 @@ def tmp_commitizen_project(tmp_git_project): yield tmp_git_project +@pytest.fixture(scope="function") +def tmp_commitizen_project_initial(tmp_git_project): + def _initial( + config_extra: Optional[str] = None, + version="0.1.0", + initial_commit="feat: new user interface", + ): + with tmp_git_project.as_cwd(): + tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") + tmp_commitizen_cfg_file.write( + f"[tool.commitizen]\n" f'version="{version}"\n' + ) + tmp_version_file = tmp_git_project.join("__version__.py") + tmp_version_file.write(version) + tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") + tmp_version_file_string = str(tmp_version_file).replace("\\", "/") + tmp_commitizen_cfg_file.write( + f"{tmp_commitizen_cfg_file.read()}\n" + f'version_files = ["{tmp_version_file_string}"]\n' + ) + if config_extra: + tmp_commitizen_cfg_file.write(config_extra, mode="a") + create_file_and_commit(initial_commit) + + return tmp_git_project + + yield _initial + + def _get_gpg_keyid(signer_mail): _new_key = cmd.run(f"gpg --list-secret-keys {signer_mail}") _m = re.search( diff --git a/tests/test_bump_find_version_type_semver.py b/tests/test_bump_find_version_type_semver.py index 6c59128738..633442f9ee 100644 --- a/tests/test_bump_find_version_type_semver.py +++ b/tests/test_bump_find_version_type_semver.py @@ -78,7 +78,7 @@ @pytest.mark.parametrize( - "test_input,expected", + "test_input, expected", itertools.chain(tdd_cases, weird_cases, simple_flow, unexpected_cases), ) def test_generate_version_type(test_input, expected): From f644bae16de6130cbf270004b3357a4c3b0b5918 Mon Sep 17 00:00:00 2001 From: apkawa Date: Tue, 18 Apr 2023 22:05:32 +0300 Subject: [PATCH 8/8] test(changelog): fix test_changelog_incremental_with_merge_prerelease issue with freeze_time Signed-off-by: apkawa --- tests/commands/test_changelog_command.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index c8af897a20..bdc384c3ca 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -696,6 +696,7 @@ def test_changelog_release_candidate_version_with_merge_prerelease( @pytest.mark.parametrize("test_input", ["rc", "alpha", "beta"]) @pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2023-04-16") def test_changelog_incremental_with_merge_prerelease( mocker: MockFixture, changelog_path, file_regression, test_input ):