Skip to content

Commit a16ff63

Browse files
fix(changelog_merge_prerelease): changelog not merged during cz bump
1 parent 4ade721 commit a16ff63

File tree

101 files changed

+3750
-2507
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+3750
-2507
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
python-check:
77
strategy:
88
matrix:
9-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
9+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1010
platform: [ubuntu-22.04, macos-latest, windows-latest]
1111
runs-on: ${{ matrix.platform }}
1212
steps:

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ repos:
4848
- tomli
4949

5050
- repo: https://github.com/commitizen-tools/commitizen
51-
rev: v4.10.0 # automatically updated by Commitizen
51+
rev: v4.10.1 # automatically updated by Commitizen
5252
hooks:
5353
- id: commitizen
5454
- id: commitizen-branch

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## v4.10.1 (2025-12-11)
2+
3+
### Fix
4+
5+
- **version**: fix the behavior of cz version --major
6+
- **cli**: debug and no_raise can be used together in sys.excepthook
7+
- **git**: replace lstrip with strip for compatibility issue
8+
- **bump**: remove NotAllowed related to --get-next option, other related refactoring
9+
10+
### Refactor
11+
12+
- **version**: rename class member to align with other classes
13+
- **cargo_provider**: cleanup and get rid of potential type errors
14+
- **bump**: extract option validation and new version resolution to new functions
15+
- **changelog**: raise NotAllow when file_name not passed instead of using assert
16+
- **bump**: rename parameter and variables
17+
18+
### Perf
19+
20+
- **ruff**: enable ruff rules TC001~TC006
21+
- add TYPE_CHECKING to CzQuestion imports
22+
123
## v4.10.0 (2025-11-10)
224

325
### Feat

commitizen/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.10.0"
1+
__version__ = "4.10.1"

commitizen/bump.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import os
44
import re
55
from collections import OrderedDict
6-
from collections.abc import Generator, Iterable
76
from glob import iglob
87
from logging import getLogger
98
from string import Template
10-
from typing import cast
9+
from typing import TYPE_CHECKING, cast
1110

1211
from commitizen.defaults import BUMP_MESSAGE, MAJOR, MINOR, PATCH
1312
from commitizen.exceptions import CurrentVersionNotFoundError
1413
from commitizen.git import GitCommit, smart_open
15-
from commitizen.version_schemes import Increment, Version
14+
15+
if TYPE_CHECKING:
16+
from collections.abc import Generator, Iterable
17+
18+
from commitizen.version_schemes import Increment, Version
1619

1720
VERSION_TYPES = [None, PATCH, MINOR, MAJOR]
1821

@@ -56,13 +59,13 @@ def find_increment(
5659
if increment == MAJOR:
5760
break
5861

59-
return cast(Increment, increment)
62+
return cast("Increment", increment)
6063

6164

6265
def update_version_in_files(
6366
current_version: str,
6467
new_version: str,
65-
files: Iterable[str],
68+
version_files: Iterable[str],
6669
*,
6770
check_consistency: bool,
6871
encoding: str,
@@ -77,7 +80,7 @@ def update_version_in_files(
7780
"""
7881
updated_files = []
7982

80-
for path, pattern in _resolve_files_and_regexes(files, current_version):
83+
for path, pattern in _resolve_files_and_regexes(version_files, current_version):
8184
current_version_found = False
8285
bumped_lines = []
8386

commitizen/changelog.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
import re
3131
from collections import OrderedDict, defaultdict
32-
from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
3332
from dataclasses import dataclass
3433
from datetime import date
3534
from itertools import chain
@@ -44,13 +43,14 @@
4443
Template,
4544
)
4645

47-
from commitizen.cz.base import ChangelogReleaseHook
4846
from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
49-
from commitizen.git import GitCommit, GitTag
5047
from commitizen.tags import TagRules
5148

5249
if TYPE_CHECKING:
53-
from commitizen.cz.base import MessageBuilderHook
50+
from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
51+
52+
from commitizen.cz.base import ChangelogReleaseHook, MessageBuilderHook
53+
from commitizen.git import GitCommit, GitTag
5454

5555

5656
@dataclass
@@ -72,6 +72,17 @@ def __post_init__(self) -> None:
7272
self.latest_version_tag = self.latest_version
7373

7474

75+
@dataclass
76+
class IncrementalMergeInfo:
77+
"""
78+
Information regarding the last non-pre-release, parsed from the changelog. Required to merge pre-releases on bump.
79+
Separate from Metadata to not mess with the interface.
80+
"""
81+
82+
name: str | None = None
83+
index: int | None = None
84+
85+
7586
def get_commit_tag(commit: GitCommit, tags: list[GitTag]) -> GitTag | None:
7687
return next((tag for tag in tags if tag.rev == commit.rev), None)
7788

@@ -86,15 +97,18 @@ def generate_tree_from_commits(
8697
changelog_message_builder_hook: MessageBuilderHook | None = None,
8798
changelog_release_hook: ChangelogReleaseHook | None = None,
8899
rules: TagRules | None = None,
100+
during_version_bump: bool = False,
89101
) -> Generator[dict[str, Any], None, None]:
90102
pat = re.compile(changelog_pattern)
91103
map_pat = re.compile(commit_parser, re.MULTILINE)
92104
body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL)
93105
rules = rules or TagRules()
94106

95107
# Check if the latest commit is not tagged
96-
97-
current_tag = get_commit_tag(commits[0], tags) if commits else None
108+
if during_version_bump and rules.merge_prereleases:
109+
current_tag = None
110+
else:
111+
current_tag = get_commit_tag(commits[0], tags) if commits else None
98112
current_tag_name = unreleased_version or "Unreleased"
99113
current_tag_date = (
100114
date.today().isoformat() if unreleased_version is not None else ""

commitizen/changelog_formats/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
from __future__ import annotations
22

33
import sys
4-
from typing import Callable, ClassVar, Protocol
4+
from typing import TYPE_CHECKING, Callable, ClassVar, Protocol
55

66
if sys.version_info >= (3, 10):
77
from importlib import metadata
88
else:
99
import importlib_metadata as metadata
1010

11-
from commitizen.changelog import Metadata
1211
from commitizen.config.base_config import BaseConfig
1312
from commitizen.exceptions import ChangelogFormatUnknown
1413

14+
if TYPE_CHECKING:
15+
from commitizen.changelog import IncrementalMergeInfo, Metadata
16+
from commitizen.config.base_config import BaseConfig
17+
1518
CHANGELOG_FORMAT_ENTRYPOINT = "commitizen.changelog_format"
1619
TEMPLATE_EXTENSION = "j2"
1720

@@ -48,6 +51,12 @@ def get_metadata(self, filepath: str) -> Metadata:
4851
"""
4952
raise NotImplementedError
5053

54+
def get_latest_full_release(self, filepath: str) -> IncrementalMergeInfo:
55+
"""
56+
Extract metadata for the last non-pre-release.
57+
"""
58+
raise NotImplementedError
59+
5160

5261
KNOWN_CHANGELOG_FORMATS: dict[str, type[ChangelogFormat]] = {
5362
ep.name: ep.load()

commitizen/changelog_formats/base.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
import os
44
from abc import ABCMeta
5-
from typing import IO, Any, ClassVar
5+
from typing import IO, TYPE_CHECKING, Any, ClassVar
66

7-
from commitizen.changelog import Metadata
7+
from commitizen.changelog import IncrementalMergeInfo, Metadata
88
from commitizen.config.base_config import BaseConfig
9+
from commitizen.git import GitTag
910
from commitizen.tags import TagRules, VersionTag
1011
from commitizen.version_schemes import get_version_scheme
1112

1213
from . import ChangelogFormat
1314

15+
if TYPE_CHECKING:
16+
from commitizen.config.base_config import BaseConfig
17+
1418

1519
class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
1620
"""
@@ -58,17 +62,42 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
5862
meta.unreleased_end = index
5963

6064
# Try to find the latest release done
61-
parsed = self.parse_version_from_title(line)
62-
if parsed:
63-
meta.latest_version = parsed.version
64-
meta.latest_version_tag = parsed.tag
65+
parsed_version = self.parse_version_from_title(line)
66+
if parsed_version:
67+
meta.latest_version = parsed_version.version
68+
meta.latest_version_tag = parsed_version.tag
6569
meta.latest_version_position = index
6670
break # there's no need for more info
6771
if meta.unreleased_start is not None and meta.unreleased_end is None:
6872
meta.unreleased_end = index
6973

7074
return meta
7175

76+
def get_latest_full_release(self, filepath: str) -> IncrementalMergeInfo:
77+
if not os.path.isfile(filepath):
78+
return IncrementalMergeInfo()
79+
80+
with open(
81+
filepath, encoding=self.config.settings["encoding"]
82+
) as changelog_file:
83+
return self.get_latest_full_release_from_file(changelog_file)
84+
85+
def get_latest_full_release_from_file(self, file: IO[Any]) -> IncrementalMergeInfo:
86+
latest_version_index: int | None = None
87+
for index, line in enumerate(file):
88+
latest_version_index = index
89+
line = line.strip().lower()
90+
91+
parsed_version = self.parse_version_from_title(line)
92+
if (
93+
parsed_version
94+
and not self.tag_rules.extract_version(
95+
GitTag(parsed_version.tag, "", "")
96+
).is_prerelease
97+
):
98+
return IncrementalMergeInfo(name=parsed_version.tag, index=index)
99+
return IncrementalMergeInfo(index=latest_version_index)
100+
72101
def parse_version_from_title(self, line: str) -> VersionTag | None:
73102
"""
74103
Extract the version from a title line if any

commitizen/cli.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -543,13 +543,13 @@ def __call__(
543543
},
544544
{
545545
"name": ["--major"],
546-
"help": "get just the major version",
546+
"help": "get just the major version. Need to be used with --project or --verbose.",
547547
"action": "store_true",
548548
"exclusive_group": "group2",
549549
},
550550
{
551551
"name": ["--minor"],
552-
"help": "get just the minor version",
552+
"help": "get just the minor version. Need to be used with --project or --verbose.",
553553
"action": "store_true",
554554
"exclusive_group": "group2",
555555
},
@@ -559,8 +559,6 @@ def __call__(
559559
},
560560
}
561561

562-
original_excepthook = sys.excepthook
563-
564562

565563
def commitizen_excepthook(
566564
type: type[BaseException],
@@ -571,26 +569,19 @@ def commitizen_excepthook(
571569
) -> None:
572570
traceback = traceback if isinstance(traceback, TracebackType) else None
573571
if not isinstance(value, CommitizenException):
574-
original_excepthook(type, value, traceback)
572+
sys.__excepthook__(type, value, traceback)
575573
return
576574

577-
if not no_raise:
578-
no_raise = []
579575
if value.message:
580576
value.output_method(value.message)
581577
if debug:
582-
original_excepthook(type, value, traceback)
578+
sys.__excepthook__(type, value, traceback)
583579
exit_code = value.exit_code
584-
if exit_code in no_raise:
585-
exit_code = ExitCode.EXPECTED_EXIT
580+
if no_raise is not None and exit_code in no_raise:
581+
sys.exit(ExitCode.EXPECTED_EXIT)
586582
sys.exit(exit_code)
587583

588584

589-
commitizen_debug_excepthook = partial(commitizen_excepthook, debug=True)
590-
591-
sys.excepthook = commitizen_excepthook
592-
593-
594585
def parse_no_raise(comma_separated_no_raise: str) -> list[int]:
595586
"""Convert the given string to exit codes.
596587
@@ -682,15 +673,12 @@ def main() -> None:
682673
elif not conf.path:
683674
conf.update({"name": "cz_conventional_commits"})
684675

676+
sys.excepthook = commitizen_excepthook
685677
if args.debug:
686678
logging.getLogger("commitizen").setLevel(logging.DEBUG)
687-
sys.excepthook = commitizen_debug_excepthook
688-
elif args.no_raise:
689-
no_raise_exit_codes = parse_no_raise(args.no_raise)
690-
no_raise_debug_excepthook = partial(
691-
commitizen_excepthook, no_raise=no_raise_exit_codes
692-
)
693-
sys.excepthook = no_raise_debug_excepthook
679+
sys.excepthook = partial(sys.excepthook, debug=True)
680+
if args.no_raise:
681+
sys.excepthook = partial(sys.excepthook, no_raise=parse_no_raise(args.no_raise))
694682

695683
args.func(conf, arguments)() # type: ignore[arg-type]
696684

commitizen/cmd.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import os
44
import subprocess
5-
from collections.abc import Mapping
6-
from typing import NamedTuple
5+
from typing import TYPE_CHECKING, NamedTuple
76

87
from charset_normalizer import from_bytes
98

109
from commitizen.exceptions import CharacterSetDecodeError
1110

11+
if TYPE_CHECKING:
12+
from collections.abc import Mapping
13+
1214

1315
class Command(NamedTuple):
1416
out: str

0 commit comments

Comments
 (0)