Skip to content

feat(ConventionalCommitsCZ): add support for customizable change type choices and bump map overrides - #2006

Open
rockleona wants to merge 2 commits into
commitizen-tools:masterfrom
rockleona:feat/override-and-extend-settings
Open

feat(ConventionalCommitsCZ): add support for customizable change type choices and bump map overrides#2006
rockleona wants to merge 2 commits into
commitizen-tools:masterfrom
rockleona:feat/override-and-extend-settings

Conversation

@rockleona

@rockleona rockleona commented May 30, 2026

Copy link
Copy Markdown
Contributor

Description

According to #1385, I've adding override and extend attributes under Settings, basically, the content was inherited using CzSettings.

Checklist

Was generative AI tooling used to co-author this PR?

  • Yes (GitHub Copilot)

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:
    • Verify the feature/bug fix works as expected in real-world scenarios
    • Test edge cases and error conditions
    • Ensure backward compatibility is maintained
    • Document any manual testing steps performed
  • Update the documentation for the changes

Expected Behavior

Steps to Test This Pull Request

Override

[tool.commitizen]
name = "cz_conventional_commits"

[tool.commitizen.override]
bump_pattern = "^((feat|fix|perf)(\\(.+\\))?!?):"
commit_parser = "^((?P<change_type>feat|fix|perf)(?:\\((?P<scope>[^()\\r\\n]*)\\))?(?P<breaking>!)?):\\s(?P<message>.*)$"
changelog_pattern = "^(feat|fix|perf)"
bump_map = { "^feat" = "MINOR", "^fix" = "PATCH", "^perf" = "PATCH" }
change_type_map = { feat = "Features", fix = "Bug Fixes", perf = "Performance" }
change_type_choices = [
  { value = "feat", name = "feat: A new feature", key = "f" },
  { value = "fix", name = "fix: A bug fix", key = "x" }
]

Extend

[tool.commitizen]
name = "cz_conventional_commits"

[tool.commitizen.extend]
bump_map = { "^deps" = "PATCH" }
change_type_map = { deps = "Dependencies" }
change_type_choices = [
  { value = "deps", name = "deps: Dependency updates", key = "e" }
]

Additional Context

issue #1385

@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.08%. Comparing base (c3f6797) to head (1461705).
⚠️ Report is 23 commits behind head on master.

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.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/extend typed settings structures to the global Settings model.
  • Teach ConventionalCommitsCz to 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.

Comment on lines 7 to 14
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
Comment on lines 46 to +48
}
change_type_choices = [
Choice(
Comment on lines +48 to +52
Choice(
value="fix",
name=("fix: A bug fix. Correlates with PATCH in SemVer"),
key="x",
),
Comment on lines +53 to +57
Choice(
value="feat",
name="feat: A new feature. Correlates with MINOR in SemVer",
key="f",
),
Comment on lines +58 to +62
Choice(
value="docs",
name="docs: Documentation only changes",
key="d",
),
Comment on lines +89 to +96
Choice(
value="build",
name=(
"build: Changes that affect the build system or "
"external dependencies (example scopes: pip, docker, npm)"
),
key="b",
),
Comment on lines +97 to +104
Choice(
value="ci",
name=(
"ci: Changes to CI configuration files and "
"scripts (example scopes: GitLabCI)"
),
key="c",
),
Comment on lines +125 to +140
def _apply_override_settings(self, settings: defaults.CzOverrideSettings) -> 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]

Comment on lines +141 to +155
def _apply_extend_settings(self, settings: defaults.CzExtendSettings) -> 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 = [
@bearomorphism
bearomorphism requested a review from Lee-W May 30, 2026 05:21
@bearomorphism

Copy link
Copy Markdown
Collaborator

@Lee-W Could you take a look? You have more context on this feature.

@woile

woile commented May 30, 2026

Copy link
Copy Markdown
Member

I'll take a look next week

@woile woile left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@Lee-W

Lee-W commented Jun 7, 2026

Copy link
Copy Markdown
Member

@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 bump_pattern / bump_map / bump_map_major_version_zero / change_type_map / change_type_order / changelog_pattern / commit_parser all live on BaseCommitizen, the override/extend logic should move there so it works for any rule set, not just conventional commits.

@Manny7717 Manny7717 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified review (head 1461705)

Locally verified on head (repo venv, Python 3.12):

  • Tests: test_cz_conventional_commits.py 30/30 pass (incl. 4 new); test_conf.py 67/67. Full suite 1285 passed / 6 failed / 2 xfailed — the 6 failures (test_bump_pre_commit_changelog*) fail byte-identically on origin/master (pre-existing env noise from pre-commit/prek hooks). Zero new failures.
  • Lint/type: ruff check clean on both changed files + tests; mypy clean 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_increment reads self.cz.bump_map so overrides take effect; instance isolation works (_isolate_mutable_defaults correctly prevents cross-instance leakage — verified two instances don't share the extended map); elif gives override precedence over extend when both are set.
  • Interop with #2081 (strict_config, by Manny7717): KNOWN_SETTINGS there derives from the Settings TypedDict keys, so the new override/extend keys 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 commit accepts the foo choice and produces foo: added thing
  • cz check --message "foo: added thing"commit validation: failed! with pattern (build|bump|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)...
  • cz bump computes no increment for foo: commits unless bump_map is also extended (expected), but note commit_parser also 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):

  1. Derive schema_pattern()'s type alternatives from change_type_choices (falling back to the hardcoded list), or
  2. Allow schema_pattern in override/extend settings, or
  3. Document explicitly in the PR that custom types require overriding commit_parser + bump_map + schema_pattern-equivalent, and add a regression test that runs cz check on a message with an extended type.

Minor notes

  • Falsy-value skip: _apply_override_settings/_apply_extend_settings use if 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/extend keys are not documented in docs/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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6 participants