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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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"), From 565ca9e2ba30fc8cb4f3f49bff13422875fdac33 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 15:42:09 +0800 Subject: [PATCH 12/35] refactor(bump_rule): typing --- commitizen/bump_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 35addf6b3..0fcdc9a43 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -18,7 +18,7 @@ def __str__(self) -> str: return self.name @classmethod - def safe_cast(cls, value: str | None) -> SemVerIncrement | None: + def safe_cast(cls, value: Any) -> SemVerIncrement | None: if value is None: return None try: From d9292b18fe21675a1a74de79c73cb61f200bd70c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 16:08:08 +0800 Subject: [PATCH 13/35] refactor(bump_rule): rename and fix test --- commitizen/bump_rule.py | 10 +- commitizen/commands/bump.py | 6 +- tests/test_bump_rule.py | 179 ++++++++++++++++++------------------ 3 files changed, 94 insertions(+), 101 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 0fcdc9a43..1f5abc16f 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -96,9 +96,9 @@ def get_increment( Returns: 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 + - 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.) """ ... @@ -149,9 +149,7 @@ def _head_pattern(self) -> re.Pattern: return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") -class OldSchoolBumpRule(BumpRule): - """TODO: rename?""" - +class CustomBumpRule(BumpRule): def __init__( self, bump_pattern: str, diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 551145fd1..0a813b51b 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -8,7 +8,7 @@ from commitizen import bump, factory, git, hooks, out from commitizen.bump_rule import ( - OldSchoolBumpRule, + CustomBumpRule, SemVerIncrement, find_increment_by_callable, ) @@ -127,8 +127,8 @@ 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"] - # Fallback to old school bump rule if no bump rule is provided - rule = self.cz.bump_rule or OldSchoolBumpRule( + # Fallback to custom bump rule if no bump rule is provided + rule = self.cz.bump_rule or CustomBumpRule( *self._get_validated_cz_bump(), ) diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 0c8974bcb..f2b4ef3ec 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -2,7 +2,7 @@ from commitizen.bump_rule import ( ConventionalCommitBumpRule, - OldSchoolBumpRule, + CustomBumpRule, SemVerIncrement, _find_highest_increment, find_increment_by_callable, @@ -268,7 +268,7 @@ def test_commit_with_scope(self, get_increment): ) -class TestOldSchoolBumpRule: +class TestCustomBumpRule: @pytest.fixture def bump_pattern(self): return r"^.*?\[(.*?)\].*$" @@ -276,140 +276,130 @@ def bump_pattern(self): @pytest.fixture def bump_map(self): return { - "SemVerIncrement.MAJOR": SemVerIncrement.MAJOR, - "SemVerIncrement.MINOR": SemVerIncrement.MINOR, - "SemVerIncrement.PATCH": SemVerIncrement.PATCH, + "MAJOR": SemVerIncrement.MAJOR, + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, } @pytest.fixture def bump_map_major_version_zero(self): return { - "SemVerIncrement.MAJOR": SemVerIncrement.MINOR, # SemVerIncrement.MAJOR becomes SemVerIncrement.MINOR in version zero - "SemVerIncrement.MINOR": SemVerIncrement.MINOR, - "SemVerIncrement.PATCH": SemVerIncrement.PATCH, + "MAJOR": SemVerIncrement.MINOR, # SemVerIncrement.MAJOR becomes SemVerIncrement.MINOR in version zero + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.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 custom_bump_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): + return CustomBumpRule(bump_pattern, bump_map, bump_map_major_version_zero) - def test_major_version(self, old_school_rule): + def test_major_version(self, custom_bump_rule): assert ( - old_school_rule.get_increment( - "feat: add new feature [SemVerIncrement.MAJOR]", False - ) + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", False) == SemVerIncrement.MAJOR ) assert ( - old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MAJOR]", False) + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", False) == SemVerIncrement.MAJOR ) - def test_minor_version(self, old_school_rule): + def test_minor_version(self, custom_bump_rule): assert ( - old_school_rule.get_increment( - "feat: add new feature [SemVerIncrement.MINOR]", False - ) + custom_bump_rule.get_increment("feat: add new feature [MINOR]", False) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MINOR]", False) + custom_bump_rule.get_increment("fix: bug fix [MINOR]", False) == SemVerIncrement.MINOR ) - def test_patch_version(self, old_school_rule): + def test_patch_version(self, custom_bump_rule): assert ( - old_school_rule.get_increment( - "feat: add new feature [SemVerIncrement.PATCH]", False - ) + custom_bump_rule.get_increment("feat: add new feature [PATCH]", False) == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("fix: bug fix [SemVerIncrement.PATCH]", False) + custom_bump_rule.get_increment("fix: bug fix [PATCH]", False) == SemVerIncrement.PATCH ) - def test_major_version_zero(self, old_school_rule): + def test_major_version_zero(self, custom_bump_rule): assert ( - old_school_rule.get_increment( - "feat: add new feature [SemVerIncrement.MAJOR]", True - ) + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", True) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("fix: bug fix [SemVerIncrement.MAJOR]", True) + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", True) == SemVerIncrement.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_no_match(self, custom_bump_rule): + assert custom_bump_rule.get_increment("feat: add new feature", False) is None + assert custom_bump_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) + CustomBumpRule("", bump_map, bump_map_major_version_zero) def test_invalid_bump_map(self, bump_pattern): with pytest.raises(NoPatternMapError): - OldSchoolBumpRule(bump_pattern, {}, {}) + CustomBumpRule(bump_pattern, {}, {}) def test_invalid_bump_map_major_version_zero(self, bump_pattern, bump_map): with pytest.raises(NoPatternMapError): - OldSchoolBumpRule(bump_pattern, bump_map, {}) + CustomBumpRule(bump_pattern, bump_map, {}) def test_all_invalid(self): with pytest.raises(NoPatternMapError): - OldSchoolBumpRule("", {}, {}) + CustomBumpRule("", {}, {}) def test_none_values(self): with pytest.raises(NoPatternMapError): - OldSchoolBumpRule(None, {}, {}) + CustomBumpRule(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) + CustomBumpRule("", bump_map, bump_map_major_version_zero) def test_empty_maps_with_valid_pattern(self, bump_pattern): with pytest.raises(NoPatternMapError): - OldSchoolBumpRule(bump_pattern, {}, {}) + CustomBumpRule(bump_pattern, {}, {}) def test_complex_pattern(self): pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" bump_map = { - "SemVerIncrement.MAJOR": SemVerIncrement.MAJOR, - "SemVerIncrement.MINOR": SemVerIncrement.MINOR, - "SemVerIncrement.PATCH": SemVerIncrement.PATCH, + "MAJOR": SemVerIncrement.MAJOR, + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, } - rule = OldSchoolBumpRule(pattern, bump_map, bump_map) + rule = CustomBumpRule(pattern, bump_map, bump_map) assert ( rule.get_increment( - "feat: add new feature [SemVerIncrement.MAJOR] [SemVerIncrement.MINOR]", + "feat: add new feature [MAJOR] [MINOR]", False, ) == SemVerIncrement.MAJOR ) assert ( - rule.get_increment( - "fix: bug fix [SemVerIncrement.MINOR] [SemVerIncrement.PATCH]", False - ) + rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) == SemVerIncrement.MINOR ) - def test_with_find_increment_by_callable(self, old_school_rule): + def test_with_find_increment_by_callable(self, custom_bump_rule): commit_messages = [ - "feat: add new feature [SemVerIncrement.MAJOR]", - "fix: bug fix [SemVerIncrement.PATCH]", - "docs: update readme [SemVerIncrement.MINOR]", + "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) + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) ) == SemVerIncrement.MAJOR ) - def test_flexible_bump_map(self, old_school_rule): + def test_flexible_bump_map(self, custom_bump_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!)?:" @@ -425,7 +415,7 @@ def test_flexible_bump_map(self, old_school_rule): "minor": SemVerIncrement.MINOR, "patch": SemVerIncrement.PATCH, } - rule = OldSchoolBumpRule(pattern, bump_map, bump_map_major_version_zero) + rule = CustomBumpRule(pattern, bump_map, bump_map_major_version_zero) # Test with multiple version tags assert ( @@ -470,112 +460,117 @@ def test_flexible_bump_map(self, old_school_rule): assert rule.get_increment("patch: fix bug", True) == SemVerIncrement.PATCH -class TestOldSchoolBumpRuleWithDefault: +class TestCustomBumpRuleWithDefault: @pytest.fixture - def old_school_rule(self): - return OldSchoolBumpRule(BUMP_PATTERN, BUMP_MAP, BUMP_MAP_MAJOR_VERSION_ZERO) + def custom_bump_rule(self): + return CustomBumpRule( + BUMP_PATTERN, + SemVerIncrement.safe_cast_dict(BUMP_MAP), + SemVerIncrement.safe_cast_dict(BUMP_MAP_MAJOR_VERSION_ZERO), + ) - def test_breaking_change_with_bang(self, old_school_rule): + def test_breaking_change_with_bang(self, custom_bump_rule): assert ( - old_school_rule.get_increment("feat!: breaking change", False) + custom_bump_rule.get_increment("feat!: breaking change", False) == SemVerIncrement.MAJOR ) assert ( - old_school_rule.get_increment("fix!: breaking change", False) + custom_bump_rule.get_increment("fix!: breaking change", False) == SemVerIncrement.MAJOR ) assert ( - old_school_rule.get_increment("feat!: breaking change", True) + custom_bump_rule.get_increment("feat!: breaking change", True) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("fix!: breaking change", True) + custom_bump_rule.get_increment("fix!: breaking change", True) == SemVerIncrement.MINOR ) - def test_breaking_change_type(self, old_school_rule): + def test_breaking_change_type(self, custom_bump_rule): assert ( - old_school_rule.get_increment("BREAKING CHANGE: major change", False) + custom_bump_rule.get_increment("BREAKING CHANGE: major change", False) == SemVerIncrement.MAJOR ) assert ( - old_school_rule.get_increment("BREAKING-CHANGE: major change", False) + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", False) == SemVerIncrement.MAJOR ) assert ( - old_school_rule.get_increment("BREAKING CHANGE: major change", True) + custom_bump_rule.get_increment("BREAKING CHANGE: major change", True) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("BREAKING-CHANGE: major change", True) + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", True) == SemVerIncrement.MINOR ) - def test_feat_commit(self, old_school_rule): + def test_feat_commit(self, custom_bump_rule): assert ( - old_school_rule.get_increment("feat: add new feature", False) + custom_bump_rule.get_increment("feat: add new feature", False) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("feat: add new feature", True) + custom_bump_rule.get_increment("feat: add new feature", True) == SemVerIncrement.MINOR ) - def test_fix_commit(self, old_school_rule): + def test_fix_commit(self, custom_bump_rule): assert ( - old_school_rule.get_increment("fix: fix bug", False) + custom_bump_rule.get_increment("fix: fix bug", False) == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("fix: fix bug", True) == SemVerIncrement.PATCH + custom_bump_rule.get_increment("fix: fix bug", True) + == SemVerIncrement.PATCH ) - def test_refactor_commit(self, old_school_rule): + def test_refactor_commit(self, custom_bump_rule): assert ( - old_school_rule.get_increment("refactor: restructure code", False) + custom_bump_rule.get_increment("refactor: restructure code", False) == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("refactor: restructure code", True) + custom_bump_rule.get_increment("refactor: restructure code", True) == SemVerIncrement.PATCH ) - def test_perf_commit(self, old_school_rule): + def test_perf_commit(self, custom_bump_rule): assert ( - old_school_rule.get_increment("perf: improve performance", False) + custom_bump_rule.get_increment("perf: improve performance", False) == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("perf: improve performance", True) + custom_bump_rule.get_increment("perf: improve performance", True) == SemVerIncrement.PATCH ) - def test_commit_with_scope(self, old_school_rule): + def test_commit_with_scope(self, custom_bump_rule): assert ( - old_school_rule.get_increment("feat(api): add new endpoint", False) + custom_bump_rule.get_increment("feat(api): add new endpoint", False) == SemVerIncrement.MINOR ) assert ( - old_school_rule.get_increment("fix(ui): fix button alignment", False) + custom_bump_rule.get_increment("fix(ui): fix button alignment", False) == SemVerIncrement.PATCH ) assert ( - old_school_rule.get_increment("refactor(core): restructure", False) + custom_bump_rule.get_increment("refactor(core): restructure", False) == SemVerIncrement.PATCH ) - def test_no_match(self, old_school_rule): + def test_no_match(self, custom_bump_rule): assert ( - old_school_rule.get_increment("docs: update documentation", False) is None + custom_bump_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 custom_bump_rule.get_increment("style: format code", False) is None + assert custom_bump_rule.get_increment("test: add unit tests", False) is None assert ( - old_school_rule.get_increment("build: update build config", False) is None + custom_bump_rule.get_increment("build: update build config", False) is None ) - assert old_school_rule.get_increment("ci: update CI pipeline", False) is None + assert custom_bump_rule.get_increment("ci: update CI pipeline", False) is None - def test_with_find_increment_by_callable(self, old_school_rule): + def test_with_find_increment_by_callable(self, custom_bump_rule): commit_messages = [ "feat!: breaking change", "fix: bug fix", @@ -583,7 +578,7 @@ def test_with_find_increment_by_callable(self, old_school_rule): ] assert ( find_increment_by_callable( - commit_messages, lambda x: old_school_rule.get_increment(x, False) + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) ) == SemVerIncrement.MAJOR ) From c77d67242993eb6b396c5db9df350b96fd7c99f0 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 16:25:50 +0800 Subject: [PATCH 14/35] refactor(bump_rule): renaming --- commitizen/bump_rule.py | 60 ++++++++++++++++++------------------- commitizen/commands/bump.py | 3 +- tests/test_bump_rule.py | 29 ++++++++++-------- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 1f5abc16f..2589ed6c5 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -34,6 +34,36 @@ def safe_cast_dict(cls, d: dict[str, Any]) -> dict[str, SemVerIncrement]: if v is not None } + @staticmethod + def get_highest_by_messages( + 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 + 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 + 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 + increment is needed. The order of precedence is MAJOR > MINOR > PATCH. + + Example: + >>> commit_messages = ["feat: new feature", "fix: bug fix"] + >>> rule = ConventionalCommitBumpRule() + >>> SemVerIncrement.get_highest_by_messages(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 _find_highest_increment(increments) + _VERSION_ORDERING = dict( zip( @@ -43,36 +73,6 @@ def safe_cast_dict(cls, d: dict[str, Any]) -> dict[str, SemVerIncrement]: ) -def find_increment_by_callable( - 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 - 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 - 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 - increment is needed. The order of precedence is MAJOR > MINOR > PATCH. - - Example: - >>> commit_messages = ["feat: new feature", "fix: bug fix"] - >>> rule = ConventionalCommitBumpRule() - >>> 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 _find_highest_increment(increments) - - def _find_highest_increment( increments: Iterable[SemVerIncrement | None], ) -> SemVerIncrement | None: diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 0a813b51b..bbf0145d2 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -10,7 +10,6 @@ from commitizen.bump_rule import ( CustomBumpRule, SemVerIncrement, - find_increment_by_callable, ) from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog @@ -132,7 +131,7 @@ def find_increment(self, commits: list[git.GitCommit]) -> SemVerIncrement | None *self._get_validated_cz_bump(), ) - return find_increment_by_callable( + return SemVerIncrement.get_highest_by_messages( (commit.message for commit in commits), lambda x: rule.get_increment(x, is_major_version_zero), ) diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index f2b4ef3ec..cb00cfa2d 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -5,7 +5,6 @@ CustomBumpRule, SemVerIncrement, _find_highest_increment, - find_increment_by_callable, ) from commitizen.defaults import ( BUMP_MAP, @@ -186,7 +185,7 @@ 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) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MINOR ) @@ -197,7 +196,7 @@ def test_multiple_commits(self, get_increment): "docs: update readme", ] assert ( - find_increment_by_callable(commit_messages, get_increment) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MINOR ) @@ -207,7 +206,7 @@ def test_breaking_change(self, get_increment): "feat!: breaking change", ] assert ( - find_increment_by_callable(commit_messages, get_increment) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MAJOR ) @@ -216,7 +215,7 @@ def test_multi_line_commit(self, get_increment): "feat: new feature\n\nBREAKING CHANGE: major change", ] assert ( - find_increment_by_callable(commit_messages, get_increment) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MAJOR ) @@ -225,11 +224,17 @@ def test_no_increment_needed(self, get_increment): "docs: update documentation", "style: format code", ] - assert find_increment_by_callable(commit_messages, get_increment) is None + assert ( + SemVerIncrement.get_highest_by_messages(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 + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) def test_major_version_zero(self): bump_rule = ConventionalCommitBumpRule() @@ -239,7 +244,7 @@ def test_major_version_zero(self): "BREAKING CHANGE: major change", ] assert ( - find_increment_by_callable( + SemVerIncrement.get_highest_by_messages( commit_messages, lambda x: bump_rule.get_increment(x, True) ) == SemVerIncrement.MINOR @@ -253,7 +258,7 @@ def test_mixed_commit_types(self, get_increment): "refactor: restructure code", ] assert ( - find_increment_by_callable(commit_messages, get_increment) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MINOR ) @@ -263,7 +268,7 @@ def test_commit_with_scope(self, get_increment): "fix(ui): fix button alignment", ] assert ( - find_increment_by_callable(commit_messages, get_increment) + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) == SemVerIncrement.MINOR ) @@ -393,7 +398,7 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): "docs: update readme [MINOR]", ] assert ( - find_increment_by_callable( + SemVerIncrement.get_highest_by_messages( commit_messages, lambda x: custom_bump_rule.get_increment(x, False) ) == SemVerIncrement.MAJOR @@ -577,7 +582,7 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): "perf: improve performance", ] assert ( - find_increment_by_callable( + SemVerIncrement.get_highest_by_messages( commit_messages, lambda x: custom_bump_rule.get_increment(x, False) ) == SemVerIncrement.MAJOR From 17b82ad586eda52b82a8163f7e5bc47fbdd8a8b3 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 16:30:05 +0800 Subject: [PATCH 15/35] test(bump_rule): try to fix test --- commitizen/bump_rule.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 2589ed6c5..340b2ac7e 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -17,6 +17,9 @@ class SemVerIncrement(Enum): def __str__(self) -> str: return self.name + def __repr__(self) -> str: + return self.name + @classmethod def safe_cast(cls, value: Any) -> SemVerIncrement | None: if value is None: From 7b3b9c6ff168bf2d9680970bbaa5c87f259aaa1f Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 16:32:47 +0800 Subject: [PATCH 16/35] test(bump_rule): try to fix test again --- commitizen/bump_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 340b2ac7e..a8744256b 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -18,7 +18,7 @@ def __str__(self) -> str: return self.name def __repr__(self) -> str: - return self.name + return f"'{self.name}'" @classmethod def safe_cast(cls, value: Any) -> SemVerIncrement | None: From 0265ad5195f6614c67f4d25c2b99e06296c6ade1 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 16:38:22 +0800 Subject: [PATCH 17/35] test(bump_rule): fix test again --- commitizen/bump_rule.py | 3 --- tests/commands/test_bump_command.py | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index a8744256b..2589ed6c5 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -17,9 +17,6 @@ class SemVerIncrement(Enum): def __str__(self) -> str: return self.name - def __repr__(self) -> str: - return f"'{self.name}'" - @classmethod def safe_cast(cls, value: Any) -> SemVerIncrement | None: if value is None: diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index e15539d8a..7b3203a98 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -13,6 +13,7 @@ import commitizen.commands.bump as bump from commitizen import cli, cmd, git, hooks +from commitizen.bump_rule import SemVerIncrement from commitizen.changelog_formats import ChangelogFormat from commitizen.cz.base import BaseCommitizen from commitizen.exceptions import ( @@ -1000,7 +1001,7 @@ def test_bump_with_pre_bump_hooks( new_version="0.2.0", new_tag_version="0.2.0", message="bump: version 0.1.0 → 0.2.0", - increment="MINOR", + increment=SemVerIncrement.MINOR, changelog_file_name=None, ), call( @@ -1012,7 +1013,7 @@ def test_bump_with_pre_bump_hooks( current_version="0.2.0", current_tag_version="0.2.0", message="bump: version 0.1.0 → 0.2.0", - increment="MINOR", + increment=SemVerIncrement.MINOR, changelog_file_name=None, ), ] From 5e8187375cc21e7f1ff1cc4cce204bc42b6fbb36 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 19:15:17 +0800 Subject: [PATCH 18/35] fix(SemVerIncrement): fix error handling and add test --- commitizen/bump_rule.py | 2 +- tests/test_bump_rule.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 2589ed6c5..e66720ce9 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -23,7 +23,7 @@ def safe_cast(cls, value: Any) -> SemVerIncrement | None: return None try: return cls[value] - except ValueError: + except KeyError: return None @classmethod diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index cb00cfa2d..2fa63ed78 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -655,3 +655,38 @@ def test_find_highest_increment(): ) == SemVerIncrement.MINOR ) + + +class TestSemVerIncrementSafeCast: + def test_safe_cast_valid_values(self): + """Test safe_cast with valid enum values.""" + assert SemVerIncrement.safe_cast("MAJOR") == SemVerIncrement.MAJOR + assert SemVerIncrement.safe_cast("MINOR") == SemVerIncrement.MINOR + assert SemVerIncrement.safe_cast("PATCH") == SemVerIncrement.PATCH + + def test_safe_cast_invalid_values(self): + """Test safe_cast with invalid values.""" + assert SemVerIncrement.safe_cast("INVALID") is None + assert SemVerIncrement.safe_cast("") is None + assert SemVerIncrement.safe_cast(123) is None + assert SemVerIncrement.safe_cast(None) is None + + def test_safe_cast_dict(self): + """Test safe_cast_dict method.""" + test_dict = { + "MAJOR": "MAJOR", + "MINOR": "MINOR", + "PATCH": "PATCH", + "INVALID": "INVALID", + "empty": "", + "number": 123, + "none": None, + } + + expected_dict = { + "MAJOR": SemVerIncrement.MAJOR, + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, + } + + assert SemVerIncrement.safe_cast_dict(test_dict) == expected_dict From db8e93aa5c58ba9e748c04b348bc8fc50526a899 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 22:15:22 +0800 Subject: [PATCH 19/35] docs(bump_rule): add docstring --- commitizen/bump_rule.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index e66720ce9..d95bcf035 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -10,6 +10,14 @@ class SemVerIncrement(Enum): + """An enumeration representing semantic versioning increments. + + This class defines the three types of version increments according to semantic versioning: + - MAJOR: For incompatible API changes + - MINOR: For backwards-compatible functionality additions + - PATCH: For backwards-compatible bug fixes + """ + MAJOR = auto() MINOR = auto() PATCH = auto() @@ -80,6 +88,16 @@ def _find_highest_increment( class BumpRule(Protocol): + """A protocol defining the interface for version bump rules. + + This protocol specifies the contract that all version bump rule implementations must follow. + It defines how commit messages should be analyzed to determine the appropriate semantic + version increment. + + The protocol is used to ensure consistent behavior across different bump rule implementations, + such as conventional commits or custom rules. + """ + def get_increment( self, commit_message: str, major_version_zero: bool ) -> SemVerIncrement | None: From ac709ecf56925891c5831eb4287ff7fa02f5a3fd Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 22:19:28 +0800 Subject: [PATCH 20/35] refactor(ConventionalCommitBumpRule): refactor --- commitizen/bump_rule.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index d95bcf035..1acbf0977 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -123,9 +123,9 @@ def get_increment( class ConventionalCommitBumpRule(BumpRule): + _BREAKING_CHANGE_TYPES = set(["BREAKING CHANGE", "BREAKING-CHANGE"]) + _MINOR_CHANGE_TYPES = set(["feat"]) _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 @@ -134,12 +134,12 @@ def get_increment( return None change_type = m.group("change_type") - if m.group("bang") or self._RE_BREAKING_CHANGE.match(change_type): + if m.group("bang") or change_type in self._BREAKING_CHANGE_TYPES: return ( SemVerIncrement.MINOR if major_version_zero else SemVerIncrement.MAJOR ) - if change_type == "feat": + if change_type in self._MINOR_CHANGE_TYPES: return SemVerIncrement.MINOR if change_type in self._PATCH_CHANGE_TYPES: @@ -150,13 +150,11 @@ def get_increment( @cached_property def _head_pattern(self) -> re.Pattern: change_types = [ - self._BREAKING_CHANGE, - "fix", - "feat", + *self._BREAKING_CHANGE_TYPES, + *self._PATCH_CHANGE_TYPES, + *self._MINOR_CHANGE_TYPES, "docs", "style", - "refactor", - "perf", "test", "build", "ci", From 4b72cf837582f4946d489b7c899e42bbae2068d5 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 22:24:46 +0800 Subject: [PATCH 21/35] test(BumpRule): improve test coverage --- commitizen/bump_rule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 1acbf0977..fd837cc9b 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -119,7 +119,6 @@ def get_increment( - PATCH: For bug fixes, performance improvements, or refactors - None: For commits that don't require a version bump (docs, style, etc.) """ - ... class ConventionalCommitBumpRule(BumpRule): From 83d6bdecd6d3d0e7b55dd6eb248e292f9c6a8085 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 22:54:36 +0800 Subject: [PATCH 22/35] refactor(Prerelease): use enum --- commitizen/commands/bump.py | 2 +- commitizen/version_schemes.py | 39 ++++-- tests/test_version_scheme_pep440.py | 173 ++++++++++++++------------- tests/test_version_scheme_semver.py | 100 ++++++++-------- tests/test_version_scheme_semver2.py | 74 ++++++------ 5 files changed, 203 insertions(+), 185 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index bbf0145d2..ca5fcc9cf 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -173,7 +173,7 @@ def __call__(self) -> None: # noqa: C901 increment: SemVerIncrement | None = SemVerIncrement.safe_cast( self.arguments["increment"] ) - prerelease: Prerelease | None = self.arguments["prerelease"] + prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) devrelease: int | None = self.arguments["devrelease"] is_files_only: bool | None = self.arguments["files_only"] is_local_version: bool = self.arguments["local_version"] diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 14bed686c..2f3b2d14d 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -3,12 +3,12 @@ import re import sys import warnings +from enum import Enum from itertools import zip_longest from typing import ( TYPE_CHECKING, Any, ClassVar, - Literal, Protocol, cast, runtime_checkable, @@ -41,7 +41,24 @@ from typing import Self -Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] +class Prerelease(Enum): + ALPHA = "alpha" + BETA = "beta" + RC = "rc" + + def __str__(self) -> str: + return self.value + + @classmethod + def safe_cast(cls, value: str | None) -> Prerelease | None: + if not value: + return None + try: + return cls[value.upper()] + except KeyError: + return None + + 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+)?)" @@ -172,7 +189,7 @@ def prerelease(self) -> str | None: return None def generate_prerelease( - self, prerelease: str | None = None, offset: int = 0 + self, prerelease: Prerelease | None = None, offset: int = 0 ) -> str: """Generate prerelease @@ -187,20 +204,18 @@ def generate_prerelease( if not prerelease: return "" + prerelease_value = prerelease.value + new_prerelease_number = offset + # prevent down-bumping the pre-release phase, e.g. from 'b1' to 'a2' # https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases # https://semver.org/#spec-item-11 if self.is_prerelease and self.pre: - prerelease = max(prerelease, self.pre[0]) + prerelease_value = max(prerelease_value, self.pre[0]) + if prerelease_value.startswith(self.pre[0]): + new_prerelease_number = self.pre[1] + 1 - # version.pre is needed for mypy check - if self.is_prerelease and self.pre and prerelease.startswith(self.pre[0]): - prev_prerelease: int = self.pre[1] - new_prerelease_number = prev_prerelease + 1 - else: - new_prerelease_number = offset - pre_version = f"{prerelease}{new_prerelease_number}" - return pre_version + return f"{prerelease_value}{new_prerelease_number}" def generate_devrelease(self, devrelease: int | None) -> str: """Generate devrelease diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index 54fd15229..e512dd2d3 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -4,7 +4,7 @@ import pytest from commitizen.bump_rule import SemVerIncrement -from commitizen.version_schemes import Pep440, VersionProtocol +from commitizen.version_schemes import Pep440, Prerelease, VersionProtocol simple_flow = [ (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), @@ -13,25 +13,25 @@ (("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", SemVerIncrement.PATCH, "alpha", 1, None), "0.3.1a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), (("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"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0a3.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0a3.dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0b1"), - (("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"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0a2.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0a3.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0a3.dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0rc1.dev1"), (("1.0.0rc0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0b0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0b0"), (("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"), @@ -48,22 +48,22 @@ # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1rc1"), ] weird_cases = [ (("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"), - (("1", None, "beta", 1, None), "1.0.0b1"), - (("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"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0rc0"), (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] @@ -72,52 +72,52 @@ (("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"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0rc2"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1a0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0rc2"), ] # additional pre-release tests run through various release scenarios prerelease_cases = [ # - (("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"), + (("3.3.3", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a0"), + (("3.3.4a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a1"), + (("3.3.4a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a0"), + (("3.4.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.4.0a1"), + (("3.4.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a2"), + (("3.4.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), + (("4.0.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "4.0.0a1"), + (("4.0.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "4.0.0a2"), + (("4.0.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a3"), # - (("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", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a0"), + (("1.0.1a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a1"), + (("1.0.1a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.MAJOR, Prerelease.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", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a3"), + (("1.1.0a3", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), # - (("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"), + (("1.0.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), + (("2.0.0a0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a1"), + (("2.0.0a1", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a2"), + (("2.0.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a3"), + (("2.0.0a3", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a4"), + (("2.0.0a4", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a5"), + (("2.0.0a5", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a6"), # - (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.0.0b1"), - (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0b1"), # (("1.0.1a0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), (("1.0.1a0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), @@ -135,41 +135,44 @@ (("3.0.0b1", None, None, 0, None), "3.0.0"), (("3.0.0rc1", None, None, 0, None), "3.0.0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4", None, "beta", 0, None), "3.1.4b0"), - (("3.1.4", None, "rc", 0, None), "3.1.4rc0"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + (("3.1.4", None, Prerelease.BETA, 0, None), "3.1.4b0"), + (("3.1.4", None, Prerelease.RC, 0, None), "3.1.4rc0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("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"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + ( + ("3.1.4a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), + "3.1.4a1", + ), # UNEXPECTED! + (("3.1.4a0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.2.0a0"), + (("3.1.4a0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), ] exact_cases = [ (("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", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1b0"), + (("1.0.0a1", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1b0"), + (("1.0.0b0", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, 1), "1.0.1rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1rc0.dev1"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0b0"), + (("1.0.0a1", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0b0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "1.1.0a0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1" - (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, 1), "1.1.0rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0rc0.dev1"), # with exact_increment=False: "2.0.0" (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" @@ -177,11 +180,11 @@ # with exact_increment=False: "2.0.0" (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", SemVerIncrement.MAJOR, "alpha", 0, None), "3.0.0a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.1.0a0"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.1a0"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1a0"), ] diff --git a/tests/test_version_scheme_semver.py b/tests/test_version_scheme_semver.py index fa1b2fd72..f1eb43d11 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -4,7 +4,7 @@ import pytest from commitizen.bump_rule import SemVerIncrement -from commitizen.version_schemes import SemVer, VersionProtocol +from commitizen.version_schemes import Prerelease, SemVer, VersionProtocol simple_flow = [ (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), @@ -13,25 +13,25 @@ (("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", SemVerIncrement.PATCH, "alpha", 1, None), "0.3.1-a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1-a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1-a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), (("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"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0-a2-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0-a3-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0-a3-dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0-rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0-rc1-dev1"), (("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.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0-b0"), (("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"), @@ -48,22 +48,22 @@ # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1-b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1-rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1-rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1-b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1-rc1"), ] weird_cases = [ (("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"), - (("1", None, "beta", 1, None), "1.0.0-b1"), - (("1beta", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), - (("1", None, "rc", 0, None), "1.0.0-rc0"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] @@ -72,41 +72,41 @@ (("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"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), - (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-a0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0-rc2"), + (("1.0.0-a0", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0-alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), ] exact_cases = [ (("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", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1-b0"), + (("1.0.0a1", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", SemVerIncrement.PATCH, "beta", 0, None), "1.0.1-b0"), + (("1.0.0b0", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1-rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, None), "1.0.1-rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", SemVerIncrement.PATCH, "rc", 0, 1), "1.0.1-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1-rc0-dev1"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0-b0"), + (("1.0.0a1", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", SemVerIncrement.MINOR, "beta", 0, None), "1.1.0-b0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0-rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, None), "1.1.0-rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", SemVerIncrement.MINOR, "rc", 0, 1), "1.1.0-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0-rc0-dev1"), # with exact_increment=False: "2.0.0" (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" @@ -114,11 +114,11 @@ # with exact_increment=False: "2.0.0" (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", SemVerIncrement.MAJOR, "alpha", 0, None), "3.0.0-a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", SemVerIncrement.MINOR, "alpha", 0, None), "2.1.0-a0"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", SemVerIncrement.PATCH, "alpha", 0, None), "2.0.1-a0"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1-a0"), ] diff --git a/tests/test_version_scheme_semver2.py b/tests/test_version_scheme_semver2.py index 9f47ed256..1e1fe1452 100644 --- a/tests/test_version_scheme_semver2.py +++ b/tests/test_version_scheme_semver2.py @@ -4,7 +4,7 @@ import pytest from commitizen.bump_rule import SemVerIncrement -from commitizen.version_schemes import SemVer2, VersionProtocol +from commitizen.version_schemes import Prerelease, SemVer2, VersionProtocol simple_flow = [ (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), @@ -13,25 +13,25 @@ (("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", 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.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-alpha.0"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 0, None), "0.3.1-alpha.1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), (("0.3.1-alpha.0", None, None, 0, None), "0.3.1"), (("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"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 1), "1.0.0-alpha.3.dev.1"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 0), "1.0.0-alpha.3.dev.0"), - (("1.0.0-alpha.1", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0-beta.0", None, "beta", 0, None), "1.0.0-beta.1"), - (("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"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("1.0.0-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.2.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.3.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 0), "1.0.0-alpha.3.dev.0"), + (("1.0.0-alpha.1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-beta.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, None), "1.0.0-rc.1"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, 1), "1.0.0-rc.1.dev.1"), (("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-alpha.3.dev.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.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"), @@ -48,22 +48,22 @@ # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1-beta.1", None, "alpha", 0, None), "0.1.1-beta.2"), - (("0.1.1-rc.0", None, "alpha", 0, None), "0.1.1-rc.1"), - (("0.1.1-rc.0", None, "beta", 0, None), "0.1.1-rc.1"), + (("0.1.1-beta.1", None, Prerelease.ALPHA, 0, None), "0.1.1-beta.2"), + (("0.1.1-rc.0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc.1"), + (("0.1.1-rc.0", None, Prerelease.BETA, 0, None), "0.1.1-rc.1"), ] weird_cases = [ (("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"), - (("1", None, "beta", 1, None), "1.0.0-beta.1"), - (("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-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1-alpha.0", None, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1-beta", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), (("1.0.0-rc.1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] @@ -72,16 +72,16 @@ (("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"), - (("1.0.0-rc.1", None, "rc", 0, None), "1.0.0-rc.2"), - (("1.0.0-alpha.0", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-alpha.0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.1", None, Prerelease.RC, 0, None), "1.0.0-rc.2"), + (("1.0.0-alpha.0", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), ] From 86e35b7369b152e21705bd24ba618f17f00b5aa5 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 23:05:00 +0800 Subject: [PATCH 23/35] refactor(SemVerIncrement): get_highest_by_message --- commitizen/bump_rule.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index fd837cc9b..07c392440 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -68,9 +68,11 @@ def get_highest_by_messages( >>> SemVerIncrement.get_highest_by_messages(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 _find_highest_increment(increments) + return _find_highest_increment( + get_increment(line) + for message in commit_messages + for line in message.split("\n") + ) _VERSION_ORDERING = dict( From 6109d98ad94a639e5c8d9d3dfa0bd05ee344cec2 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 23:22:21 +0800 Subject: [PATCH 24/35] refactor(bump_rule): make bump_rule a property --- commitizen/commands/bump.py | 29 ++----------------- commitizen/cz/base.py | 27 +++++++++++++++-- .../conventional_commits.py | 2 +- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index ca5fcc9cf..81991330b 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -8,7 +8,6 @@ from commitizen import bump, factory, git, hooks, out from commitizen.bump_rule import ( - CustomBumpRule, SemVerIncrement, ) from commitizen.changelog_formats import get_changelog_format @@ -24,7 +23,6 @@ InvalidManualVersion, NoCommitsFoundError, NoneIncrementExit, - NoPatternMapError, NotAGitProjectError, NotAllowed, NoVersionSpecifiedError, @@ -124,34 +122,11 @@ def is_initial_tag( 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"] - - # Fallback to custom bump rule if no bump rule is provided - rule = self.cz.bump_rule or CustomBumpRule( - *self._get_validated_cz_bump(), - ) + is_major_version_zero = bool(self.bump_settings["major_version_zero"]) return SemVerIncrement.get_highest_by_messages( (commit.message for commit in commits), - lambda x: rule.get_increment(x, is_major_version_zero), - ) - - def _get_validated_cz_bump( - self, - ) -> 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 - 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" - ) - - return ( - bump_pattern, - SemVerIncrement.safe_cast_dict(bump_map), - SemVerIncrement.safe_cast_dict(bump_map_major_version_zero), + lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero), ) def __call__(self) -> None: # noqa: C901 diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index dd48dd718..b45303532 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -2,15 +2,17 @@ from abc import ABCMeta, abstractmethod from collections.abc import Iterable +from functools import cached_property from typing import Any, Callable, Protocol from jinja2 import BaseLoader, PackageLoader from prompt_toolkit.styles import Style, merge_styles from commitizen import git -from commitizen.bump_rule import BumpRule +from commitizen.bump_rule import BumpRule, CustomBumpRule, SemVerIncrement from commitizen.config.base_config import BaseConfig from commitizen.defaults import Questions +from commitizen.exceptions import NoPatternMapError class MessageBuilderHook(Protocol): @@ -26,9 +28,9 @@ def __call__( class BaseCommitizen(metaclass=ABCMeta): - bump_rule: BumpRule | None = None + _bump_rule: BumpRule | None = None - # TODO: deprecate these + # TODO: decide if these should be removed bump_pattern: str | None = None bump_map: dict[str, str] | None = None bump_map_major_version_zero: dict[str, str] | None = None @@ -89,6 +91,25 @@ def style(self): ] ) + @cached_property + def bump_rule(self) -> BumpRule: + if self._bump_rule: + return self._bump_rule + + # Fallback to custom bump rule if no bump rule is provided + bump_pattern = self.bump_pattern + bump_map = self.bump_map + bump_map_major_version_zero = self.bump_map_major_version_zero + if not bump_pattern or not bump_map or not bump_map_major_version_zero: + raise NoPatternMapError( + f"Rule does not support bump: {bump_pattern=}, {bump_map=}, {bump_map_major_version_zero=}" + ) + return CustomBumpRule( + bump_pattern, + SemVerIncrement.safe_cast_dict(bump_map), + SemVerIncrement.safe_cast_dict(bump_map_major_version_zero), + ) + def example(self) -> str: """Example of the commit message.""" raise NotImplementedError("Not Implemented yet") diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index 9c696bc39..1738fab07 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -29,7 +29,7 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): - bump_rule = ConventionalCommitBumpRule() + _bump_rule = ConventionalCommitBumpRule() bump_pattern = defaults.BUMP_PATTERN bump_map = defaults.BUMP_MAP From 537904e0243e5e6099a630e92a3f963fe0a1a552 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 23:28:21 +0800 Subject: [PATCH 25/35] refactor(BaseVersion): increment_base --- commitizen/version_schemes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 2f3b2d14d..25e8c6753 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -240,13 +240,13 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" def increment_base(self, increment: SemVerIncrement | None = None) -> str: - prev_release = list(self.release) - increments = [ - SemVerIncrement.MAJOR, - SemVerIncrement.MINOR, - SemVerIncrement.PATCH, - ] - base = dict(zip_longest(increments, prev_release, fillvalue=0)) + base = dict( + zip_longest( + (SemVerIncrement.MAJOR, SemVerIncrement.MINOR, SemVerIncrement.PATCH), + self.release, + fillvalue=0, + ) + ) if increment == SemVerIncrement.MAJOR: base[SemVerIncrement.MAJOR] += 1 From d820bf7f6cdb8f90f21b6c507a5cadec9866b80b Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 23:39:27 +0800 Subject: [PATCH 26/35] test(BaseCommitizen): align with test --- commitizen/cz/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index b45303532..48b4e9150 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -102,7 +102,7 @@ def bump_rule(self) -> BumpRule: bump_map_major_version_zero = self.bump_map_major_version_zero if not bump_pattern or not bump_map or not bump_map_major_version_zero: raise NoPatternMapError( - f"Rule does not support bump: {bump_pattern=}, {bump_map=}, {bump_map_major_version_zero=}" + f"'{self.config.settings['name']}' rule does not support bump: {bump_pattern=}, {bump_map=}, {bump_map_major_version_zero=}" ) return CustomBumpRule( bump_pattern, From 261e754b3b4df592d25fb50453d7b3234b51ebf7 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 01:55:14 +0800 Subject: [PATCH 27/35] refactor(SemVerIncrement): use IntEnum --- commitizen/bump_rule.py | 46 ++++++-------- tests/test_bump_rule.py | 134 ++++++++++------------------------------ 2 files changed, 53 insertions(+), 127 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 07c392440..ac7c62c9c 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -1,33 +1,33 @@ from __future__ import annotations import re -from collections.abc import Iterable -from enum import Enum, auto +from collections.abc import Iterable, Mapping +from enum import IntEnum, auto from functools import cached_property -from typing import Any, Callable, Protocol +from typing import Callable, Protocol from commitizen.exceptions import NoPatternMapError -class SemVerIncrement(Enum): +class SemVerIncrement(IntEnum): """An enumeration representing semantic versioning increments. This class defines the three types of version increments according to semantic versioning: - - MAJOR: For incompatible API changes - - MINOR: For backwards-compatible functionality additions - PATCH: For backwards-compatible bug fixes + - MINOR: For backwards-compatible functionality additions + - MAJOR: For incompatible API changes """ - MAJOR = auto() - MINOR = auto() PATCH = auto() + MINOR = auto() + MAJOR = auto() def __str__(self) -> str: return self.name @classmethod - def safe_cast(cls, value: Any) -> SemVerIncrement | None: - if value is None: + def safe_cast(cls, value: object) -> SemVerIncrement | None: + if not isinstance(value, str): return None try: return cls[value] @@ -35,7 +35,7 @@ def safe_cast(cls, value: Any) -> SemVerIncrement | None: return None @classmethod - def safe_cast_dict(cls, d: dict[str, Any]) -> dict[str, SemVerIncrement]: + def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, SemVerIncrement]: return { k: v for k, v in ((k, SemVerIncrement.safe_cast(v)) for k, v in d.items()) @@ -68,25 +68,17 @@ def get_highest_by_messages( >>> SemVerIncrement.get_highest_by_messages(commit_messages, lambda x: rule.get_increment(x, False)) 'MINOR' """ - return _find_highest_increment( + return SemVerIncrement.get_highest( get_increment(line) for message in commit_messages for line in message.split("\n") ) - -_VERSION_ORDERING = dict( - zip( - (None, SemVerIncrement.PATCH, SemVerIncrement.MINOR, SemVerIncrement.MAJOR), - range(4), - ) -) - - -def _find_highest_increment( - increments: Iterable[SemVerIncrement | None], -) -> SemVerIncrement | None: - return max(increments, key=lambda x: _VERSION_ORDERING[x], default=None) + @staticmethod + def get_highest( + increments: Iterable[SemVerIncrement | None], + ) -> SemVerIncrement | None: + return max(filter(None, increments), default=None) class BumpRule(Protocol): @@ -193,8 +185,8 @@ def get_increment( ) try: - if ret := _find_highest_increment( - (increment for name, increment in bump_map.items() if m.group(name)) + if ret := SemVerIncrement.get_highest( + (increment for name, increment in bump_map.items() if m.group(name)), ): return ret except IndexError: diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 2fa63ed78..43185c178 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -4,7 +4,6 @@ ConventionalCommitBumpRule, CustomBumpRule, SemVerIncrement, - _find_highest_increment, ) from commitizen.defaults import ( BUMP_MAP, @@ -589,104 +588,39 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): ) -def test_find_highest_increment(): - """Test the _find_highest_increment function.""" - # Test with single increment - 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( - [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, 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, 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 [ - SemVerIncrement.PATCH, - SemVerIncrement.MINOR, - SemVerIncrement.MAJOR, - ] - ) - == SemVerIncrement.MAJOR - ) - assert ( - _find_highest_increment( - x for x in [None, SemVerIncrement.PATCH, SemVerIncrement.MINOR] - ) - == SemVerIncrement.MINOR - ) - - -class TestSemVerIncrementSafeCast: - def test_safe_cast_valid_values(self): - """Test safe_cast with valid enum values.""" - assert SemVerIncrement.safe_cast("MAJOR") == SemVerIncrement.MAJOR - assert SemVerIncrement.safe_cast("MINOR") == SemVerIncrement.MINOR - assert SemVerIncrement.safe_cast("PATCH") == SemVerIncrement.PATCH - - def test_safe_cast_invalid_values(self): - """Test safe_cast with invalid values.""" - assert SemVerIncrement.safe_cast("INVALID") is None - assert SemVerIncrement.safe_cast("") is None - assert SemVerIncrement.safe_cast(123) is None - assert SemVerIncrement.safe_cast(None) is None - - def test_safe_cast_dict(self): - """Test safe_cast_dict method.""" - test_dict = { - "MAJOR": "MAJOR", - "MINOR": "MINOR", - "PATCH": "PATCH", - "INVALID": "INVALID", - "empty": "", - "number": 123, - "none": None, - } +class TestGetHighest: + def test_get_highest_with_major(self): + increments = [ + SemVerIncrement.PATCH, + SemVerIncrement.MINOR, + SemVerIncrement.MAJOR, + ] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MAJOR - expected_dict = { - "MAJOR": SemVerIncrement.MAJOR, - "MINOR": SemVerIncrement.MINOR, - "PATCH": SemVerIncrement.PATCH, - } + def test_get_highest_with_minor(self): + increments = [SemVerIncrement.PATCH, SemVerIncrement.MINOR, None] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MINOR + + def test_get_highest_with_patch(self): + increments = [SemVerIncrement.PATCH, None, None] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.PATCH + + def test_get_highest_with_none(self): + increments = [None, None, None] + assert SemVerIncrement.get_highest(increments) is None + + def test_get_highest_empty(self): + increments = [] + assert SemVerIncrement.get_highest(increments) is None + + def test_get_highest_mixed_order(self): + increments = [ + SemVerIncrement.MAJOR, + SemVerIncrement.PATCH, + SemVerIncrement.MINOR, + ] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MAJOR - assert SemVerIncrement.safe_cast_dict(test_dict) == expected_dict + def test_get_highest_with_none_values(self): + increments = [None, SemVerIncrement.MINOR, None, SemVerIncrement.PATCH] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MINOR From 30a36da70931af792a928561b0be48fc506ff636 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 02:29:09 +0800 Subject: [PATCH 28/35] test(SemVerIncrement): safe_cast --- tests/test_bump_rule.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 43185c178..e67c5da7c 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -624,3 +624,25 @@ def test_get_highest_mixed_order(self): def test_get_highest_with_none_values(self): increments = [None, SemVerIncrement.MINOR, None, SemVerIncrement.PATCH] assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MINOR + + +class TestSafeCast: + def test_safe_cast_valid_strings(self): + assert SemVerIncrement.safe_cast("MAJOR") == SemVerIncrement.MAJOR + assert SemVerIncrement.safe_cast("MINOR") == SemVerIncrement.MINOR + assert SemVerIncrement.safe_cast("PATCH") == SemVerIncrement.PATCH + + def test_safe_cast_invalid_strings(self): + assert SemVerIncrement.safe_cast("invalid") is None + assert SemVerIncrement.safe_cast("major") is None # case sensitive + assert SemVerIncrement.safe_cast("") is None + + def test_safe_cast_non_string_values(self): + assert SemVerIncrement.safe_cast(None) is None + assert SemVerIncrement.safe_cast(1) is None + assert SemVerIncrement.safe_cast(True) is None + assert SemVerIncrement.safe_cast([]) is None + assert SemVerIncrement.safe_cast({}) is None + assert ( + SemVerIncrement.safe_cast(SemVerIncrement.MAJOR) is None + ) # enum value itself From 47c994c059ec42c561e4517488fc94d00c670751 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 02:37:02 +0800 Subject: [PATCH 29/35] test(Prerelease): test --- commitizen/version_schemes.py | 7 ++----- tests/test_version_schemes.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 25e8c6753..40ac9929a 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -46,12 +46,9 @@ class Prerelease(Enum): BETA = "beta" RC = "rc" - def __str__(self) -> str: - return self.value - @classmethod - def safe_cast(cls, value: str | None) -> Prerelease | None: - if not value: + def safe_cast(cls, value: object) -> Prerelease | None: + if not isinstance(value, str): return None try: return cls[value.upper()] diff --git a/tests/test_version_schemes.py b/tests/test_version_schemes.py index 8e2dae902..ef646d6d2 100644 --- a/tests/test_version_schemes.py +++ b/tests/test_version_schemes.py @@ -12,7 +12,35 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionSchemeUnknown -from commitizen.version_schemes import Pep440, SemVer, get_version_scheme +from commitizen.version_schemes import Pep440, Prerelease, SemVer, get_version_scheme + + +class TestPrereleaseSafeCast: + def test_safe_cast_valid_strings(self): + assert Prerelease.safe_cast("ALPHA") == Prerelease.ALPHA + assert Prerelease.safe_cast("BETA") == Prerelease.BETA + assert Prerelease.safe_cast("RC") == Prerelease.RC + + def test_safe_cast_case_insensitive(self): + assert Prerelease.safe_cast("alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("beta") == Prerelease.BETA + assert Prerelease.safe_cast("rc") == Prerelease.RC + assert Prerelease.safe_cast("Alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("Beta") == Prerelease.BETA + assert Prerelease.safe_cast("Rc") == Prerelease.RC + + def test_safe_cast_invalid_strings(self): + assert Prerelease.safe_cast("invalid") is None + assert Prerelease.safe_cast("") is None + assert Prerelease.safe_cast("release") is None + + def test_safe_cast_non_string_values(self): + assert Prerelease.safe_cast(None) is None + assert Prerelease.safe_cast(1) is None + assert Prerelease.safe_cast(True) is None + assert Prerelease.safe_cast([]) is None + assert Prerelease.safe_cast({}) is None + assert Prerelease.safe_cast(Prerelease.ALPHA) is None # enum value itself def test_default_version_scheme_is_pep440(config: BaseConfig): From c45c96f115f19da0bc7e1b0b068e576873205e5c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:05:05 +0800 Subject: [PATCH 30/35] style(bump): remove redundant type hint --- commitizen/commands/bump.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 81991330b..da0bb24cc 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -145,9 +145,7 @@ def __call__(self) -> None: # noqa: C901 dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] - increment: SemVerIncrement | None = SemVerIncrement.safe_cast( - self.arguments["increment"] - ) + increment = SemVerIncrement.safe_cast(self.arguments["increment"]) prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) devrelease: int | None = self.arguments["devrelease"] is_files_only: bool | None = self.arguments["files_only"] From dd85165bec510b4ee52c60d98139744241910ca4 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:07:57 +0800 Subject: [PATCH 31/35] docs(BaseCommitizen): doc bump_rule --- commitizen/cz/base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 48b4e9150..150542949 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -93,6 +93,24 @@ def style(self): @cached_property def bump_rule(self) -> BumpRule: + """Get the bump rule for version incrementing. + + This property returns a BumpRule instance that determines how version numbers + should be incremented based on commit messages. It first checks if a custom + bump rule was set via `_bump_rule`. If not, it falls back to creating a + CustomBumpRule using the class's bump pattern and maps. + + The CustomBumpRule requires three components to be defined: + - bump_pattern: A regex pattern to match commit messages + - bump_map: A mapping of commit types to version increments + - bump_map_major_version_zero: A mapping for version increments when major version is 0 + + Returns: + BumpRule: A rule instance that determines version increments + + Raises: + NoPatternMapError: If the required bump pattern or maps are not defined + """ if self._bump_rule: return self._bump_rule From 7bf082dea62fb5ead93af846720b13447e10f863 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:10:46 +0800 Subject: [PATCH 32/35] refactor(BaseCommitizen): remove redundant vars --- commitizen/cz/base.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 150542949..cf9c20ea0 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -115,17 +115,18 @@ def bump_rule(self) -> BumpRule: return self._bump_rule # Fallback to custom bump rule if no bump rule is provided - bump_pattern = self.bump_pattern - bump_map = self.bump_map - bump_map_major_version_zero = self.bump_map_major_version_zero - if not bump_pattern or not bump_map or not bump_map_major_version_zero: + if ( + not self.bump_pattern + or not self.bump_map + or not self.bump_map_major_version_zero + ): raise NoPatternMapError( - f"'{self.config.settings['name']}' rule does not support bump: {bump_pattern=}, {bump_map=}, {bump_map_major_version_zero=}" + f"'{self.config.settings['name']}' rule does not support bump: {self.bump_pattern=}, {self.bump_map=}, {self.bump_map_major_version_zero=}" ) return CustomBumpRule( - bump_pattern, - SemVerIncrement.safe_cast_dict(bump_map), - SemVerIncrement.safe_cast_dict(bump_map_major_version_zero), + self.bump_pattern, + SemVerIncrement.safe_cast_dict(self.bump_map), + SemVerIncrement.safe_cast_dict(self.bump_map_major_version_zero), ) def example(self) -> str: From 2c9af0821bc30d891c58419e740f5204c558c5da Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:15:01 +0800 Subject: [PATCH 33/35] style(CustomBumpRule): replace type dict with Mapping --- commitizen/bump_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index ac7c62c9c..19f79f884 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -162,8 +162,8 @@ class CustomBumpRule(BumpRule): def __init__( self, bump_pattern: str, - bump_map: dict[str, SemVerIncrement], - bump_map_major_version_zero: dict[str, SemVerIncrement], + bump_map: Mapping[str, SemVerIncrement], + bump_map_major_version_zero: Mapping[str, SemVerIncrement], ): if not bump_map or not bump_pattern or not bump_map_major_version_zero: raise NoPatternMapError( From 02454693b2a9e00e4bc5f8c0a2ebec6c8dce218b Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:16:05 +0800 Subject: [PATCH 34/35] style(CustomBumpRule): rename var --- commitizen/bump_rule.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 19f79f884..8d85bd43d 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -180,13 +180,17 @@ def get_increment( if not (m := self.bump_pattern.search(commit_message)): return None - bump_map = ( + effective_bump_map = ( self.bump_map_major_version_zero if major_version_zero else self.bump_map ) try: if ret := SemVerIncrement.get_highest( - (increment for name, increment in bump_map.items() if m.group(name)), + ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ), ): return ret except IndexError: @@ -195,7 +199,7 @@ def get_increment( # Fallback to old school bump rule found_keyword = m.group(1) - for match_pattern, increment in bump_map.items(): + for match_pattern, increment in effective_bump_map.items(): if re.match(match_pattern, found_keyword): return increment return None From 2eaf52cb0ed80ddebdbe6f3a760bbb19e28c8802 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Mon, 19 May 2025 23:35:54 +0800 Subject: [PATCH 35/35] docs(CustomBumpRule): add docstring --- commitizen/bump_rule.py | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 8d85bd43d..24ec77d6b 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -165,6 +165,55 @@ def __init__( bump_map: Mapping[str, SemVerIncrement], bump_map_major_version_zero: Mapping[str, SemVerIncrement], ): + """Initialize a custom bump rule for version incrementing. + + This constructor creates a rule that determines how version numbers should be + incremented based on commit messages. It validates and compiles the provided + pattern and maps for use in version bumping. + + The fallback logic is used for backward compatibility. + + Args: + bump_pattern: A regex pattern string used to match commit messages. + Example: r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + Or with fallback regex: r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" # First group is type + bump_map: A mapping of commit types to their corresponding version increments. + Example: { + "major": SemVerIncrement.MAJOR, + "bang": SemVerIncrement.MAJOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", SemVerIncrement.MAJOR), + (r"^BREAKING[\-\ ]CHANGE", SemVerIncrement.MAJOR), + (r"^feat", SemVerIncrement.MINOR), + (r"^fix", SemVerIncrement.PATCH), + (r"^refactor", SemVerIncrement.PATCH), + (r"^perf", SemVerIncrement.PATCH), + } + bump_map_major_version_zero: A mapping of commit types to version increments + specifically for when the major version is 0. This allows for different + versioning behavior during initial development. + The format is the same as bump_map. + Example: { + "major": SemVerIncrement.MINOR, # MAJOR becomes MINOR in version zero + "bang": SemVerIncrement.MINOR, # Breaking changes become MINOR in version zero + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", SemVerIncrement.MINOR), + (r"^BREAKING[\-\ ]CHANGE", SemVerIncrement.MINOR), + (r"^feat", SemVerIncrement.MINOR), + (r"^fix", SemVerIncrement.PATCH), + (r"^refactor", SemVerIncrement.PATCH), + (r"^perf", SemVerIncrement.PATCH), + } + + Raises: + NoPatternMapError: If any of the required parameters are empty or None + """ 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=} and {bump_map_major_version_zero=}" @@ -194,10 +243,9 @@ def get_increment( ): return ret except IndexError: - # Fallback to old school bump rule pass - # Fallback to old school bump rule + # Fallback to old school bump rule, for backward compatibility found_keyword = m.group(1) for match_pattern, increment in effective_bump_map.items(): if re.match(match_pattern, found_keyword):