feat(Conventional提交CZ): add support for customizable change type choices and bump map overrides - #2006
Conversation
… choices and bump map overrides
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2006 +/- ##
==========================================
+ Coverage 98.24% 99.08% +0.84%
==========================================
Files 61 61
Lines 2785 2836 +51
==========================================
+ Hits 2736 2810 +74
+ Misses 49 26 -23 ☔ View full report in Codecov by Harness. |
There was a problem hiding this comment.
Pull request overview
This PR extends the cz_conventional_commits convention to support user configuration via new override and extend settings, enabling customization of bump/changelog parsing and change type choice lists without switching to cz_customize.
Changes:
- Add
override/extendtyped settings structures to the global设置model. - Teach
Conventional提交Czto apply override/extend settings and isolate mutable defaults per instance. - Add tests covering precedence (override > extend), supported settings application, and mutation isolation between instances.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 39 comments.
| File | Description |
|---|---|
commitizen/cz/conventional_commits/conventional_commits.py |
Adds per-instance isolation and applies override/extend settings; refactors change type choices into a reusable attribute. |
commitizen/defaults.py |
Extends the typed settings model with override/extend sections and typed change_type_choices. |
tests/test_cz_conventional_commits.py |
Adds test coverage for override/extend precedence, application behavior, and instance isolation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from commitizen import defaults | ||
| from commitizen.cz.base import BaseCommitizen | ||
| from commitizen.cz.utils import multiple_line_breaker, required_validator | ||
| from commitizen.question import Choice | ||
|
|
||
| if TYPE_CHECKING: | ||
| from commitizen.config import BaseConfig | ||
| from commitizen.question import CzQuestion |
| } | ||
| change_type_choices = [ | ||
| Choice( |
| Choice( | ||
| value="fix", | ||
| name=("fix: A bug fix. Correlates with PATCH in SemVer"), | ||
| key="x", | ||
| ), |
| Choice( | ||
| value="feat", | ||
| name="feat: A new feature. Correlates with MINOR in SemVer", | ||
| key="f", | ||
| ), |
| Choice( | ||
| value="docs", | ||
| name="docs: Documentation only changes", | ||
| key="d", | ||
| ), |
| Choice( | ||
| value="build", | ||
| name=( | ||
| "build: Changes that affect the build system or " | ||
| "external dependencies (example scopes: pip, docker, npm)" | ||
| ), | ||
| key="b", | ||
| ), |
| Choice( | ||
| value="ci", | ||
| name=( | ||
| "ci: Changes to CI configuration files and " | ||
| "scripts (example scopes: GitLabCI)" | ||
| ), | ||
| key="c", | ||
| ), |
| def _apply_override_settings(self, settings: defaults.CzOverride设置) -> None: | ||
| if bump_pattern := settings.get("bump_pattern"): | ||
| self.bump_pattern = bump_pattern | ||
| if bump_map := settings.get("bump_map"): | ||
| self.bump_map = OrderedDict(bump_map) | ||
| if bump_map_major_version_zero := settings.get("bump_map_major_version_zero"): | ||
| self.bump_map_major_version_zero = OrderedDict(bump_map_major_version_zero) | ||
| if commit_parser := settings.get("commit_parser"): | ||
| self.commit_parser = commit_parser | ||
| if changelog_pattern := settings.get("changelog_pattern"): | ||
| self.changelog_pattern = changelog_pattern | ||
| if change_type_map := settings.get("change_type_map"): | ||
| self.change_type_map = dict(change_type_map) | ||
| if change_type_choices := settings.get("change_type_choices"): | ||
| self.change_type_choices = [*change_type_choices] | ||
|
|
| def _apply_extend_settings(self, settings: defaults.CzExtend设置) -> None: | ||
| if bump_pattern := settings.get("bump_pattern"): | ||
| self.bump_pattern = bump_pattern | ||
| if bump_map := settings.get("bump_map"): | ||
| self.bump_map.update(bump_map) | ||
| if bump_map_major_version_zero := settings.get("bump_map_major_version_zero"): | ||
| self.bump_map_major_version_zero.update(bump_map_major_version_zero) | ||
| if commit_parser := settings.get("commit_parser"): | ||
| self.commit_parser = commit_parser | ||
| if changelog_pattern := settings.get("changelog_pattern"): | ||
| self.changelog_pattern = changelog_pattern | ||
| if change_type_map := settings.get("change_type_map"): | ||
| self.change_type_map.update(change_type_map) | ||
| if change_type_choices := settings.get("change_type_choices"): | ||
| self.change_type_choices.extend(change_type_choices) |
| "refactor": "Refactor", | ||
| "perf": "Perf", | ||
| } | ||
| change_type_choices = [ |
|
@Lee-W Could you take a look? You have more context on this feature. |
|
I'll take a look next week |
woile
left a comment
There was a problem hiding this comment.
This is good work!
However, I don't think override and extend should happen at the conventional_commits.py level. This change should work for any custom rules.
Instead, I think the override should directly happen here:
https://github.com/commitizen-tools/commitizen/blob/master/commitizen/cli.py#L710
The hierarchy is:
settings <- override/extend <- cli
Meaning that anything I provide via cli should override the override/extend and the settings. Which is not happening now.
cc: @Lee-W
|
@rockleona could you try to resolve the comments on this PR? It's super hard to read with the OpenAI comments.... Yep, agreed with @woile — since |
Manny7717
left a comment
There was a problem hiding this comment.
Verified review (head 1461705)
Locally verified on head (repo venv, Python 3.12):
- Tests:
test_cz_conventional_commits.py30/30 pass (incl. 4 new);test_conf.py67/67. Full suite 1285 passed / 6 failed / 2 xfailed — the 6 failures (test_bump_pre_commit_changelog*) fail byte-identically onorigin/master(pre-existing env noise from pre-commit/prek hooks). Zero new failures. - Lint/type:
ruff checkclean on both changed files + tests;mypyclean on the two source files. - Behavior probes (all passed): override replaces maps wholesale; extend merges into defaults while retaining them (OrderedDict append);
questions()[0]carries extended choices;bump._find_incrementreadsself.cz.bump_mapso overrides take effect; instance isolation works (_isolate_mutable_defaultscorrectly prevents cross-instance leakage — verified two instances don't share the extended map);elifgives override precedence over extend when both are set. - Interop with #2081 (strict_config, by Manny7717): KNOWN_SETTINGS there derives from the
设置TypedDict keys, so the newoverride/extendkeys are auto-included when both merge — no conflict.
Finding: custom change types cannot pass cz check (integration gap)
The feature lets users extend change_type_choices, and the interactive prompt happily offers the custom type (verified: questions()[0]["choices"] ends with the extended choice). But cz check uses schema_pattern(), whose type list is hardcoded (conventional_commits.py:243-266) and is not covered by the new override/extend settings.
Verified end-to-end with the real CLI:
[tool.commitizen.extend]
change_type_choices = [{ value = "foo", name = "foo: custom type", key = "o" }]
cz commitaccepts thefoochoice and producesfoo: added thingcz check --message "foo: added thing"→commit validation: failed!with pattern(build|bump|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)...cz bumpcomputes no increment forfoo:commits unlessbump_mapis also extended (expected), but notecommit_parseralso needs overriding for changelog classification
So the three consumers (prompt, check, bump/changelog) each use different source-of-truth lists, and the settings mechanism covers only two of them. A user who follows the PR's own "Steps to Test" (extend.change_type_choices only) ends up able to create a commit that the project's own validator rejects. This is a concrete instance of the design concern woile/Lee-W raised in June (override/extend should live on BaseCommitizen / apply to all rule sets): even scoped to conventional commits, the feature is internally inconsistent.
Suggested fixes (any one):
- Derive
schema_pattern()'s type alternatives fromchange_type_choices(falling back to the hardcoded list), or - Allow
schema_patternin override/extend settings, or - Document explicitly in the PR that custom types require overriding
commit_parser+bump_map+schema_pattern-equivalent, and add a regression test that runscz checkon a message with an extended type.
Minor notes
- Falsy-value skip:
_apply_override_settings/_apply_extend_settingsuseif x := settings.get(...), so empty-dict values are silently ignored —override: {bump_map: {}}keeps the default map instead of clearing it. Edge case, but a user attempting to remove entries this way gets no feedback. - Docs: the new
override/extendkeys are not documented indocs/config/option.md/configuration_file.md(the PR checklist's "Update the documentation" is unchecked). For a user-facing config feature this will likely be requested before merge. - Staleness: no commits since 2026-05-30 and the June maintainer feedback (woile, Lee-W) is unaddressed — the design-direction question should be resolved before this lands.
Description
According to #1385, I've adding
overrideandextendattributes under设置, basically, the content was inherited usingCz设置.Checklist
Was generative AI tooling used to co-author this PR?
Code Changes
uv run poe alllocally to ensure this change passes linter check and testsExpected Behavior
Steps to Test This Pull Request
Override
Extend
Additional Context
issue #1385