diff --git a/commitizen/bump.py b/commitizen/bump.py index 6d6b6dc06..30e83f8c3 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -2,61 +2,14 @@ import os import re -from collections import OrderedDict from collections.abc import Iterable from glob import iglob -from logging import getLogger from string import Template -from typing import cast -from commitizen.defaults import BUMP_MESSAGE, ENCODING, MAJOR, MINOR, PATCH +from commitizen.defaults import BUMP_MESSAGE, ENCODING from commitizen.exceptions import CurrentVersionNotFoundError -from commitizen.git import GitCommit, smart_open -from commitizen.version_schemes import Increment, Version - -VERSION_TYPES = [None, PATCH, MINOR, MAJOR] - -logger = getLogger("commitizen") - - -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) +from commitizen.git import smart_open +from commitizen.version_schemes import Version def update_version_in_files( diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py new file mode 100644 index 000000000..bfdcfcd99 --- /dev/null +++ b/commitizen/bump_rule.py @@ -0,0 +1,253 @@ +from __future__ import annotations + +import re +from collections.abc import Iterable, Mapping +from enum import IntEnum, auto +from functools import cached_property +from typing import Callable, Protocol + +from commitizen.exceptions import NoPatternMapError + + +class VersionIncrement(IntEnum): + """An enumeration representing semantic versioning increments. + + This class defines the three types of version increments according to semantic versioning: + - PATCH: For backwards-compatible bug fixes + - MINOR: For backwards-compatible functionality additions + - MAJOR: For incompatible API changes + """ + + PATCH = auto() + MINOR = auto() + MAJOR = auto() + + def __str__(self) -> str: + return self.name + + @classmethod + def safe_cast(cls, value: object) -> VersionIncrement | None: + if not isinstance(value, str): + return None + try: + return cls[value] + except KeyError: + return None + + @classmethod + def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, VersionIncrement]: + return { + k: v + for k, v in ((k, VersionIncrement.safe_cast(v)) for k, v in d.items()) + if v is not None + } + + @staticmethod + def get_highest_by_messages( + commit_messages: Iterable[str], + get_increment: Callable[[str], VersionIncrement | None], + ) -> VersionIncrement | 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 + VersionIncrement 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() + >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.get_increment(x, False)) + 'MINOR' + """ + return VersionIncrement.get_highest( + get_increment(line) + for message in commit_messages + for line in message.split("\n") + ) + + @staticmethod + def get_highest( + increments: Iterable[VersionIncrement | None], + ) -> VersionIncrement | None: + return max(filter(None, increments), default=None) + + +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 + ) -> VersionIncrement | 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: + VersionIncrement | 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): + _BREAKING_CHANGE_TYPES = set(["BREAKING CHANGE", "BREAKING-CHANGE"]) + _MINOR_CHANGE_TYPES = set(["feat"]) + _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> VersionIncrement | 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 in self._BREAKING_CHANGE_TYPES: + return ( + VersionIncrement.MINOR if major_version_zero else VersionIncrement.MAJOR + ) + + if change_type in self._MINOR_CHANGE_TYPES: + return VersionIncrement.MINOR + + if change_type in self._PATCH_CHANGE_TYPES: + return VersionIncrement.PATCH + + return None + + @cached_property + def _head_pattern(self) -> re.Pattern: + change_types = [ + *self._BREAKING_CHANGE_TYPES, + *self._PATCH_CHANGE_TYPES, + *self._MINOR_CHANGE_TYPES, + "docs", + "style", + "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): + def __init__( + self, + bump_pattern: str, + bump_map: Mapping[str, VersionIncrement], + bump_map_major_version_zero: Mapping[str, VersionIncrement], + ) -> None: + """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": VersionIncrement.MAJOR, + "bang": VersionIncrement.MAJOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", VersionIncrement.MAJOR), + (r"^BREAKING[\-\ ]CHANGE", VersionIncrement.MAJOR), + (r"^feat", VersionIncrement.MINOR), + (r"^fix", VersionIncrement.PATCH), + (r"^refactor", VersionIncrement.PATCH), + (r"^perf", VersionIncrement.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": VersionIncrement.MINOR, # MAJOR becomes MINOR in version zero + "bang": VersionIncrement.MINOR, # Breaking changes become MINOR in version zero + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", VersionIncrement.MINOR), + (r"^BREAKING[\-\ ]CHANGE", VersionIncrement.MINOR), + (r"^feat", VersionIncrement.MINOR), + (r"^fix", VersionIncrement.PATCH), + (r"^refactor", VersionIncrement.PATCH), + (r"^perf", VersionIncrement.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=}" + ) + + 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 + ) -> VersionIncrement | None: + if not (m := self.bump_pattern.search(commit_message)): + return None + + effective_bump_map = ( + self.bump_map_major_version_zero if major_version_zero else self.bump_map + ) + + try: + if ret := VersionIncrement.get_highest( + ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ), + ): + return ret + except IndexError: + pass + + # Fallback to legacy 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): + return increment + return None diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index ba252fcf2..b3e17045f 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,6 +7,9 @@ import questionary from commitizen import bump, factory, git, hooks, out +from commitizen.bump_rule import ( + VersionIncrement, +) from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -20,7 +23,6 @@ InvalidManualVersion, NoCommitsFoundError, NoneIncrementExit, - NoPatternMapError, NotAGitProjectError, NotAllowed, NoVersionSpecifiedError, @@ -28,7 +30,6 @@ from commitizen.providers import get_provider from commitizen.tags import TagRules from commitizen.version_schemes import ( - Increment, InvalidVersion, Prerelease, get_version_scheme, @@ -51,7 +52,7 @@ class BumpArgs(Settings, total=False): get_next: bool git_output_to_stderr: bool increment_mode: str - increment: Increment | None + increment: VersionIncrement | None local_version: bool manual_version: str | None no_verify: bool @@ -144,21 +145,14 @@ def _is_initial_tag( ) return bool(questionary.confirm("Is this the first tag created?").ask()) - def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None: + def _find_increment(self, commits: list[git.GitCommit]) -> VersionIncrement | None: # Update the bump map to ensure major version doesn't increment. - # self.cz.bump_map = defaults.bump_map_major_version_zero - bump_map = ( - self.cz.bump_map_major_version_zero - if self.bump_settings["major_version_zero"] - else self.cz.bump_map - ) - bump_pattern = self.cz.bump_pattern + is_major_version_zero = self.bump_settings["major_version_zero"] - if not bump_map or not bump_pattern: - raise NoPatternMapError( - f"'{self.config.settings['name']}' rule does not support bump" - ) - return bump.find_increment(commits, regex=bump_pattern, increments_map=bump_map) + return VersionIncrement.get_highest_by_messages( + (commit.message for commit in commits), + lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero), + ) def __call__(self) -> None: """Steps executed to bump.""" @@ -169,10 +163,11 @@ def __call__(self) -> None: except TypeError: raise NoVersionSpecifiedError() - increment = self.arguments["increment"] - prerelease = self.arguments["prerelease"] - devrelease = self.arguments["devrelease"] - is_local_version = self.arguments["local_version"] + dry_run: bool = self.arguments["dry_run"] + increment = VersionIncrement.safe_cast(self.arguments["increment"]) + prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) + devrelease: int | None = self.arguments["devrelease"] + is_local_version: bool = self.arguments["local_version"] manual_version = self.arguments["manual_version"] build_metadata = self.arguments["build_metadata"] get_next = self.arguments["get_next"] @@ -275,7 +270,7 @@ def __call__(self) -> None: # we create an empty PATCH increment for empty tag if increment is None and allow_no_commit: - increment = "PATCH" + increment = VersionIncrement.PATCH new_version = current_version.bump( increment, diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index cdc147669..597c65962 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -2,13 +2,16 @@ from abc import ABCMeta, abstractmethod from collections.abc import Iterable, Mapping +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, CustomBumpRule, VersionIncrement from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import NoPatternMapError from commitizen.question import CzQuestion @@ -25,9 +28,13 @@ def __call__( class BaseCommitizen(metaclass=ABCMeta): + _bump_rule: BumpRule | None = None + + # 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 + default_style_config: list[tuple[str, str]] = [ ("qmark", "fg:#ff9d00 bold"), ("question", "bold"), @@ -84,6 +91,44 @@ def style(self) -> Style: ] ) # type: ignore[return-value] + @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 + + # Fallback to custom bump rule if no bump rule is provided + 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: {self.bump_pattern=}, {self.bump_map=}, {self.bump_map_major_version_zero=}" + ) + return CustomBumpRule( + self.bump_pattern, + VersionIncrement.safe_cast_dict(self.bump_map), + VersionIncrement.safe_cast_dict(self.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 689342347..fd8e3062e 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -2,6 +2,7 @@ from typing import TypedDict 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.question import CzQuestion @@ -27,6 +28,8 @@ class ConventionalCommitsAnswers(TypedDict): 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/commitizen/defaults.py b/commitizen/defaults.py index a49d6d942..90c019394 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -6,6 +6,7 @@ from collections.abc import Iterable, MutableMapping, Sequence from typing import Any, TypedDict +from commitizen.bump_rule import VersionIncrement from commitizen.question import CzQuestion # Type @@ -113,31 +114,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(VersionIncrement.MAJOR)), + (r"^BREAKING[\-\ ]CHANGE", str(VersionIncrement.MAJOR)), + (r"^feat", str(VersionIncrement.MINOR)), + (r"^fix", str(VersionIncrement.PATCH)), + (r"^refactor", str(VersionIncrement.PATCH)), + (r"^perf", str(VersionIncrement.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(VersionIncrement.MINOR)), + (r"^BREAKING[\-\ ]CHANGE", str(VersionIncrement.MINOR)), + (r"^feat", str(VersionIncrement.MINOR)), + (r"^fix", str(VersionIncrement.PATCH)), + (r"^refactor", str(VersionIncrement.PATCH)), + (r"^perf", str(VersionIncrement.PATCH)), ) ) CHANGE_TYPE_ORDER = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] @@ -176,6 +173,9 @@ def __getattr__(name: str) -> Any: "change_type_order": (CHANGE_TYPE_ORDER, "CHANGE_TYPE_ORDER"), "encoding": (ENCODING, "ENCODING"), "name": (DEFAULT_SETTINGS["name"], "DEFAULT_SETTINGS['name']"), + "MAJOR": str(VersionIncrement.MAJOR), + "MINOR": str(VersionIncrement.MINOR), + "PATCH": str(VersionIncrement.PATCH), } if name in deprecated_vars: value, replacement = deprecated_vars[name] diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index a59d3c0aa..0f9af31c4 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -3,17 +3,19 @@ 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, ) +from commitizen.bump_rule import VersionIncrement + 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,8 +41,21 @@ from typing import Self -Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] -Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] +class Prerelease(Enum): + ALPHA = "alpha" + BETA = "beta" + RC = "rc" + + @classmethod + def safe_cast(cls, value: object) -> Prerelease | None: + if not isinstance(value, str): + return None + try: + return cls[value.upper()] + except KeyError: + return None + + _DEFAULT_VERSION_PARSER = re.compile( r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)" ) @@ -128,7 +143,7 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: Increment | None, + increment: VersionIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -173,7 +188,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 @@ -188,20 +203,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 @@ -225,26 +238,34 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: Increment | None = None) -> str: - prev_release = list(self.release) - increments = [MAJOR, MINOR, PATCH] - base = dict(zip_longest(increments, prev_release, fillvalue=0)) + def increment_base(self, increment: VersionIncrement | None = None) -> str: + base = dict( + zip_longest( + ( + VersionIncrement.MAJOR, + VersionIncrement.MINOR, + VersionIncrement.PATCH, + ), + self.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 == VersionIncrement.MAJOR: + base[VersionIncrement.MAJOR] += 1 + base[VersionIncrement.MINOR] = 0 + base[VersionIncrement.PATCH] = 0 + elif increment == VersionIncrement.MINOR: + base[VersionIncrement.MINOR] += 1 + base[VersionIncrement.PATCH] = 0 + elif increment == VersionIncrement.PATCH: + base[VersionIncrement.PATCH] += 1 - return f"{base[MAJOR]}.{base[MINOR]}.{base[PATCH]}" + return f"{base[VersionIncrement.MAJOR]}.{base[VersionIncrement.MINOR]}.{base[VersionIncrement.PATCH]}" def bump( self, - increment: Increment | None, + increment: VersionIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -286,13 +307,16 @@ def bump( ) # type: ignore def _get_increment_base( - self, increment: Increment | None, exact_increment: bool + self, increment: VersionIncrement | None, exact_increment: bool ) -> str: if ( not self.is_prerelease or exact_increment - or (increment == MINOR and self.micro != 0) - or (increment == MAJOR and (self.minor != 0 or self.micro != 0)) + or (increment == VersionIncrement.MINOR and self.micro != 0) + or ( + increment == VersionIncrement.MAJOR + and (self.minor != 0 or self.micro != 0) + ) ): return self.increment_base(increment) return f"{self.major}.{self.minor}.{self.micro}" diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 64b810e4d..e9fe79103 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, defaults, git, hooks +from commitizen.bump_rule import VersionIncrement from commitizen.changelog_formats import ChangelogFormat from commitizen.config.base_config import BaseConfig from commitizen.cz.base import BaseCommitizen @@ -1001,7 +1002,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=VersionIncrement.MINOR, changelog_file_name=None, ), call( @@ -1013,7 +1014,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=VersionIncrement.MINOR, changelog_file_name=None, ), ] diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py deleted file mode 100644 index 77e11c78c..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", - "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", - "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 new file mode 100644 index 000000000..77bf28386 --- /dev/null +++ b/tests/test_bump_rule.py @@ -0,0 +1,649 @@ +import pytest + +from commitizen.bump_rule import ( + ConventionalCommitBumpRule, + CustomBumpRule, + VersionIncrement, +) +from commitizen.defaults import ( + BUMP_MAP, + BUMP_MAP_MAJOR_VERSION_ZERO, + BUMP_PATTERN, +) +from commitizen.exceptions import NoPatternMapError + + +@pytest.fixture +def bump_rule(): + return ConventionalCommitBumpRule() + + +class TestConventionalCommitBumpRule: + def test_feat_commit(self, bump_rule): + assert ( + bump_rule.get_increment("feat: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("feat: add new feature", True) + == VersionIncrement.MINOR + ) + + def test_fix_commit(self, bump_rule): + assert bump_rule.get_increment("fix: fix bug", False) == VersionIncrement.PATCH + assert bump_rule.get_increment("fix: fix bug", True) == VersionIncrement.PATCH + + def test_perf_commit(self, bump_rule): + assert ( + bump_rule.get_increment("perf: improve performance", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("perf: improve performance", True) + == VersionIncrement.PATCH + ) + + def test_refactor_commit(self, bump_rule): + assert ( + bump_rule.get_increment("refactor: restructure code", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: restructure code", True) + == VersionIncrement.PATCH + ) + + def test_breaking_change_with_bang(self, bump_rule): + assert ( + bump_rule.get_increment("feat!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat!: breaking change", True) + == VersionIncrement.MINOR + ) + + def test_breaking_change_type(self, bump_rule): + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", True) + == VersionIncrement.MINOR + ) + + def test_commit_with_scope(self, bump_rule): + assert ( + bump_rule.get_increment("feat(api): add new endpoint", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui): fix button alignment", False) + == VersionIncrement.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) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(database_connection): handle timeout", False) + == VersionIncrement.PATCH + ) + + # Test with nested scopes + assert ( + bump_rule.get_increment("feat(api/auth): implement OAuth", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui/components): fix dropdown", False) + == VersionIncrement.PATCH + ) + + # Test with breaking changes and scopes + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) + == VersionIncrement.MINOR + ) + + # Test with BREAKING CHANGE and scopes + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", False + ) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", True + ) + == VersionIncrement.MINOR + ) + + 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 + + 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) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + + # Breaking change with refactor type and scope + assert ( + bump_rule.get_increment( + "refactor(api)!: remove deprecated endpoints", False + ) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) + == VersionIncrement.MINOR + ) + + # Regular refactor (should be VersionIncrement.PATCH) + assert ( + bump_rule.get_increment("refactor: improve code structure", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: improve code structure", True) + == VersionIncrement.PATCH + ) + + +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 ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_multiple_commits(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "docs: update readme", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_breaking_change(self, get_increment): + commit_messages = [ + "feat: new feature", + "feat!: breaking change", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MAJOR + ) + + def test_multi_line_commit(self, get_increment): + commit_messages = [ + "feat: new feature\n\nBREAKING CHANGE: major change", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MAJOR + ) + + def test_no_increment_needed(self, get_increment): + commit_messages = [ + "docs: update documentation", + "style: format code", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_empty_commits(self, get_increment): + commit_messages = [] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_major_version_zero(self): + bump_rule = ConventionalCommitBumpRule() + + commit_messages = [ + "feat!: breaking change", + "BREAKING CHANGE: major change", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: bump_rule.get_increment(x, True) + ) + == VersionIncrement.MINOR + ) + + def test_mixed_commit_types(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "perf: improve performance", + "refactor: restructure code", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_commit_with_scope(self, get_increment): + commit_messages = [ + "feat(api): add new endpoint", + "fix(ui): fix button alignment", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + +class TestCustomBumpRule: + @pytest.fixture + def bump_pattern(self): + return r"^.*?\[(.*?)\].*$" + + @pytest.fixture + def bump_map(self): + return { + "MAJOR": VersionIncrement.MAJOR, + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + + @pytest.fixture + def bump_map_major_version_zero(self): + return { + "MAJOR": VersionIncrement.MINOR, # VersionIncrement.MAJOR becomes VersionIncrement.MINOR in version zero + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + + @pytest.fixture + 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, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", False) + == VersionIncrement.MAJOR + ) + + def test_minor_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MINOR]", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MINOR]", False) + == VersionIncrement.MINOR + ) + + def test_patch_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [PATCH]", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [PATCH]", False) + == VersionIncrement.PATCH + ) + + def test_major_version_zero(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", True) + == VersionIncrement.MINOR + ) + + 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): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_invalid_bump_map(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_invalid_bump_map_major_version_zero(self, bump_pattern, bump_map): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, bump_map, {}) + + def test_all_invalid(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", {}, {}) + + def test_none_values(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule(None, {}, {}) + + def test_empty_pattern_with_valid_maps(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_empty_maps_with_valid_pattern(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_complex_pattern(self): + pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" + bump_map = { + "MAJOR": VersionIncrement.MAJOR, + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + rule = CustomBumpRule(pattern, bump_map, bump_map) + + assert ( + rule.get_increment( + "feat: add new feature [MAJOR] [MINOR]", + False, + ) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) + == VersionIncrement.MINOR + ) + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat: add new feature [MAJOR]", + "fix: bug fix [PATCH]", + "docs: update readme [MINOR]", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == VersionIncrement.MAJOR + ) + + 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!)?:" + bump_map = { + "major": VersionIncrement.MAJOR, + "bang": VersionIncrement.MAJOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH, + } + bump_map_major_version_zero = { + "major": VersionIncrement.MINOR, + "bang": VersionIncrement.MINOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH, + } + rule = CustomBumpRule(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) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("major!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", False) + == VersionIncrement.PATCH + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", True) + == VersionIncrement.PATCH + ) + assert ( + rule.get_increment("minor: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("minor: add new feature", True) == VersionIncrement.MINOR + ) + assert rule.get_increment("patch: fix bug", False) == VersionIncrement.PATCH + assert rule.get_increment("patch: fix bug", True) == VersionIncrement.PATCH + + +class TestCustomBumpRuleWithDefault: + @pytest.fixture + def custom_bump_rule(self): + return CustomBumpRule( + BUMP_PATTERN, + VersionIncrement.safe_cast_dict(BUMP_MAP), + VersionIncrement.safe_cast_dict(BUMP_MAP_MAJOR_VERSION_ZERO), + ) + + def test_breaking_change_with_bang(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("feat!: breaking change", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", True) + == VersionIncrement.MINOR + ) + + def test_breaking_change_type(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", True) + == VersionIncrement.MINOR + ) + + def test_feat_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("feat: add new feature", True) + == VersionIncrement.MINOR + ) + + def test_fix_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("fix: fix bug", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: fix bug", True) + == VersionIncrement.PATCH + ) + + def test_refactor_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("refactor: restructure code", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor: restructure code", True) + == VersionIncrement.PATCH + ) + + def test_perf_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("perf: improve performance", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("perf: improve performance", True) + == VersionIncrement.PATCH + ) + + def test_commit_with_scope(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat(api): add new endpoint", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix(ui): fix button alignment", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor(core): restructure", False) + == VersionIncrement.PATCH + ) + + def test_no_match(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("docs: update documentation", 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 ( + custom_bump_rule.get_increment("build: update build config", False) is None + ) + assert custom_bump_rule.get_increment("ci: update CI pipeline", False) is None + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat!: breaking change", + "fix: bug fix", + "perf: improve performance", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == VersionIncrement.MAJOR + ) + + +class TestGetHighest: + def test_get_highest_with_major(self): + increments = [ + VersionIncrement.PATCH, + VersionIncrement.MINOR, + VersionIncrement.MAJOR, + ] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR + + def test_get_highest_with_minor(self): + increments = [VersionIncrement.PATCH, VersionIncrement.MINOR, None] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR + + def test_get_highest_with_patch(self): + increments = [VersionIncrement.PATCH, None, None] + assert VersionIncrement.get_highest(increments) == VersionIncrement.PATCH + + def test_get_highest_with_none(self): + increments = [None, None, None] + assert VersionIncrement.get_highest(increments) is None + + def test_get_highest_empty(self): + increments = [] + assert VersionIncrement.get_highest(increments) is None + + def test_get_highest_mixed_order(self): + increments = [ + VersionIncrement.MAJOR, + VersionIncrement.PATCH, + VersionIncrement.MINOR, + ] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR + + def test_get_highest_with_none_values(self): + increments = [None, VersionIncrement.MINOR, None, VersionIncrement.PATCH] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR + + +class TestSafeCast: + def test_safe_cast_valid_strings(self): + assert VersionIncrement.safe_cast("MAJOR") == VersionIncrement.MAJOR + assert VersionIncrement.safe_cast("MINOR") == VersionIncrement.MINOR + assert VersionIncrement.safe_cast("PATCH") == VersionIncrement.PATCH + + def test_safe_cast_invalid_strings(self): + assert VersionIncrement.safe_cast("invalid") is None + assert VersionIncrement.safe_cast("major") is None # case sensitive + assert VersionIncrement.safe_cast("") is None + + def test_safe_cast_non_string_values(self): + assert VersionIncrement.safe_cast(None) is None + assert VersionIncrement.safe_cast(1) is None + assert VersionIncrement.safe_cast(True) is None + assert VersionIncrement.safe_cast([]) is None + assert VersionIncrement.safe_cast({}) is None + assert ( + VersionIncrement.safe_cast(VersionIncrement.MAJOR) is None + ) # enum value itself diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index a983dad14..2ba7d7dc1 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -3,184 +3,188 @@ import pytest -from commitizen.version_schemes import Pep440, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Pep440, Prerelease, 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.3.1a0", None, "alpha", 0, None), "0.3.1a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1.dev1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0.dev1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1a1"), + (("0.3.0", VersionIncrement.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", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "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"), - (("1.0.0rc0", "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"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # 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", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.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"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("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", VersionIncrement.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"), - (("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.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1a0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("0.9.0", VersionIncrement.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", "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", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a0"), + (("3.3.4a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a1"), + (("3.3.4a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a0"), + (("3.4.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.4.0a1"), + (("3.4.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a2"), + (("3.4.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), + (("4.0.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "4.0.0a1"), + (("4.0.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "4.0.0a2"), + (("4.0.0a2", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a0"), + (("1.0.1a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a1"), + (("1.0.1a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a3"), + (("1.1.0a3", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), + (("2.0.0a0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a1"), + (("2.0.0a1", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a2"), + (("2.0.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a3"), + (("2.0.0a3", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a4"), + (("2.0.0a4", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a5"), + (("2.0.0a5", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0b1"), + (("2.0.0b0", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1a0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.0.1a0", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "1.1.0"), + (("1.1.0a0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0a0", VersionIncrement.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", VersionIncrement.MINOR, None, 0, None), "2.0.0"), + (("2.0.0a0", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("2.0.0a0", VersionIncrement.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"), (("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", "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.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + ( + ("3.1.4a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), + "3.1.4a1", + ), # UNEXPECTED! + (("3.1.4a0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.2.0a0"), + (("3.1.4a0", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", VersionIncrement.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.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", VersionIncrement.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", VersionIncrement.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", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.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 8785717a3..712d63052 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -3,121 +3,122 @@ import pytest -from commitizen.version_schemes import SemVer, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Prerelease, 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.3.1a0", None, "alpha", 0, None), "0.3.1-a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1-dev1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0-dev1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1-a1"), + (("0.3.0", VersionIncrement.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", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("1.0.0a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1.0.0a1", None, "alpha", 0, None), "1.0.0-a2"), - (("1.0.0a1", None, "alpha", 0, 1), "1.0.0-a2-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # 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", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1a0", None, "alpha", 1, None), "1.0.0-a1"), - (("1", None, "beta", 0, None), "1.0.0-b0"), - (("1", None, "beta", 1, None), "1.0.0-b1"), - (("1beta", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), - (("1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("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", VersionIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-a1"), - (("1.0.0a2", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0a2", None, "beta", 1, None), "1.0.0-b1"), - (("1.0.0beta1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), - (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("0.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-a0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("0.9.0", VersionIncrement.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", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", VersionIncrement.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.PATCH, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.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", VersionIncrement.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", VersionIncrement.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", VersionIncrement.MAJOR, Prerelease.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", VersionIncrement.MINOR, Prerelease.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", VersionIncrement.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 d18a058a7..be7795d03 100644 --- a/tests/test_version_scheme_semver2.py +++ b/tests/test_version_scheme_semver2.py @@ -3,84 +3,85 @@ import pytest -from commitizen.version_schemes import SemVer2, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Prerelease, 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.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.1-alpha.0", None, "alpha", 1, None), "0.3.1-alpha.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1-dev.1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0-dev.1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.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", VersionIncrement.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", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "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"), - (("1.0.0-rc.0", "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"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0-alpha.3.dev.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.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", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # 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", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "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.0.0-rc.1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.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", VersionIncrement.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"), - (("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.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-alpha.0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-alpha.0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("0.9.0", VersionIncrement.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"), ] 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):