diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e09fb63e..06bf4c2ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - id: debug-statements - id: no-commit-to-branch - id: check-merge-conflict - - id: check-toml + - id: check-toml # TOML linter (syntax checker) - id: check-yaml args: [ '--unsafe' ] # for mkdocs.yml - id: detect-private-key @@ -55,17 +55,22 @@ repos: stages: - post-commit + - repo: https://github.com/ComPWA/taplo-pre-commit + rev: v0.9.3 + hooks: + - id: taplo-format + - repo: local hooks: - id: format - name: Format + name: Format Python code via Poetry language: system pass_filenames: false entry: poetry format types: [ python ] - id: linter and test - name: Linters + name: Linters via Poetry language: system pass_filenames: false entry: poetry lint diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 000000000..36a3453ac --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,4 @@ +include = ["pyproject.toml", ".taplo.toml"] + +[formatting] +indent_string = " " diff --git a/commitizen/cli.py b/commitizen/cli.py index 6f7556df4..a73377fe4 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -52,16 +52,13 @@ def __call__( ) -> None: if not isinstance(values, str): return - if "=" not in values: + + key, sep, value = values.partition("=") + if not key or not sep: raise InvalidCommandArgumentError( f"Option {option_string} expect a key=value format" ) kwargs = getattr(namespace, self.dest, None) or {} - key, value = values.split("=", 1) - if not key: - raise InvalidCommandArgumentError( - f"Option {option_string} expect a key=value format" - ) kwargs[key] = value.strip("'\"") setattr(namespace, self.dest, kwargs) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 2a84483c9..07338afb8 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -23,7 +23,6 @@ NoPatternMapError, NotAGitProjectError, NotAllowed, - NoVersionSpecifiedError, ) from commitizen.providers import get_provider from commitizen.tags import TagRules @@ -163,11 +162,7 @@ def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None: def __call__(self) -> None: """Steps executed to bump.""" provider = get_provider(self.config) - - try: - current_version = self.scheme(provider.get_version()) - except TypeError: - raise NoVersionSpecifiedError() + current_version = self.scheme(provider.get_version()) increment = self.arguments["increment"] prerelease = self.arguments["prerelease"] @@ -177,36 +172,22 @@ def __call__(self) -> None: build_metadata = self.arguments["build_metadata"] get_next = self.arguments["get_next"] allow_no_commit = self.arguments["allow_no_commit"] + major_version_zero = self.arguments["major_version_zero"] if manual_version: - if increment: - raise NotAllowed("--increment cannot be combined with MANUAL_VERSION") - - if prerelease: - raise NotAllowed("--prerelease cannot be combined with MANUAL_VERSION") - - if devrelease is not None: - raise NotAllowed("--devrelease cannot be combined with MANUAL_VERSION") - - if is_local_version: - raise NotAllowed( - "--local-version cannot be combined with MANUAL_VERSION" - ) - - if build_metadata: - raise NotAllowed( - "--build-metadata cannot be combined with MANUAL_VERSION" - ) - - if self.bump_settings["major_version_zero"]: - raise NotAllowed( - "--major-version-zero cannot be combined with MANUAL_VERSION" - ) - - if get_next: - raise NotAllowed("--get-next cannot be combined with MANUAL_VERSION") - - if self.bump_settings["major_version_zero"] and current_version.release[0]: + for val, option in ( + (increment, "--increment"), + (prerelease, "--prerelease"), + (devrelease is not None, "--devrelease"), + (is_local_version, "--local-version"), + (build_metadata, "--build-metadata"), + (major_version_zero, "--major-version-zero"), + (get_next, "--get-next"), + ): + if val: + raise NotAllowed(f"{option} cannot be combined with MANUAL_VERSION") + + if major_version_zero and current_version.release[0]: raise NotAllowed( f"--major-version-zero is meaningless for current version {current_version}" ) @@ -215,11 +196,13 @@ def __call__(self) -> None: raise NotAllowed("--local-version cannot be combined with --build-metadata") if get_next: - # if trying to use --get-next, we should not allow --changelog or --changelog-to-stdout - if self.changelog_flag or self.changelog_to_stdout: - raise NotAllowed( - "--changelog or --changelog-to-stdout is not allowed with --get-next" - ) + for value, option in ( + (self.changelog_flag, "--changelog"), + (self.changelog_to_stdout, "--changelog-to-stdout"), + ): + if value: + raise NotAllowed(f"{option} cannot be combined with --get-next") + # --get-next is a special case, taking precedence over config for 'update_changelog_on_bump' self.changelog_config = False # Setting dry_run to prevent any unwanted changes to the repo or files diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index dc50eb3ad..8668dd44c 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -174,11 +174,15 @@ def _write_changelog( changelog_file.write(changelog_out) - def _export_template(self) -> None: - tpl = changelog.get_changelog_template(self.cz.template_loader, self.template) - # TODO: fix the following type ignores - src = Path(tpl.filename) # type: ignore[arg-type] - Path(self.export_template_to).write_text(src.read_text()) # type: ignore[arg-type] + def _export_template(self, dist: str) -> None: + filename = changelog.get_changelog_template( + self.cz.template_loader, self.template + ).filename + if filename is None: + raise NotAllowed("Template filename is not set") + + text = Path(filename).read_text() + Path(dist).write_text(text) def __call__(self) -> None: commit_parser = self.cz.commit_parser @@ -195,7 +199,7 @@ def __call__(self) -> None: ) if self.export_template_to: - return self._export_template() + return self._export_template(self.export_template_to) if not changelog_pattern or not commit_parser: raise NoPatternMapError( diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 1207cd02e..356c4ce27 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -159,9 +159,9 @@ def __call__(self) -> None: out.success("Configuration complete 🚀") def _ask_config_path(self) -> str: - default_path = ".cz.toml" - if self.project_info.has_pyproject: - default_path = "pyproject.toml" + default_path = ( + "pyproject.toml" if self.project_info.has_pyproject else ".cz.toml" + ) name: str = questionary.select( "Please choose a supported config file: ", @@ -270,15 +270,13 @@ def _ask_version_provider(self) -> str: def _ask_version_scheme(self) -> str: """Ask for setting: version_scheme""" - default = "semver" - if self.project_info.is_python: - default = "pep440" + default_scheme = "pep440" if self.project_info.is_python else "semver" scheme: str = questionary.select( "Choose version scheme: ", - choices=list(KNOWN_SCHEMES), + choices=KNOWN_SCHEMES, style=self.cz.style, - default=default, + default=default_scheme, ).unsafe_ask() return scheme @@ -318,10 +316,9 @@ def _gen_pre_commit_cmd(self, hook_types: list[str]) -> str: """Generate pre-commit command according to given hook types""" if not hook_types: raise ValueError("At least 1 hook type should be provided.") - cmd_str = "pre-commit install " + " ".join( + return "pre-commit install " + " ".join( f"--hook-type {ty}" for ty in hook_types ) - return cmd_str def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: pre_commit_config_filename = ".pre-commit-config.yaml" diff --git a/commitizen/commands/version.py b/commitizen/commands/version.py index 6b0aa331a..35e9aa6cd 100644 --- a/commitizen/commands/version.py +++ b/commitizen/commands/version.py @@ -5,6 +5,7 @@ from commitizen import out from commitizen.__version__ import __version__ from commitizen.config import BaseConfig +from commitizen.exceptions import NoVersionSpecifiedError from commitizen.providers import get_provider @@ -28,19 +29,20 @@ def __call__(self) -> None: out.write(f"Commitizen Version: {__version__}") out.write(f"Python Version: {self.python_version}") out.write(f"Operating System: {self.operating_system}") - elif self.parameter.get("project"): - version = get_provider(self.config).get_version() - if version: - out.write(f"{version}") - else: - out.error("No project information in this project.") - elif self.parameter.get("verbose"): - out.write(f"Installed Commitizen Version: {__version__}") - version = get_provider(self.config).get_version() - if version: - out.write(f"Project Version: {version}") - else: + return + + if (verbose := self.parameter.get("verbose")) or self.parameter.get("project"): + if verbose: + out.write(f"Installed Commitizen Version: {__version__}") + + try: + version = get_provider(self.config).get_version() + except NoVersionSpecifiedError: out.error("No project information in this project.") - else: - # if no argument is given, show installed commitizen version - out.write(f"{__version__}") + return + + out.write(f"Project Version: {version}" if verbose else version) + return + + # if no argument is given, show installed commitizen version + out.write(f"{__version__}") diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py index 8c0956be5..8be792258 100644 --- a/commitizen/exceptions.py +++ b/commitizen/exceptions.py @@ -59,8 +59,7 @@ class ExpectedExit(CommitizenException): exit_code = ExitCode.EXPECTED_EXIT def __init__(self, *args: str, **kwargs: Any) -> None: - output_method = kwargs.get("output_method") or out.write - kwargs["output_method"] = output_method + kwargs["output_method"] = kwargs.get("output_method") or out.write super().__init__(*args, **kwargs) diff --git a/commitizen/git.py b/commitizen/git.py index 8025041ab..4883b34e7 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -328,6 +328,4 @@ def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]: c = cmd.run(command) if c.return_code != 0: raise GitCommandError(c.err) - if not c.out: - return [] return c.out.split(f"{delimiter}\n") diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py index d9207b19f..f90d7d9b6 100644 --- a/commitizen/providers/commitizen_provider.py +++ b/commitizen/providers/commitizen_provider.py @@ -1,5 +1,6 @@ from __future__ import annotations +from commitizen.exceptions import NoVersionSpecifiedError from commitizen.providers.base_provider import VersionProvider @@ -9,7 +10,9 @@ class CommitizenProvider(VersionProvider): """ def get_version(self) -> str: - return self.config.settings["version"] # type: ignore[return-value] # TODO: check if we can fix this by tweaking the `Settings` type + if ret := self.config.settings["version"]: + return ret + raise NoVersionSpecifiedError() def set_version(self, version: str) -> None: self.config.set_key("version", version) diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py index 3085b16ef..687775da3 100644 --- a/commitizen/providers/scm_provider.py +++ b/commitizen/providers/scm_provider.py @@ -18,10 +18,8 @@ def get_version(self) -> str: rules = TagRules.from_settings(self.config.settings) tags = get_tags(reachable_only=True) version_tags = rules.get_version_tags(tags) - versions = sorted(rules.extract_version(t) for t in version_tags) - if not versions: - return "0.0.0" - return str(versions[-1]) + version = max((rules.extract_version(t) for t in version_tags), default=None) + return str(version) if version is not None else "0.0.0" def set_version(self, version: str) -> None: # Not necessary diff --git a/commitizen/tags.py b/commitizen/tags.py index b19bb89e0..e3f54370f 100644 --- a/commitizen/tags.py +++ b/commitizen/tags.py @@ -5,6 +5,7 @@ from collections.abc import Iterable, Sequence from dataclasses import dataclass, field from functools import cached_property +from itertools import chain from string import Template from typing import TYPE_CHECKING, NamedTuple @@ -88,18 +89,22 @@ class TagRules: ignored_tag_formats: Sequence[str] = field(default_factory=list) merge_prereleases: bool = False + @property + def tag_formats(self) -> Iterable[str]: + return chain([self.tag_format], self.legacy_tag_formats) + @cached_property def version_regexes(self) -> list[re.Pattern]: """Regexes for all legit tag formats, current and legacy""" - tag_formats = [self.tag_format, *self.legacy_tag_formats] - regexes = (self._format_regex(p) for p in tag_formats) - return [re.compile(r) for r in regexes] + return [re.compile(self._format_regex(f)) for f in self.tag_formats] @cached_property def ignored_regexes(self) -> list[re.Pattern]: """Regexes for known but ignored tag formats""" - regexes = (self._format_regex(p, star=True) for p in self.ignored_tag_formats) - return [re.compile(r) for r in regexes] + return [ + re.compile(self._format_regex(f, star=True)) + for f in self.ignored_tag_formats + ] def _format_regex(self, tag_pattern: str, star: bool = False) -> str: """ @@ -240,10 +245,7 @@ def find_tag_for( ) -> GitTag | None: """Find the first matching tag for a given version.""" version = self.scheme(version) if isinstance(version, str) else version - possible_tags = set( - self.normalize_tag(version, f) - for f in (self.tag_format, *self.legacy_tag_formats) - ) + possible_tags = set(self.normalize_tag(version, f) for f in self.tag_formats) candidates = [t for t in tags if t.name in possible_tags] if len(candidates) > 1: warnings.warn(