Title: fix(cz_customize): derive bump_map_major_version_zero from custom bump_map by bearomorphism · Pull Request #1977 · commitizen-tools/commitizen · GitHub
Open Graph Title: fix(cz_customize): derive bump_map_major_version_zero from custom bump_map by bearomorphism · Pull Request #1977 · commitizen-tools/commitizen
X Title: fix(cz_customize): derive bump_map_major_version_zero from custom bump_map by bearomorphism · Pull Request #1977 · commitizen-tools/commitizen
Description: Description Closes #1728. Why When cz_customize is active and the user sets major_version_zero = true, commitizen's bump command selects self.cz.bump_map_major_version_zero instead of self.cz.bump_map to determine the version increment (see commitizen/commands/bump.py:150-154). CustomizeCommitsCz inherits bump_map_major_version_zero as a class attribute pointing at defaults.BUMP_MAP_MAJOR_VERSION_ZERO (line 30 of commitizen/cz/customize/customize.py). The __init__ attribute-copy loop (lines 40-50) only overwrites it if the user explicitly sets bump_map_major_version_zero in their [tool.commitizen.customize] section. The result is that a user who carefully crafts a bump_map — mapping feat and docs to PATCH instead of the conventional MINOR — finds that their mapping is completely ignored whenever major_version_zero = true is set. The bump falls through to defaults.BUMP_MAP_MAJOR_VERSION_ZERO, which maps feat to MINOR. The user's custom rule produces a MINOR increment when they expected PATCH, with no warning and no indication that a fallback was used. The reporter @JeannotJeannot confirmed this on commitizen 4.10.1 (Windows); a triage comment on the #1964 audit reproduced it against master (v4.15.1). The correct fix is for CustomizeCommitsCz.__init__ to derive bump_map_major_version_zero from the user's own bump_map whenever the former is absent: copy every pattern from bump_map and demote any MAJOR rule to MINOR (since the semantic purpose of major_version_zero is to prevent 1.0.0 from being crossed). Users who want a completely different mapping for the 0.x series can still set bump_map_major_version_zero explicitly; that value takes unconditional precedence. What changed File Change commitizen/cz/customize/customize.py Add _derive_major_version_zero() module-level helper; extend CustomizeCommitsCz.__init__ to call it when bump_map is set in customize settings but bump_map_major_version_zero is not tests/test_cz_customize.py Three new tests: derivation from bump_map (regression for #1728), explicit user value wins, and neither-set fallback to class default How it works MAJOR → MINOR demotion is the right semantic. The entire point of major_version_zero is that a project still in 0.x treats breaking changes as MINOR rather than MAJOR (to avoid leaving 0.x prematurely). Keeping a user's MAJOR rule as-is in the derived map would defeat that intent, so every MAJOR entry is demoted to MINOR while all MINOR and PATCH entries pass through unchanged. OrderedDict is used deliberately. commitizen/bump.py:find_increment (line 43) iterates the bump map in insertion order to find the first matching pattern. A plain dict would be fine on Python 3.7+ (insertion-ordered), but the rest of the codebase types bump_map_major_version_zero as OrderedDict[str, str] (see commitizen/defaults.py:137 and commitizen/defaults.py:17). Using OrderedDict in the derived value keeps the type consistent and preserves the user's pattern-priority order exactly. Strict trigger: both conditions must hold. The derivation only fires when self.custom_settings.get("bump_map") is truthy AND self.custom_settings.get("bump_map_major_version_zero") is falsy. This is a backwards-compatible opt-in: existing configs that already set both keys see no change; configs that set neither key continue to inherit defaults.BUMP_MAP_MAJOR_VERSION_ZERO unchanged. self.bump_map is read after the attribute-copy loop. Because the loop at lines 40-50 runs first, self.bump_map already holds the user's custom map (not the class-level default) by the time the derivation check runs. The derived bump_map_major_version_zero is therefore always consistent with whatever bump_map the bump command will actually use. Backward compatibility Configs that set both bump_map and bump_map_major_version_zero explicitly are unaffected — the not self.custom_settings.get("bump_map_major_version_zero") guard short-circuits derivation. Configs that set neither key continue to use defaults.BUMP_MAP_MAJOR_VERSION_ZERO as the class attribute — the derivation branch is never entered. All 68 existing cz_customize tests (as of master v4.15.1) continue to pass. No CLI flags, config keys, or public APIs are added or removed. Checklist I have read the contributing guidelines Was generative AI tooling used to co-author this PR? Yes (please specify the tool below) Generated-by: Claude following the guidelines Code Changes Add test cases to all the changes you introduce Run uv run poe all locally to ensure this change passes linter check and tests Manually test the changes (see "Steps to Test" below) Update the documentation for the changes Expected Behavior Scenario Outcome cz_customize with custom bump_map = {feat = "PATCH"} and major_version_zero = true, no explicit bump_map_major_version_zero cz bump --dry-run reports increment detected: PATCH — user's map is honoured Same config but with bump_map_major_version_zero explicitly set Explicit value wins unchanged — derivation does not run cz_customize with no bump_map and no bump_map_major_version_zero Falls back to defaults.BUMP_MAP_MAJOR_VERSION_ZERO — identical to current behaviour cz_customize with bump_map containing a MAJOR entry and major_version_zero = true Derived map demotes MAJOR to MINOR; no version crossing from 0.x to 1.0.0 Steps to Test This Pull Request git fetch fork fix/1728-customize-bump-map-major-zero git checkout fork/fix/1728-customize-bump-map-major-zero # 1. Targeted regression tests. uv run pytest tests/test_cz_customize.py::test_bump_map_major_version_zero_is_derived_from_bump_map \ tests/test_cz_customize.py::test_bump_map_major_version_zero_explicit_user_value_wins \ tests/test_cz_customize.py::test_bump_map_major_version_zero_falls_back_to_defaults_without_bump_map \ -v # 2. Reproduce-the-bug-then-verify-the-fix sequence (exact reproducer from #1728). mkdir cz1728 && cd cz1728 git init -b main git config user.name test && git config user.email test@example.com cat > pyproject.toml << 'EOF' [tool.commitizen] name = "cz_customize" version = "0.1.0" major_version_zero = true [tool.commitizen.customize] bump_pattern = "^(feat|fix|docs)" bump_map = {feat = "PATCH", docs = "PATCH"} EOF git add pyproject.toml && git commit -m "feat: initial setup" # Before fix: reports "increment detected: MINOR" (defaults map used). # After fix: reports "increment detected: PATCH" (user's map honoured). cz bump --dry-run --yes # Verify explicit override still wins (both maps set): sed -i 's/bump_map = .*/bump_map = {feat = "PATCH"}\nbump_map_major_version_zero = {feat = "MAJOR"}/' pyproject.toml cz bump --dry-run --yes # expect MAJOR (explicit value, not derived) Additional Context This fix was identified during the issue audit in #1964. The root cause — the gap between bump_map and bump_map_major_version_zero in cz_customize — was acknowledged in the triage comment on #1728. The workaround documented there (set both keys explicitly) remains valid for users on older releases; this PR makes the no-explicit-bump_map_major_version_zero path do the obvious thing automatically.
Open Graph Description: Description Closes #1728. Why When cz_customize is active and the user sets major_version_zero = true, commitizen's bump command selects self.cz.bump_map_major_version_zero instead of self.cz.b...
X Description: Description Closes #1728. Why When cz_customize is active and the user sets major_version_zero = true, commitizen's bump command selects self.cz.bump_map_major_version_zero instead of self....
Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1977
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:414a23d4-38d4-1b7d-94db-7f6ae99db715 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | D410:3885D3:9FD068:E90657:6A4E1D4A |
| html-safe-nonce | f243b48705e0621d9190fd00148dcb205ac8318ff6f9e86583ed5785602987ce |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJENDEwOjM4ODVEMzo5RkQwNjg6RTkwNjU3OjZBNEUxRDRBIiwidmlzaXRvcl9pZCI6Ijg5NjQyOTUzOTM2ODU2NzUzMzgiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 5d0f89a29c1ad953f0e5070a1730cc1728736d5f2bddb929b7f508f62dc51145 |
| hovercard-subject-tag | pull_request:3654754860 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1977/files |
| twitter:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| og:image:alt | Description Closes #1728. Why When cz_customize is active and the user sets major_version_zero = true, commitizen's bump command selects self.cz.bump_map_major_version_zero instead of self.cz.b... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 030096ee0db095447bfe77409d33bfac127ca7128299c58deef27c52eaa1b1f0 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git |
| octolytics-dimension-user_id | 62252524 |
| octolytics-dimension-user_login | commitizen-tools |
| octolytics-dimension-repository_id | 106127589 |
| octolytics-dimension-repository_nwo | commitizen-tools/commitizen |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 106127589 |
| octolytics-dimension-repository_network_root_nwo | commitizen-tools/commitizen |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 5fe497b4815fcce3df76c0e246faa739a82c3124 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width