diff --git a/commitizen/bump.py b/commitizen/bump.py index cb572d361..030c8f1e5 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from collections.abc import Generator, Iterable - from commitizen.version_schemes import Increment, Version + from commitizen.version_schemes import Increment, VersionProtocol VERSION_TYPES = [None, PATCH, MINOR, MAJOR] @@ -131,8 +131,8 @@ def _resolve_files_and_regexes( def create_commit_message( - current_version: Version | str, - new_version: Version | str, + current_version: VersionProtocol | str, + new_version: VersionProtocol | str, message_template: str | None = None, ) -> str: if message_template is None: diff --git a/commitizen/cli.py b/commitizen/cli.py index 79988fb5c..85674e8c4 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -21,6 +21,7 @@ InvalidCommandArgumentError, NoCommandFoundError, ) +from commitizen.version_increment import VersionIncrement logger = logging.getLogger(__name__) @@ -542,13 +543,19 @@ def __call__( }, { "name": ["--major"], - "help": "Output just the major version. Must be used with --project or --verbose.", + "help": ( + "Output just the major version. Must be used with MANUAL_VERSION, " + "--project, or --verbose." + ), "action": "store_true", "exclusive_group": "group2", }, { "name": ["--minor"], - "help": "Output just the minor version. Must be used with --project or --verbose.", + "help": ( + "Output just the minor version. Must be used with MANUAL_VERSION, " + "--project, or --verbose." + ), "action": "store_true", "exclusive_group": "group2", }, @@ -558,6 +565,33 @@ def __call__( "action": "store_true", "exclusive_group": "group2", }, + { + "name": ["--patch"], + "help": ( + "Output the patch version only. Must be used with MANUAL_VERSION, " + "--project, or --verbose." + ), + "action": "store_true", + "exclusive_group": "group2", + }, + { + "name": ["--next"], + "help": "Output the next version.", + "type": str, + "nargs": "?", + "default": None, + "const": "USE_GIT_COMMITS", + "choices": ["USE_GIT_COMMITS"] + + [str(increment) for increment in VersionIncrement], + "exclusive_group": "group2", + }, + { + "name": "manual_version", + "type": str, + "nargs": "?", + "help": "Use the version provided instead of the version from the project. Can be used to test the selected version scheme.", + "metavar": "MANUAL_VERSION", + }, ], }, ], diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index d0f4b686a..ad33d884b 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -17,7 +17,11 @@ NoAnswersError, ) from commitizen.git import get_latest_tag_name, get_tag_names, smart_open -from commitizen.version_schemes import KNOWN_SCHEMES, Version, get_version_scheme +from commitizen.version_schemes import ( + KNOWN_SCHEMES, + VersionProtocol, + get_version_scheme, +) if TYPE_CHECKING: from commitizen.config import ( @@ -265,7 +269,7 @@ def _ask_version_scheme(self) -> str: ).unsafe_ask() return scheme - def _ask_major_version_zero(self, version: Version) -> bool: + def _ask_major_version_zero(self, version: VersionProtocol) -> bool: """Ask for setting: major_version_zero""" if version.major > 0: return False @@ -323,7 +327,7 @@ def _write_config_to_file( cz_name: str, version_provider: str, version_scheme: str, - version: Version, + version: VersionProtocol, tag_format: str, update_changelog_on_bump: bool, major_version_zero: bool, diff --git a/commitizen/commands/version.py b/commitizen/commands/version.py index c8a76fe27..d652b622e 100644 --- a/commitizen/commands/version.py +++ b/commitizen/commands/version.py @@ -2,22 +2,32 @@ import sys from typing import TypedDict +from packaging.version import InvalidVersion + from commitizen import out from commitizen.__version__ import __version__ from commitizen.config import BaseConfig from commitizen.exceptions import NoVersionSpecifiedError, VersionSchemeUnknown from commitizen.providers import get_provider from commitizen.tags import TagRules -from commitizen.version_schemes import get_version_scheme +from commitizen.version_increment import VersionIncrement +from commitizen.version_schemes import Increment, get_version_scheme class VersionArgs(TypedDict, total=False): + manual_version: str | None + next: str | None + + # Exclusive groups 1 commitizen: bool report: bool project: bool verbose: bool + + # Exclusive groups 2 major: bool minor: bool + patch: bool tag: bool @@ -43,40 +53,82 @@ def __call__(self) -> None: if self.arguments.get("verbose"): out.write(f"Installed Commitizen Version: {__version__}") - if not self.arguments.get("commitizen") and ( - self.arguments.get("project") or self.arguments.get("verbose") + if self.arguments.get("commitizen"): + out.write(__version__) + return + + if ( + self.arguments.get("project") + or self.arguments.get("verbose") + or self.arguments.get("next") + or self.arguments.get("manual_version") ): + version_str = self.arguments.get("manual_version") + if version_str is None: + try: + version_str = get_provider(self.config).get_version() + except NoVersionSpecifiedError: + out.error("No project information in this project.") + return try: - version = get_provider(self.config).get_version() - except NoVersionSpecifiedError: - out.error("No project information in this project.") - return - try: - version_scheme = get_version_scheme(self.config.settings)(version) + scheme_factory = get_version_scheme(self.config.settings) except VersionSchemeUnknown: out.error("Unknown version scheme.") return + try: + version = scheme_factory(version_str) + except InvalidVersion: + out.error(f"Invalid version: '{version_str}'") + return + + if next_increment_str := self.arguments.get("next"): + if next_increment_str == "USE_GIT_COMMITS": + out.error("--next USE_GIT_COMMITS is not implemented yet.") + return + + next_increment = VersionIncrement.from_value(next_increment_str) + increment: Increment | None + if next_increment == VersionIncrement.NONE: + increment = None + elif next_increment == VersionIncrement.PATCH: + increment = "PATCH" + elif next_increment == VersionIncrement.MINOR: + increment = "MINOR" + else: + increment = "MAJOR" + version = version.bump(increment=increment) + if self.arguments.get("major"): - version = f"{version_scheme.major}" - elif self.arguments.get("minor"): - version = f"{version_scheme.minor}" - elif self.arguments.get("tag"): + out.write(version.major) + return + if self.arguments.get("minor"): + out.write(version.minor) + return + if self.arguments.get("patch"): + out.write(version.micro) + return + + display_version: str + if self.arguments.get("tag"): tag_rules = TagRules.from_settings(self.config.settings) - version = tag_rules.normalize_tag(version_scheme) + display_version = tag_rules.normalize_tag(version) + else: + display_version = str(version) out.write( - f"Project Version: {version}" + f"Project Version: {display_version}" if self.arguments.get("verbose") - else version + else display_version ) return - if self.arguments.get("major") or self.arguments.get("minor"): - out.error( - "Major or minor version can only be used with --project or --verbose." - ) - return + for argument in ("major", "minor", "patch"): + if self.arguments.get(argument): + out.error( + f"{argument} can only be used with MANUAL_VERSION, --project or --verbose." + ) + return if self.arguments.get("tag"): out.error("Tag can only be used with --project or --verbose.") diff --git a/commitizen/out.py b/commitizen/out.py index 1bbfe4329..cdc80cf52 100644 --- a/commitizen/out.py +++ b/commitizen/out.py @@ -9,35 +9,35 @@ sys.stdout.reconfigure(encoding="utf-8") -def write(value: str, *args: object) -> None: +def write(value: object, *args: object) -> None: """Intended to be used when value is multiline.""" print(value, *args) -def line(value: str, *args: object, **kwargs: Any) -> None: +def line(value: object, *args: object, **kwargs: Any) -> None: """Wrapper in case I want to do something different later.""" print(value, *args, **kwargs) -def error(value: str) -> None: - message = colored(value, "red") +def error(value: object) -> None: + message = colored(str(value), "red") line(message, file=sys.stderr) -def success(value: str) -> None: - message = colored(value, "green") +def success(value: object) -> None: + message = colored(str(value), "green") line(message) -def info(value: str) -> None: - message = colored(value, "blue") +def info(value: object) -> None: + message = colored(str(value), "blue") line(message) -def diagnostic(value: str) -> None: +def diagnostic(value: object) -> None: line(value, file=sys.stderr) -def warn(value: str) -> None: - message = colored(value, "magenta") +def warn(value: object) -> None: + message = colored(str(value), "magenta") line(message, file=sys.stderr) diff --git a/commitizen/tags.py b/commitizen/tags.py index 11b9899be..b3bcbe7a0 100644 --- a/commitizen/tags.py +++ b/commitizen/tags.py @@ -14,7 +14,7 @@ from commitizen.version_schemes import ( DEFAULT_SCHEME, InvalidVersion, - Version, + VersionProtocol, VersionScheme, get_version_scheme, ) @@ -23,8 +23,6 @@ import sys from collections.abc import Iterable, Sequence - from commitizen.version_schemes import VersionScheme - # Self is Python 3.11+ but backported in typing-extensions if sys.version_info < (3, 11): from typing_extensions import Self @@ -75,7 +73,7 @@ class TagRules: assert not rules.is_version_tag("warn1.0.0", warn=True) # Does warn assert rules.search_version("# My v1.0.0 version").version == "1.0.0" - assert rules.extract_version("v1.0.0") == Version("1.0.0") + assert rules.extract_version("v1.0.0") == rules.scheme("1.0.0") try: assert rules.extract_version("not-a-v1.0.0") except InvalidVersion: @@ -145,7 +143,7 @@ def get_version_tags( """Filter in version tags and warn on unexpected tags""" return [tag for tag in tags if self.is_version_tag(tag, warn)] - def extract_version(self, tag: GitTag) -> Version: + def extract_version(self, tag: GitTag) -> VersionProtocol: """ Extract a version from the tag as defined in tag formats. @@ -195,7 +193,7 @@ def search_version(self, text: str, last: bool = False) -> VersionTag | None: return VersionTag(version, match.group(0)) def normalize_tag( - self, version: Version | str, tag_format: str | None = None + self, version: VersionProtocol | str, tag_format: str | None = None ) -> str: """ The tag and the software version might be different. @@ -225,7 +223,7 @@ def normalize_tag( ) def find_tag_for( - self, tags: Iterable[GitTag], version: Version | str + self, tags: Iterable[GitTag], version: VersionProtocol | str ) -> GitTag | None: """Find the first matching tag for a given version.""" version = self.scheme(version) if isinstance(version, str) else version @@ -234,7 +232,7 @@ def find_tag_for( # If the requested version is incomplete (e.g., "1.2"), try to find the latest # matching tag that shares the provided prefix. if len(release) < 3: - matching_versions: list[tuple[Version, GitTag]] = [] + matching_versions: list[tuple[VersionProtocol, GitTag]] = [] for tag in tags: try: tag_version = self.extract_version(tag) diff --git a/commitizen/version_increment.py b/commitizen/version_increment.py new file mode 100644 index 000000000..9320fe8f9 --- /dev/null +++ b/commitizen/version_increment.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from enum import IntEnum + + +class VersionIncrement(IntEnum): + """Semantic versioning bump increments. + + IntEnum keeps a total order compatible with NONE < PATCH < MINOR < MAJOR + for comparisons across the codebase. + + - NONE: no bump (docs-only / style commits, etc.) + - PATCH: backwards-compatible bug fixes + - MINOR: backwards-compatible features + - MAJOR: incompatible API changes + """ + + NONE = 0 + PATCH = 1 + MINOR = 2 + MAJOR = 3 + + def __str__(self) -> str: + return self.name + + @classmethod + def from_value(cls, value: object) -> VersionIncrement: + if not isinstance(value, str): + return VersionIncrement.NONE + try: + return cls[value] + except KeyError: + return VersionIncrement.NONE diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 1f17e9087..90df07996 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -10,6 +10,7 @@ ClassVar, Literal, Protocol, + TypeAlias, cast, runtime_checkable, ) @@ -22,7 +23,6 @@ if TYPE_CHECKING: import sys - from typing import TypeAlias # Self is Python 3.11+ but backported in typing-extensions if sys.version_info < (3, 11): @@ -31,7 +31,7 @@ from typing import Self -Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] +Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] # TODO: deprecate Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] _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+)?)" @@ -140,7 +140,7 @@ def bump( """ -# With PEP 440 and SemVer semantic, Scheme is the type, Version is an instance +# With PEP 440 and SemVer semantics, a scheme is the class; a version is an instance. Version: TypeAlias = VersionProtocol VersionScheme: TypeAlias = type[VersionProtocol] @@ -422,6 +422,10 @@ def get_version_scheme(settings: Settings, name: str | None = None) -> VersionSc raise VersionSchemeUnknown(f'Version scheme "{name}" unknown.') scheme = cast("VersionScheme", ep.load()) + # `VersionProtocol` is a `@runtime_checkable` Protocol with properties, so + # `issubclass(scheme, VersionProtocol)` is not supported. The historical + # `isinstance(scheme, VersionProtocol)` check is only meaningful for instances; + # for loaded classes it is effectively a no-op for valid schemes. if not isinstance(scheme, VersionProtocol): warnings.warn(f"Version scheme {name} does not implement the VersionProtocol") diff --git a/docs/commands/version.md b/docs/commands/version.md index 4d2e6a032..198df5b48 100644 --- a/docs/commands/version.md +++ b/docs/commands/version.md @@ -1,5 +1,27 @@ -Get the version of the installed Commitizen or the current project (default: installed commitizen) +Get the version of the installed Commitizen or the current project (default: installed commitizen). ## Usage ![cz version --help](../images/cli_help/cz_version___help.svg) + +## Project version and scheme + +- **`cz version --project`** prints the version from your configured [version provider](../config/version_provider.md). +- **`cz version MANUAL_VERSION`** (optional positional) uses that string instead of the provider, so you can try how your configured scheme parses and formats it. + +## Components and next version + +- **`--major`**, **`--minor`**, **`--patch`**: print only that component of the (possibly manual) project version. Requires `--project`, `--verbose`, or a manual version. +- **`--next` `[MAJOR|MINOR|PATCH|NONE]`**: print the version after applying that bump to the current project or manual version. `NONE` leaves the version unchanged. +- **`--tag`**: print the version formatted with your `tag_format` (requires `--project` or `--verbose`). + +`--next USE_GIT_COMMITS` is reserved for a future feature (derive the bump from git history) and is not implemented yet. + +## Examples + +```bash +cz version --project +cz version 2.0.0 --next MAJOR +cz version --project --major +cz version --verbose +``` diff --git a/docs/images/cli_help/cz_version___help.svg b/docs/images/cli_help/cz_version___help.svg index 20331e097..e3b9d5576 100644 --- a/docs/images/cli_help/cz_version___help.svg +++ b/docs/images/cli_help/cz_version___help.svg @@ -1,4 +1,4 @@ - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - $ cz version --help -usage: cz version [-h][-r | -p | -c | -v][--major | --minor | --tag] - -Get the version of the installed commitizen or the current project (default: -installed commitizen) - -options: -  -h, --help        show this help message and exit -  -r, --report      Output the system information for reporting bugs. -  -p, --project     Output the version of the current project. -  -c, --commitizen  Output the version of the installed commitizen. -  -v, --verbose     Output the version of both the installed commitizen and -                    the current project. -  --major           Output just the major version. Must be used with --project -                    or --verbose. -  --minor           Output just the minor version. Must be used with --project -                    or --verbose. -  --tag             get the version with tag prefix. Need to be used with -                    --project or --verbose. - + + $ cz version --help + diff --git a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_10_version_.txt b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_10_version_.txt index 5085d0fd3..824f6e9fc 100644 --- a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_10_version_.txt +++ b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_10_version_.txt @@ -1,18 +1,29 @@ -usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag] +usage: cz version [-h] [-r | -p | -c | -v] + [--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]] + [MANUAL_VERSION] Get the version of the installed commitizen or the current project (default: installed commitizen) +positional arguments: + MANUAL_VERSION Use the version provided instead of the version from + the project. Can be used to test the selected version + scheme. + options: - -h, --help show this help message and exit - -r, --report Output the system information for reporting bugs. - -p, --project Output the version of the current project. - -c, --commitizen Output the version of the installed commitizen. - -v, --verbose Output the version of both the installed commitizen and - the current project. - --major Output just the major version. Must be used with --project - or --verbose. - --minor Output just the minor version. Must be used with --project - or --verbose. - --tag get the version with tag prefix. Need to be used with - --project or --verbose. + -h, --help show this help message and exit + -r, --report Output the system information for reporting bugs. + -p, --project Output the version of the current project. + -c, --commitizen Output the version of the installed commitizen. + -v, --verbose Output the version of both the installed commitizen + and the current project. + --major Output just the major version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --minor Output just the minor version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --tag get the version with tag prefix. Need to be used with + --project or --verbose. + --patch Output the patch version only. Must be used with + MANUAL_VERSION, --project, or --verbose. + --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}] + Output the next version. diff --git a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_11_version_.txt b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_11_version_.txt index 5085d0fd3..824f6e9fc 100644 --- a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_11_version_.txt +++ b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_11_version_.txt @@ -1,18 +1,29 @@ -usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag] +usage: cz version [-h] [-r | -p | -c | -v] + [--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]] + [MANUAL_VERSION] Get the version of the installed commitizen or the current project (default: installed commitizen) +positional arguments: + MANUAL_VERSION Use the version provided instead of the version from + the project. Can be used to test the selected version + scheme. + options: - -h, --help show this help message and exit - -r, --report Output the system information for reporting bugs. - -p, --project Output the version of the current project. - -c, --commitizen Output the version of the installed commitizen. - -v, --verbose Output the version of both the installed commitizen and - the current project. - --major Output just the major version. Must be used with --project - or --verbose. - --minor Output just the minor version. Must be used with --project - or --verbose. - --tag get the version with tag prefix. Need to be used with - --project or --verbose. + -h, --help show this help message and exit + -r, --report Output the system information for reporting bugs. + -p, --project Output the version of the current project. + -c, --commitizen Output the version of the installed commitizen. + -v, --verbose Output the version of both the installed commitizen + and the current project. + --major Output just the major version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --minor Output just the minor version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --tag get the version with tag prefix. Need to be used with + --project or --verbose. + --patch Output the patch version only. Must be used with + MANUAL_VERSION, --project, or --verbose. + --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}] + Output the next version. diff --git a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_12_version_.txt b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_12_version_.txt index 5085d0fd3..824f6e9fc 100644 --- a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_12_version_.txt +++ b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_12_version_.txt @@ -1,18 +1,29 @@ -usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag] +usage: cz version [-h] [-r | -p | -c | -v] + [--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]] + [MANUAL_VERSION] Get the version of the installed commitizen or the current project (default: installed commitizen) +positional arguments: + MANUAL_VERSION Use the version provided instead of the version from + the project. Can be used to test the selected version + scheme. + options: - -h, --help show this help message and exit - -r, --report Output the system information for reporting bugs. - -p, --project Output the version of the current project. - -c, --commitizen Output the version of the installed commitizen. - -v, --verbose Output the version of both the installed commitizen and - the current project. - --major Output just the major version. Must be used with --project - or --verbose. - --minor Output just the minor version. Must be used with --project - or --verbose. - --tag get the version with tag prefix. Need to be used with - --project or --verbose. + -h, --help show this help message and exit + -r, --report Output the system information for reporting bugs. + -p, --project Output the version of the current project. + -c, --commitizen Output the version of the installed commitizen. + -v, --verbose Output the version of both the installed commitizen + and the current project. + --major Output just the major version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --minor Output just the minor version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --tag get the version with tag prefix. Need to be used with + --project or --verbose. + --patch Output the patch version only. Must be used with + MANUAL_VERSION, --project, or --verbose. + --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}] + Output the next version. diff --git a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_13_version_.txt b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_13_version_.txt index 5085d0fd3..51c985679 100644 --- a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_13_version_.txt +++ b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_13_version_.txt @@ -1,18 +1,29 @@ -usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag] +usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag | + --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]] + [MANUAL_VERSION] Get the version of the installed commitizen or the current project (default: installed commitizen) +positional arguments: + MANUAL_VERSION Use the version provided instead of the version from + the project. Can be used to test the selected version + scheme. + options: - -h, --help show this help message and exit - -r, --report Output the system information for reporting bugs. - -p, --project Output the version of the current project. - -c, --commitizen Output the version of the installed commitizen. - -v, --verbose Output the version of both the installed commitizen and - the current project. - --major Output just the major version. Must be used with --project - or --verbose. - --minor Output just the minor version. Must be used with --project - or --verbose. - --tag get the version with tag prefix. Need to be used with - --project or --verbose. + -h, --help show this help message and exit + -r, --report Output the system information for reporting bugs. + -p, --project Output the version of the current project. + -c, --commitizen Output the version of the installed commitizen. + -v, --verbose Output the version of both the installed commitizen + and the current project. + --major Output just the major version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --minor Output just the minor version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --tag get the version with tag prefix. Need to be used with + --project or --verbose. + --patch Output the patch version only. Must be used with + MANUAL_VERSION, --project, or --verbose. + --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}] + Output the next version. diff --git a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_14_version_.txt b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_14_version_.txt index 5085d0fd3..51c985679 100644 --- a/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_14_version_.txt +++ b/tests/commands/test_common_command/test_command_shows_description_when_use_help_option_py_3_14_version_.txt @@ -1,18 +1,29 @@ -usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag] +usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag | + --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]] + [MANUAL_VERSION] Get the version of the installed commitizen or the current project (default: installed commitizen) +positional arguments: + MANUAL_VERSION Use the version provided instead of the version from + the project. Can be used to test the selected version + scheme. + options: - -h, --help show this help message and exit - -r, --report Output the system information for reporting bugs. - -p, --project Output the version of the current project. - -c, --commitizen Output the version of the installed commitizen. - -v, --verbose Output the version of both the installed commitizen and - the current project. - --major Output just the major version. Must be used with --project - or --verbose. - --minor Output just the minor version. Must be used with --project - or --verbose. - --tag get the version with tag prefix. Need to be used with - --project or --verbose. + -h, --help show this help message and exit + -r, --report Output the system information for reporting bugs. + -p, --project Output the version of the current project. + -c, --commitizen Output the version of the installed commitizen. + -v, --verbose Output the version of both the installed commitizen + and the current project. + --major Output just the major version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --minor Output just the minor version. Must be used with + MANUAL_VERSION, --project, or --verbose. + --tag get the version with tag prefix. Need to be used with + --project or --verbose. + --patch Output the patch version only. Must be used with + MANUAL_VERSION, --project, or --verbose. + --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}] + Output the next version. diff --git a/tests/commands/test_version_command.py b/tests/commands/test_version_command.py index dce710ac8..c1c29ac35 100644 --- a/tests/commands/test_version_command.py +++ b/tests/commands/test_version_command.py @@ -20,13 +20,13 @@ def test_version_for_showing_project_version_error(config, capsys): def test_version_for_showing_project_version(config, capsys): - config.settings["version"] = "v0.0.1" + config.settings["version"] = "0.0.1" commands.Version( config, {"project": True}, )() captured = capsys.readouterr() - assert "v0.0.1" in captured.out + assert "0.0.1" in captured.out @pytest.mark.parametrize("project", [True, False]) @@ -50,14 +50,14 @@ def test_version_for_showing_both_versions_no_project(config, capsys): def test_version_for_showing_both_versions(config, capsys): - config.settings["version"] = "v0.0.1" + config.settings["version"] = "0.0.1" commands.Version( config, {"verbose": True}, )() captured = capsys.readouterr() expected_out = ( - f"Installed Commitizen Version: {__version__}\nProject Version: v0.0.1" + f"Installed Commitizen Version: {__version__}\nProject Version: 0.0.1" ) assert expected_out in captured.out @@ -147,7 +147,7 @@ def test_version_just_minor(config, capsys, version: str, expected_version: str) assert expected_version == captured.out -@pytest.mark.parametrize("argument", ["major", "minor"]) +@pytest.mark.parametrize("argument", ["major", "minor", "patch"]) def test_version_just_major_error_no_project(config, capsys, argument: str): commands.Version( config, @@ -158,8 +158,7 @@ def test_version_just_major_error_no_project(config, capsys, argument: str): captured = capsys.readouterr() assert not captured.out assert ( - "Major or minor version can only be used with --project or --verbose." - in captured.err + "can only be used with MANUAL_VERSION, --project or --verbose." in captured.err ) @@ -200,3 +199,102 @@ def test_version_tag_without_project_error(config, capsys): captured = capsys.readouterr() assert not captured.out assert "Tag can only be used with --project or --verbose." in captured.err + + +@pytest.mark.parametrize( + ("next_increment", "current_version", "expected_version"), + [ + ("MAJOR", "1.1.0", "2.0.0"), + ("MAJOR", "1.0.0", "2.0.0"), + ("MAJOR", "0.0.1", "1.0.0"), + ("MINOR", "1.1.0", "1.2.0"), + ("MINOR", "1.0.0", "1.1.0"), + ("MINOR", "0.0.1", "0.1.0"), + ("PATCH", "1.1.0", "1.1.1"), + ("PATCH", "1.0.0", "1.0.1"), + ("PATCH", "0.0.1", "0.0.2"), + ("NONE", "1.0.0", "1.0.0"), + ], +) +def test_next_version( + config, capsys, next_increment: str, current_version: str, expected_version: str +): + config.settings["version"] = current_version + for project in (True, False): + commands.Version( + config, + { + "next": next_increment, + "project": project, + }, + )() + captured = capsys.readouterr() + assert expected_version in captured.out + + # Use the same settings to test the manual version + commands.Version( + config, + { + "manual_version": current_version, + "next": next_increment, + }, + )() + captured = capsys.readouterr() + assert expected_version in captured.out + + +def test_next_version_invalid_version(config, capsys): + commands.Version( + config, + { + "manual_version": "INVALID", + }, + )() + captured = capsys.readouterr() + assert "Invalid version: 'INVALID'" in captured.err + + +@pytest.mark.parametrize( + ("version", "expected_version"), + [ + ("1.0.0", "0\n"), + ("2.1.3", "3\n"), + ("0.0.1", "1\n"), + ("0.1.0", "0\n"), + ], +) +def test_version_just_patch(config, capsys, version: str, expected_version: str): + config.settings["version"] = version + commands.Version( + config, + { + "project": True, + "patch": True, + }, + )() + captured = capsys.readouterr() + assert expected_version == captured.out + + +def test_version_unknown_scheme(config, capsys): + config.settings["version"] = "1.0.0" + config.settings["version_scheme"] = "not_a_registered_scheme_name_xyz" + commands.Version(config, {"project": True})() + captured = capsys.readouterr() + assert "Unknown version scheme." in captured.err + + +def test_version_use_git_commits_not_implemented(config, capsys): + config.settings["version"] = "1.0.0" + commands.Version( + config, + {"project": True, "next": "USE_GIT_COMMITS"}, + )() + captured = capsys.readouterr() + assert "USE_GIT_COMMITS is not implemented" in captured.err + + +def test_version_no_arguments_shows_commitizen_version(config, capsys): + commands.Version(config, {})() + captured = capsys.readouterr() + assert captured.out.strip() == __version__ diff --git a/tests/test_version_increment.py b/tests/test_version_increment.py new file mode 100644 index 000000000..5832140a8 --- /dev/null +++ b/tests/test_version_increment.py @@ -0,0 +1,25 @@ +import pytest + +from commitizen.version_increment import VersionIncrement + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + ("MAJOR", VersionIncrement.MAJOR), + ("MINOR", VersionIncrement.MINOR), + ("PATCH", VersionIncrement.PATCH), + ("NONE", VersionIncrement.NONE), + ("not_a_valid_name", VersionIncrement.NONE), + (None, VersionIncrement.NONE), + (123, VersionIncrement.NONE), + ], +) +def test_version_increment_from_value( + value: object, expected: VersionIncrement +) -> None: + assert VersionIncrement.from_value(value) == expected + + +def test_version_increment_str() -> None: + assert str(VersionIncrement.PATCH) == "PATCH"