From 03aa5968363d683c4beaca849de83e4d46258e2c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 01:25:19 +0800 Subject: [PATCH 01/11] refactor(bump_rule): add bump_rule interface, DefaultBumpRule and its tests --- commitizen/bump_rule.py | 63 ++++++++++++ .../conventional_commits.py | 8 +- commitizen/cz/customize/customize.py | 6 +- commitizen/defaults.py | 6 +- tests/test_bump_rule.py | 95 +++++++++++++++++++ 5 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 commitizen/bump_rule.py create mode 100644 tests/test_bump_rule.py diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py new file mode 100644 index 000000000..d9fb473b9 --- /dev/null +++ b/commitizen/bump_rule.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import re +from functools import cached_property +from typing import Protocol + +from commitizen.version_schemes import Increment + + +class BumpRule(Protocol): + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> Increment | None: ... + + +class DefaultBumpRule(BumpRule): + _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> Increment | None: + if not (m := self._head_pattern.match(commit_message)): + return None + + change_type = m.group("change_type") + if m.group("bang") or change_type == "BREAKING CHANGE": + return "MAJOR" if major_version_zero else "MINOR" + + if change_type == "feat": + return "MINOR" + + if change_type in self._PATCH_CHANGE_TYPES: + return "PATCH" + + return None + + @cached_property + def _head_pattern(self) -> re.Pattern: + change_types = [ + r"BREAKING[\-\ ]CHANGE", + "fix", + "feat", + "docs", + "style", + "refactor", + "perf", + "test", + "build", + "ci", + ] + re_change_type = r"(?P" + "|".join(change_types) + r")" + re_scope = r"(?P\(.+\))?" + re_bang = r"(?P!)?" + return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") + + +class CustomBumpRule(BumpRule): + """TODO: Implement""" + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> Increment | None: + return None diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index c7b88258c..af29a209f 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -28,9 +28,9 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): - bump_pattern = defaults.bump_pattern - bump_map = defaults.bump_map - bump_map_major_version_zero = defaults.bump_map_major_version_zero + bump_pattern = defaults.BUMP_PATTERN + bump_map = defaults.BUMP_MAP + bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO commit_parser = r"^((?Pfeat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P[^()\r\n]*)\)|\()?(?P!)?|\w+!):\s(?P.*)?" # noqa change_type_map = { "feat": "Feat", @@ -38,7 +38,7 @@ class ConventionalCommitsCz(BaseCommitizen): "refactor": "Refactor", "perf": "Perf", } - changelog_pattern = defaults.bump_pattern + changelog_pattern = defaults.BUMP_PATTERN def questions(self) -> Questions: questions: Questions = [ diff --git a/commitizen/cz/customize/customize.py b/commitizen/cz/customize/customize.py index d53ae29f1..de29fa984 100644 --- a/commitizen/cz/customize/customize.py +++ b/commitizen/cz/customize/customize.py @@ -21,9 +21,9 @@ class CustomizeCommitsCz(BaseCommitizen): - bump_pattern = defaults.bump_pattern - bump_map = defaults.bump_map - bump_map_major_version_zero = defaults.bump_map_major_version_zero + bump_pattern = defaults.BUMP_PATTERN + bump_map = defaults.BUMP_MAP + bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO change_type_order = defaults.change_type_order def __init__(self, config: BaseConfig): diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 0b78e1b0b..c61c54cbb 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -114,8 +114,8 @@ class Settings(TypedDict, total=False): CHANGELOG_FORMAT = "markdown" -bump_pattern = r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" -bump_map = OrderedDict( +BUMP_PATTERN = r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" +BUMP_MAP = OrderedDict( ( (r"^.+!$", MAJOR), (r"^BREAKING[\-\ ]CHANGE", MAJOR), @@ -125,7 +125,7 @@ class Settings(TypedDict, total=False): (r"^perf", PATCH), ) ) -bump_map_major_version_zero = OrderedDict( +BUMP_MAP_MAJOR_VERSION_ZERO = OrderedDict( ( (r"^.+!$", MINOR), (r"^BREAKING[\-\ ]CHANGE", MINOR), diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py new file mode 100644 index 000000000..a47575d36 --- /dev/null +++ b/tests/test_bump_rule.py @@ -0,0 +1,95 @@ +import pytest + +from commitizen.bump_rule import DefaultBumpRule +from commitizen.defaults import MAJOR, MINOR, PATCH + + +@pytest.fixture +def bump_rule(): + return DefaultBumpRule() + + +class TestDefaultBumpRule: + def test_feat_commit(self, bump_rule): + assert bump_rule.get_increment("feat: add new feature", False) == MINOR + assert bump_rule.get_increment("feat: add new feature", True) == MINOR + + def test_fix_commit(self, bump_rule): + assert bump_rule.get_increment("fix: fix bug", False) == PATCH + assert bump_rule.get_increment("fix: fix bug", True) == PATCH + + def test_perf_commit(self, bump_rule): + assert bump_rule.get_increment("perf: improve performance", False) == PATCH + assert bump_rule.get_increment("perf: improve performance", True) == PATCH + + def test_refactor_commit(self, bump_rule): + assert bump_rule.get_increment("refactor: restructure code", False) == PATCH + assert bump_rule.get_increment("refactor: restructure code", True) == PATCH + + def test_breaking_change_with_bang(self, bump_rule): + assert bump_rule.get_increment("feat!: breaking change", False) == MINOR + assert bump_rule.get_increment("feat!: breaking change", True) == MAJOR + + def test_breaking_change_type(self, bump_rule): + assert bump_rule.get_increment("BREAKING CHANGE: major change", False) == MINOR + assert bump_rule.get_increment("BREAKING CHANGE: major change", True) == MAJOR + + def test_commit_with_scope(self, bump_rule): + assert bump_rule.get_increment("feat(api): add new endpoint", False) == MINOR + assert bump_rule.get_increment("fix(ui): fix button alignment", False) == PATCH + + def test_commit_with_complex_scopes(self, bump_rule): + # Test with multiple word scopes + assert ( + bump_rule.get_increment("feat(user_management): add user roles", False) + == MINOR + ) + assert ( + bump_rule.get_increment("fix(database_connection): handle timeout", False) + == PATCH + ) + + # Test with nested scopes + assert ( + bump_rule.get_increment("feat(api/auth): implement OAuth", False) == MINOR + ) + assert ( + bump_rule.get_increment("fix(ui/components): fix dropdown", False) == PATCH + ) + + # Test with breaking changes and scopes + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) + == MINOR + ) + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) + == MAJOR + ) + + # Test with BREAKING CHANGE and scopes + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", False + ) + == MINOR + ) + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", True + ) + == MAJOR + ) + + def test_invalid_commit_message(self, bump_rule): + assert bump_rule.get_increment("invalid commit message", False) is None + assert bump_rule.get_increment("", False) is None + assert bump_rule.get_increment("feat", False) is None + + def test_other_commit_types(self, bump_rule): + # These commit types should not trigger any version bump + assert bump_rule.get_increment("docs: update documentation", False) is None + assert bump_rule.get_increment("style: format code", False) is None + assert bump_rule.get_increment("test: add unit tests", False) is None + assert bump_rule.get_increment("build: update build config", False) is None + assert bump_rule.get_increment("ci: update CI pipeline", False) is None From 4dea3fa25f8552bfd278a01705f2a27270a54de0 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 02:27:39 +0800 Subject: [PATCH 02/11] refactor(bump_rule): add find_increment_by_callable --- commitizen/bump.py | 1 + commitizen/bump_rule.py | 33 +++++++++++++++++- tests/test_bump_rule.py | 74 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index adfab64cb..6412df6f0 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -18,6 +18,7 @@ logger = getLogger("commitizen") +# TODO: replace this with find_increment_by_callable? def find_increment( commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict ) -> Increment | None: diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index d9fb473b9..9b42db805 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -2,10 +2,41 @@ import re from functools import cached_property -from typing import Protocol +from typing import Callable, Protocol from commitizen.version_schemes import Increment +_VERSION_ORDERING = dict(zip((None, "PATCH", "MINOR", "MAJOR"), range(4))) + + +def find_increment_by_callable( + commit_messages: list[str], get_increment: Callable[[str], Increment | None] +) -> Increment | None: + """Find the highest version increment from a list of messages. + + This function processes a list of messages and determines the highest version + increment needed based on the commit messages. It splits multi-line commit messages + and evaluates each line using the provided get_increment callable. + + Args: + commit_messages: A list of messages to analyze. + get_increment: A callable that takes a commit message string and returns an + Increment value (MAJOR, MINOR, PATCH) or None if no increment is needed. + + Returns: + The highest version increment needed (MAJOR, MINOR, PATCH) or None if no + increment is needed. The order of precedence is MAJOR > MINOR > PATCH. + + Example: + >>> commit_messages = ["feat: new feature", "fix: bug fix"] + >>> rule = DefaultBumpRule() + >>> find_increment_by_callable(commit_messages, lambda x: rule.get_increment(x, False)) + 'MINOR' + """ + lines = (line for message in commit_messages for line in message.split("\n")) + increments = map(get_increment, lines) + return max(increments, key=lambda x: _VERSION_ORDERING[x], default=None) + class BumpRule(Protocol): def get_increment( diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index a47575d36..920ef6c3b 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -1,6 +1,6 @@ import pytest -from commitizen.bump_rule import DefaultBumpRule +from commitizen.bump_rule import DefaultBumpRule, find_increment_by_callable from commitizen.defaults import MAJOR, MINOR, PATCH @@ -93,3 +93,75 @@ def test_other_commit_types(self, bump_rule): assert bump_rule.get_increment("test: add unit tests", False) is None assert bump_rule.get_increment("build: update build config", False) is None assert bump_rule.get_increment("ci: update CI pipeline", False) is None + + +class TestFindIncrementByCallable: + @pytest.fixture + def get_increment(self, bump_rule): + return lambda x: bump_rule.get_increment(x, False) + + def test_single_commit(self, get_increment): + commit_messages = ["feat: add new feature"] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + def test_multiple_commits(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "docs: update readme", + ] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + def test_breaking_change(self, get_increment): + commit_messages = [ + "feat: new feature", + "feat!: breaking change", + ] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + def test_multi_line_commit(self, get_increment): + commit_messages = [ + "feat: new feature\n\nBREAKING CHANGE: major change", + ] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + def test_no_increment_needed(self, get_increment): + commit_messages = [ + "docs: update documentation", + "style: format code", + ] + assert find_increment_by_callable(commit_messages, get_increment) is None + + def test_empty_commits(self, get_increment): + commit_messages = [] + assert find_increment_by_callable(commit_messages, get_increment) is None + + def test_major_version_zero(self): + bump_rule = DefaultBumpRule() + + commit_messages = [ + "feat!: breaking change", + "BREAKING CHANGE: major change", + ] + assert ( + find_increment_by_callable( + commit_messages, lambda x: bump_rule.get_increment(x, True) + ) + == MAJOR + ) + + def test_mixed_commit_types(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "perf: improve performance", + "refactor: restructure code", + ] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + def test_commit_with_scope(self, get_increment): + commit_messages = [ + "feat(api): add new endpoint", + "fix(ui): fix button alignment", + ] + assert find_increment_by_callable(commit_messages, get_increment) == MINOR From 57205e4762b64d50ec8525c15e04b12a54c54f09 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 02:59:37 +0800 Subject: [PATCH 03/11] refactor(bump_rule): deprecate wip --- commitizen/bump_rule.py | 15 +++++---------- commitizen/commands/bump.py | 9 ++++++++- commitizen/cz/base.py | 5 +++++ .../conventional_commits/conventional_commits.py | 3 +++ tests/test_bump_rule.py | 8 ++++---- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 9b42db805..67c147424 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from collections.abc import Iterable from functools import cached_property from typing import Callable, Protocol @@ -10,7 +11,7 @@ def find_increment_by_callable( - commit_messages: list[str], get_increment: Callable[[str], Increment | None] + commit_messages: Iterable[str], get_increment: Callable[[str], Increment | None] ) -> Increment | None: """Find the highest version increment from a list of messages. @@ -29,7 +30,7 @@ def find_increment_by_callable( Example: >>> commit_messages = ["feat: new feature", "fix: bug fix"] - >>> rule = DefaultBumpRule() + >>> rule = ConventionalCommitBumpRule() >>> find_increment_by_callable(commit_messages, lambda x: rule.get_increment(x, False)) 'MINOR' """ @@ -44,7 +45,7 @@ def get_increment( ) -> Increment | None: ... -class DefaultBumpRule(BumpRule): +class ConventionalCommitBumpRule(BumpRule): _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) def get_increment( @@ -85,10 +86,4 @@ def _head_pattern(self) -> re.Pattern: return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") -class CustomBumpRule(BumpRule): - """TODO: Implement""" - - def get_increment( - self, commit_message: str, major_version_zero: bool - ) -> Increment | None: - return None +# TODO: Implement CustomBumpRule diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 0a2bbe37f..cf588e87d 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,6 +7,7 @@ import questionary from commitizen import bump, factory, git, hooks, out +from commitizen.bump_rule import find_increment_by_callable from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -122,7 +123,13 @@ def is_initial_tag( def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: # Update the bump map to ensure major version doesn't increment. is_major_version_zero: bool = self.bump_settings["major_version_zero"] - # self.cz.bump_map = defaults.bump_map_major_version_zero + + if rule := self.cz.bump_rule: + return find_increment_by_callable( + (commit.message for commit in commits), + lambda x: rule.get_increment(x, is_major_version_zero), + ) + bump_map = ( self.cz.bump_map_major_version_zero if is_major_version_zero diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 43455a74c..dd48dd718 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -8,6 +8,7 @@ from prompt_toolkit.styles import Style, merge_styles from commitizen import git +from commitizen.bump_rule import BumpRule from commitizen.config.base_config import BaseConfig from commitizen.defaults import Questions @@ -25,9 +26,13 @@ def __call__( class BaseCommitizen(metaclass=ABCMeta): + bump_rule: BumpRule | None = None + + # TODO: deprecate these bump_pattern: str | None = None bump_map: dict[str, str] | None = None bump_map_major_version_zero: dict[str, str] | None = None + default_style_config: list[tuple[str, str]] = [ ("qmark", "fg:#ff9d00 bold"), ("question", "bold"), diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index af29a209f..9c696bc39 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -2,6 +2,7 @@ import re from commitizen import defaults +from commitizen.bump_rule import ConventionalCommitBumpRule from commitizen.cz.base import BaseCommitizen from commitizen.cz.utils import multiple_line_breaker, required_validator from commitizen.defaults import Questions @@ -28,6 +29,8 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): + bump_rule = ConventionalCommitBumpRule() + bump_pattern = defaults.BUMP_PATTERN bump_map = defaults.BUMP_MAP bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 920ef6c3b..dae8942f1 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -1,15 +1,15 @@ import pytest -from commitizen.bump_rule import DefaultBumpRule, find_increment_by_callable +from commitizen.bump_rule import ConventionalCommitBumpRule, find_increment_by_callable from commitizen.defaults import MAJOR, MINOR, PATCH @pytest.fixture def bump_rule(): - return DefaultBumpRule() + return ConventionalCommitBumpRule() -class TestDefaultBumpRule: +class TestConventionalCommitBumpRule: def test_feat_commit(self, bump_rule): assert bump_rule.get_increment("feat: add new feature", False) == MINOR assert bump_rule.get_increment("feat: add new feature", True) == MINOR @@ -137,7 +137,7 @@ def test_empty_commits(self, get_increment): assert find_increment_by_callable(commit_messages, get_increment) is None def test_major_version_zero(self): - bump_rule = DefaultBumpRule() + bump_rule = ConventionalCommitBumpRule() commit_messages = [ "feat!: breaking change", From afa0063d00592c716ada2ca834ae53ed2c4509b7 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 03:23:56 +0800 Subject: [PATCH 04/11] refactor(bump_rule): add old school bump rule for backward compatibility --- commitizen/bump_rule.py | 36 ++++++++ tests/test_bump_rule.py | 197 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 231 insertions(+), 2 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 67c147424..934cf22b2 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -5,6 +5,7 @@ from functools import cached_property from typing import Callable, Protocol +from commitizen.exceptions import NoPatternMapError from commitizen.version_schemes import Increment _VERSION_ORDERING = dict(zip((None, "PATCH", "MINOR", "MAJOR"), range(4))) @@ -86,4 +87,39 @@ def _head_pattern(self) -> re.Pattern: return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") +class OldSchoolBumpRule(BumpRule): + """TODO: rename?""" + + def __init__( + self, + bump_pattern: str, + bump_map: dict[str, Increment], + bump_map_major_version_zero: dict[str, Increment], + ): + if not bump_map or not bump_pattern: + raise NoPatternMapError( + f"Invalid bump rule: {bump_pattern=} and {bump_map=}" + ) + + self.bump_pattern = re.compile(bump_pattern) + self.bump_map = bump_map + self.bump_map_major_version_zero = bump_map_major_version_zero + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> Increment | None: + if not (m := self.bump_pattern.search(commit_message)): + return None + + bump_map = ( + self.bump_map_major_version_zero if major_version_zero else self.bump_map + ) + + found_keyword = m.group(1) + for match_pattern, increment in bump_map.items(): + if re.match(match_pattern, found_keyword): + return increment + return None + + # TODO: Implement CustomBumpRule diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index dae8942f1..5eeab7497 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -1,7 +1,19 @@ import pytest -from commitizen.bump_rule import ConventionalCommitBumpRule, find_increment_by_callable -from commitizen.defaults import MAJOR, MINOR, PATCH +from commitizen.bump_rule import ( + ConventionalCommitBumpRule, + OldSchoolBumpRule, + find_increment_by_callable, +) +from commitizen.defaults import ( + BUMP_MAP, + BUMP_MAP_MAJOR_VERSION_ZERO, + BUMP_PATTERN, + MAJOR, + MINOR, + PATCH, +) +from commitizen.exceptions import NoPatternMapError @pytest.fixture @@ -165,3 +177,184 @@ def test_commit_with_scope(self, get_increment): "fix(ui): fix button alignment", ] assert find_increment_by_callable(commit_messages, get_increment) == MINOR + + +class TestOldSchoolBumpRule: + @pytest.fixture + def bump_pattern(self): + return r"^.*?\[(.*?)\].*$" + + @pytest.fixture + def bump_map(self): + return { + "MAJOR": MAJOR, + "MINOR": MINOR, + "PATCH": PATCH, + } + + @pytest.fixture + def bump_map_major_version_zero(self): + return { + "MAJOR": MINOR, # MAJOR becomes MINOR in version zero + "MINOR": MINOR, + "PATCH": PATCH, + } + + @pytest.fixture + def old_school_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): + return OldSchoolBumpRule(bump_pattern, bump_map, bump_map_major_version_zero) + + def test_major_version(self, old_school_rule): + assert ( + old_school_rule.get_increment("feat: add new feature [MAJOR]", False) + == MAJOR + ) + assert old_school_rule.get_increment("fix: bug fix [MAJOR]", False) == MAJOR + + def test_minor_version(self, old_school_rule): + assert ( + old_school_rule.get_increment("feat: add new feature [MINOR]", False) + == MINOR + ) + assert old_school_rule.get_increment("fix: bug fix [MINOR]", False) == MINOR + + def test_patch_version(self, old_school_rule): + assert ( + old_school_rule.get_increment("feat: add new feature [PATCH]", False) + == PATCH + ) + assert old_school_rule.get_increment("fix: bug fix [PATCH]", False) == PATCH + + def test_major_version_zero(self, old_school_rule): + assert ( + old_school_rule.get_increment("feat: add new feature [MAJOR]", True) + == MINOR + ) + assert old_school_rule.get_increment("fix: bug fix [MAJOR]", True) == MINOR + + def test_no_match(self, old_school_rule): + assert old_school_rule.get_increment("feat: add new feature", False) is None + assert old_school_rule.get_increment("fix: bug fix", False) is None + + def test_invalid_pattern(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule("", bump_map, bump_map_major_version_zero) + + def test_invalid_bump_map(self, bump_pattern): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule(bump_pattern, {}, {}) + + def test_complex_pattern(self): + pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" + bump_map = { + "MAJOR": MAJOR, + "MINOR": MINOR, + "PATCH": PATCH, + } + rule = OldSchoolBumpRule(pattern, bump_map, bump_map) + + assert ( + rule.get_increment("feat: add new feature [MAJOR] [MINOR]", False) == MAJOR + ) + assert rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) == MINOR + + def test_with_find_increment_by_callable(self, old_school_rule): + commit_messages = [ + "feat: add new feature [MAJOR]", + "fix: bug fix [PATCH]", + "docs: update readme [MINOR]", + ] + assert ( + find_increment_by_callable( + commit_messages, lambda x: old_school_rule.get_increment(x, False) + ) + == MAJOR + ) + + +class TestOldSchoolBumpRuleWithDefault: + @pytest.fixture + def old_school_rule(self): + return OldSchoolBumpRule(BUMP_PATTERN, BUMP_MAP, BUMP_MAP_MAJOR_VERSION_ZERO) + + def test_breaking_change_with_bang(self, old_school_rule): + assert old_school_rule.get_increment("feat!: breaking change", False) == MAJOR + assert old_school_rule.get_increment("fix!: breaking change", False) == MAJOR + assert old_school_rule.get_increment("feat!: breaking change", True) == MINOR + assert old_school_rule.get_increment("fix!: breaking change", True) == MINOR + + def test_breaking_change_type(self, old_school_rule): + assert ( + old_school_rule.get_increment("BREAKING CHANGE: major change", False) + == MAJOR + ) + assert ( + old_school_rule.get_increment("BREAKING-CHANGE: major change", False) + == MAJOR + ) + assert ( + old_school_rule.get_increment("BREAKING CHANGE: major change", True) + == MINOR + ) + assert ( + old_school_rule.get_increment("BREAKING-CHANGE: major change", True) + == MINOR + ) + + def test_feat_commit(self, old_school_rule): + assert old_school_rule.get_increment("feat: add new feature", False) == MINOR + assert old_school_rule.get_increment("feat: add new feature", True) == MINOR + + def test_fix_commit(self, old_school_rule): + assert old_school_rule.get_increment("fix: fix bug", False) == PATCH + assert old_school_rule.get_increment("fix: fix bug", True) == PATCH + + def test_refactor_commit(self, old_school_rule): + assert ( + old_school_rule.get_increment("refactor: restructure code", False) == PATCH + ) + assert ( + old_school_rule.get_increment("refactor: restructure code", True) == PATCH + ) + + def test_perf_commit(self, old_school_rule): + assert ( + old_school_rule.get_increment("perf: improve performance", False) == PATCH + ) + assert old_school_rule.get_increment("perf: improve performance", True) == PATCH + + def test_commit_with_scope(self, old_school_rule): + assert ( + old_school_rule.get_increment("feat(api): add new endpoint", False) == MINOR + ) + assert ( + old_school_rule.get_increment("fix(ui): fix button alignment", False) + == PATCH + ) + assert ( + old_school_rule.get_increment("refactor(core): restructure", False) == PATCH + ) + + def test_no_match(self, old_school_rule): + assert ( + old_school_rule.get_increment("docs: update documentation", False) is None + ) + assert old_school_rule.get_increment("style: format code", False) is None + assert old_school_rule.get_increment("test: add unit tests", False) is None + assert ( + old_school_rule.get_increment("build: update build config", False) is None + ) + assert old_school_rule.get_increment("ci: update CI pipeline", False) is None + + def test_with_find_increment_by_callable(self, old_school_rule): + commit_messages = [ + "feat!: breaking change", + "fix: bug fix", + "perf: improve performance", + ] + assert ( + find_increment_by_callable( + commit_messages, lambda x: old_school_rule.get_increment(x, False) + ) + == MAJOR + ) From facb51c1e8ea7fb6c5d64eabf926cedb368f87b4 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 03:30:44 +0800 Subject: [PATCH 05/11] test(bump_rule): raise error --- commitizen/bump_rule.py | 4 ++-- tests/test_bump_rule.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 934cf22b2..c6865d3d6 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -96,9 +96,9 @@ def __init__( bump_map: dict[str, Increment], bump_map_major_version_zero: dict[str, Increment], ): - if not bump_map or not bump_pattern: + if not bump_map or not bump_pattern or not bump_map_major_version_zero: raise NoPatternMapError( - f"Invalid bump rule: {bump_pattern=} and {bump_map=}" + f"Invalid bump rule: {bump_pattern=} and {bump_map=} and {bump_map_major_version_zero=}" ) self.bump_pattern = re.compile(bump_pattern) diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 5eeab7497..c2bf76d7d 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -244,6 +244,26 @@ def test_invalid_bump_map(self, bump_pattern): with pytest.raises(NoPatternMapError): OldSchoolBumpRule(bump_pattern, {}, {}) + def test_invalid_bump_map_major_version_zero(self, bump_pattern, bump_map): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule(bump_pattern, bump_map, {}) + + def test_all_invalid(self): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule("", {}, {}) + + def test_none_values(self): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule(None, {}, {}) + + def test_empty_pattern_with_valid_maps(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule("", bump_map, bump_map_major_version_zero) + + def test_empty_maps_with_valid_pattern(self, bump_pattern): + with pytest.raises(NoPatternMapError): + OldSchoolBumpRule(bump_pattern, {}, {}) + def test_complex_pattern(self): pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" bump_map = { From be7cb23ba0330f52738dfbed16cdc95ca39e27a4 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 03:57:39 +0800 Subject: [PATCH 06/11] refactor(command_bump): use bump rules --- commitizen/bump.py | 47 +---------- commitizen/bump_rule.py | 8 +- commitizen/commands/bump.py | 36 +++++---- tests/test_bump_find_increment.py | 124 ------------------------------ tests/test_bump_rule.py | 22 +++--- 5 files changed, 38 insertions(+), 199 deletions(-) delete mode 100644 tests/test_bump_find_increment.py diff --git a/commitizen/bump.py b/commitizen/bump.py index 6412df6f0..b3b782fa1 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -2,63 +2,20 @@ import os import re -from collections import OrderedDict from glob import iglob from logging import getLogger from string import Template -from typing import cast from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message, encoding from commitizen.exceptions import CurrentVersionNotFoundError -from commitizen.git import GitCommit, smart_open -from commitizen.version_schemes import Increment, Version +from commitizen.git import smart_open +from commitizen.version_schemes import Version VERSION_TYPES = [None, PATCH, MINOR, MAJOR] logger = getLogger("commitizen") -# TODO: replace this with find_increment_by_callable? -def find_increment( - commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict -) -> Increment | None: - if isinstance(increments_map, dict): - increments_map = OrderedDict(increments_map) - - # Most important cases are major and minor. - # Everything else will be considered patch. - select_pattern = re.compile(regex) - increment: str | None = None - - for commit in commits: - for message in commit.message.split("\n"): - result = select_pattern.search(message) - - if result: - found_keyword = result.group(1) - new_increment = None - for match_pattern in increments_map.keys(): - if re.match(match_pattern, found_keyword): - new_increment = increments_map[match_pattern] - break - - if new_increment is None: - logger.debug( - f"no increment needed for '{found_keyword}' in '{message}'" - ) - - if VERSION_TYPES.index(increment) < VERSION_TYPES.index(new_increment): - logger.debug( - f"increment detected is '{new_increment}' due to '{found_keyword}' in '{message}'" - ) - increment = new_increment - - if increment == MAJOR: - break - - return cast(Increment, increment) - - def update_version_in_files( current_version: str, new_version: str, diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index c6865d3d6..0aa5663ed 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -48,6 +48,8 @@ def get_increment( class ConventionalCommitBumpRule(BumpRule): _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + _BREAKING_CHANGE = r"BREAKING[\-\ ]CHANGE" + _RE_BREAKING_CHANGE = re.compile(_BREAKING_CHANGE) def get_increment( self, commit_message: str, major_version_zero: bool @@ -56,8 +58,8 @@ def get_increment( return None change_type = m.group("change_type") - if m.group("bang") or change_type == "BREAKING CHANGE": - return "MAJOR" if major_version_zero else "MINOR" + if m.group("bang") or self._RE_BREAKING_CHANGE.match(change_type): + return "MINOR" if major_version_zero else "MAJOR" if change_type == "feat": return "MINOR" @@ -70,7 +72,7 @@ def get_increment( @cached_property def _head_pattern(self) -> re.Pattern: change_types = [ - r"BREAKING[\-\ ]CHANGE", + self._BREAKING_CHANGE, "fix", "feat", "docs", diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index cf588e87d..a18db544f 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,7 +7,7 @@ import questionary from commitizen import bump, factory, git, hooks, out -from commitizen.bump_rule import find_increment_by_callable +from commitizen.bump_rule import OldSchoolBumpRule, find_increment_by_callable from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -124,27 +124,31 @@ def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: # Update the bump map to ensure major version doesn't increment. is_major_version_zero: bool = self.bump_settings["major_version_zero"] - if rule := self.cz.bump_rule: - return find_increment_by_callable( - (commit.message for commit in commits), - lambda x: rule.get_increment(x, is_major_version_zero), - ) - - bump_map = ( - self.cz.bump_map_major_version_zero - if is_major_version_zero - else self.cz.bump_map + # Fallback to old school bump rule if no bump rule is provided + rule = self.cz.bump_rule or OldSchoolBumpRule( + *self._get_validated_cz_bump(), + ) + return find_increment_by_callable( + (commit.message for commit in commits), + lambda x: rule.get_increment(x, is_major_version_zero), ) - bump_pattern = self.cz.bump_pattern - if not bump_map or not bump_pattern: + def _get_validated_cz_bump( + self, + ) -> tuple[str, dict[str, Increment], dict[str, Increment]]: + """For fixing the type errors""" + bump_pattern = self.cz.bump_pattern + bump_map = self.cz.bump_map + bump_map_major_version_zero = self.cz.bump_map_major_version_zero + if not bump_pattern or not bump_map or not bump_map_major_version_zero: raise NoPatternMapError( f"'{self.config.settings['name']}' rule does not support bump" ) - increment = bump.find_increment( - commits, regex=bump_pattern, increments_map=bump_map + + return cast( + tuple[str, dict[str, Increment], dict[str, Increment]], + (bump_pattern, bump_map, bump_map_major_version_zero), ) - return increment def __call__(self) -> None: # noqa: C901 """Steps executed to bump.""" diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py deleted file mode 100644 index ff24ff17a..000000000 --- a/tests/test_bump_find_increment.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -CC: Conventional commits -SVE: Semantic version at the end -""" - -import pytest - -from commitizen import bump -from commitizen.cz.conventional_commits import ConventionalCommitsCz -from commitizen.git import GitCommit - -NONE_INCREMENT_CC = [ - "docs(README): motivation", - "ci: added travis", - "performance. Remove or disable the reimplemented linters", - "refactor that how this line starts", -] - -PATCH_INCREMENTS_CC = [ - "fix(setup.py): future is now required for every python version", - "docs(README): motivation", -] - -MINOR_INCREMENTS_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", - "perf: app is much faster", - "refactor: app is much faster", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING CHANGE: `extends` key in config file is now used for extending other config files", # noqa - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING-CHANGE: `extends` key in config file is now used for extending other config files", # noqa - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC = [ - "feat(cli)!: added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2 = [ - "feat(pipeline)!: some text with breaking change" -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC = [ - "chore!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC = [ - "chore(deps)!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -PATCH_INCREMENTS_SVE = ["readme motivation PATCH", "fix setup.py PATCH"] - -MINOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", -] - -MAJOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", - "extends key is used for other config files MAJOR", -] - -semantic_version_pattern = r"(MAJOR|MINOR|PATCH)" -semantic_version_map = {"MAJOR": "MAJOR", "MINOR": "MINOR", "PATCH": "PATCH"} - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_CC, "PATCH"), - (MINOR_INCREMENTS_CC, "MINOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_CC, "MAJOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2, "MAJOR"), - (NONE_INCREMENT_CC, None), - ), -) -def test_find_increment(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, - regex=ConventionalCommitsCz.bump_pattern, - increments_map=ConventionalCommitsCz.bump_map, - ) - assert increment_type == expected_type - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_SVE, "PATCH"), - (MINOR_INCREMENTS_SVE, "MINOR"), - (MAJOR_INCREMENTS_SVE, "MAJOR"), - ), -) -def test_find_increment_sve(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, regex=semantic_version_pattern, increments_map=semantic_version_map - ) - assert increment_type == expected_type diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index c2bf76d7d..1f7ba4f8c 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -39,12 +39,12 @@ def test_refactor_commit(self, bump_rule): assert bump_rule.get_increment("refactor: restructure code", True) == PATCH def test_breaking_change_with_bang(self, bump_rule): - assert bump_rule.get_increment("feat!: breaking change", False) == MINOR - assert bump_rule.get_increment("feat!: breaking change", True) == MAJOR + assert bump_rule.get_increment("feat!: breaking change", False) == MAJOR + assert bump_rule.get_increment("feat!: breaking change", True) == MINOR def test_breaking_change_type(self, bump_rule): - assert bump_rule.get_increment("BREAKING CHANGE: major change", False) == MINOR - assert bump_rule.get_increment("BREAKING CHANGE: major change", True) == MAJOR + assert bump_rule.get_increment("BREAKING CHANGE: major change", False) == MAJOR + assert bump_rule.get_increment("BREAKING CHANGE: major change", True) == MINOR def test_commit_with_scope(self, bump_rule): assert bump_rule.get_increment("feat(api): add new endpoint", False) == MINOR @@ -72,11 +72,11 @@ def test_commit_with_complex_scopes(self, bump_rule): # Test with breaking changes and scopes assert ( bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) - == MINOR + == MAJOR ) assert ( bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) - == MAJOR + == MINOR ) # Test with BREAKING CHANGE and scopes @@ -84,13 +84,13 @@ def test_commit_with_complex_scopes(self, bump_rule): bump_rule.get_increment( "BREAKING CHANGE(api): remove deprecated endpoints", False ) - == MINOR + == MAJOR ) assert ( bump_rule.get_increment( "BREAKING CHANGE(api): remove deprecated endpoints", True ) - == MAJOR + == MINOR ) def test_invalid_commit_message(self, bump_rule): @@ -129,13 +129,13 @@ def test_breaking_change(self, get_increment): "feat: new feature", "feat!: breaking change", ] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert find_increment_by_callable(commit_messages, get_increment) == MAJOR def test_multi_line_commit(self, get_increment): commit_messages = [ "feat: new feature\n\nBREAKING CHANGE: major change", ] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert find_increment_by_callable(commit_messages, get_increment) == MAJOR def test_no_increment_needed(self, get_increment): commit_messages = [ @@ -159,7 +159,7 @@ def test_major_version_zero(self): find_increment_by_callable( commit_messages, lambda x: bump_rule.get_increment(x, True) ) - == MAJOR + == MINOR ) def test_mixed_commit_types(self, get_increment): From 88efb6171596521fb941b2c4b0f018500032612f Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 04:28:40 +0800 Subject: [PATCH 07/11] docs(bump_rule): docstring --- commitizen/bump_rule.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 0aa5663ed..b84eb4e12 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -43,7 +43,26 @@ def find_increment_by_callable( class BumpRule(Protocol): def get_increment( self, commit_message: str, major_version_zero: bool - ) -> Increment | None: ... + ) -> Increment | None: + """Determine the version increment based on a commit message. + + This method analyzes a commit message to determine what kind of version increment + is needed according to the Conventional Commits specification. It handles special + cases for breaking changes and respects the major_version_zero flag. + + Args: + commit_message: The commit message to analyze. Should follow conventional commit format. + major_version_zero: If True, breaking changes will result in a MINOR version bump + instead of MAJOR. This is useful for projects in 0.x.x versions. + + Returns: + Increment | None: The type of version increment needed: + - "MAJOR": For breaking changes when major_version_zero is False + - "MINOR": For breaking changes when major_version_zero is True, or for new features + - "PATCH": For bug fixes, performance improvements, or refactors + - None: For commits that don't require a version bump (docs, style, etc.) + """ + ... class ConventionalCommitBumpRule(BumpRule): @@ -122,6 +141,3 @@ def get_increment( if re.match(match_pattern, found_keyword): return increment return None - - -# TODO: Implement CustomBumpRule From a4d6eaa1e55bd7001130f8681a3789596bc941b4 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 05:04:58 +0800 Subject: [PATCH 08/11] docs(bump_rule): add todo --- commitizen/bump_rule.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index b84eb4e12..0699ee2d8 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -136,6 +136,17 @@ def get_increment( self.bump_map_major_version_zero if major_version_zero else self.bump_map ) + # TODO: need more flexibility for the pattern + # "refactor!: drop support for Python 2.7" => MAJOR + # bump_pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + # bump_map = { + # "major": "MAJOR", + # "bang": "MAJOR", + # "minor": "MINOR", + # "patch": "PATCH", + # } + # bump_map_major_version_zero = { ... } + found_keyword = m.group(1) for match_pattern, increment in bump_map.items(): if re.match(match_pattern, found_keyword): From 39f8bc7a5742b7476c1e314cd0818c994aaa67d9 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 05:24:41 +0800 Subject: [PATCH 09/11] fix(bump_rule): support flexible bump map --- commitizen/bump_rule.py | 23 +++++----- tests/test_bump_rule.py | 95 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 0699ee2d8..149b9cc48 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -37,6 +37,10 @@ def find_increment_by_callable( """ lines = (line for message in commit_messages for line in message.split("\n")) increments = map(get_increment, lines) + return _find_highest_increment(increments) + + +def _find_highest_increment(increments: Iterable[Increment | None]) -> Increment | None: return max(increments, key=lambda x: _VERSION_ORDERING[x], default=None) @@ -136,17 +140,16 @@ def get_increment( self.bump_map_major_version_zero if major_version_zero else self.bump_map ) - # TODO: need more flexibility for the pattern - # "refactor!: drop support for Python 2.7" => MAJOR - # bump_pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" - # bump_map = { - # "major": "MAJOR", - # "bang": "MAJOR", - # "minor": "MINOR", - # "patch": "PATCH", - # } - # bump_map_major_version_zero = { ... } + try: + if ret := _find_highest_increment( + (increment for name, increment in bump_map.items() if m.group(name)) + ): + return ret + except IndexError: + # Fallback to old school bump rule + pass + # Fallback to old school bump rule found_keyword = m.group(1) for match_pattern, increment in bump_map.items(): if re.match(match_pattern, found_keyword): diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 1f7ba4f8c..9be38545f 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -3,6 +3,7 @@ from commitizen.bump_rule import ( ConventionalCommitBumpRule, OldSchoolBumpRule, + _find_highest_increment, find_increment_by_callable, ) from commitizen.defaults import ( @@ -106,6 +107,38 @@ def test_other_commit_types(self, bump_rule): assert bump_rule.get_increment("build: update build config", False) is None assert bump_rule.get_increment("ci: update CI pipeline", False) is None + def test_breaking_change_with_refactor(self, bump_rule): + """Test breaking changes with refactor type commit messages.""" + # Breaking change with refactor type + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", False) + == MAJOR + ) + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", True) + == MINOR + ) + + # Breaking change with refactor type and scope + assert ( + bump_rule.get_increment( + "refactor(api)!: remove deprecated endpoints", False + ) + == MAJOR + ) + assert ( + bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) + == MINOR + ) + + # Regular refactor (should be PATCH) + assert ( + bump_rule.get_increment("refactor: improve code structure", False) == PATCH + ) + assert ( + bump_rule.get_increment("refactor: improve code structure", True) == PATCH + ) + class TestFindIncrementByCallable: @pytest.fixture @@ -291,6 +324,38 @@ def test_with_find_increment_by_callable(self, old_school_rule): == MAJOR ) + def test_flexible_bump_map(self, old_school_rule): + """Test that _find_highest_increment is used correctly in bump map processing.""" + # Test with multiple matching patterns + pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + bump_map = { + "major": MAJOR, + "bang": MAJOR, + "minor": MINOR, + "patch": PATCH, + } + bump_map_major_version_zero = { + "major": MINOR, + "bang": MINOR, + "minor": MINOR, + "patch": PATCH, + } + rule = OldSchoolBumpRule(pattern, bump_map, bump_map_major_version_zero) + + # Test with multiple version tags + assert rule.get_increment("major!: drop support for Python 2.7", False) == MAJOR + assert rule.get_increment("major!: drop support for Python 2.7", True) == MINOR + assert rule.get_increment("major: drop support for Python 2.7", False) == MAJOR + assert rule.get_increment("major: drop support for Python 2.7", True) == MINOR + assert rule.get_increment("patch!: drop support for Python 2.7", False) == MAJOR + assert rule.get_increment("patch!: drop support for Python 2.7", True) == MINOR + assert rule.get_increment("patch: drop support for Python 2.7", False) == PATCH + assert rule.get_increment("patch: drop support for Python 2.7", True) == PATCH + assert rule.get_increment("minor: add new feature", False) == MINOR + assert rule.get_increment("minor: add new feature", True) == MINOR + assert rule.get_increment("patch: fix bug", False) == PATCH + assert rule.get_increment("patch: fix bug", True) == PATCH + class TestOldSchoolBumpRuleWithDefault: @pytest.fixture @@ -378,3 +443,33 @@ def test_with_find_increment_by_callable(self, old_school_rule): ) == MAJOR ) + + +def test_find_highest_increment(): + """Test the _find_highest_increment function.""" + # Test with single increment + assert _find_highest_increment([MAJOR]) == MAJOR + assert _find_highest_increment([MINOR]) == MINOR + assert _find_highest_increment([PATCH]) == PATCH + + # Test with multiple increments + assert _find_highest_increment([PATCH, MINOR, MAJOR]) == MAJOR + assert _find_highest_increment([PATCH, MINOR]) == MINOR + assert _find_highest_increment([PATCH, PATCH]) == PATCH + + # Test with None values + assert _find_highest_increment([None, PATCH]) == PATCH + assert _find_highest_increment([None, None]) is None + assert _find_highest_increment([]) is None + + # Test with mixed values + assert _find_highest_increment([None, PATCH, MINOR, MAJOR]) == MAJOR + assert _find_highest_increment([None, PATCH, MINOR]) == MINOR + assert _find_highest_increment([None, PATCH]) == PATCH + + # Test with empty iterator + assert _find_highest_increment(iter([])) is None + + # Test with generator expression + assert _find_highest_increment(x for x in [PATCH, MINOR, MAJOR]) == MAJOR + assert _find_highest_increment(x for x in [None, PATCH, MINOR]) == MINOR From 42d965e953065d72a0a79295f95df0b71fb61766 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 14:45:13 +0800 Subject: [PATCH 10/11] refactor(bump): remove unused var --- commitizen/bump.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/commitizen/bump.py b/commitizen/bump.py index b3b782fa1..454505333 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -3,18 +3,13 @@ import os import re from glob import iglob -from logging import getLogger from string import Template -from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message, encoding +from commitizen.defaults import bump_message, encoding from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import smart_open from commitizen.version_schemes import Version -VERSION_TYPES = [None, PATCH, MINOR, MAJOR] - -logger = getLogger("commitizen") - def update_version_in_files( current_version: str, From 166f768776f759f4c0e3590557c4ebc153c3f25d Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 15:36:31 +0800 Subject: [PATCH 11/11] refactor(bump_rule): use enum on increment --- commitizen/bump_rule.py | 69 ++++- commitizen/commands/bump.py | 25 +- commitizen/defaults.py | 34 +-- commitizen/version_schemes.py | 43 +-- tests/test_bump_rule.py | 418 +++++++++++++++++++-------- tests/test_version_scheme_pep440.py | 183 ++++++------ tests/test_version_scheme_semver.py | 99 +++---- tests/test_version_scheme_semver2.py | 63 ++-- 8 files changed, 583 insertions(+), 351 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 149b9cc48..35addf6b3 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -2,18 +2,51 @@ import re from collections.abc import Iterable +from enum import Enum, auto from functools import cached_property -from typing import Callable, Protocol +from typing import Any, Callable, Protocol from commitizen.exceptions import NoPatternMapError -from commitizen.version_schemes import Increment -_VERSION_ORDERING = dict(zip((None, "PATCH", "MINOR", "MAJOR"), range(4))) + +class SemVerIncrement(Enum): + MAJOR = auto() + MINOR = auto() + PATCH = auto() + + def __str__(self) -> str: + return self.name + + @classmethod + def safe_cast(cls, value: str | None) -> SemVerIncrement | None: + if value is None: + return None + try: + return cls[value] + except ValueError: + return None + + @classmethod + def safe_cast_dict(cls, d: dict[str, Any]) -> dict[str, SemVerIncrement]: + return { + k: v + for k, v in ((k, SemVerIncrement.safe_cast(v)) for k, v in d.items()) + if v is not None + } + + +_VERSION_ORDERING = dict( + zip( + (None, SemVerIncrement.PATCH, SemVerIncrement.MINOR, SemVerIncrement.MAJOR), + range(4), + ) +) def find_increment_by_callable( - commit_messages: Iterable[str], get_increment: Callable[[str], Increment | None] -) -> Increment | None: + commit_messages: Iterable[str], + get_increment: Callable[[str], SemVerIncrement | None], +) -> SemVerIncrement | None: """Find the highest version increment from a list of messages. This function processes a list of messages and determines the highest version @@ -23,7 +56,7 @@ def find_increment_by_callable( Args: commit_messages: A list of messages to analyze. get_increment: A callable that takes a commit message string and returns an - Increment value (MAJOR, MINOR, PATCH) or None if no increment is needed. + SemVerIncrement value (MAJOR, MINOR, PATCH) or None if no increment is needed. Returns: The highest version increment needed (MAJOR, MINOR, PATCH) or None if no @@ -40,14 +73,16 @@ def find_increment_by_callable( return _find_highest_increment(increments) -def _find_highest_increment(increments: Iterable[Increment | None]) -> Increment | None: +def _find_highest_increment( + increments: Iterable[SemVerIncrement | None], +) -> SemVerIncrement | None: return max(increments, key=lambda x: _VERSION_ORDERING[x], default=None) class BumpRule(Protocol): def get_increment( self, commit_message: str, major_version_zero: bool - ) -> Increment | None: + ) -> SemVerIncrement | None: """Determine the version increment based on a commit message. This method analyzes a commit message to determine what kind of version increment @@ -60,7 +95,7 @@ def get_increment( instead of MAJOR. This is useful for projects in 0.x.x versions. Returns: - Increment | None: The type of version increment needed: + SemVerIncrement | None: The type of version increment needed: - "MAJOR": For breaking changes when major_version_zero is False - "MINOR": For breaking changes when major_version_zero is True, or for new features - "PATCH": For bug fixes, performance improvements, or refactors @@ -76,19 +111,21 @@ class ConventionalCommitBumpRule(BumpRule): def get_increment( self, commit_message: str, major_version_zero: bool - ) -> Increment | None: + ) -> SemVerIncrement | None: if not (m := self._head_pattern.match(commit_message)): return None change_type = m.group("change_type") if m.group("bang") or self._RE_BREAKING_CHANGE.match(change_type): - return "MINOR" if major_version_zero else "MAJOR" + return ( + SemVerIncrement.MINOR if major_version_zero else SemVerIncrement.MAJOR + ) if change_type == "feat": - return "MINOR" + return SemVerIncrement.MINOR if change_type in self._PATCH_CHANGE_TYPES: - return "PATCH" + return SemVerIncrement.PATCH return None @@ -118,8 +155,8 @@ class OldSchoolBumpRule(BumpRule): def __init__( self, bump_pattern: str, - bump_map: dict[str, Increment], - bump_map_major_version_zero: dict[str, Increment], + bump_map: dict[str, SemVerIncrement], + bump_map_major_version_zero: dict[str, SemVerIncrement], ): if not bump_map or not bump_pattern or not bump_map_major_version_zero: raise NoPatternMapError( @@ -132,7 +169,7 @@ def __init__( def get_increment( self, commit_message: str, major_version_zero: bool - ) -> Increment | None: + ) -> SemVerIncrement | None: if not (m := self.bump_pattern.search(commit_message)): return None diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index a18db544f..551145fd1 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,7 +7,11 @@ import questionary from commitizen import bump, factory, git, hooks, out -from commitizen.bump_rule import OldSchoolBumpRule, find_increment_by_callable +from commitizen.bump_rule import ( + OldSchoolBumpRule, + SemVerIncrement, + find_increment_by_callable, +) from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -29,7 +33,6 @@ from commitizen.providers import get_provider from commitizen.tags import TagRules from commitizen.version_schemes import ( - Increment, InvalidVersion, Prerelease, get_version_scheme, @@ -120,7 +123,7 @@ def is_initial_tag( is_initial = questionary.confirm("Is this the first tag created?").ask() return is_initial - def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: + def find_increment(self, commits: list[git.GitCommit]) -> SemVerIncrement | None: # Update the bump map to ensure major version doesn't increment. is_major_version_zero: bool = self.bump_settings["major_version_zero"] @@ -128,6 +131,7 @@ def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: rule = self.cz.bump_rule or OldSchoolBumpRule( *self._get_validated_cz_bump(), ) + return find_increment_by_callable( (commit.message for commit in commits), lambda x: rule.get_increment(x, is_major_version_zero), @@ -135,7 +139,7 @@ def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: def _get_validated_cz_bump( self, - ) -> tuple[str, dict[str, Increment], dict[str, Increment]]: + ) -> tuple[str, dict[str, SemVerIncrement], dict[str, SemVerIncrement]]: """For fixing the type errors""" bump_pattern = self.cz.bump_pattern bump_map = self.cz.bump_map @@ -145,9 +149,10 @@ def _get_validated_cz_bump( f"'{self.config.settings['name']}' rule does not support bump" ) - return cast( - tuple[str, dict[str, Increment], dict[str, Increment]], - (bump_pattern, bump_map, bump_map_major_version_zero), + return ( + bump_pattern, + SemVerIncrement.safe_cast_dict(bump_map), + SemVerIncrement.safe_cast_dict(bump_map_major_version_zero), ) def __call__(self) -> None: # noqa: C901 @@ -166,7 +171,9 @@ def __call__(self) -> None: # noqa: C901 dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] - increment: Increment | None = self.arguments["increment"] + increment: SemVerIncrement | None = SemVerIncrement.safe_cast( + self.arguments["increment"] + ) prerelease: Prerelease | None = self.arguments["prerelease"] devrelease: int | None = self.arguments["devrelease"] is_files_only: bool | None = self.arguments["files_only"] @@ -283,7 +290,7 @@ def __call__(self) -> None: # noqa: C901 # we create an empty PATCH increment for empty tag if increment is None and allow_no_commit: - increment = "PATCH" + increment = SemVerIncrement.PATCH new_version = current_version.bump( increment, diff --git a/commitizen/defaults.py b/commitizen/defaults.py index c61c54cbb..3e6841b05 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -5,6 +5,8 @@ from collections.abc import Iterable, MutableMapping, Sequence from typing import Any, TypedDict +from commitizen.bump_rule import SemVerIncrement + # Type Questions = Iterable[MutableMapping[str, Any]] @@ -108,31 +110,27 @@ class Settings(TypedDict, total=False): "extras": {}, } -MAJOR = "MAJOR" -MINOR = "MINOR" -PATCH = "PATCH" - CHANGELOG_FORMAT = "markdown" BUMP_PATTERN = r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" -BUMP_MAP = OrderedDict( +BUMP_MAP = dict( ( - (r"^.+!$", MAJOR), - (r"^BREAKING[\-\ ]CHANGE", MAJOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(SemVerIncrement.MAJOR)), + (r"^BREAKING[\-\ ]CHANGE", str(SemVerIncrement.MAJOR)), + (r"^feat", str(SemVerIncrement.MINOR)), + (r"^fix", str(SemVerIncrement.PATCH)), + (r"^refactor", str(SemVerIncrement.PATCH)), + (r"^perf", str(SemVerIncrement.PATCH)), ) ) -BUMP_MAP_MAJOR_VERSION_ZERO = OrderedDict( +BUMP_MAP_MAJOR_VERSION_ZERO = dict( ( - (r"^.+!$", MINOR), - (r"^BREAKING[\-\ ]CHANGE", MINOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(SemVerIncrement.MINOR)), + (r"^BREAKING[\-\ ]CHANGE", str(SemVerIncrement.MINOR)), + (r"^feat", str(SemVerIncrement.MINOR)), + (r"^fix", str(SemVerIncrement.PATCH)), + (r"^refactor", str(SemVerIncrement.PATCH)), + (r"^perf", str(SemVerIncrement.PATCH)), ) ) change_type_order = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 84ded9316..14bed686c 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -14,6 +14,8 @@ runtime_checkable, ) +from commitizen.bump_rule import SemVerIncrement + if sys.version_info >= (3, 10): from importlib import metadata else: @@ -22,7 +24,7 @@ from packaging.version import InvalidVersion # noqa: F401: expose the common exception from packaging.version import Version as _BaseVersion -from commitizen.defaults import MAJOR, MINOR, PATCH, Settings +from commitizen.defaults import Settings from commitizen.exceptions import VersionSchemeUnknown if TYPE_CHECKING: @@ -39,7 +41,6 @@ from typing import Self -Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] DEFAULT_VERSION_PARSER = r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)" @@ -126,7 +127,7 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: Increment | None, + increment: SemVerIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -223,26 +224,30 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: Increment | None = None) -> str: + def increment_base(self, increment: SemVerIncrement | None = None) -> str: prev_release = list(self.release) - increments = [MAJOR, MINOR, PATCH] + increments = [ + SemVerIncrement.MAJOR, + SemVerIncrement.MINOR, + SemVerIncrement.PATCH, + ] base = dict(zip_longest(increments, prev_release, fillvalue=0)) - if increment == MAJOR: - base[MAJOR] += 1 - base[MINOR] = 0 - base[PATCH] = 0 - elif increment == MINOR: - base[MINOR] += 1 - base[PATCH] = 0 - elif increment == PATCH: - base[PATCH] += 1 + if increment == SemVerIncrement.MAJOR: + base[SemVerIncrement.MAJOR] += 1 + base[SemVerIncrement.MINOR] = 0 + base[SemVerIncrement.PATCH] = 0 + elif increment == SemVerIncrement.MINOR: + base[SemVerIncrement.MINOR] += 1 + base[SemVerIncrement.PATCH] = 0 + elif increment == SemVerIncrement.PATCH: + base[SemVerIncrement.PATCH] += 1 - return f"{base[MAJOR]}.{base[MINOR]}.{base[PATCH]}" + return f"{base[SemVerIncrement.MAJOR]}.{base[SemVerIncrement.MINOR]}.{base[SemVerIncrement.PATCH]}" def bump( self, - increment: Increment | None, + increment: SemVerIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -272,12 +277,12 @@ def bump( base = self.increment_base(increment) else: base = f"{self.major}.{self.minor}.{self.micro}" - if increment == PATCH: + if increment == SemVerIncrement.PATCH: pass - elif increment == MINOR: + elif increment == SemVerIncrement.MINOR: if self.micro != 0: base = self.increment_base(increment) - elif increment == MAJOR: + elif increment == SemVerIncrement.MAJOR: if self.minor != 0 or self.micro != 0: base = self.increment_base(increment) dev_version = self.generate_devrelease(devrelease) diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 9be38545f..0c8974bcb 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -3,6 +3,7 @@ from commitizen.bump_rule import ( ConventionalCommitBumpRule, OldSchoolBumpRule, + SemVerIncrement, _find_highest_increment, find_increment_by_callable, ) @@ -10,9 +11,6 @@ BUMP_MAP, BUMP_MAP_MAJOR_VERSION_ZERO, BUMP_PATTERN, - MAJOR, - MINOR, - PATCH, ) from commitizen.exceptions import NoPatternMapError @@ -24,60 +22,98 @@ def bump_rule(): class TestConventionalCommitBumpRule: def test_feat_commit(self, bump_rule): - assert bump_rule.get_increment("feat: add new feature", False) == MINOR - assert bump_rule.get_increment("feat: add new feature", True) == MINOR + assert ( + bump_rule.get_increment("feat: add new feature", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("feat: add new feature", True) + == SemVerIncrement.MINOR + ) def test_fix_commit(self, bump_rule): - assert bump_rule.get_increment("fix: fix bug", False) == PATCH - assert bump_rule.get_increment("fix: fix bug", True) == PATCH + assert bump_rule.get_increment("fix: fix bug", False) == SemVerIncrement.PATCH + assert bump_rule.get_increment("fix: fix bug", True) == SemVerIncrement.PATCH def test_perf_commit(self, bump_rule): - assert bump_rule.get_increment("perf: improve performance", False) == PATCH - assert bump_rule.get_increment("perf: improve performance", True) == PATCH + assert ( + bump_rule.get_increment("perf: improve performance", False) + == SemVerIncrement.PATCH + ) + assert ( + bump_rule.get_increment("perf: improve performance", True) + == SemVerIncrement.PATCH + ) def test_refactor_commit(self, bump_rule): - assert bump_rule.get_increment("refactor: restructure code", False) == PATCH - assert bump_rule.get_increment("refactor: restructure code", True) == PATCH + assert ( + bump_rule.get_increment("refactor: restructure code", False) + == SemVerIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: restructure code", True) + == SemVerIncrement.PATCH + ) def test_breaking_change_with_bang(self, bump_rule): - assert bump_rule.get_increment("feat!: breaking change", False) == MAJOR - assert bump_rule.get_increment("feat!: breaking change", True) == MINOR + assert ( + bump_rule.get_increment("feat!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat!: breaking change", True) + == SemVerIncrement.MINOR + ) def test_breaking_change_type(self, bump_rule): - assert bump_rule.get_increment("BREAKING CHANGE: major change", False) == MAJOR - assert bump_rule.get_increment("BREAKING CHANGE: major change", True) == MINOR + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", True) + == SemVerIncrement.MINOR + ) def test_commit_with_scope(self, bump_rule): - assert bump_rule.get_increment("feat(api): add new endpoint", False) == MINOR - assert bump_rule.get_increment("fix(ui): fix button alignment", False) == PATCH + assert ( + bump_rule.get_increment("feat(api): add new endpoint", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui): fix button alignment", False) + == SemVerIncrement.PATCH + ) def test_commit_with_complex_scopes(self, bump_rule): # Test with multiple word scopes assert ( bump_rule.get_increment("feat(user_management): add user roles", False) - == MINOR + == SemVerIncrement.MINOR ) assert ( bump_rule.get_increment("fix(database_connection): handle timeout", False) - == PATCH + == SemVerIncrement.PATCH ) # Test with nested scopes assert ( - bump_rule.get_increment("feat(api/auth): implement OAuth", False) == MINOR + bump_rule.get_increment("feat(api/auth): implement OAuth", False) + == SemVerIncrement.MINOR ) assert ( - bump_rule.get_increment("fix(ui/components): fix dropdown", False) == PATCH + bump_rule.get_increment("fix(ui/components): fix dropdown", False) + == SemVerIncrement.PATCH ) # Test with breaking changes and scopes assert ( bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) - == MINOR + == SemVerIncrement.MINOR ) # Test with BREAKING CHANGE and scopes @@ -85,13 +121,13 @@ def test_commit_with_complex_scopes(self, bump_rule): bump_rule.get_increment( "BREAKING CHANGE(api): remove deprecated endpoints", False ) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( bump_rule.get_increment( "BREAKING CHANGE(api): remove deprecated endpoints", True ) - == MINOR + == SemVerIncrement.MINOR ) def test_invalid_commit_message(self, bump_rule): @@ -112,11 +148,11 @@ def test_breaking_change_with_refactor(self, bump_rule): # Breaking change with refactor type assert ( bump_rule.get_increment("refactor!: drop support for Python 2.7", False) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( bump_rule.get_increment("refactor!: drop support for Python 2.7", True) - == MINOR + == SemVerIncrement.MINOR ) # Breaking change with refactor type and scope @@ -124,19 +160,21 @@ def test_breaking_change_with_refactor(self, bump_rule): bump_rule.get_increment( "refactor(api)!: remove deprecated endpoints", False ) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) - == MINOR + == SemVerIncrement.MINOR ) - # Regular refactor (should be PATCH) + # Regular refactor (should be SemVerIncrement.PATCH) assert ( - bump_rule.get_increment("refactor: improve code structure", False) == PATCH + bump_rule.get_increment("refactor: improve code structure", False) + == SemVerIncrement.PATCH ) assert ( - bump_rule.get_increment("refactor: improve code structure", True) == PATCH + bump_rule.get_increment("refactor: improve code structure", True) + == SemVerIncrement.PATCH ) @@ -147,7 +185,10 @@ def get_increment(self, bump_rule): def test_single_commit(self, get_increment): commit_messages = ["feat: add new feature"] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) def test_multiple_commits(self, get_increment): commit_messages = [ @@ -155,20 +196,29 @@ def test_multiple_commits(self, get_increment): "fix: bug fix", "docs: update readme", ] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) def test_breaking_change(self, get_increment): commit_messages = [ "feat: new feature", "feat!: breaking change", ] - assert find_increment_by_callable(commit_messages, get_increment) == MAJOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MAJOR + ) def test_multi_line_commit(self, get_increment): commit_messages = [ "feat: new feature\n\nBREAKING CHANGE: major change", ] - assert find_increment_by_callable(commit_messages, get_increment) == MAJOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MAJOR + ) def test_no_increment_needed(self, get_increment): commit_messages = [ @@ -192,7 +242,7 @@ def test_major_version_zero(self): find_increment_by_callable( commit_messages, lambda x: bump_rule.get_increment(x, True) ) - == MINOR + == SemVerIncrement.MINOR ) def test_mixed_commit_types(self, get_increment): @@ -202,14 +252,20 @@ def test_mixed_commit_types(self, get_increment): "perf: improve performance", "refactor: restructure code", ] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) def test_commit_with_scope(self, get_increment): commit_messages = [ "feat(api): add new endpoint", "fix(ui): fix button alignment", ] - assert find_increment_by_callable(commit_messages, get_increment) == MINOR + assert ( + find_increment_by_callable(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) class TestOldSchoolBumpRule: @@ -220,17 +276,17 @@ def bump_pattern(self): @pytest.fixture def bump_map(self): return { - "MAJOR": MAJOR, - "MINOR": MINOR, - "PATCH": PATCH, + "SemVerIncrement.MAJOR": SemVerIncrement.MAJOR, + "SemVerIncrement.MINOR": SemVerIncrement.MINOR, + "SemVerIncrement.PATCH": SemVerIncrement.PATCH, } @pytest.fixture def bump_map_major_version_zero(self): return { - "MAJOR": MINOR, # MAJOR becomes MINOR in version zero - "MINOR": MINOR, - "PATCH": PATCH, + "SemVerIncrement.MAJOR": SemVerIncrement.MINOR, # SemVerIncrement.MAJOR becomes SemVerIncrement.MINOR in version zero + "SemVerIncrement.MINOR": SemVerIncrement.MINOR, + "SemVerIncrement.PATCH": SemVerIncrement.PATCH, } @pytest.fixture @@ -239,31 +295,51 @@ def old_school_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): def test_major_version(self, old_school_rule): assert ( - old_school_rule.get_increment("feat: add new feature [MAJOR]", False) - == MAJOR + old_school_rule.get_increment( + "feat: add new feature [SemVerIncrement.MAJOR]", False + ) + == SemVerIncrement.MAJOR + ) + assert ( + old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MAJOR]", False) + == SemVerIncrement.MAJOR ) - assert old_school_rule.get_increment("fix: bug fix [MAJOR]", False) == MAJOR def test_minor_version(self, old_school_rule): assert ( - old_school_rule.get_increment("feat: add new feature [MINOR]", False) - == MINOR + old_school_rule.get_increment( + "feat: add new feature [SemVerIncrement.MINOR]", False + ) + == SemVerIncrement.MINOR + ) + assert ( + old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MINOR]", False) + == SemVerIncrement.MINOR ) - assert old_school_rule.get_increment("fix: bug fix [MINOR]", False) == MINOR def test_patch_version(self, old_school_rule): assert ( - old_school_rule.get_increment("feat: add new feature [PATCH]", False) - == PATCH + old_school_rule.get_increment( + "feat: add new feature [SemVerIncrement.PATCH]", False + ) + == SemVerIncrement.PATCH + ) + assert ( + old_school_rule.get_increment("fix: bug fix [SemVerIncrement.PATCH]", False) + == SemVerIncrement.PATCH ) - assert old_school_rule.get_increment("fix: bug fix [PATCH]", False) == PATCH def test_major_version_zero(self, old_school_rule): assert ( - old_school_rule.get_increment("feat: add new feature [MAJOR]", True) - == MINOR + old_school_rule.get_increment( + "feat: add new feature [SemVerIncrement.MAJOR]", True + ) + == SemVerIncrement.MINOR + ) + assert ( + old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MAJOR]", True) + == SemVerIncrement.MINOR ) - assert old_school_rule.get_increment("fix: bug fix [MAJOR]", True) == MINOR def test_no_match(self, old_school_rule): assert old_school_rule.get_increment("feat: add new feature", False) is None @@ -300,28 +376,37 @@ def test_empty_maps_with_valid_pattern(self, bump_pattern): def test_complex_pattern(self): pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" bump_map = { - "MAJOR": MAJOR, - "MINOR": MINOR, - "PATCH": PATCH, + "SemVerIncrement.MAJOR": SemVerIncrement.MAJOR, + "SemVerIncrement.MINOR": SemVerIncrement.MINOR, + "SemVerIncrement.PATCH": SemVerIncrement.PATCH, } rule = OldSchoolBumpRule(pattern, bump_map, bump_map) assert ( - rule.get_increment("feat: add new feature [MAJOR] [MINOR]", False) == MAJOR + rule.get_increment( + "feat: add new feature [SemVerIncrement.MAJOR] [SemVerIncrement.MINOR]", + False, + ) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment( + "fix: bug fix [SemVerIncrement.MINOR] [SemVerIncrement.PATCH]", False + ) + == SemVerIncrement.MINOR ) - assert rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) == MINOR def test_with_find_increment_by_callable(self, old_school_rule): commit_messages = [ - "feat: add new feature [MAJOR]", - "fix: bug fix [PATCH]", - "docs: update readme [MINOR]", + "feat: add new feature [SemVerIncrement.MAJOR]", + "fix: bug fix [SemVerIncrement.PATCH]", + "docs: update readme [SemVerIncrement.MINOR]", ] assert ( find_increment_by_callable( commit_messages, lambda x: old_school_rule.get_increment(x, False) ) - == MAJOR + == SemVerIncrement.MAJOR ) def test_flexible_bump_map(self, old_school_rule): @@ -329,32 +414,60 @@ def test_flexible_bump_map(self, old_school_rule): # Test with multiple matching patterns pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" bump_map = { - "major": MAJOR, - "bang": MAJOR, - "minor": MINOR, - "patch": PATCH, + "major": SemVerIncrement.MAJOR, + "bang": SemVerIncrement.MAJOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH, } bump_map_major_version_zero = { - "major": MINOR, - "bang": MINOR, - "minor": MINOR, - "patch": PATCH, + "major": SemVerIncrement.MINOR, + "bang": SemVerIncrement.MINOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH, } rule = OldSchoolBumpRule(pattern, bump_map, bump_map_major_version_zero) # Test with multiple version tags - assert rule.get_increment("major!: drop support for Python 2.7", False) == MAJOR - assert rule.get_increment("major!: drop support for Python 2.7", True) == MINOR - assert rule.get_increment("major: drop support for Python 2.7", False) == MAJOR - assert rule.get_increment("major: drop support for Python 2.7", True) == MINOR - assert rule.get_increment("patch!: drop support for Python 2.7", False) == MAJOR - assert rule.get_increment("patch!: drop support for Python 2.7", True) == MINOR - assert rule.get_increment("patch: drop support for Python 2.7", False) == PATCH - assert rule.get_increment("patch: drop support for Python 2.7", True) == PATCH - assert rule.get_increment("minor: add new feature", False) == MINOR - assert rule.get_increment("minor: add new feature", True) == MINOR - assert rule.get_increment("patch: fix bug", False) == PATCH - assert rule.get_increment("patch: fix bug", True) == PATCH + assert ( + rule.get_increment("major!: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("major!: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", False) + == SemVerIncrement.PATCH + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", True) + == SemVerIncrement.PATCH + ) + assert ( + rule.get_increment("minor: add new feature", False) == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("minor: add new feature", True) == SemVerIncrement.MINOR + ) + assert rule.get_increment("patch: fix bug", False) == SemVerIncrement.PATCH + assert rule.get_increment("patch: fix bug", True) == SemVerIncrement.PATCH class TestOldSchoolBumpRuleWithDefault: @@ -363,61 +476,92 @@ def old_school_rule(self): return OldSchoolBumpRule(BUMP_PATTERN, BUMP_MAP, BUMP_MAP_MAJOR_VERSION_ZERO) def test_breaking_change_with_bang(self, old_school_rule): - assert old_school_rule.get_increment("feat!: breaking change", False) == MAJOR - assert old_school_rule.get_increment("fix!: breaking change", False) == MAJOR - assert old_school_rule.get_increment("feat!: breaking change", True) == MINOR - assert old_school_rule.get_increment("fix!: breaking change", True) == MINOR + assert ( + old_school_rule.get_increment("feat!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + old_school_rule.get_increment("fix!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + old_school_rule.get_increment("feat!: breaking change", True) + == SemVerIncrement.MINOR + ) + assert ( + old_school_rule.get_increment("fix!: breaking change", True) + == SemVerIncrement.MINOR + ) def test_breaking_change_type(self, old_school_rule): assert ( old_school_rule.get_increment("BREAKING CHANGE: major change", False) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( old_school_rule.get_increment("BREAKING-CHANGE: major change", False) - == MAJOR + == SemVerIncrement.MAJOR ) assert ( old_school_rule.get_increment("BREAKING CHANGE: major change", True) - == MINOR + == SemVerIncrement.MINOR ) assert ( old_school_rule.get_increment("BREAKING-CHANGE: major change", True) - == MINOR + == SemVerIncrement.MINOR ) def test_feat_commit(self, old_school_rule): - assert old_school_rule.get_increment("feat: add new feature", False) == MINOR - assert old_school_rule.get_increment("feat: add new feature", True) == MINOR + assert ( + old_school_rule.get_increment("feat: add new feature", False) + == SemVerIncrement.MINOR + ) + assert ( + old_school_rule.get_increment("feat: add new feature", True) + == SemVerIncrement.MINOR + ) def test_fix_commit(self, old_school_rule): - assert old_school_rule.get_increment("fix: fix bug", False) == PATCH - assert old_school_rule.get_increment("fix: fix bug", True) == PATCH + assert ( + old_school_rule.get_increment("fix: fix bug", False) + == SemVerIncrement.PATCH + ) + assert ( + old_school_rule.get_increment("fix: fix bug", True) == SemVerIncrement.PATCH + ) def test_refactor_commit(self, old_school_rule): assert ( - old_school_rule.get_increment("refactor: restructure code", False) == PATCH + old_school_rule.get_increment("refactor: restructure code", False) + == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("refactor: restructure code", True) == PATCH + old_school_rule.get_increment("refactor: restructure code", True) + == SemVerIncrement.PATCH ) def test_perf_commit(self, old_school_rule): assert ( - old_school_rule.get_increment("perf: improve performance", False) == PATCH + old_school_rule.get_increment("perf: improve performance", False) + == SemVerIncrement.PATCH + ) + assert ( + old_school_rule.get_increment("perf: improve performance", True) + == SemVerIncrement.PATCH ) - assert old_school_rule.get_increment("perf: improve performance", True) == PATCH def test_commit_with_scope(self, old_school_rule): assert ( - old_school_rule.get_increment("feat(api): add new endpoint", False) == MINOR + old_school_rule.get_increment("feat(api): add new endpoint", False) + == SemVerIncrement.MINOR ) assert ( old_school_rule.get_increment("fix(ui): fix button alignment", False) - == PATCH + == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("refactor(core): restructure", False) == PATCH + old_school_rule.get_increment("refactor(core): restructure", False) + == SemVerIncrement.PATCH ) def test_no_match(self, old_school_rule): @@ -441,35 +585,73 @@ def test_with_find_increment_by_callable(self, old_school_rule): find_increment_by_callable( commit_messages, lambda x: old_school_rule.get_increment(x, False) ) - == MAJOR + == SemVerIncrement.MAJOR ) def test_find_highest_increment(): """Test the _find_highest_increment function.""" # Test with single increment - assert _find_highest_increment([MAJOR]) == MAJOR - assert _find_highest_increment([MINOR]) == MINOR - assert _find_highest_increment([PATCH]) == PATCH + assert _find_highest_increment([SemVerIncrement.MAJOR]) == SemVerIncrement.MAJOR + assert _find_highest_increment([SemVerIncrement.MINOR]) == SemVerIncrement.MINOR + assert _find_highest_increment([SemVerIncrement.PATCH]) == SemVerIncrement.PATCH # Test with multiple increments - assert _find_highest_increment([PATCH, MINOR, MAJOR]) == MAJOR - assert _find_highest_increment([PATCH, MINOR]) == MINOR - assert _find_highest_increment([PATCH, PATCH]) == PATCH + assert ( + _find_highest_increment( + [SemVerIncrement.PATCH, SemVerIncrement.MINOR, SemVerIncrement.MAJOR] + ) + == SemVerIncrement.MAJOR + ) + assert ( + _find_highest_increment([SemVerIncrement.PATCH, SemVerIncrement.MINOR]) + == SemVerIncrement.MINOR + ) + assert ( + _find_highest_increment([SemVerIncrement.PATCH, SemVerIncrement.PATCH]) + == SemVerIncrement.PATCH + ) # Test with None values - assert _find_highest_increment([None, PATCH]) == PATCH + assert ( + _find_highest_increment([None, SemVerIncrement.PATCH]) == SemVerIncrement.PATCH + ) assert _find_highest_increment([None, None]) is None assert _find_highest_increment([]) is None # Test with mixed values - assert _find_highest_increment([None, PATCH, MINOR, MAJOR]) == MAJOR - assert _find_highest_increment([None, PATCH, MINOR]) == MINOR - assert _find_highest_increment([None, PATCH]) == PATCH + assert ( + _find_highest_increment( + [None, SemVerIncrement.PATCH, SemVerIncrement.MINOR, SemVerIncrement.MAJOR] + ) + == SemVerIncrement.MAJOR + ) + assert ( + _find_highest_increment([None, SemVerIncrement.PATCH, SemVerIncrement.MINOR]) + == SemVerIncrement.MINOR + ) + assert ( + _find_highest_increment([None, SemVerIncrement.PATCH]) == SemVerIncrement.PATCH + ) # Test with empty iterator assert _find_highest_increment(iter([])) is None # Test with generator expression - assert _find_highest_increment(x for x in [PATCH, MINOR, MAJOR]) == MAJOR - assert _find_highest_increment(x for x in [None, PATCH, MINOR]) == MINOR + assert ( + _find_highest_increment( + x + for x in [ + SemVerIncrement.PATCH, + SemVerIncrement.MINOR, + SemVerIncrement.MAJOR, + ] + ) + == SemVerIncrement.MAJOR + ) + assert ( + _find_highest_increment( + x for x in [None, SemVerIncrement.PATCH, SemVerIncrement.MINOR] + ) + == SemVerIncrement.MINOR + ) diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index a983dad14..54fd15229 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -3,22 +3,23 @@ import pytest +from commitizen.bump_rule import SemVerIncrement from commitizen.version_schemes import Pep440, VersionProtocol 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.1a0"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1.dev1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0.dev1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.PATCH, "alpha", 0, None), "0.3.1a0"), (("0.3.1a0", None, "alpha", 0, None), "0.3.1a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1a1"), + (("0.3.0", SemVerIncrement.PATCH, "alpha", 1, None), "0.3.1a1"), (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), (("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.0a0"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.MAJOR, "alpha", 0, None), "1.0.0a0"), (("1.0.0a0", None, "alpha", 0, None), "1.0.0a1"), (("1.0.0a1", None, "alpha", 0, None), "1.0.0a2"), (("1.0.0a1", None, "alpha", 0, 1), "1.0.0a2.dev1"), @@ -29,20 +30,20 @@ (("1.0.0b1", None, "rc", 0, None), "1.0.0rc0"), (("1.0.0rc0", None, "rc", 0, None), "1.0.0rc1"), (("1.0.0rc0", None, "rc", 0, 1), "1.0.0rc1.dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0rc0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0b0"), - (("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"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases @@ -53,9 +54,9 @@ ] 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"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), (("1a0", None, "alpha", 0, None), "1.0.0a1"), (("1a0", None, "alpha", 1, None), "1.0.0a1"), (("1", None, "beta", 0, None), "1.0.0b0"), @@ -63,18 +64,18 @@ (("1beta", None, "beta", 0, None), "1.0.0b1"), (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0a2"), (("1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.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.1a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0a1"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, "alpha", 0, None), "0.9.1a0"), + (("0.9.0", SemVerIncrement.MINOR, "alpha", 0, None), "0.10.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, "alpha", 0, None), "1.0.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, "alpha", 1, None), "1.0.0a1"), (("1.0.0a2", None, "beta", 0, None), "1.0.0b0"), (("1.0.0a2", None, "beta", 1, None), "1.0.0b1"), (("1.0.0beta1", None, "rc", 0, None), "1.0.0rc0"), @@ -84,51 +85,51 @@ # additional pre-release tests run through various release scenarios prerelease_cases = [ # - (("3.3.3", "PATCH", "alpha", 0, None), "3.3.4a0"), - (("3.3.4a0", "PATCH", "alpha", 0, None), "3.3.4a1"), - (("3.3.4a1", "MINOR", "alpha", 0, None), "3.4.0a0"), - (("3.4.0a0", "PATCH", "alpha", 0, None), "3.4.0a1"), - (("3.4.0a1", "MINOR", "alpha", 0, None), "3.4.0a2"), - (("3.4.0a2", "MAJOR", "alpha", 0, None), "4.0.0a0"), - (("4.0.0a0", "PATCH", "alpha", 0, None), "4.0.0a1"), - (("4.0.0a1", "MINOR", "alpha", 0, None), "4.0.0a2"), - (("4.0.0a2", "MAJOR", "alpha", 0, None), "4.0.0a3"), + (("3.3.3", SemVerIncrement.PATCH, "alpha", 0, None), "3.3.4a0"), + (("3.3.4a0", SemVerIncrement.PATCH, "alpha", 0, None), "3.3.4a1"), + (("3.3.4a1", SemVerIncrement.MINOR, "alpha", 0, None), "3.4.0a0"), + (("3.4.0a0", SemVerIncrement.PATCH, "alpha", 0, None), "3.4.0a1"), + (("3.4.0a1", SemVerIncrement.MINOR, "alpha", 0, None), "3.4.0a2"), + (("3.4.0a2", SemVerIncrement.MAJOR, "alpha", 0, None), "4.0.0a0"), + (("4.0.0a0", SemVerIncrement.PATCH, "alpha", 0, None), "4.0.0a1"), + (("4.0.0a1", SemVerIncrement.MINOR, "alpha", 0, None), "4.0.0a2"), + (("4.0.0a2", SemVerIncrement.MAJOR, "alpha", 0, None), "4.0.0a3"), # - (("1.0.0", "PATCH", "alpha", 0, None), "1.0.1a0"), - (("1.0.1a0", "PATCH", "alpha", 0, None), "1.0.1a1"), - (("1.0.1a1", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", SemVerIncrement.PATCH, "alpha", 0, None), "1.0.1a0"), + (("1.0.1a0", SemVerIncrement.PATCH, "alpha", 0, None), "1.0.1a1"), + (("1.0.1a1", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, "alpha", 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.MAJOR, "alpha", 0, None), "2.0.0a0"), # - (("1.0.0", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "PATCH", "alpha", 0, None), "1.1.0a3"), - (("1.1.0a3", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, "alpha", 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.PATCH, "alpha", 0, None), "1.1.0a3"), + (("1.1.0a3", SemVerIncrement.MAJOR, "alpha", 0, None), "2.0.0a0"), # - (("1.0.0", "MAJOR", "alpha", 0, None), "2.0.0a0"), - (("2.0.0a0", "MINOR", "alpha", 0, None), "2.0.0a1"), - (("2.0.0a1", "PATCH", "alpha", 0, None), "2.0.0a2"), - (("2.0.0a2", "MAJOR", "alpha", 0, None), "2.0.0a3"), - (("2.0.0a3", "MINOR", "alpha", 0, None), "2.0.0a4"), - (("2.0.0a4", "PATCH", "alpha", 0, None), "2.0.0a5"), - (("2.0.0a5", "MAJOR", "alpha", 0, None), "2.0.0a6"), + (("1.0.0", SemVerIncrement.MAJOR, "alpha", 0, None), "2.0.0a0"), + (("2.0.0a0", SemVerIncrement.MINOR, "alpha", 0, None), "2.0.0a1"), + (("2.0.0a1", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.0a2"), + (("2.0.0a2", SemVerIncrement.MAJOR, "alpha", 0, None), "2.0.0a3"), + (("2.0.0a3", SemVerIncrement.MINOR, "alpha", 0, None), "2.0.0a4"), + (("2.0.0a4", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.0a5"), + (("2.0.0a5", SemVerIncrement.MAJOR, "alpha", 0, None), "2.0.0a6"), # - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.0.0b1"), - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.0b1"), # - (("1.0.1a0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1a0", "MINOR", None, 0, None), "1.1.0"), - (("1.0.1a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.0.1a0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1a0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.0.1a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("1.1.0a0", "PATCH", None, 0, None), "1.1.0"), - (("1.1.0a0", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.1.0a0", SemVerIncrement.PATCH, None, 0, None), "1.1.0"), + (("1.1.0a0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("2.0.0a0", "MINOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "MAJOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "PATCH", None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.MINOR, None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.PATCH, None, 0, None), "2.0.0"), # (("3.0.0a1", None, None, 0, None), "3.0.0"), (("3.0.0b1", None, None, 0, None), "3.0.0"), @@ -139,48 +140,48 @@ (("3.1.4", None, "rc", 0, None), "3.1.4rc0"), # (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4a0", "PATCH", "alpha", 0, None), "3.1.4a1"), # UNEXPECTED! - (("3.1.4a0", "MINOR", "alpha", 0, None), "3.2.0a0"), - (("3.1.4a0", "MAJOR", "alpha", 0, None), "4.0.0a0"), + (("3.1.4a0", SemVerIncrement.PATCH, "alpha", 0, None), "3.1.4a1"), # UNEXPECTED! + (("3.1.4a0", SemVerIncrement.MINOR, "alpha", 0, None), "3.2.0a0"), + (("3.1.4a0", SemVerIncrement.MAJOR, "alpha", 0, None), "4.0.0a0"), ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0a1", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0b0", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, 1), "1.0.1rc0.dev1"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0a1", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0b0", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "alpha", 0, None), "1.1.0a0"), + (("1.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, 1), "1.1.0rc0.dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", SemVerIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, "alpha", 0, None), "3.0.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0a0"), + (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.1.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1a0"), + (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.1a0"), ] diff --git a/tests/test_version_scheme_semver.py b/tests/test_version_scheme_semver.py index 8785717a3..fa1b2fd72 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -3,22 +3,23 @@ import pytest +from commitizen.bump_rule import SemVerIncrement from commitizen.version_schemes import SemVer, VersionProtocol 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.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1-dev1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0-dev1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.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.0", SemVerIncrement.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"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.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"), @@ -29,20 +30,20 @@ (("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.0rc0", SemVerIncrement.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"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases @@ -53,9 +54,9 @@ ] 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"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.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"), @@ -63,18 +64,18 @@ (("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"), + (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.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"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, "alpha", 0, None), "0.9.1-a0"), + (("0.9.0", SemVerIncrement.MINOR, "alpha", 0, None), "0.10.0-a0"), + (("0.9.0", SemVerIncrement.MAJOR, "alpha", 0, None), "1.0.0-a0"), + (("0.9.0", SemVerIncrement.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"), @@ -84,40 +85,40 @@ ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0a1", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0b0", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, 1), "1.0.1-rc0-dev1"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0a1", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0b0", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, 1), "1.1.0-rc0-dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", SemVerIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0-a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, "alpha", 0, None), "3.0.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0-a0"), + (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.1.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1-a0"), + (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.1-a0"), ] diff --git a/tests/test_version_scheme_semver2.py b/tests/test_version_scheme_semver2.py index d18a058a7..9f47ed256 100644 --- a/tests/test_version_scheme_semver2.py +++ b/tests/test_version_scheme_semver2.py @@ -3,22 +3,23 @@ import pytest +from commitizen.bump_rule import SemVerIncrement from commitizen.version_schemes import SemVer2, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev.1"), - (("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-dev.1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-alpha.0"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1-dev.1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0-dev.1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.PATCH, "alpha", 0, None), "0.3.1-alpha.0"), (("0.3.1-alpha.0", None, "alpha", 0, None), "0.3.1-alpha.1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-alpha.1"), + (("0.3.0", SemVerIncrement.PATCH, "alpha", 1, None), "0.3.1-alpha.1"), (("0.3.1-alpha.0", None, "alpha", 1, None), "0.3.1-alpha.1"), (("0.3.1-alpha.0", 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-alpha.0"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.MAJOR, "alpha", 0, None), "1.0.0-alpha.0"), (("1.0.0-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), (("1.0.0-alpha.1", None, "alpha", 0, 1), "1.0.0-alpha.2.dev.1"), @@ -29,20 +30,20 @@ (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"), (("1.0.0-rc.0", None, "rc", 0, None), "1.0.0-rc.1"), (("1.0.0-rc.0", None, "rc", 0, 1), "1.0.0-rc.1.dev.1"), - (("1.0.0-rc.0", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0-rc.0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), (("1.0.0-alpha.3.dev.0", None, "beta", 0, None), "1.0.0-beta.0"), - (("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"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases @@ -53,9 +54,9 @@ ] 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"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), (("1-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), (("1-alpha.0", None, "alpha", 1, None), "1.0.0-alpha.1"), (("1", None, "beta", 0, None), "1.0.0-beta.0"), @@ -63,18 +64,18 @@ (("1-beta", None, "beta", 0, None), "1.0.0-beta.1"), (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), (("1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0-rc.1+e20d7b57f3eb", SemVerIncrement.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-alpha.0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-alpha.1"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, "alpha", 0, None), "0.9.1-alpha.0"), + (("0.9.0", SemVerIncrement.MINOR, "alpha", 0, None), "0.10.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, "alpha", 0, None), "1.0.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, "alpha", 1, None), "1.0.0-alpha.1"), (("1.0.0-alpha.2", None, "beta", 0, None), "1.0.0-beta.0"), (("1.0.0-alpha.2", None, "beta", 1, None), "1.0.0-beta.1"), (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"),