diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1120176b9..a40195711e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: exclude: "poetry.lock" - repo: https://github.com/commitizen-tools/commitizen - rev: v3.15.0 # automatically updated by Commitizen + rev: v3.16.0 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/CHANGELOG.md b/CHANGELOG.md index c61279fe1e..045d308e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ +## v3.16.0 (2024-02-26) + +### Feat + +- **commands**: add bump --exact + +### Fix + +- **bump**: change --exact-increment to --increment-mode +- **bump**: only get and validate commits if increment is not provided +- Improve type annotations + ## v3.15.0 (2024-02-17) ### Feat diff --git a/commitizen/__version__.py b/commitizen/__version__.py index 11d9fee7b3..331093ae1a 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "3.15.0" +__version__ = "3.16.0" diff --git a/commitizen/bump.py b/commitizen/bump.py index 74d08381dc..f0e45e3432 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -4,18 +4,19 @@ import re from collections import OrderedDict from string import Template +from typing import cast from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message, encoding from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import GitCommit, smart_open -from commitizen.version_schemes import DEFAULT_SCHEME, Version, VersionScheme +from commitizen.version_schemes import DEFAULT_SCHEME, Increment, Version, VersionScheme VERSION_TYPES = [None, PATCH, MINOR, MAJOR] def find_increment( commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict -) -> str | None: +) -> Increment | None: if isinstance(increments_map, dict): increments_map = OrderedDict(increments_map) @@ -42,7 +43,7 @@ def find_increment( if increment == MAJOR: break - return increment + return cast(Increment, increment) def update_version_in_files( diff --git a/commitizen/cli.py b/commitizen/cli.py index 12e3aa6451..c25bd4f713 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -230,6 +230,19 @@ def __call__( "choices": ["MAJOR", "MINOR", "PATCH"], "type": str.upper, }, + { + "name": ["--increment-mode"], + "choices": ["linear", "exact"], + "default": "linear", + "help": ( + "set the method by which the new version is chosen. " + "'linear' (default) guesses the next version based on typical linear version progression, " + "such that bumping of a pre-release with lower precedence than the current pre-release " + "phase maintains the current phase of higher precedence. " + "'exact' applies the changes that have been specified (or determined from the commit log) " + "without interpretation, such that the increment and pre-release are always honored" + ), + }, { "name": ["--check-consistency", "-cc"], "help": ( diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 7eb9ead144..c29c4c35c5 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -26,7 +26,9 @@ from commitizen.providers import get_provider from commitizen.version_schemes import ( get_version_scheme, + Increment, InvalidVersion, + Prerelease, ) logger = getLogger("commitizen") @@ -50,6 +52,7 @@ def __init__(self, config: BaseConfig, arguments: dict): "tag_format", "prerelease", "increment", + "increment_mode", "bump_message", "gpg_sign", "annotated_tag", @@ -112,7 +115,7 @@ def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool is_initial = questionary.confirm("Is this the first tag created?").ask() return is_initial - def find_increment(self, commits: list[git.GitCommit]) -> str | None: + def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: # Update the bump map to ensure major version doesn't increment. is_major_version_zero: bool = self.bump_settings["major_version_zero"] # self.cz.bump_map = defaults.bump_map_major_version_zero @@ -132,7 +135,7 @@ def find_increment(self, commits: list[git.GitCommit]) -> str | None: ) return increment - def __call__(self): # noqa: C901 + def __call__(self) -> None: # noqa: C901 """Steps executed to bump.""" provider = get_provider(self.config) @@ -149,13 +152,14 @@ def __call__(self): # noqa: C901 dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] - increment: str | None = self.arguments["increment"] - prerelease: str | None = self.arguments["prerelease"] + increment: Increment | None = self.arguments["increment"] + prerelease: Prerelease | None = self.arguments["prerelease"] devrelease: int | None = self.arguments["devrelease"] is_files_only: bool | None = self.arguments["files_only"] - is_local_version: bool | None = self.arguments["local_version"] + is_local_version: bool = self.arguments["local_version"] manual_version = self.arguments["manual_version"] build_metadata = self.arguments["build_metadata"] + increment_mode: str = self.arguments["increment_mode"] if manual_version: if increment: @@ -205,21 +209,10 @@ def __call__(self): # noqa: C901 scheme=self.scheme, ) - is_initial = self.is_initial_tag(current_tag_version, is_yes) - if is_initial: - commits = git.get_commits() - else: - commits = git.get_commits(current_tag_version) - # If user specified changelog_to_stdout, they probably want the # changelog to be generated as well, this is the most intuitive solution self.changelog = self.changelog or bool(self.changelog_to_stdout) - # No commits, there is no need to create an empty tag. - # Unless we previously had a prerelease. - if not commits and not current_version.is_prerelease: - raise NoCommitsFoundError("[NO_COMMITS_FOUND]\n" "No new commits found.") - if manual_version: try: new_version = self.scheme(manual_version) @@ -230,6 +223,19 @@ def __call__(self): # noqa: C901 ) from exc else: if increment is None: + is_initial = self.is_initial_tag(current_tag_version, is_yes) + if is_initial: + commits = git.get_commits() + else: + commits = git.get_commits(current_tag_version) + + # No commits, there is no need to create an empty tag. + # Unless we previously had a prerelease. + if not commits and not current_version.is_prerelease: + raise NoCommitsFoundError( + "[NO_COMMITS_FOUND]\n" "No new commits found." + ) + increment = self.find_increment(commits) # It may happen that there are commits, but they are not eligible @@ -248,6 +254,7 @@ def __call__(self): # noqa: C901 devrelease=devrelease, is_local_version=is_local_version, build_metadata=build_metadata, + exact_increment=increment_mode == "exact", ) new_tag_version = bump.normalize_tag( @@ -349,6 +356,7 @@ def __call__(self): # noqa: C901 if is_files_only: raise ExpectedExit() + # FIXME: check if any changes have been staged c = git.commit(message, args=self._get_commit_args()) if self.retry and c.return_code != 0 and self.changelog: # Maybe pre-commit reformatted some files? Retry once @@ -404,7 +412,7 @@ def __call__(self): # noqa: C901 else: out.success("Done!") - def _get_commit_args(self): + def _get_commit_args(self) -> str: commit_args = ["-a"] if self.no_verify: commit_args.append("--no-verify") diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 0b04c4bc36..ec04fde1e7 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -4,7 +4,16 @@ import sys import warnings from itertools import zip_longest -from typing import TYPE_CHECKING, Any, ClassVar, Protocol, Type, cast, runtime_checkable +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Literal, + Protocol, + Type, + cast, + runtime_checkable, +) import importlib_metadata as metadata from packaging.version import InvalidVersion # noqa: F401: Rexpose the common exception @@ -28,6 +37,8 @@ from typing import Self +Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] +Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] DEFAULT_VERSION_PARSER = r"v?(?P([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?(\w+)?)" @@ -113,16 +124,23 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: str, - prerelease: str | None = None, + increment: Increment | None, + prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, is_local_version: bool = False, build_metadata: str | None = None, - force_bump: bool = False, + exact_increment: bool = False, ) -> Self: """ Based on the given increment, generate the next bumped version according to the version scheme + + Args: + increment: The component to increase + prerelease: The type of prerelease, if Any + is_local_version: Whether to increment the local version instead + exact_increment: Treat the increment and prerelease arguments explicitly. Disables logic + that attempts to deduce the correct increment when a prelease suffix is present. """ @@ -203,7 +221,7 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: str | None = None) -> str: + 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)) @@ -222,13 +240,13 @@ def increment_base(self, increment: str | None = None) -> str: def bump( self, - increment: str, - prerelease: str | None = None, + increment: Increment | None, + prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, is_local_version: bool = False, build_metadata: str | None = None, - force_bump: bool = False, + exact_increment: bool = False, ) -> Self: """Based on the given increment a proper semver will be generated. @@ -248,7 +266,7 @@ def bump( else: if not self.is_prerelease: base = self.increment_base(increment) - elif force_bump: + elif exact_increment: base = self.increment_base(increment) else: base = f"{self.major}.{self.minor}.{self.micro}" diff --git a/docs/bump.md b/docs/bump.md index 6045024dd4..9a968f7500 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -79,6 +79,12 @@ options: specify non-negative integer for dev. release --increment {MAJOR,MINOR,PATCH} manually specify the desired increment + --increment-mode + set the method by which the new version is chosen. 'linear' (default) guesses the next version based + on typical linear version progression, such that bumping of a pre-release with lower precedence than + the current pre-release phase maintains the current phase of higher precedence. 'exact' applies the + changes that have been specified (or determined from the commit log) without interpretation, such that + the increment and pre-release are always honored --check-consistency, -cc check consistency among versions defined in commitizen configuration and version_files --annotated-tag, -at create annotated tag instead of lightweight one @@ -139,9 +145,27 @@ by their precedence and showcase how a release might flow through a development - `1.1.0rc0` after bumping the release candidate - `1.1.0` next feature release -Also note that bumping pre-releases _maintains linearity_: bumping of a pre-release with lower precedence than -the current pre-release phase maintains the current phase of higher precedence. For example, if the current -version is `1.0.0b1` then bumping with `--prerelease alpha` will continue to bump the “beta” phase. +### `--increment-mode` + +By default, `--increment-mode` is set to `linear`, which ensures taht bumping pre-releases _maintains linearity_: +bumping of a pre-release with lower precedence than the current pre-release phase maintains the current phase of +higher precedence. For example, if the current version is `1.0.0b1` then bumping with `--prerelease alpha` will +continue to bump the “beta” phase. + +Setting `--increment-mode` to `exact` instructs `cz bump` to instead apply the +exact changes that have been specified with `--increment` or determined from the commit log. For example, +`--prerelease beta` will always result in a `b` tag, and `--increment PATCH` will always increase the patch component. + +Below are some examples that illustrate the difference in behavior: + +| Increment | Pre-release | Start Version | `--increment-mode=linear` | `--increment-mode=exact` | +|-----------|-------------|---------------|---------------------------|--------------------------| +| `MAJOR` | | `2.0.0b0` | `2.0.0` | `3.0.0` | +| `MINOR` | | `2.0.0b0` | `2.0.0` | `2.1.0` | +| `PATCH` | | `2.0.0b0` | `2.0.0` | `2.0.1` | +| `MAJOR` | `alpha` | `2.0.0b0` | `3.0.0a0` | `3.0.0a0` | +| `MINOR` | `alpha` | `2.0.0b0` | `2.0.0b1` | `2.1.0a0` | +| `PATCH` | `alpha` | `2.0.0b0` | `2.0.0b1` | `2.0.1a0` | ### `--check-consistency` diff --git a/poetry.lock b/poetry.lock index b05a5ab1f5..9f02b028b8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -744,13 +744,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp [[package]] name = "mkdocs-material" -version = "9.5.9" +version = "9.5.11" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.9-py3-none-any.whl", hash = "sha256:a5d62b73b3b74349e45472bfadc129c871dd2d4add68d84819580597b2f50d5d"}, - {file = "mkdocs_material-9.5.9.tar.gz", hash = "sha256:635df543c01c25c412d6c22991872267723737d5a2f062490f33b2da1c013c6d"}, + {file = "mkdocs_material-9.5.11-py3-none-any.whl", hash = "sha256:788ee0f3e036dca2dc20298d65e480297d348a44c9d7b2ee05c5262983e66072"}, + {file = "mkdocs_material-9.5.11.tar.gz", hash = "sha256:7af7f8af0dea16175558f3fb9245d26c83a17199baa5f157755e63d7437bf971"}, ] [package.dependencies] @@ -1048,13 +1048,13 @@ extra = ["pygments (>=2.12)"] [[package]] name = "pytest" -version = "8.0.0" +version = "8.0.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, - {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, + {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, + {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, ] [package.dependencies] @@ -1407,28 +1407,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.2.1" +version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, - {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, - {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, - {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, - {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, ] [[package]] @@ -1574,13 +1574,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index a8ef9705cf..3a2d3bf79f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.commitizen] -version = "3.15.0" +version = "3.16.0" tag_format = "v$version" version_files = [ "pyproject.toml:version", @@ -9,7 +9,7 @@ version_files = [ [tool.poetry] name = "commitizen" -version = "3.15.0" +version = "3.16.0" description = "Python commitizen client tool" authors = ["Santiago Fraire "] license = "MIT" diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 9a877c9ac0..b39271f284 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -314,6 +314,69 @@ def test_bump_command_prelease_increment(mocker: MockFixture): assert git.tag_exist("1.0.0a0") +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_command_prelease_exact_mode(mocker: MockFixture): + # PRERELEASE + create_file_and_commit("feat: location") + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0a0") + assert tag_exists is True + + # PRERELEASE + PATCH BUMP + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--increment-mode=exact", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0a1") + assert tag_exists is True + + # PRERELEASE + MINOR BUMP + # --increment-mode allows the minor version to bump, and restart the prerelease + create_file_and_commit("feat: location") + + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--increment-mode=exact", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.3.0a0") + assert tag_exists is True + + # PRERELEASE + MAJOR BUMP + # --increment-mode=exact allows the major version to bump, and restart the prerelease + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--increment=MAJOR", + "--increment-mode=exact", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("1.0.0a0") + assert tag_exists is True + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_bump_on_git_with_hooks_no_verify_disabled(mocker: MockFixture): """Bump commit without --no-verify""" diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index ac99450652..6b1f621cb8 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -115,6 +115,9 @@ (("2.0.0a4", "PATCH", "alpha", 0, None), "2.0.0a5"), (("2.0.0a5", "MAJOR", "alpha", 0, None), "2.0.0a6"), # + (("2.0.0b0", "MINOR", "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.0b1"), + # (("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"), @@ -141,27 +144,43 @@ (("3.1.4a0", "MAJOR", "alpha", 0, None), "4.0.0a0"), ] - -# test driven development -sortability = [ - "0.10.0a0", - "0.1.1", - "0.1.2", - "2.1.1", - "3.0.0", - "0.9.1a0", - "1.0.0a1", - "1.0.0b1", - "1.0.0a1", - "1.0.0a2.dev1", - "1.0.0rc2", - "1.0.0a3.dev0", - "1.0.0a2.dev0", - "1.0.0a3.dev1", - "1.0.0a2.dev0", - "1.0.0b0", - "1.0.0rc0", - "1.0.0rc1", +excact_cases = [ + (("1.0.0", "PATCH", None, 0, None), "1.0.1"), + (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + # with exact_increment=False: "1.0.0b0" + (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1b0"), + # with exact_increment=False: "1.0.0b1" + (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1b0"), + # with exact_increment=False: "1.0.0rc0" + (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1rc0"), + # with exact_increment=False: "1.0.0-rc1" + (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1rc0"), + # with exact_increment=False: "1.0.0rc1-dev1" + (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1rc0.dev1"), + # with exact_increment=False: "1.0.0b0" + (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0b0"), + # with exact_increment=False: "1.0.0b1" + (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0b0"), + # with exact_increment=False: "1.0.0b1" + (("1.0.0b0", "MINOR", "alpha", 0, None), "1.1.0a0"), + # with exact_increment=False: "1.0.0rc0" + (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0rc0"), + # with exact_increment=False: "1.0.0rc1" + (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0rc0"), + # with exact_increment=False: "1.0.0rc1-dev1" + (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0rc0.dev1"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + # same with exact_increment=False + (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0a0"), + # with exact_increment=False: "2.0.0b1" + (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0a0"), + # with exact_increment=False: "2.0.0b1" + (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1a0"), ] @@ -194,6 +213,27 @@ def test_bump_pep440_version(test_input, expected): ) +@pytest.mark.parametrize("test_input, expected", excact_cases) +def test_bump_pep440_version_force(test_input, expected): + current_version = test_input[0] + increment = test_input[1] + prerelease = test_input[2] + prerelease_offset = test_input[3] + devrelease = test_input[4] + assert ( + str( + Pep440(current_version).bump( + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + exact_increment=True, + ) + ) + == expected + ) + + @pytest.mark.parametrize("test_input,expected", local_versions) def test_bump_pep440_version_local(test_input, expected): current_version = test_input[0] diff --git a/tests/test_version_scheme_semver.py b/tests/test_version_scheme_semver.py index a0d6e14b50..71d5e5876c 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -83,6 +83,43 @@ (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), ] +excact_cases = [ + (("1.0.0", "PATCH", None, 0, None), "1.0.1"), + (("1.0.0", "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"), + # with exact_increment=False: "1.0.0-b1" + (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1-b0"), + # with exact_increment=False: "1.0.0-rc0" + (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1-rc0"), + # with exact_increment=False: "1.0.0-rc1" + (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1-rc0"), + # with exact_increment=False: "1.0.0-rc1-dev1" + (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1-rc0-dev1"), + # with exact_increment=False: "1.0.0-b0" + (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0-b0"), + # with exact_increment=False: "1.0.0-b1" + (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0-b0"), + # with exact_increment=False: "1.0.0-rc0" + (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0-rc0"), + # with exact_increment=False: "1.0.0-rc1" + (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0-rc0"), + # with exact_increment=False: "1.0.0-rc1-dev1" + (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0-rc0-dev1"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + # with exact_increment=False: "2.0.0" + (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + # same with exact_increment=False + (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0-a0"), + # with exact_increment=False: "2.0.0b1" + (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0-a0"), + # with exact_increment=False: "2.0.0b1" + (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1-a0"), +] + @pytest.mark.parametrize( "test_input, expected", @@ -107,6 +144,27 @@ def test_bump_semver_version(test_input, expected): ) +@pytest.mark.parametrize("test_input, expected", excact_cases) +def test_bump_semver_version_force(test_input, expected): + current_version = test_input[0] + increment = test_input[1] + prerelease = test_input[2] + prerelease_offset = test_input[3] + devrelease = test_input[4] + assert ( + str( + SemVer(current_version).bump( + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + exact_increment=True, + ) + ) + == expected + ) + + @pytest.mark.parametrize("test_input,expected", local_versions) def test_bump_semver_version_local(test_input, expected): current_version = test_input[0]