Skip to content

feat(bump_rule): add BumpRule, VersionIncrement enum, ConventionalCommitBumpRule#1518

Draft
bearomorphism wants to merge 1 commit intocommitizen-tools:masterfrom
bearomorphism:bump-rule-interface
Draft

feat(bump_rule): add BumpRule, VersionIncrement enum, ConventionalCommitBumpRule#1518
bearomorphism wants to merge 1 commit intocommitizen-tools:masterfrom
bearomorphism:bump-rule-interface

Conversation

@bearomorphism
Copy link
Copy Markdown
Collaborator

@bearomorphism bearomorphism commented Jun 9, 2025

Related issue: #129
Original PR: #1431
Previous iteration of this PR (force-pushed; commit 9644e1a1) covered a Prerelease enum that has now been dropped — see "Out of scope" below.

Description

Refactor commitizen's bump-rule machinery to be strict, explicit, and pluggable, while staying backward-compatible with existing cz plugins and configurations.

What changes

  1. New BumpRule protocol (commitizen/bump_rule.py)

    • BumpRule.extract_increment(commit_message, major_version_zero) -> VersionIncrement — single-message → single-increment.
    • ConventionalCommitBumpRule — handles the conventional-commits spec (feat, fix, perf, refactor, BREAKING CHANGE/BREAKING-CHANGE, feat!: bang).
    • CustomBumpRule — drop-in replacement for the legacy bump_pattern + bump_map flow. Tries the modern named-group form first, then falls back to the legacy regex-key form so existing user configurations keep working.
    • BaseCommitizen.bump_rule is a new cached_property that returns _bump_rule if a plugin sets one, otherwise builds a CustomBumpRule from bump_pattern/bump_map/bump_map_major_version_zero.
    • ConventionalCommitsCz._bump_rule = ConventionalCommitBumpRule() opts the built-in into the new path.
  2. Consolidated VersionIncrement enum (commitizen/version_increment.py)

    • Reuses the IntEnum already added in feat(version): add MANUAL_VERSION, --next and --patch to version command #1724 (NONE < PATCH < MINOR < MAJOR).
    • New helpers required by the bump-rule plumbing:
      • safe_cast (alias of existing from_value) — str | object → VersionIncrement, returns NONE for unknown.
      • safe_cast_dict — bulk-cast a Mapping[str, object] for bump_map conversion.
      • get_highest_by_messages(messages, extract_increment) — finds the highest increment across many commit messages, splitting multi-line messages and skipping NONE.
    • __str__ continues to return self.name ("MAJOR" / "MINOR" / "PATCH") so log lines and hook env vars are unchanged.
  3. BaseVersion.bump() accepts VersionIncrement directly

    • Replaces the Increment: Literal["MAJOR","MINOR","PATCH"] string-typed parameter with the enum.
    • Increment alias is removed from commitizen.version_schemes.
    • commitizen.commands.version.Version.__call__ is simplified (no more 9-line if/elif mapping enum back to string before calling bump()).
  4. commands/bump.py uses the new BumpRule flow

    • Bump._find_increment now does:
      return VersionIncrement.get_highest_by_messages(
          (commit.message for commit in commits),
          lambda x: self.cz.bump_rule.extract_increment(x, is_major_version_zero),
      )
      instead of building a bump_map and calling bump.find_increment.
    • Pre/post-bump hooks still receive CZ_PRE_INCREMENT / CZ_POST_INCREMENT as "MAJOR" / "MINOR" / "PATCH" strings (or empty when no bump). The VersionIncrement.NONE case is mapped back to None at the call site so env-var output matches master byte-for-byte.
  5. commitizen.bump.find_increment is removed. Only callers were the deleted tests/test_bump_find_increment.py. Coverage is now provided by the per-rule tests in tests/test_bump_rule.py and the bump-flow tests in tests/commands/test_bump_command.py.

  6. commitizen.defaults.MAJOR / MINOR / PATCH are now exposed only via PEP-562 __getattr__ with a DeprecationWarning directing users to commitizen.version_increment.VersionIncrement. The string values ("MAJOR", "MINOR", "PATCH") are unchanged. Plus a regression test in tests/test_deprecated.py to lock the contract in (a previous iteration shipped a buggy non-tuple form that crashed on access).

  7. Docs. docs/commands/bump.md gains a "Custom bump" section describing bump_pattern / bump_map / bump_map_major_version_zero with both the new named-group form and the legacy regex-key form, all in valid Python dict syntax.

Out of scope (explicitly dropped from a previous iteration of this PR)

  • Prerelease is NOT converted to an Enum. It stays as Prerelease: TypeAlias = Literal["alpha", "beta", "rc"]. Reasoning:
    • The CLI already restricts --prerelease to {"alpha", "beta", "rc"} via argparse choices, and the Literal already pins the type-level contract.
    • No internal code path passes other strings.
    • Converting to an Enum would break external Python callers of BaseVersion.bump(prerelease="alpha") for negligible gain inside the codebase.
    • Could be revisited later as its own PR (e.g. class Prerelease(str, Enum) with a backward-compatible mixin).

Backward compatibility

  • All third-party cz plugins that define bump_pattern + bump_map keep working: the CustomBumpRule fallback path matches the legacy bump.find_increment semantics for both named-group and regex-key bump maps.
  • bump_map_major_version_zero is optional. Plugins that only define bump_pattern and bump_map (without bump_map_major_version_zero) continue to work — CustomBumpRule validates this lazily and only raises NoPatternMapError when an actual bump is attempted with major_version_zero=True. This mirrors master's commands/bump.py::_find_increment behaviour exactly.
  • Iteration order is preserved through the bump_mapsafe_cast_dictCustomBumpRule pipeline. Order matters for the legacy fallback path (it returns the first matching pattern), and tests now lock the order-sensitivity in.
  • commitizen.defaults.MAJOR / MINOR / PATCH keep working via __getattr__ (with a deprecation warning).
  • Hook env vars (CZ_PRE_INCREMENT, CZ_POST_INCREMENT) and cz bump stdout output are byte-for-byte identical.
  • Default BUMP_MAP values still serialize as "MAJOR" / "MINOR" / "PATCH" strings via str(VersionIncrement.X).

Diff at a glance

 19 files changed, 1548 insertions(+), 518 deletions(-)

Checklist

Code Changes

  • Add test cases to all the changes you introduce — tests/test_bump_rule.py (635 new lines), expanded tests/test_version_increment.py, regression entry in tests/test_deprecated.py.
  • Run uv run poe all locally to ensure this change passes linter check and tests
    • uv run ruff check — clean
    • uv run ruff format --check — clean
    • uv run mypy commitizen tests — clean
    • uv run pytest — 1322 passed, 3 skipped, 4 GPG-deselected on Windows
  • Manually test the changes:
    • Verify the feature/bug fix works as expected in real-world scenarios
    • Test edge cases and error conditions (major_version_zero, multi-line messages, custom bump_pattern fallback path)
    • Ensure backward compatibility is maintained
    • Document any manual testing steps performed (existing test suite covers them)
  • Update the documentation for the changes

Documentation Changes

  • Run uv run poe doc locally to ensure the documentation pages render correctly

Additional Context

  • The original branch had been sitting in conflict for ~11 months on top of v4.10.0. This iteration was rebuilt fresh on top of current master (4d99415a).
  • Maintainer feedback from August 2025 (Lee-W): "we can likely add some of the logic incrementally while keeping compatibility for a few minor versions" — this iteration explicitly trades the more invasive Prerelease enum change for incrementality.
  • A self code-review pass found and fixed three issues before publishing:
    1. commitizen.defaults.MAJOR/MINOR/PATCH were stored as bare strings instead of (value, replacement) tuples — from commitizen.defaults import MAJOR would have crashed with ValueError. Fixed with regression test.
    2. bump_map_major_version_zero was incorrectly required at construction time, breaking plugins that only defined bump_pattern + bump_map. Fixed by making it optional and validating lazily, matching master's behaviour exactly. Regression tests added.
    3. The legacy fallback path's order-dependence on dict iteration is now explicitly tested and documented.

@Lee-W Lee-W added this to the 4.9.0 milestone Jun 9, 2025
@bearomorphism bearomorphism force-pushed the bump-rule-interface branch from bb305ad to 8a6c84f Compare June 9, 2025 15:53
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.26%. Comparing base (4b93a50) to head (07a03cf).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1518      +/-   ##
==========================================
+ Coverage   98.23%   98.26%   +0.02%     
==========================================
  Files          61       62       +1     
  Lines        2779     2818      +39     
==========================================
+ Hits         2730     2769      +39     
  Misses         49       49              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bearomorphism bearomorphism force-pushed the bump-rule-interface branch from 8a6c84f to 8dd8d42 Compare June 9, 2025 16:04
@Lee-W Lee-W changed the base branch from master to v4-9-0-test June 10, 2025 02:59
Copy link
Copy Markdown
Member

@Lee-W Lee-W left a comment

Choose a reason for hiding this comment

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

This is definitely a great pull request that cleans up a lot of legacy code. However, we need to be cautious about maintaining backward compatibility. We can likely add some of the logic incrementally while keeping compatibility for a few minor versions. After that, we can bump the major version and remove the compatibility handling code.

Comment thread commitizen/bump_rule.py Outdated
Comment thread commitizen/bump_rule.py Outdated
Comment thread commitizen/bump_rule.py Outdated
Comment thread commitizen/bump_rule.py Outdated
Comment thread commitizen/bump_rule.py Outdated
Comment thread commitizen/commands/bump.py Outdated
return bump.find_increment(commits, regex=bump_pattern, increments_map=bump_map)
return VersionIncrement.get_highest_by_messages(
(commit.message for commit in commits),
lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero),
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 might be a breaking change for other non-standard czs

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.

We'll need a way to keep backward compatibility or move it to 5.0.0 instead (but that would be a pity since the PR looks os good)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Could you provide more details about why this might be a breaking change for non-standard cz s? I am not familiar with non-standard cz. Thanks

Comment thread commitizen/defaults.py
@bearomorphism
Copy link
Copy Markdown
Collaborator Author

Can we do squash rebase on this PR when it is ready for merge?

@Lee-W
Copy link
Copy Markdown
Member

Lee-W commented Aug 25, 2025

Can we do squash rebase on this PR when it is ready for merge?

yep, I can do that

@bearomorphism
Copy link
Copy Markdown
Collaborator Author

What are the next steps for this PR? If there are backward compatibility issues, I can adjust it so that this PR can be checked in. Thanks

@Lee-W Lee-W modified the milestones: 4.9.0, 4.10.0 Sep 9, 2025
@Lee-W Lee-W deleted the branch commitizen-tools:master September 9, 2025 06:09
@Lee-W Lee-W closed this Sep 9, 2025
@Lee-W Lee-W reopened this Sep 9, 2025
@Lee-W Lee-W changed the base branch from v4-9-0-test to master September 9, 2025 06:19
Comment thread commitizen/bump_rule.py
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Core feature change

@bearomorphism
Copy link
Copy Markdown
Collaborator Author

We can get back to this PR after #1598

@bearomorphism bearomorphism force-pushed the bump-rule-interface branch 2 times, most recently from 9914feb to 9644e1a Compare November 11, 2025 15:00
@bearomorphism
Copy link
Copy Markdown
Collaborator Author

@Lee-W It would be great if we can complete this big PR. I can fix those backward-compatibility issues, but I need more details so I can address the issues.

@bearomorphism
Copy link
Copy Markdown
Collaborator Author

(I've resolved merge conflicts for several times for this PR)

@Lee-W
Copy link
Copy Markdown
Member

Lee-W commented Nov 13, 2025

This would fall into 4.11.0. Let's foucs on merging the non-features PRs first and then get back to this one.

@bearomorphism
Copy link
Copy Markdown
Collaborator Author

A lot of conflicts... I will probably create another branch and do the change again...

@bearomorphism bearomorphism force-pushed the bump-rule-interface branch 2 times, most recently from 59fc269 to 4271c36 Compare May 9, 2026 10:13
@bearomorphism bearomorphism changed the title feat(bump_rule): add BumpRule, VersionIncrement, Prerelease Enum feat(bump_rule): add BumpRule, VersionIncrement enum, ConventionalCommitBumpRule May 9, 2026
@bearomorphism bearomorphism force-pushed the bump-rule-interface branch from 4271c36 to 16ac67d Compare May 9, 2026 10:36
@bearomorphism
Copy link
Copy Markdown
Collaborator Author

will do a human review later...

…mitBumpRule

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bearomorphism bearomorphism force-pushed the bump-rule-interface branch from 16ac67d to 07a03cf Compare May 9, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants