Skip to content

Commit 3f8b8ca

Browse files
fix(cz_customize): derive bump_map_major_version_zero from bump_map (#1977)
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 1d89cc5 commit 3f8b8ca

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
@@ -414,6 +414,86 @@ def test_bump_map_unicode(config_with_unicode):
414414
}
415415

416416

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

0 commit comments

Comments
 (0)