diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f528cb552..18b877b2e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,7 +56,7 @@ repos: - tomli - repo: https://github.com/commitizen-tools/commitizen - rev: v4.16.0 # automatically updated by Commitizen + rev: v4.16.1 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/commitizen/__version__.py b/commitizen/__version__.py index 80de35676..e7e757cb6 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "4.16.0" +__version__ = "4.16.1" diff --git a/commitizen/cz/customize/customize.py b/commitizen/cz/customize/customize.py index 8fcc63fac..8f8857d21 100644 --- a/commitizen/cz/customize/customize.py +++ b/commitizen/cz/customize/customize.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections import OrderedDict from pathlib import Path from typing import TYPE_CHECKING, Any @@ -19,11 +20,29 @@ from commitizen import defaults from commitizen.cz.base import BaseCommitizen +from commitizen.defaults import MAJOR, MINOR from commitizen.exceptions import MissingCzCustomizeConfigError __all__ = ["CustomizeCommitsCz"] +def _derive_major_version_zero( + bump_map: Mapping[str, str], +) -> OrderedDict[str, str]: + """Derive a ``bump_map_major_version_zero`` from a user-supplied + ``bump_map`` by demoting any ``MAJOR`` rule to ``MINOR``. + + See #1728: when a ``cz_customize`` user supplies ``bump_map`` but not + ``bump_map_major_version_zero``, the latter previously fell through to + ``defaults.BUMP_MAP_MAJOR_VERSION_ZERO``, silently overriding the + user's intent during ``major_version_zero = true`` bumps. + """ + return OrderedDict( + (pattern, MINOR if increment == MAJOR else increment) + for pattern, increment in bump_map.items() + ) + + class CustomizeCommitsCz(BaseCommitizen): bump_pattern = defaults.BUMP_PATTERN bump_map = defaults.BUMP_MAP @@ -49,6 +68,16 @@ def __init__(self, config: BaseConfig) -> None: if value := self.custom_settings.get(attr_name): setattr(self, attr_name, value) + # When the user supplies a custom ``bump_map`` but no matching + # ``bump_map_major_version_zero``, derive the latter so that bumps + # under ``major_version_zero = true`` use the user's mapping rather + # than the (totally unrelated) ``defaults.BUMP_MAP_MAJOR_VERSION_ZERO`` + # fallback. See #1728. + if self.custom_settings.get("bump_map") and not self.custom_settings.get( + "bump_map_major_version_zero" + ): + self.bump_map_major_version_zero = _derive_major_version_zero(self.bump_map) + def questions(self) -> list[CzQuestion]: return self.custom_settings.get("questions", [{}]) # type: ignore[return-value] diff --git a/docs/images/cli_interactive/bump.gif b/docs/images/cli_interactive/bump.gif index 1bbf940d5..0d7cd5485 100644 Binary files a/docs/images/cli_interactive/bump.gif and b/docs/images/cli_interactive/bump.gif differ diff --git a/docs/images/cli_interactive/commit.gif b/docs/images/cli_interactive/commit.gif index 02f41d51b..53d031da0 100644 Binary files a/docs/images/cli_interactive/commit.gif and b/docs/images/cli_interactive/commit.gif differ diff --git a/docs/images/cli_interactive/init.gif b/docs/images/cli_interactive/init.gif index 0ea2341b8..1f7f3ffe0 100644 Binary files a/docs/images/cli_interactive/init.gif and b/docs/images/cli_interactive/init.gif differ diff --git a/docs/images/cli_interactive/shortcut_custom.gif b/docs/images/cli_interactive/shortcut_custom.gif index b4d70b5fd..0876dd91e 100644 Binary files a/docs/images/cli_interactive/shortcut_custom.gif and b/docs/images/cli_interactive/shortcut_custom.gif differ diff --git a/docs/images/cli_interactive/shortcut_default.gif b/docs/images/cli_interactive/shortcut_default.gif index 1e599171d..20d1418e8 100644 Binary files a/docs/images/cli_interactive/shortcut_default.gif and b/docs/images/cli_interactive/shortcut_default.gif differ diff --git a/pyproject.toml b/pyproject.toml index fe0ee0cd4..a6402494f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "commitizen" -version = "4.16.0" +version = "4.16.1" description = "Python commitizen client tool" authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }] maintainers = [ diff --git a/tests/test_cz_customize.py b/tests/test_cz_customize.py index fa058a639..726177247 100644 --- a/tests/test_cz_customize.py +++ b/tests/test_cz_customize.py @@ -414,6 +414,86 @@ def test_bump_map_unicode(config_with_unicode): } +def test_bump_map_major_version_zero_is_derived_from_bump_map(): + """Regression test for #1728: when the user provides ``bump_map`` but no + explicit ``bump_map_major_version_zero``, the latter is derived from the + former (``MAJOR`` → ``MINOR``) instead of falling through to the default + ``defaults.BUMP_MAP_MAJOR_VERSION_ZERO``.""" + config = BaseConfig() + config.settings.update( + { + "name": "cz_customize", + "customize": { + "bump_pattern": r"^(feat|fix|docs)", + "bump_map": { + "break": "MAJOR", + "feat": "PATCH", + "docs": "PATCH", + }, + }, + } + ) + + cz = CustomizeCommitsCz(config) + + # Same patterns, MAJOR demoted to MINOR. + assert dict(cz.bump_map_major_version_zero) == { + "break": "MINOR", + "feat": "PATCH", + "docs": "PATCH", + } + + +def test_bump_map_major_version_zero_explicit_user_value_wins(): + """If the user explicitly sets ``bump_map_major_version_zero``, that + value is used verbatim (no derivation).""" + config = BaseConfig() + config.settings.update( + { + "name": "cz_customize", + "customize": { + "bump_pattern": r"^(feat|fix|docs)", + "bump_map": { + "break": "MAJOR", + "feat": "PATCH", + }, + "bump_map_major_version_zero": { + "break": "MAJOR", # NB: kept as MAJOR + "feat": "PATCH", + }, + }, + } + ) + + cz = CustomizeCommitsCz(config) + + assert dict(cz.bump_map_major_version_zero) == { + "break": "MAJOR", + "feat": "PATCH", + } + + +def test_bump_map_major_version_zero_falls_back_to_defaults_without_bump_map(): + """When the user provides neither ``bump_map`` nor + ``bump_map_major_version_zero``, the class default applies.""" + from commitizen import defaults + + config = BaseConfig() + config.settings.update( + { + "name": "cz_customize", + "customize": { + # No bump_map, no bump_map_major_version_zero. + "schema_pattern": r"^(feat|fix): (.*)$", + }, + } + ) + + cz = CustomizeCommitsCz(config) + + assert cz.bump_map_major_version_zero is defaults.BUMP_MAP_MAJOR_VERSION_ZERO + + def test_change_type_order(config): cz = CustomizeCommitsCz(config) assert cz.change_type_order == [ diff --git a/uv.lock b/uv.lock index ba4a9b2a5..40ec44c59 100644 --- a/uv.lock +++ b/uv.lock @@ -216,7 +216,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.16.0" +version = "4.16.1" source = { editable = "." } dependencies = [ { name = "argcomplete" },