diff --git a/commitizen/bump.py b/commitizen/bump.py index 766ce1fbd2..c405414e87 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -1,9 +1,11 @@ import os import re +import sys +import typing 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 @@ -11,6 +13,12 @@ from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import GitCommit, smart_open +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( commits: List[GitCommit], regex: str, increments_map: Union[dict, OrderedDict] @@ -120,7 +128,8 @@ def generate_version( prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, -) -> 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 @@ -132,15 +141,17 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ + if version_type_cls is None: + version_type_cls = Version if is_local_version: - version = Version(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(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( @@ -149,7 +160,7 @@ def generate_version( semver = semver_generator(current_version, increment=increment) # TODO: post version - return Version(f"{semver}{pre_version}{dev_version}") + return version_type_cls(f"{semver}{pre_version}{dev_version}") def update_version_in_files( @@ -208,7 +219,9 @@ def _version_to_regex(version: str) -> str: def normalize_tag( - version: Union[Version, str], tag_format: Optional[str] = None + version: Union[VersionProtocol, str], + tag_format: Optional[str] = None, + version_type_cls: Optional[Type[VersionProtocol]] = None, ) -> str: """The tag and the software version might be different. @@ -221,8 +234,10 @@ def normalize_tag( | ver1.0.0 | 1.0.0 | | ver1.0.0.a0 | 1.0.0a0 | """ + if version_type_cls is None: + version_type_cls = Version if isinstance(version, str): - version = Version(version) + version = version_type_cls(version) if not tag_format: return str(version) 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/cli.py b/commitizen/cli.py index d6b2d5aae7..b59c257db3 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_types 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-type"], + "help": "choose version type", + "default": None, + "choices": version_types.VERSION_TYPES, + }, ], }, { diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 00d90b48c8..1477f79449 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_types from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig from commitizen.exceptions import ( @@ -62,6 +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_type = arguments["version_type"] or self.config.settings.get( + "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.""" @@ -153,7 +157,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_type_cls=self.version_type, ) is_initial = self.is_initial_tag(current_tag_version, is_yes) @@ -209,9 +215,14 @@ def __call__(self): # noqa: C901 prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, + version_type_cls=self.version_type, ) - 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_type_cls=self.version_type, + ) message = bump.create_commit_message( current_version, new_version, bump_commit_message ) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 10fc890e9d..4c291d3ac2 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.VERSION_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/commitizen/defaults.py b/commitizen/defaults.py index 2ab349367d..cae1d493fd 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_type: 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_type": None, } MAJOR = "MAJOR" diff --git a/commitizen/version_types.py b/commitizen/version_types.py new file mode 100644 index 0000000000..d896d809b1 --- /dev/null +++ b/commitizen/version_types.py @@ -0,0 +1,99 @@ +import sys +from typing import Optional, Tuple, Union + +if sys.version_info >= (3, 8): + from typing import Protocol as _Protocol +else: + _Protocol = object + +from packaging.version import Version + + +class VersionProtocol(_Protocol): + 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) -> Tuple[int, ...]: + raise NotImplementedError("must be implemented") + + @property + def is_prerelease(self) -> bool: + raise NotImplementedError("must be implemented") + + @property + def pre(self) -> Optional[Tuple[str, int]]: + raise NotImplementedError("must be implemented") + + @property + def local(self) -> 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) -> Tuple[int, ...]: + return self._version.release + + @property + def is_prerelease(self) -> bool: + return self._version.is_prerelease + + @property + def pre(self) -> Optional[Tuple[str, int]]: + return self._version.pre + + @property + def local(self) -> 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 version.epoch != 0: + parts.append(f"{version.epoch}!") + + # Release segment + parts.append(".".join(str(x) for x in version.release)) + + # Pre-release + if version.pre: + pre = "".join(str(x) for x in version.pre) + parts.append(f"-{pre}") + + # Post-release + if version.post is not None: + parts.append(f"-post{version.post}") + + # Development release + if version.dev is not None: + parts.append(f"-dev{version.dev}") + + # Local version segment + if version.local: + parts.append(f"+{version.local}") + + return "".join(parts) + + +VERSION_TYPES = { + "pep440": Version, + "semver": SemVerVersion, +} diff --git a/docs/bump.md b/docs/bump.md index ca5bdf7fe2..4a686b77ef 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-type {pep440,semver} + choose version type + ``` ### `--files-only` @@ -476,6 +479,26 @@ Defaults to: `0` prerelease_offset = 1 ``` +### `version_type` + +Choose version type + +* `pep440` - default version type. + - prerelease - `1.0.1a0` + - devrelease - `1.0.1dev0` + - dev and pre - `1.0.1a0.dev0` +* `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: `pep440` + +```toml +[tool.commitizen] +version_type = "semver" +``` + ## Custom bump Read the [customizing section](./customization.md). diff --git a/docs/config.md b/docs/config.md index d752c1210e..e30d8beaa0 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 diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 2a117aa5a1..59a0e01ef3 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -853,3 +853,130 @@ 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_type_via_cli( + tmp_commitizen_project_initial, mocker: MockFixture +): + tmp_commitizen_project = tmp_commitizen_project_initial() + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--version-type", + "semver", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a0") + assert tag_exists is True + + 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"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + 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_initial, mocker: MockFixture +): + tmp_commitizen_project = tmp_commitizen_project_initial( + config_extra='version_type = "semver"\n', + ) + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + 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 + + 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) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a1") + assert tag_exists is True + + 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"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + 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_initial, mocker: MockFixture +): + 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_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + 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 + + 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) + cli.main() + + tag_exists = git.tag_exist("v0.2.0-a1") + assert tag_exists is True + + 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"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0") + assert tag_exists is True + + 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 01290c1d0a..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 ): @@ -1152,3 +1153,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() + + 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.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 + 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 new file mode 100644 index 0000000000..633442f9ee --- /dev/null +++ b/tests/test_bump_find_version_type_semver.py @@ -0,0 +1,102 @@ +import itertools + +import pytest + +from commitizen.bump import generate_version +from commitizen.version_types 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_type(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_type_cls=SemVerVersion, + ) + ) + == expected + ) diff --git a/tests/test_conf.py b/tests/test_conf.py index df17a108ef..4226096371 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_type": 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_type": None, } _read_settings = {