GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — refactor(bump): use VersionIncrement ordering for bump detection (#2097) · commitizen-tools/commitizen@667de74

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • commitizen
    • bump.py
    • commands
      • bump.py
      • version.py
    • cz
      • base.py
      • conventional_commits
        • conventional_commits.py
      • customize
        • customize.py
    • defaults.py
    • version_increment.py
  • docs/customization
    • config_file.md
    • python_class.md
  • tests
    • commands
      • test_bump_command.py
    • test_bump_find_increment.py
    • test_version_increment.py
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎commitizen/bump.py‎

Copy file name to clipboardExpand all lines: commitizen/bump.py
+4-50Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,18 @@
22

33
import os
44
import re
5-
from collections import OrderedDict
65
from glob import iglob
7-
from logging import getLogger
86
from string import Template
9-
from typing import TYPE_CHECKING, cast
7+
from typing import TYPE_CHECKING
108

11-
from commitizen.defaults import BUMP_MESSAGE, MAJOR, MINOR, PATCH
9+
from commitizen.defaults import BUMP_MESSAGE
1210
from commitizen.exceptions import CurrentVersionNotFoundError
13-
from commitizen.git import GitCommit, smart_open
11+
from commitizen.git import smart_open
1412

1513
if TYPE_CHECKING:
1614
from collections.abc import Generator, Iterable
1715

18-
from commitizen.version_schemes import Increment, VersionProtocol
19-
20-
VERSION_TYPES = [None, PATCH, MINOR, MAJOR]
21-
22-
logger = getLogger("commitizen")
23-
24-
25-
def find_increment(
26-
commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict
27-
) -> Increment | None:
28-
if isinstance(increments_map, dict):
29-
increments_map = OrderedDict(increments_map)
30-
31-
# Most important cases are major and minor.
32-
# Everything else will be considered patch.
33-
select_pattern = re.compile(regex)
34-
increment: str | None = None
35-
36-
for commit in commits:
37-
for message in commit.message.split("\n"):
38-
result = select_pattern.search(message)
39-
40-
if result:
41-
found_keyword = result.group(1)
42-
new_increment = None
43-
for match_pattern in increments_map.keys():
44-
if re.match(match_pattern, found_keyword):
45-
new_increment = increments_map[match_pattern]
46-
break
47-
48-
if new_increment is None:
49-
logger.debug(
50-
f"no increment needed for '{found_keyword}' in '{message}'"
51-
)
52-
53-
if VERSION_TYPES.index(increment) < VERSION_TYPES.index(new_increment):
54-
logger.debug(
55-
f"increment detected is '{new_increment}' due to '{found_keyword}' in '{message}'"
56-
)
57-
increment = new_increment
58-
59-
if increment == MAJOR:
60-
break
61-
62-
return cast("Increment", increment)
16+
from commitizen.version_schemes import VersionProtocol
6317

6418

6519
def update_version_in_files(
Collapse file

‎commitizen/commands/bump.py‎

Copy file name to clipboardExpand all lines: commitizen/commands/bump.py
+13-5Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
)
2525
from commitizen.providers import get_provider
2626
from commitizen.tags import TagRules
27+
from commitizen.version_increment import VersionIncrement
2728
from commitizen.version_schemes import (
2829
Increment,
2930
InvalidVersion,
@@ -145,8 +146,8 @@ def _is_initial_tag(
145146
return bool(questionary.confirm("Is this the first tag created?").ask())
146147

147148
def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None:
149+
"""Determine the highest configured increment across candidate commits."""
148150
# Update the bump map to ensure major version doesn't increment.
149-
# self.cz.bump_map = defaults.bump_map_major_version_zero
150151
bump_map = (
151152
self.cz.bump_map_major_version_zero
152153
if self.bump_settings["major_version_zero"]
@@ -158,11 +159,18 @@ def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None:
158159
raise NoPatternMapError(
159160
f"'{self.config.settings['name']}' rule does not support bump"
160161
)
161-
return bump.find_increment(
162-
self.cz.filter_commits_before_bump(commits),
163-
regex=bump_pattern,
164-
increments_map=bump_map,
162+
163+
commit_messages = (
164+
commit.message for commit in self.cz.filter_commits_before_bump(commits)
165+
)
166+
increment = VersionIncrement.get_highest_by_messages(
167+
commit_messages,
168+
bump_pattern,
169+
bump_map,
165170
)
171+
if increment == VersionIncrement.NONE:
172+
return None
173+
return cast("Increment", str(increment))
166174

167175
def _validate_arguments(self, current_version: VersionProtocol) -> None:
168176
errors: list[str] = []
Collapse file

‎commitizen/commands/version.py‎

Copy file name to clipboardExpand all lines: commitizen/commands/version.py
+14-5Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from packaging.version import InvalidVersion
66

7-
from commitizen import bump, factory, git, out
7+
from commitizen import factory, git, out
88
from commitizen.__version__ import __version__
99
from commitizen.config import BaseConfig
1010
from commitizen.exceptions import (
@@ -180,11 +180,20 @@ def _get_next_git_version(
180180
raise NoPatternMapError(
181181
f"'{self.config.settings['name']}' rule does not support bump"
182182
)
183-
increment = bump.find_increment(
184-
self.cz.filter_commits_before_bump(commits),
185-
regex=bump_pattern,
186-
increments_map=bump_map,
183+
next_increment = VersionIncrement.get_highest_by_messages(
184+
(commit.message for commit in self.cz.filter_commits_before_bump(commits)),
185+
bump_pattern,
186+
bump_map,
187187
)
188+
increment: Increment | None
189+
if next_increment == VersionIncrement.NONE:
190+
increment = None
191+
elif next_increment == VersionIncrement.PATCH:
192+
increment = "PATCH"
193+
elif next_increment == VersionIncrement.MINOR:
194+
increment = "MINOR"
195+
else:
196+
increment = "MAJOR"
188197

189198
# TODO: Consider adding all the parameters `.bump` supports:
190199
# prerelease, prerelease_offset,exact_increment, etc..
Collapse file

‎commitizen/cz/base.py‎

Copy file name to clipboardExpand all lines: commitizen/cz/base.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ class ValidationResult(NamedTuple):
3737

3838
class BaseCommitizen(metaclass=ABCMeta):
3939
bump_pattern: str | None = None
40-
bump_map: dict[str, str] | None = None
41-
bump_map_major_version_zero: dict[str, str] | None = None
40+
bump_map: Mapping[str, str | None] | None = None
41+
bump_map_major_version_zero: Mapping[str, str | None] | None = None
4242
default_style_config: list[tuple[str, str]] = [
4343
("qmark", "fg:#ff9d00 bold"),
4444
("question", "bold"),
Collapse file

‎commitizen/cz/conventional_commits/conventional_commits.py‎

Copy file name to clipboardExpand all lines: commitizen/cz/conventional_commits/conventional_commits.py
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from commitizen.cz.utils import multiple_line_breaker, required_validator
99

1010
if TYPE_CHECKING:
11+
from collections.abc import Mapping
12+
1113
from commitizen.question import CzQuestion
1214

1315
__all__ = ["ConventionalCommitsCz"]
@@ -32,8 +34,10 @@ class ConventionalCommitsAnswers(TypedDict):
3234

3335
class ConventionalCommitsCz(BaseCommitizen):
3436
bump_pattern = defaults.BUMP_PATTERN
35-
bump_map = defaults.BUMP_MAP
36-
bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO
37+
bump_map: Mapping[str, str | None] = defaults.BUMP_MAP
38+
bump_map_major_version_zero: Mapping[str, str | None] = (
39+
defaults.BUMP_MAP_MAJOR_VERSION_ZERO
40+
)
3741
commit_parser = r"^((?P<change_type>feat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?|\w+!):\s(?P<message>.*)?"
3842
change_type_map = {
3943
"feat": "Feat",
Collapse file

‎commitizen/cz/customize/customize.py‎

Copy file name to clipboardExpand all lines: commitizen/cz/customize/customize.py
+6-4Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727

2828

2929
def _derive_major_version_zero(
30-
bump_map: Mapping[str, str],
31-
) -> OrderedDict[str, str]:
30+
bump_map: Mapping[str, str | None],
31+
) -> OrderedDict[str, str | None]:
3232
"""Derive a ``bump_map_major_version_zero`` from a user-supplied
3333
``bump_map`` by demoting any ``MAJOR`` rule to ``MINOR``.
3434
@@ -45,8 +45,10 @@ def _derive_major_version_zero(
4545

4646
class CustomizeCommitsCz(BaseCommitizen):
4747
bump_pattern = defaults.BUMP_PATTERN
48-
bump_map = defaults.BUMP_MAP
49-
bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO
48+
bump_map: Mapping[str, str | None] = defaults.BUMP_MAP
49+
bump_map_major_version_zero: Mapping[str, str | None] = (
50+
defaults.BUMP_MAP_MAJOR_VERSION_ZERO
51+
)
5052
change_type_order = defaults.CHANGE_TYPE_ORDER
5153

5254
def __init__(self, config: BaseConfig) -> None:
Collapse file

‎commitizen/defaults.py‎

Copy file name to clipboardExpand all lines: commitizen/defaults.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
class CzSettings(TypedDict, total=False):
1515
bump_pattern: str
16-
bump_map: OrderedDict[str, str]
17-
bump_map_major_version_zero: OrderedDict[str, str]
16+
bump_map: OrderedDict[str, str | None]
17+
bump_map_major_version_zero: OrderedDict[str, str | None]
1818
change_type_order: list[str]
1919

2020
questions: Iterable[CzQuestion]
Collapse file

‎commitizen/version_increment.py‎

Copy file name to clipboardExpand all lines: commitizen/version_increment.py
+118Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
"""Helpers for comparing and deriving semantic-version increments.
2+
3+
This module centralizes the conversion between external increment labels and the
4+
ordered enum used internally by bump-related commands. The distinction matters
5+
because some callers intentionally coerce unknown values to ``NONE`` while
6+
matched ``bump_map`` outputs must preserve the historical fail-fast behavior for
7+
invalid custom rule values.
8+
"""
9+
110
from __future__ import annotations
211

12+
import re
313
from enum import IntEnum
14+
from typing import TYPE_CHECKING
15+
16+
if TYPE_CHECKING:
17+
from collections.abc import Iterable, Mapping
18+
19+
RegexPattern = str | re.Pattern[str]
420

521

622
class VersionIncrement(IntEnum):
@@ -13,6 +29,12 @@ class VersionIncrement(IntEnum):
1329
- PATCH: backwards-compatible bug fixes
1430
- MINOR: backwards-compatible features
1531
- MAJOR: incompatible API changes
32+
33+
Examples:
34+
```python
35+
max([VersionIncrement.PATCH, VersionIncrement.MINOR])
36+
# VersionIncrement.MINOR
37+
```
1638
"""
1739

1840
NONE = 0
@@ -21,13 +43,109 @@ class VersionIncrement(IntEnum):
2143
MAJOR = 3
2244

2345
def __str__(self) -> str:
46+
"""Return the legacy string representation used by existing callers."""
2447
return self.name
2548

2649
@classmethod
2750
def from_value(cls, value: object) -> VersionIncrement:
51+
"""Convert external increment input into a known increment.
52+
53+
This lenient path is used for inputs such as CLI arguments where an
54+
unknown string should behave like "no increment" instead of raising.
55+
Matched ``bump_map`` values are validated separately so custom rules keep
56+
their historical fail-fast behavior.
57+
"""
2858
if not isinstance(value, str):
2959
return VersionIncrement.NONE
3060
try:
3161
return cls[value]
3262
except KeyError:
3363
return VersionIncrement.NONE
64+
65+
@classmethod
66+
def _compile_pattern(cls, regex: RegexPattern) -> re.Pattern[str]:
67+
"""Normalize a bump-pattern input into a compiled regular expression."""
68+
if isinstance(regex, re.Pattern):
69+
return regex
70+
return re.compile(regex)
71+
72+
@classmethod
73+
def _from_bump_map_value(cls, value: object) -> VersionIncrement:
74+
"""Resolve a configured bump-map value or reject invalid rule output.
75+
76+
This preserves the historical behavior where a matched custom bump-map
77+
entry with an unsupported increment fails immediately instead of being
78+
treated as a no-op.
79+
"""
80+
if value is None:
81+
return cls.NONE
82+
if value == "PATCH":
83+
return cls.PATCH
84+
if value == "MINOR":
85+
return cls.MINOR
86+
if value == "MAJOR":
87+
return cls.MAJOR
88+
raise ValueError(f"Invalid bump increment: {value!r}")
89+
90+
@classmethod
91+
def from_match(
92+
cls, matched_text: str, increments_map: Mapping[str, object]
93+
) -> VersionIncrement:
94+
"""Resolve one extracted bump token against a configured bump map.
95+
96+
The first regular-expression key that matches wins, preserving the
97+
current user-visible behavior of ``bump_map`` ordering.
98+
"""
99+
for match_pattern, increment in increments_map.items():
100+
if re.match(match_pattern, matched_text):
101+
return cls._from_bump_map_value(increment)
102+
return cls.NONE
103+
104+
@classmethod
105+
def from_message(
106+
cls, message: str, regex: RegexPattern, increments_map: Mapping[str, object]
107+
) -> VersionIncrement:
108+
"""Compute the highest increment contributed by one commit message.
109+
110+
Commitizen historically evaluates each line of the full commit message
111+
against ``bump_pattern`` and then keeps the highest resulting bump.
112+
This method preserves that behavior while returning a comparable enum.
113+
"""
114+
select_pattern = cls._compile_pattern(regex)
115+
increment = cls.NONE
116+
117+
for line in message.split("\n"):
118+
result = select_pattern.search(line)
119+
if not result:
120+
continue
121+
122+
increment = max(
123+
increment,
124+
cls.from_match(result.group(1), increments_map),
125+
)
126+
if increment == cls.MAJOR:
127+
return increment
128+
129+
return increment
130+
131+
@classmethod
132+
def get_highest_by_messages(
133+
cls,
134+
messages: Iterable[str],
135+
regex: RegexPattern,
136+
increments_map: Mapping[str, object],
137+
) -> VersionIncrement:
138+
"""Compute the highest increment across many commit messages.
139+
140+
An empty iterable yields ``NONE`` so callers can preserve the existing
141+
"no increment" behavior explicitly instead of relying on exceptions from
142+
``max()``.
143+
"""
144+
select_pattern = cls._compile_pattern(regex)
145+
return max(
146+
(
147+
cls.from_message(message, select_pattern, increments_map)
148+
for message in messages
149+
),
150+
default=cls.NONE,
151+
)
Collapse file

‎docs/customization/config_file.md‎

Copy file name to clipboardExpand all lines: docs/customization/config_file.md
+1-1Lines changed: 1 addition & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ Example:
160160
| `schema_pattern` | `str` | `""` | (OPTIONAL) The regular expression used to do commit message validation. Used by `cz check`. |
161161
| `info_path` | `str` | `""` | (OPTIONAL) The path to the file that contains explanation of the commit rules. Used by `cz info`. If not provided `cz info`, will load `info` instead. |
162162
| `info` | `str` | `""` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. |
163-
| `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) |
163+
| `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`). In JSON and YAML configs, `null` keeps a matched rule from bumping the version. |
164164
| `bump_pattern` | `str` | `None` | (OPTIONAL) Regex to extract information from commit (subject and body) |
165165
| `change_type_order` | `str` | `None` | (OPTIONAL) List of strings used to order the Changelog. All other types will be sorted alphabetically. Default is `["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"]` |
166166
| `commit_parser` | `str` | `None` | (OPTIONAL) Regex to extract information used in creating changelog. [See more][changelog-spec] |
Collapse file

‎docs/customization/python_class.md‎

Copy file name to clipboardExpand all lines: docs/customization/python_class.md
+1-1Lines changed: 1 addition & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ You need to define 2 parameters inside your custom `BaseCommitizen`.
9999
| Parameter | Type | Default | Description |
100100
| -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- |
101101
| `bump_pattern` | `str` | `None` | Regex to extract information from commit (subject and body) |
102-
| `bump_map` | `dict` | `None` | Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) |
102+
| `bump_map` | `dict` | `None` | Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`). Use `None` when a matched rule should not bump the version. |
103103

104104
Let's see an example.
105105

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.