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 2a84483c9..9d4fbb854 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,8 +163,8 @@ def __call__(self) -> None: except TypeError: raise NoVersionSpecifiedError() - increment = self.arguments["increment"] - prerelease = self.arguments["prerelease"] + increment = VersionIncrement.safe_cast(self.arguments["increment"]) + prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) devrelease = self.arguments["devrelease"] is_local_version = self.arguments["local_version"] manual_version = self.arguments["manual_version"] @@ -275,7 +269,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 e9f99c551..9e982f265 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[return-value] 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/docs/commands/bump.md b/docs/commands/bump.md index 8219cc346..112a4afba 100644 --- a/docs/commands/bump.md +++ b/docs/commands/bump.md @@ -99,7 +99,7 @@ cz bump --changelog ### `--prerelease` The bump is a pre-release bump, meaning that in addition to a possible version bump the new version receives a -pre-release segment compatible with the bump’s version scheme, where the segment consist of a _phase_ and a +pre-release segment compatible with the bump's version scheme, where the segment consist of a _phase_ and a non-negative number. Supported options for `--prerelease` are the following phase names `alpha`, `beta`, or `rc` (release candidate). For more details, refer to the [Python Packaging User Guide](https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases). @@ -658,7 +658,95 @@ version_scheme = "semver" ## Custom bump -Read the [customizing section](../customization.md). +You can create custom bump rules to define how version numbers should be incremented based on your commit messages. This is done by configuring three main components: + +### Bump Pattern + +The `bump_pattern` is a regex pattern string used to match commit messages. It defines the structure of your commit messages and captures named groups for different types of changes. The captured groups will be used as keys in the bump map. + +Example: +```python +# Simple pattern matching major/minor/patch types +r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + +# Conventional commits style pattern +r"^((?Pfeat)|(?Pfix|perf|refactor)|(?PBREAKING[\-\ ]CHANGE))(?P\(.+\))?(?P!)?:" +``` + +### Bump Map + +The `bump_map` defines how different commit types map to version increments. The keys in this dictionary must match the named capture groups from your pattern. The values are the version increment types: + +- `"MAJOR"` +- `"MINOR"` +- `"PATCH"` + +Example for conventional commits: +```python +{ + # Breaking changes (either by type or bang) + "breaking": "MAJOR", # When type is "BREAKING CHANGE" + "bang": "MAJOR", # When commit ends with ! (e.g., feat!: new feature) + # New features + "minor": "MINOR", + # Bug fixes and improvements + "patch": "PATCH", +} +``` + +Or using regex patterns: +```python +{ + (r"^.+!$", "MAJOR"), + (r"^BREAKING[\-\ ]CHANGE", "MAJOR"), + (r"^feat", "MINOR"), + (r"^fix", "PATCH"), + (r"^refactor", "PATCH"), + (r"^perf", "PATCH"), +} +``` + +### Major Version Zero Map + +The `bump_map_major_version_zero` allows you to define different versioning behavior when your project is in initial development (major version is 0). This is useful for following semantic versioning principles where breaking changes in 0.x.x versions don't require a major version bump. + +Example for conventional commits during initial development: +```python +{ + # Breaking changes (either by type or bang) + "breaking": "MINOR", # When type is "BREAKING CHANGE" + "bang": "MINOR", # When commit ends with ! (e.g., feat!: new feature) + # New features + "minor": "MINOR", + # Bug fixes and improvements + "patch": "PATCH", +} +``` + +Or with regex patterns: +```python +{ + (r"^.+!$", "MINOR"), + (r"^BREAKING[\-\ ]CHANGE", "MINOR"), + (r"^feat", "MINOR"), + (r"^fix", "PATCH"), + (r"^refactor", "PATCH"), + (r"^perf", "PATCH"), +} +``` + +This configuration will handle commit messages like: + +- `BREAKING CHANGE: remove deprecated API` → MAJOR (or MINOR in major version zero) +- `feat!: add new API` → MAJOR (or MINOR in major version zero) +- `feat: add new feature` → MINOR +- `fix: fix bug` → PATCH +- `perf: improve performance` → PATCH +- `refactor: restructure code` → PATCH + +For more details on implementing custom bump rules, see the [customization guide](../customization.md). + + [pep440]: https://www.python.org/dev/peps/pep-0440/ [semver]: https://semver.org/ diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 59297b172..94fb136f0 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):