Skip to content

Commit f17a654

Browse files
committed
fix(commit): resolve conflicts with upstream master
2 parents 3dc5471 + dd972c9 commit f17a654

File tree

82 files changed

+1764
-1377
lines changed

Some content is hidden

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

82 files changed

+1764
-1377
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ repos:
5656
- tomli
5757

5858
- repo: https://github.com/commitizen-tools/commitizen
59-
rev: v4.13.0 # automatically updated by Commitizen
59+
rev: v4.13.7 # automatically updated by Commitizen
6060
hooks:
6161
- id: commitizen
6262
- id: commitizen-branch

.pre-commit-hooks.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
the fact (e.g., pre-push or in CI) without an expensive check of the entire
2020
repository history.
2121
entry: cz check
22-
args: [--rev-range, origin/HEAD..HEAD]
22+
args: [--rev-range, "$PRE_COMMIT_FROM_REF..$PRE_COMMIT_TO_REF"]
2323
always_run: true
2424
pass_filenames: false
2525
language: python
2626
language_version: python3
2727
minimum_pre_commit_version: "3.2.0"
28+
stages: [pre-push]

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,46 @@
1+
## v4.13.7 (2026-02-09)
2+
3+
### Fix
4+
5+
- **provider**: use encoding settings in config (#1857)
6+
7+
## v4.13.6 (2026-02-07)
8+
9+
### Fix
10+
11+
- **bump**: preserve existing changelog header when `changelog_merge_prerelease` is used with `cz bump --changelog` (#1850)
12+
13+
## v4.13.5 (2026-02-05)
14+
15+
### Fix
16+
17+
- **changelog**: add incremental parameter to changelog generation (#1808)
18+
19+
## v4.13.4 (2026-02-04)
20+
21+
### Fix
22+
23+
- **pre-commit-hooks**: correct rev-range syntax in commitizen-branch (#1841)
24+
25+
## v4.13.3 (2026-02-04)
26+
27+
### Refactor
28+
29+
- **version_schemes**: shorten generate_prerelease (#1838)
30+
31+
## v4.13.2 (2026-02-03)
32+
33+
### Refactor
34+
35+
- simplify code with pathlib Path object (#1840)
36+
- **tags**: extract version resolution method (#1839)
37+
38+
## v4.13.1 (2026-02-03)
39+
40+
### Refactor
41+
42+
- **config**: replace is_empty_config with contains_commitizen_section, improve multi config resolution algorithm (#1842)
43+
144
## v4.13.0 (2026-02-01)
245

346
### Feat

commitizen/__version__.py

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

commitizen/changelog_formats/base.py

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

3-
import os
43
from abc import ABCMeta
4+
from pathlib import Path
55
from typing import IO, TYPE_CHECKING, Any, ClassVar
66

77
from commitizen.changelog import IncrementalMergeInfo, Metadata
@@ -36,12 +36,11 @@ def __init__(self, config: BaseConfig) -> None:
3636
)
3737

3838
def get_metadata(self, filepath: str) -> Metadata:
39-
if not os.path.isfile(filepath):
39+
file = Path(filepath)
40+
if not file.is_file():
4041
return Metadata()
4142

42-
with open(
43-
filepath, encoding=self.config.settings["encoding"]
44-
) as changelog_file:
43+
with file.open(encoding=self.config.settings["encoding"]) as changelog_file:
4544
return self.get_metadata_from_file(changelog_file)
4645

4746
def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
@@ -74,12 +73,11 @@ def get_metadata_from_file(self, file: IO[Any]) -> Metadata:
7473
return meta
7574

7675
def get_latest_full_release(self, filepath: str) -> IncrementalMergeInfo:
77-
if not os.path.isfile(filepath):
76+
file = Path(filepath)
77+
if not file.is_file():
7878
return IncrementalMergeInfo()
7979

80-
with open(
81-
filepath, encoding=self.config.settings["encoding"]
82-
) as changelog_file:
80+
with file.open(encoding=self.config.settings["encoding"]) as changelog_file:
8381
return self.get_latest_full_release_from_file(changelog_file)
8482

8583
def get_latest_full_release_from_file(self, file: IO[Any]) -> IncrementalMergeInfo:

commitizen/commands/changelog.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import annotations
22

3-
import os
4-
import os.path
53
from difflib import SequenceMatcher
64
from operator import itemgetter
75
from pathlib import Path
@@ -66,7 +64,7 @@ def __init__(self, config: BaseConfig, arguments: ChangelogArgs) -> None:
6664
f"or the setting `changelog_file` in {self.config.path}"
6765
)
6866
self.file_name = (
69-
os.path.join(str(self.config.path.parent), changelog_file_name)
67+
Path(self.config.path.parent, changelog_file_name).as_posix()
7068
if self.config.path is not None
7169
else changelog_file_name
7270
)
@@ -229,16 +227,32 @@ def __call__(self) -> None:
229227
latest_full_release_info = self.changelog_format.get_latest_full_release(
230228
self.file_name
231229
)
232-
if latest_full_release_info.index:
233-
changelog_meta.unreleased_start = 0
230+
# Determine if there are prereleases to merge:
231+
# - Only prereleases in changelog (no full release found), OR
232+
# - First version in changelog is before first full release (prereleases exist)
233+
if latest_full_release_info.index is not None and (
234+
latest_full_release_info.name is None
235+
or (
236+
changelog_meta.latest_version_position is not None
237+
and changelog_meta.latest_version_position
238+
< latest_full_release_info.index
239+
)
240+
):
241+
# Use the existing unreleased_start if available (from get_metadata()).
242+
# Otherwise, use the position of the first version entry (prerelease)
243+
# to preserve the changelog header.
244+
if changelog_meta.unreleased_start is None:
245+
changelog_meta.unreleased_start = (
246+
changelog_meta.latest_version_position
247+
)
234248
changelog_meta.latest_version_position = latest_full_release_info.index
235249
changelog_meta.unreleased_end = latest_full_release_info.index - 1
236250

237-
start_rev = latest_full_release_info.name or ""
238-
if not start_rev and latest_full_release_info.index:
239-
# Only pre-releases in changelog
240-
changelog_meta.latest_version_position = None
241-
changelog_meta.unreleased_end = latest_full_release_info.index + 1
251+
start_rev = latest_full_release_info.name or ""
252+
if not start_rev:
253+
# Only pre-releases in changelog
254+
changelog_meta.latest_version_position = None
255+
changelog_meta.unreleased_end = latest_full_release_info.index + 1
242256

243257
commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
244258
if not commits and (
@@ -268,6 +282,7 @@ def __call__(self) -> None:
268282
self.cz.template_loader,
269283
self.template,
270284
**{
285+
"incremental": self.incremental, # extra variable for the template
271286
**self.cz.template_extras,
272287
**self.config.settings["extras"],
273288
**self.extras,
@@ -282,9 +297,10 @@ def __call__(self) -> None:
282297
raise DryRunExit()
283298

284299
lines = []
285-
if self.incremental and os.path.isfile(self.file_name):
286-
with open(
287-
self.file_name, encoding=self.config.settings["encoding"]
300+
changelog_path = Path(self.file_name)
301+
if self.incremental and changelog_path.is_file():
302+
with changelog_path.open(
303+
encoding=self.config.settings["encoding"]
288304
) as changelog_file:
289305
lines = changelog_file.readlines()
290306

commitizen/commands/check.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import re
44
import sys
5+
from pathlib import Path
56
from typing import TYPE_CHECKING, TypedDict
67

78
from commitizen import factory, git, out
@@ -116,11 +117,10 @@ def _get_commit_message(self) -> str | None:
116117
# Get commit message from command line (--message)
117118
return self.commit_msg
118119

119-
with open(
120-
self.commit_msg_file, encoding=self.config.settings["encoding"]
121-
) as commit_file:
122-
# Get commit message from file (--commit-msg-file)
123-
return commit_file.read()
120+
# Get commit message from file (--commit-msg-file)
121+
return Path(self.commit_msg_file).read_text(
122+
encoding=self.config.settings["encoding"]
123+
)
124124

125125
def _get_commits(self) -> list[git.GitCommit]:
126126
if (msg := self._get_commit_message()) is not None:

commitizen/commands/commit.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tempfile
88
import textwrap
99
from itertools import chain
10+
from pathlib import Path
1011
from typing import TYPE_CHECKING, TypedDict
1112

1213
import questionary
@@ -28,8 +29,6 @@
2829
from commitizen.git import smart_open
2930

3031
if TYPE_CHECKING:
31-
from pathlib import Path
32-
3332
from commitizen.config import BaseConfig
3433

3534

@@ -64,10 +63,9 @@ def _read_backup_message(self) -> str | None:
6463
return None
6564

6665
# Read commit message from backup
67-
with open(
68-
self.backup_file_path, encoding=self.config.settings["encoding"]
69-
) as f:
70-
return f.read().strip()
66+
return self.backup_file_path.read_text(
67+
encoding=self.config.settings["encoding"]
68+
).strip()
7169

7270
def _get_message_by_prompt_commit_questions(self) -> str:
7371
# Prompt user for the commit message
@@ -140,8 +138,7 @@ def manual_edit(self, message: str) -> str:
140138
file_path = file.name
141139
argv = [exec_path, file_path]
142140
subprocess.call(argv)
143-
with open(file_path) as temp_file:
144-
message = temp_file.read().strip()
141+
message = Path(file_path).read_text().strip()
145142
os.unlink(file.name)
146143
return message
147144

commitizen/commands/init.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,24 +171,31 @@ def _ask_config_path(self) -> Path:
171171
def _ask_name(self) -> str:
172172
name: str = questionary.select(
173173
f"Please choose a cz (commit rule): (default: {DEFAULT_SETTINGS['name']})",
174-
choices=self._construct_name_choice_with_description(),
174+
choices=self._construct_name_choices_from_registry(),
175175
default=DEFAULT_SETTINGS["name"],
176176
style=self.cz.style,
177177
).unsafe_ask()
178178
return name
179179

180-
def _construct_name_choice_with_description(self) -> list[questionary.Choice]:
180+
def _construct_name_choices_from_registry(self) -> list[questionary.Choice]:
181+
"""
182+
Construct questionary choices of cz names from registry.
183+
"""
181184
choices = []
182185
for cz_name, cz_class in registry.items():
183186
try:
184187
cz_obj = cz_class(self.config)
185188
except MissingCzCustomizeConfigError:
189+
# Fallback if description is not available
186190
choices.append(questionary.Choice(title=cz_name, value=cz_name))
187191
continue
188-
first_example = cz_obj.schema().partition("\n")[0]
192+
193+
# Get the first line of the schema as the description
194+
# TODO(bearomorphism): schema is a workaround. Add a description method to the cz class.
195+
description = cz_obj.schema().partition("\n")[0]
189196
choices.append(
190197
questionary.Choice(
191-
title=cz_name, value=cz_name, description=first_example
198+
title=cz_name, value=cz_name, description=description
192199
)
193200
)
194201
return choices
@@ -288,11 +295,12 @@ def _get_config_data(self) -> dict[str, Any]:
288295
],
289296
}
290297

291-
if not Path(".pre-commit-config.yaml").is_file():
298+
pre_commit_config_path = Path(self._PRE_COMMIT_CONFIG_PATH)
299+
if not pre_commit_config_path.is_file():
292300
return {"repos": [CZ_HOOK_CONFIG]}
293301

294-
with open(
295-
self._PRE_COMMIT_CONFIG_PATH, encoding=self.config.settings["encoding"]
302+
with pre_commit_config_path.open(
303+
encoding=self.config.settings["encoding"]
296304
) as config_file:
297305
config_data: dict[str, Any] = yaml.safe_load(config_file) or {}
298306

commitizen/config/__init__.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,31 @@ def _resolve_config_candidates() -> list[BaseConfig]:
1313
git_project_root = git.find_git_project_root()
1414
cfg_search_paths = [Path(".")]
1515

16-
if git_project_root and not cfg_search_paths[0].samefile(git_project_root):
16+
if git_project_root and cfg_search_paths[0].resolve() != git_project_root.resolve():
1717
cfg_search_paths.append(git_project_root)
1818

19-
# The following algorithm is ugly, but we need to ensure that the order of the candidates are preserved before v5.
20-
# Also, the number of possible config files is limited, so the complexity is not a problem.
2119
candidates: list[BaseConfig] = []
2220
for dir in cfg_search_paths:
2321
for filename in defaults.CONFIG_FILES:
24-
out_path = dir / Path(filename)
25-
if (
26-
out_path.exists()
27-
and not any(
28-
out_path.samefile(candidate.path) for candidate in candidates
29-
)
30-
and not (conf := _create_config_from_path(out_path)).is_empty_config
31-
):
32-
candidates.append(conf)
22+
out_path = dir / filename
23+
if out_path.is_file():
24+
conf = _create_config_from_path(out_path)
25+
if conf.contains_commitizen_section():
26+
candidates.append(conf)
3327
return candidates
3428

3529

3630
def _create_config_from_path(path: Path) -> BaseConfig:
37-
with open(path, "rb") as f:
38-
data: bytes = f.read()
39-
40-
return create_config(data=data, path=path)
31+
return create_config(data=path.read_bytes(), path=path)
4132

4233

4334
def read_cfg(filepath: str | None = None) -> BaseConfig:
4435
if filepath is not None:
4536
conf_path = Path(filepath)
46-
if not conf_path.exists():
37+
if not conf_path.is_file():
4738
raise ConfigFileNotFound()
4839
conf = _create_config_from_path(conf_path)
49-
if conf.is_empty_config:
40+
if not conf.contains_commitizen_section():
5041
raise ConfigFileIsEmpty()
5142
return conf
5243

0 commit comments

Comments
 (0)