Skip to content

Commit ef4650f

Browse files
fix(cz_customize): derive bump_map_major_version_zero from bump_map
Previously, when a `cz_customize` user supplied a custom `bump_map` but no explicit `bump_map_major_version_zero`, `cz bump` with `major_version_zero = true` silently fell back to `defaults.BUMP_MAP_MAJOR_VERSION_ZERO` -- a totally unrelated mapping that ignored the user's bump rules. The user's intent (e.g. `feat` = PATCH while major version is 0.x) was overridden. When the user provides `bump_map` but not `bump_map_major_version_zero`, derive the latter from the former by demoting any `MAJOR` rule to `MINOR` -- the only difference in the default mapping pair. Users who want a custom split between the two maps can still set `bump_map_major_version_zero` explicitly. Closes #1728 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4d99415 commit ef4650f

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

commitizen/cz/customize/customize.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from collections import OrderedDict
34
from pathlib import Path
45
from typing import TYPE_CHECKING, Any
56

@@ -19,11 +20,29 @@
1920

2021
from commitizen import defaults
2122
from commitizen.cz.base import BaseCommitizen
23+
from commitizen.defaults import MAJOR, MINOR
2224
from commitizen.exceptions import MissingCzCustomizeConfigError
2325

2426
__all__ = ["CustomizeCommitsCz"]
2527

2628

29+
def _derive_major_version_zero(
30+
bump_map: Mapping[str, str],
31+
) -> OrderedDict[str, str]:
32+
"""Derive a ``bump_map_major_version_zero`` from a user-supplied
33+
``bump_map`` by demoting any ``MAJOR`` rule to ``MINOR``.
34+
35+
See #1728: when a ``cz_customize`` user supplies ``bump_map`` but not
36+
``bump_map_major_version_zero``, the latter previously fell through to
37+
``defaults.BUMP_MAP_MAJOR_VERSION_ZERO``, silently overriding the
38+
user's intent during ``major_version_zero = true`` bumps.
39+
"""
40+
return OrderedDict(
41+
(pattern, MINOR if increment == MAJOR else increment)
42+
for pattern, increment in bump_map.items()
43+
)
44+
45+
2746
class CustomizeCommitsCz(BaseCommitizen):
2847
bump_pattern = defaults.BUMP_PATTERN
2948
bump_map = defaults.BUMP_MAP
@@ -49,6 +68,16 @@ def __init__(self, config: BaseConfig) -> None:
4968
if value := self.custom_settings.get(attr_name):
5069
setattr(self, attr_name, value)
5170

71+
# When the user supplies a custom ``bump_map`` but no matching
72+
# ``bump_map_major_version_zero``, derive the latter so that bumps
73+
# under ``major_version_zero = true`` use the user's mapping rather
74+
# than the (totally unrelated) ``defaults.BUMP_MAP_MAJOR_VERSION_ZERO``
75+
# fallback. See #1728.
76+
if self.custom_settings.get("bump_map") and not self.custom_settings.get(
77+
"bump_map_major_version_zero"
78+
):
79+
self.bump_map_major_version_zero = _derive_major_version_zero(self.bump_map)
80+
5281
def questions(self) -> list[CzQuestion]:
5382
return self.custom_settings.get("questions", [{}]) # type: ignore[return-value]
5483

tests/test_cz_customize.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,86 @@ def test_bump_map_unicode(config_with_unicode):
412412
}
413413

414414

415+
def test_bump_map_major_version_zero_is_derived_from_bump_map():
416+
"""Regression test for #1728: when the user provides ``bump_map`` but no
417+
explicit ``bump_map_major_version_zero``, the latter is derived from the
418+
former (``MAJOR`` → ``MINOR``) instead of falling through to the default
419+
``defaults.BUMP_MAP_MAJOR_VERSION_ZERO``."""
420+
config = BaseConfig()
421+
config.settings.update(
422+
{
423+
"name": "cz_customize",
424+
"customize": {
425+
"bump_pattern": r"^(feat|fix|docs)",
426+
"bump_map": {
427+
"break": "MAJOR",
428+
"feat": "PATCH",
429+
"docs": "PATCH",
430+
},
431+
},
432+
}
433+
)
434+
435+
cz = CustomizeCommitsCz(config)
436+
437+
# Same patterns, MAJOR demoted to MINOR.
438+
assert dict(cz.bump_map_major_version_zero) == {
439+
"break": "MINOR",
440+
"feat": "PATCH",
441+
"docs": "PATCH",
442+
}
443+
444+
445+
def test_bump_map_major_version_zero_explicit_user_value_wins():
446+
"""If the user explicitly sets ``bump_map_major_version_zero``, that
447+
value is used verbatim (no derivation)."""
448+
config = BaseConfig()
449+
config.settings.update(
450+
{
451+
"name": "cz_customize",
452+
"customize": {
453+
"bump_pattern": r"^(feat|fix|docs)",
454+
"bump_map": {
455+
"break": "MAJOR",
456+
"feat": "PATCH",
457+
},
458+
"bump_map_major_version_zero": {
459+
"break": "MAJOR", # NB: kept as MAJOR
460+
"feat": "PATCH",
461+
},
462+
},
463+
}
464+
)
465+
466+
cz = CustomizeCommitsCz(config)
467+
468+
assert dict(cz.bump_map_major_version_zero) == {
469+
"break": "MAJOR",
470+
"feat": "PATCH",
471+
}
472+
473+
474+
def test_bump_map_major_version_zero_falls_back_to_defaults_without_bump_map():
475+
"""When the user provides neither ``bump_map`` nor
476+
``bump_map_major_version_zero``, the class default applies."""
477+
from commitizen import defaults
478+
479+
config = BaseConfig()
480+
config.settings.update(
481+
{
482+
"name": "cz_customize",
483+
"customize": {
484+
# No bump_map, no bump_map_major_version_zero.
485+
"schema_pattern": r"^(feat|fix): (.*)$",
486+
},
487+
}
488+
)
489+
490+
cz = CustomizeCommitsCz(config)
491+
492+
assert cz.bump_map_major_version_zero is defaults.BUMP_MAP_MAJOR_VERSION_ZERO
493+
494+
415495
def test_change_type_order(config):
416496
cz = CustomizeCommitsCz(config)
417497
assert cz.change_type_order == [

0 commit comments

Comments
 (0)