commitizen 4.8.3__tar.gz → 4.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {commitizen-4.8.3 → commitizen-4.9.0}/PKG-INFO +5 -4
  2. commitizen-4.9.0/commitizen/__version__.py +1 -0
  3. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog.py +81 -84
  4. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cli.py +22 -19
  5. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/bump.py +22 -39
  6. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/changelog.py +27 -25
  7. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/check.py +21 -13
  8. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/init.py +144 -133
  9. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/version.py +17 -15
  10. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/defaults.py +5 -3
  11. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/exceptions.py +11 -4
  12. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/git.py +10 -2
  13. commitizen-4.9.0/commitizen/providers/cargo_provider.py +94 -0
  14. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/commitizen_provider.py +4 -1
  15. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/scm_provider.py +2 -4
  16. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/tags.py +11 -9
  17. {commitizen-4.8.3 → commitizen-4.9.0}/docs/README.md +2 -1
  18. {commitizen-4.8.3 → commitizen-4.9.0}/pyproject.toml +6 -5
  19. commitizen-4.8.3/commitizen/__version__.py +0 -1
  20. commitizen-4.8.3/commitizen/providers/cargo_provider.py +0 -57
  21. {commitizen-4.8.3 → commitizen-4.9.0}/LICENSE +0 -0
  22. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/__init__.py +0 -0
  23. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/__main__.py +0 -0
  24. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/bump.py +0 -0
  25. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/__init__.py +0 -0
  26. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/asciidoc.py +0 -0
  27. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/base.py +0 -0
  28. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/markdown.py +0 -0
  29. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/restructuredtext.py +0 -0
  30. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/changelog_formats/textile.py +0 -0
  31. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cmd.py +0 -0
  32. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/__init__.py +0 -0
  33. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/commit.py +0 -0
  34. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/example.py +0 -0
  35. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/info.py +0 -0
  36. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/list_cz.py +0 -0
  37. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/commands/schema.py +0 -0
  38. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/config/__init__.py +0 -0
  39. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/config/base_config.py +0 -0
  40. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/config/json_config.py +0 -0
  41. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/config/toml_config.py +0 -0
  42. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/config/yaml_config.py +0 -0
  43. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/__init__.py +0 -0
  44. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/base.py +0 -0
  45. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/conventional_commits/__init__.py +0 -0
  46. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/conventional_commits/conventional_commits.py +0 -0
  47. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/conventional_commits/conventional_commits_info.txt +0 -0
  48. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/customize/__init__.py +0 -0
  49. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/customize/customize.py +0 -0
  50. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/customize/customize_info.txt +0 -0
  51. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/exceptions.py +0 -0
  52. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/jira/__init__.py +0 -0
  53. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/jira/jira.py +0 -0
  54. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/jira/jira_info.txt +0 -0
  55. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/cz/utils.py +0 -0
  56. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/factory.py +0 -0
  57. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/hooks.py +0 -0
  58. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/out.py +0 -0
  59. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/__init__.py +0 -0
  60. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/base_provider.py +0 -0
  61. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/composer_provider.py +0 -0
  62. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/npm_provider.py +0 -0
  63. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/pep621_provider.py +0 -0
  64. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/poetry_provider.py +0 -0
  65. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/providers/uv_provider.py +0 -0
  66. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/py.typed +0 -0
  67. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/question.py +0 -0
  68. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/templates/CHANGELOG.adoc.j2 +0 -0
  69. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/templates/CHANGELOG.md.j2 +0 -0
  70. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/templates/CHANGELOG.rst.j2 +0 -0
  71. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/templates/CHANGELOG.textile.j2 +0 -0
  72. {commitizen-4.8.3 → commitizen-4.9.0}/commitizen/version_schemes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: commitizen
3
- Version: 4.8.3
3
+ Version: 4.9.0
4
4
  Summary: Python commitizen client tool
5
5
  License: MIT License
6
6
 
@@ -47,10 +47,10 @@ Requires-Dist: argcomplete (>=1.12.1,<3.7)
47
47
  Requires-Dist: charset-normalizer (>=2.1.0,<4)
48
48
  Requires-Dist: colorama (>=0.4.1,<1.0)
49
49
  Requires-Dist: decli (>=0.6.0,<1.0)
50
- Requires-Dist: importlib-metadata (>=8.0.0,!=8.7.0,<9.0.0) ; python_version == "3.9"
51
- Requires-Dist: importlib-metadata (>=8.0.0,<9.0.0) ; python_version != "3.9"
50
+ Requires-Dist: importlib-metadata (>=8.0.0,<8.7.0) ; python_version < "3.10"
52
51
  Requires-Dist: jinja2 (>=2.10.3)
53
52
  Requires-Dist: packaging (>=19)
53
+ Requires-Dist: prompt_toolkit (!=3.0.52)
54
54
  Requires-Dist: pyyaml (>=3.08)
55
55
  Requires-Dist: questionary (>=2.0,<3.0)
56
56
  Requires-Dist: termcolor (>=1.1.0,<4.0.0)
@@ -128,6 +128,7 @@ Before installing Commitizen, ensure you have:
128
128
  #### Global Installation (Recommended)
129
129
 
130
130
  The recommended way to install Commitizen is using [`pipx`](https://pipx.pypa.io/) or [`uv`](https://docs.astral.sh/uv/), which ensures a clean, isolated installation:
131
+
131
132
  **Using pipx:**
132
133
  ```bash
133
134
  # Install Commitizen
@@ -176,7 +177,7 @@ poetry add commitizen --dev
176
177
 
177
178
  **Using uv:**
178
179
  ```bash
179
- uv add commitizen
180
+ uv add --dev commitizen
180
181
  ```
181
182
 
182
183
  **Using pdm:**
@@ -0,0 +1 @@
1
+ __version__ = "4.9.0"
@@ -29,11 +29,13 @@ from __future__ import annotations
29
29
 
30
30
  import re
31
31
  from collections import OrderedDict, defaultdict
32
- from collections.abc import Generator, Iterable, Mapping, Sequence
32
+ from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
33
33
  from dataclasses import dataclass
34
34
  from datetime import date
35
+ from itertools import chain
35
36
  from typing import TYPE_CHECKING, Any
36
37
 
38
+ from deprecated import deprecated
37
39
  from jinja2 import (
38
40
  BaseLoader,
39
41
  ChoiceLoader,
@@ -88,33 +90,32 @@ def generate_tree_from_commits(
88
90
  pat = re.compile(changelog_pattern)
89
91
  map_pat = re.compile(commit_parser, re.MULTILINE)
90
92
  body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL)
91
- current_tag: GitTag | None = None
92
93
  rules = rules or TagRules()
93
94
 
94
95
  # Check if the latest commit is not tagged
95
- if commits:
96
- latest_commit = commits[0]
97
- current_tag = get_commit_tag(latest_commit, tags)
98
-
99
- current_tag_name: str = unreleased_version or "Unreleased"
100
- current_tag_date: str = ""
101
- if unreleased_version is not None:
102
- current_tag_date = date.today().isoformat()
103
- if current_tag is not None and current_tag.name:
104
- current_tag_name = current_tag.name
105
- current_tag_date = current_tag.date
106
96
 
97
+ current_tag = get_commit_tag(commits[0], tags) if commits else None
98
+ current_tag_name = unreleased_version or "Unreleased"
99
+ current_tag_date = (
100
+ date.today().isoformat() if unreleased_version is not None else ""
101
+ )
102
+
103
+ used_tags: set[GitTag] = set()
104
+ if current_tag:
105
+ used_tags.add(current_tag)
106
+ if current_tag.name:
107
+ current_tag_name = current_tag.name
108
+ current_tag_date = current_tag.date
109
+
110
+ commit_tag: GitTag | None = None
107
111
  changes: dict = defaultdict(list)
108
- used_tags: list = [current_tag]
109
112
  for commit in commits:
110
- commit_tag = get_commit_tag(commit, tags)
111
-
112
113
  if (
113
- commit_tag
114
+ (commit_tag := get_commit_tag(commit, tags))
114
115
  and commit_tag not in used_tags
115
116
  and rules.include_in_changelog(commit_tag)
116
117
  ):
117
- used_tags.append(commit_tag)
118
+ used_tags.add(commit_tag)
118
119
  release = {
119
120
  "version": current_tag_name,
120
121
  "date": current_tag_date,
@@ -127,24 +128,15 @@ def generate_tree_from_commits(
127
128
  current_tag_date = commit_tag.date
128
129
  changes = defaultdict(list)
129
130
 
130
- matches = pat.match(commit.message)
131
- if not matches:
131
+ if not pat.match(commit.message):
132
132
  continue
133
133
 
134
- # Process subject from commit message
135
- if message := map_pat.match(commit.message):
136
- process_commit_message(
137
- changelog_message_builder_hook,
138
- message,
139
- commit,
140
- changes,
141
- change_type_map,
142
- )
143
-
144
- # Process body from commit message
145
- body_parts = commit.body.split("\n\n")
146
- for body_part in body_parts:
147
- if message := body_map_pat.match(body_part):
134
+ # Process subject and body from commit message
135
+ for message in chain(
136
+ [map_pat.match(commit.message)],
137
+ (body_map_pat.match(block) for block in commit.body.split("\n\n")),
138
+ ):
139
+ if message:
148
140
  process_commit_message(
149
141
  changelog_message_builder_hook,
150
142
  message,
@@ -167,8 +159,8 @@ def process_commit_message(
167
159
  hook: MessageBuilderHook | None,
168
160
  parsed: re.Match[str],
169
161
  commit: GitCommit,
170
- changes: dict[str | None, list],
171
- change_type_map: dict[str, str] | None = None,
162
+ ref_changes: MutableMapping[str | None, list],
163
+ change_type_map: Mapping[str, str] | None = None,
172
164
  ) -> None:
173
165
  message: dict[str, Any] = {
174
166
  "sha1": commit.rev,
@@ -178,13 +170,16 @@ def process_commit_message(
178
170
  **parsed.groupdict(),
179
171
  }
180
172
 
181
- if processed := hook(message, commit) if hook else message:
182
- messages = [processed] if isinstance(processed, dict) else processed
183
- for msg in messages:
184
- change_type = msg.pop("change_type", None)
185
- if change_type_map:
186
- change_type = change_type_map.get(change_type, change_type)
187
- changes[change_type].append(msg)
173
+ processed_msg = hook(message, commit) if hook else message
174
+ if not processed_msg:
175
+ return
176
+
177
+ messages = [processed_msg] if isinstance(processed_msg, dict) else processed_msg
178
+ for msg in messages:
179
+ change_type = msg.pop("change_type", None)
180
+ if change_type_map:
181
+ change_type = change_type_map.get(change_type, change_type)
182
+ ref_changes[change_type].append(msg)
188
183
 
189
184
 
190
185
  def generate_ordered_changelog_tree(
@@ -251,6 +246,7 @@ def incremental_build(
251
246
  unreleased_start = metadata.unreleased_start
252
247
  unreleased_end = metadata.unreleased_end
253
248
  latest_version_position = metadata.latest_version_position
249
+
254
250
  skip = False
255
251
  output_lines: list[str] = []
256
252
  for index, line in enumerate(lines):
@@ -260,9 +256,7 @@ def incremental_build(
260
256
  skip = False
261
257
  if (
262
258
  latest_version_position is None
263
- or isinstance(latest_version_position, int)
264
- and isinstance(unreleased_end, int)
265
- and latest_version_position > unreleased_end
259
+ or latest_version_position > unreleased_end
266
260
  ):
267
261
  continue
268
262
 
@@ -271,16 +265,32 @@ def incremental_build(
271
265
 
272
266
  if index == latest_version_position:
273
267
  output_lines.extend([new_content, "\n"])
274
-
275
268
  output_lines.append(line)
276
- if not isinstance(latest_version_position, int):
277
- if output_lines and output_lines[-1].strip():
278
- # Ensure at least one blank line between existing and new content.
279
- output_lines.append("\n")
280
- output_lines.append(new_content)
269
+
270
+ if latest_version_position is not None:
271
+ return output_lines
272
+
273
+ if output_lines and output_lines[-1].strip():
274
+ # Ensure at least one blank line between existing and new content.
275
+ output_lines.append("\n")
276
+ output_lines.append(new_content)
281
277
  return output_lines
282
278
 
283
279
 
280
+ def get_next_tag_name_after_version(tags: Iterable[GitTag], version: str) -> str | None:
281
+ it = iter(tag.name for tag in tags)
282
+ for name in it:
283
+ if name == version:
284
+ return next(it, None)
285
+
286
+ raise NoCommitsFoundError(f"Could not find a valid revision range. {version=}")
287
+
288
+
289
+ @deprecated(
290
+ reason="This function is unused and will be removed in v5",
291
+ version="5.0.0",
292
+ category=DeprecationWarning,
293
+ )
284
294
  def get_smart_tag_range(
285
295
  tags: Sequence[GitTag], newest: str, oldest: str | None = None
286
296
  ) -> list[GitTag]:
@@ -308,7 +318,7 @@ def get_smart_tag_range(
308
318
 
309
319
 
310
320
  def get_oldest_and_newest_rev(
311
- tags: Sequence[GitTag],
321
+ tags: Iterable[GitTag],
312
322
  version: str,
313
323
  rules: TagRules,
314
324
  ) -> tuple[str | None, str]:
@@ -318,39 +328,26 @@ def get_oldest_and_newest_rev(
318
328
  - `0.1.0..0.4.0`: as a range
319
329
  - `0.3.0`: as a single version
320
330
  """
321
- oldest: str | None = None
322
- newest: str | None = None
323
- try:
324
- oldest, newest = version.split("..")
325
- except ValueError:
326
- newest = version
327
- if not (newest_tag := rules.find_tag_for(tags, newest)):
331
+ oldest_version, sep, newest_version = version.partition("..")
332
+ if not sep:
333
+ newest_version = version
334
+ oldest_version = ""
335
+
336
+ def get_tag_name(v: str) -> str:
337
+ if tag := rules.find_tag_for(tags, v):
338
+ return tag.name
328
339
  raise NoCommitsFoundError("Could not find a valid revision range.")
329
340
 
330
- oldest_tag = None
331
- oldest_tag_name = None
332
- if oldest:
333
- if not (oldest_tag := rules.find_tag_for(tags, oldest)):
334
- raise NoCommitsFoundError("Could not find a valid revision range.")
335
- oldest_tag_name = oldest_tag.name
341
+ newest_tag_name = get_tag_name(newest_version)
342
+ oldest_tag_name = get_tag_name(oldest_version) if oldest_version else None
336
343
 
337
- tags_range = get_smart_tag_range(
338
- tags, newest=newest_tag.name, oldest=oldest_tag_name
344
+ oldest_rev = get_next_tag_name_after_version(
345
+ tags, oldest_tag_name or newest_tag_name
339
346
  )
340
- if not tags_range:
341
- raise NoCommitsFoundError("Could not find a valid revision range.")
342
-
343
- oldest_rev: str | None = tags_range[-1].name
344
- newest_rev = newest_tag.name
345
-
346
- # check if it's the first tag created
347
- # and it's also being requested as part of the range
348
- if oldest_rev == tags[-1].name and oldest_rev == oldest_tag_name:
349
- return None, newest_rev
350
-
351
- # when they are the same, and it's also the
352
- # first tag created
353
- if oldest_rev == newest_rev:
354
- return None, newest_rev
355
347
 
356
- return oldest_rev, newest_rev
348
+ # Return None for oldest_rev if:
349
+ # 1. The oldest tag is the last tag in the list and matches the requested oldest tag
350
+ # 2. The oldest and the newest tag are the same
351
+ if oldest_rev == newest_tag_name:
352
+ return None, newest_tag_name
353
+ return oldest_rev, newest_tag_name
@@ -52,16 +52,13 @@ class ParseKwargs(argparse.Action):
52
52
  ) -> None:
53
53
  if not isinstance(values, str):
54
54
  return
55
- if "=" not in values:
55
+
56
+ key, sep, value = values.partition("=")
57
+ if not key or not sep:
56
58
  raise InvalidCommandArgumentError(
57
59
  f"Option {option_string} expect a key=value format"
58
60
  )
59
61
  kwargs = getattr(namespace, self.dest, None) or {}
60
- key, value = values.split("=", 1)
61
- if not key:
62
- raise InvalidCommandArgumentError(
63
- f"Option {option_string} expect a key=value format"
64
- )
65
62
  kwargs[key] = value.strip("'\"")
66
63
  setattr(namespace, self.dest, kwargs)
67
64
 
@@ -474,6 +471,13 @@ data = {
474
471
  "help": "a range of git rev to check. e.g, master..HEAD",
475
472
  "exclusive_group": "group1",
476
473
  },
474
+ {
475
+ "name": ["-d", "--use-default-range"],
476
+ "action": "store_true",
477
+ "default": False,
478
+ "help": "check from the default branch to HEAD. e.g, refs/remotes/origin/master..HEAD",
479
+ "exclusive_group": "group1",
480
+ },
477
481
  {
478
482
  "name": ["-m", "--message"],
479
483
  "help": "commit message that needs to be checked",
@@ -583,20 +587,19 @@ def parse_no_raise(comma_separated_no_raise: str) -> list[int]:
583
587
  Receives digits and strings and outputs the parsed integer which
584
588
  represents the exit code found in exceptions.
585
589
  """
586
- no_raise_items: list[str] = comma_separated_no_raise.split(",")
587
- no_raise_codes: list[int] = []
588
- for item in no_raise_items:
589
- if item.isdecimal():
590
- no_raise_codes.append(int(item))
591
- continue
590
+
591
+ def exit_code_from_str_or_skip(s: str) -> ExitCode | None:
592
592
  try:
593
- exit_code = ExitCode[item.strip()]
594
- except KeyError:
595
- out.warn(f"WARN: no_raise key `{item}` does not exist. Skipping.")
596
- continue
597
- else:
598
- no_raise_codes.append(exit_code.value)
599
- return no_raise_codes
593
+ return ExitCode.from_str(s)
594
+ except (KeyError, ValueError):
595
+ out.warn(f"WARN: no_raise value `{s}` is not a valid exit code. Skipping.")
596
+ return None
597
+
598
+ return [
599
+ code.value
600
+ for s in comma_separated_no_raise.split(",")
601
+ if (code := exit_code_from_str_or_skip(s)) is not None
602
+ ]
600
603
 
601
604
 
602
605
  if TYPE_CHECKING:
@@ -23,7 +23,6 @@ from commitizen.exceptions import (
23
23
  NoPatternMapError,
24
24
  NotAGitProjectError,
25
25
  NotAllowed,
26
- NoVersionSpecifiedError,
27
26
  )
28
27
  from commitizen.providers import get_provider
29
28
  from commitizen.tags import TagRules
@@ -163,11 +162,7 @@ class Bump:
163
162
  def __call__(self) -> None:
164
163
  """Steps executed to bump."""
165
164
  provider = get_provider(self.config)
166
-
167
- try:
168
- current_version = self.scheme(provider.get_version())
169
- except TypeError:
170
- raise NoVersionSpecifiedError()
165
+ current_version = self.scheme(provider.get_version())
171
166
 
172
167
  increment = self.arguments["increment"]
173
168
  prerelease = self.arguments["prerelease"]
@@ -177,36 +172,22 @@ class Bump:
177
172
  build_metadata = self.arguments["build_metadata"]
178
173
  get_next = self.arguments["get_next"]
179
174
  allow_no_commit = self.arguments["allow_no_commit"]
175
+ major_version_zero = self.arguments["major_version_zero"]
180
176
 
181
177
  if manual_version:
182
- if increment:
183
- raise NotAllowed("--increment cannot be combined with MANUAL_VERSION")
184
-
185
- if prerelease:
186
- raise NotAllowed("--prerelease cannot be combined with MANUAL_VERSION")
187
-
188
- if devrelease is not None:
189
- raise NotAllowed("--devrelease cannot be combined with MANUAL_VERSION")
190
-
191
- if is_local_version:
192
- raise NotAllowed(
193
- "--local-version cannot be combined with MANUAL_VERSION"
194
- )
195
-
196
- if build_metadata:
197
- raise NotAllowed(
198
- "--build-metadata cannot be combined with MANUAL_VERSION"
199
- )
200
-
201
- if self.bump_settings["major_version_zero"]:
202
- raise NotAllowed(
203
- "--major-version-zero cannot be combined with MANUAL_VERSION"
204
- )
205
-
206
- if get_next:
207
- raise NotAllowed("--get-next cannot be combined with MANUAL_VERSION")
208
-
209
- if self.bump_settings["major_version_zero"] and current_version.release[0]:
178
+ for val, option in (
179
+ (increment, "--increment"),
180
+ (prerelease, "--prerelease"),
181
+ (devrelease is not None, "--devrelease"),
182
+ (is_local_version, "--local-version"),
183
+ (build_metadata, "--build-metadata"),
184
+ (major_version_zero, "--major-version-zero"),
185
+ (get_next, "--get-next"),
186
+ ):
187
+ if val:
188
+ raise NotAllowed(f"{option} cannot be combined with MANUAL_VERSION")
189
+
190
+ if major_version_zero and current_version.release[0]:
210
191
  raise NotAllowed(
211
192
  f"--major-version-zero is meaningless for current version {current_version}"
212
193
  )
@@ -215,11 +196,13 @@ class Bump:
215
196
  raise NotAllowed("--local-version cannot be combined with --build-metadata")
216
197
 
217
198
  if get_next:
218
- # if trying to use --get-next, we should not allow --changelog or --changelog-to-stdout
219
- if self.changelog_flag or self.changelog_to_stdout:
220
- raise NotAllowed(
221
- "--changelog or --changelog-to-stdout is not allowed with --get-next"
222
- )
199
+ for value, option in (
200
+ (self.changelog_flag, "--changelog"),
201
+ (self.changelog_to_stdout, "--changelog-to-stdout"),
202
+ ):
203
+ if value:
204
+ raise NotAllowed(f"{option} cannot be combined with --get-next")
205
+
223
206
  # --get-next is a special case, taking precedence over config for 'update_changelog_on_bump'
224
207
  self.changelog_config = False
225
208
  # Setting dry_run to prevent any unwanted changes to the repo or files
@@ -11,7 +11,6 @@ from typing import Any, TypedDict, cast
11
11
  from commitizen import changelog, defaults, factory, git, out
12
12
  from commitizen.changelog_formats import get_changelog_format
13
13
  from commitizen.config import BaseConfig
14
- from commitizen.cz.base import ChangelogReleaseHook, MessageBuilderHook
15
14
  from commitizen.cz.utils import strip_local_version
16
15
  from commitizen.exceptions import (
17
16
  DryRunExit,
@@ -174,28 +173,23 @@ class Changelog:
174
173
 
175
174
  changelog_file.write(changelog_out)
176
175
 
177
- def _export_template(self) -> None:
178
- tpl = changelog.get_changelog_template(self.cz.template_loader, self.template)
179
- # TODO: fix the following type ignores
180
- src = Path(tpl.filename) # type: ignore[arg-type]
181
- Path(self.export_template_to).write_text(src.read_text()) # type: ignore[arg-type]
176
+ def _export_template(self, dist: str) -> None:
177
+ filename = changelog.get_changelog_template(
178
+ self.cz.template_loader, self.template
179
+ ).filename
180
+ if filename is None:
181
+ raise NotAllowed("Template filename is not set")
182
+
183
+ text = Path(filename).read_text()
184
+ Path(dist).write_text(text)
182
185
 
183
186
  def __call__(self) -> None:
184
187
  commit_parser = self.cz.commit_parser
185
188
  changelog_pattern = self.cz.changelog_pattern
186
189
  start_rev = self.start_rev
187
- unreleased_version = self.unreleased_version
188
- changelog_meta = changelog.Metadata()
189
- change_type_map: dict[str, str] | None = self.change_type_map
190
- changelog_message_builder_hook: MessageBuilderHook | None = (
191
- self.cz.changelog_message_builder_hook
192
- )
193
- changelog_release_hook: ChangelogReleaseHook | None = (
194
- self.cz.changelog_release_hook
195
- )
196
190
 
197
191
  if self.export_template_to:
198
- return self._export_template()
192
+ return self._export_template(self.export_template_to)
199
193
 
200
194
  if not changelog_pattern or not commit_parser:
201
195
  raise NoPatternMapError(
@@ -209,33 +203,37 @@ class Changelog:
209
203
  assert self.file_name
210
204
 
211
205
  tags = self.tag_rules.get_version_tags(git.get_tags(), warn=True)
212
- end_rev = ""
206
+ changelog_meta = changelog.Metadata()
213
207
  if self.incremental:
214
208
  changelog_meta = self.changelog_format.get_metadata(self.file_name)
215
209
  if changelog_meta.latest_version:
216
210
  start_rev = self._find_incremental_rev(
217
211
  strip_local_version(changelog_meta.latest_version_tag or ""), tags
218
212
  )
213
+
214
+ end_rev = ""
219
215
  if self.rev_range:
220
216
  start_rev, end_rev = changelog.get_oldest_and_newest_rev(
221
217
  tags,
222
218
  self.rev_range,
223
219
  self.tag_rules,
224
220
  )
221
+
225
222
  commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
226
223
  if not commits and (
227
224
  self.current_version is None or not self.current_version.is_prerelease
228
225
  ):
229
226
  raise NoCommitsFoundError("No commits found")
227
+
230
228
  tree = changelog.generate_tree_from_commits(
231
229
  commits,
232
230
  tags,
233
231
  commit_parser,
234
232
  changelog_pattern,
235
- unreleased_version,
236
- change_type_map=change_type_map,
237
- changelog_message_builder_hook=changelog_message_builder_hook,
238
- changelog_release_hook=changelog_release_hook,
233
+ self.unreleased_version,
234
+ change_type_map=self.change_type_map,
235
+ changelog_message_builder_hook=self.cz.changelog_message_builder_hook,
236
+ changelog_release_hook=self.cz.changelog_release_hook,
239
237
  rules=self.tag_rules,
240
238
  )
241
239
  if self.change_type_order:
@@ -243,11 +241,15 @@ class Changelog:
243
241
  tree, self.change_type_order
244
242
  )
245
243
 
246
- extras = self.cz.template_extras.copy()
247
- extras.update(self.config.settings["extras"])
248
- extras.update(self.extras)
249
244
  changelog_out = changelog.render_changelog(
250
- tree, loader=self.cz.template_loader, template=self.template, **extras
245
+ tree,
246
+ self.cz.template_loader,
247
+ self.template,
248
+ **{
249
+ **self.cz.template_extras,
250
+ **self.config.settings["extras"],
251
+ **self.extras,
252
+ },
251
253
  ).lstrip("\n")
252
254
 
253
255
  # Dry_run is executed here to avoid checking and reading the files
@@ -21,6 +21,7 @@ class CheckArgs(TypedDict, total=False):
21
21
  message_length_limit: int
22
22
  allowed_prefixes: list[str]
23
23
  message: str
24
+ use_default_range: bool
24
25
 
25
26
 
26
27
  class Check:
@@ -40,6 +41,7 @@ class Check:
40
41
  self.allow_abort = bool(
41
42
  arguments.get("allow_abort", config.settings["allow_abort"])
42
43
  )
44
+ self.use_default_range = bool(arguments.get("use_default_range"))
43
45
  self.max_msg_length = arguments.get("message_length_limit", 0)
44
46
 
45
47
  # we need to distinguish between None and [], which is a valid value
@@ -50,25 +52,28 @@ class Check:
50
52
  else config.settings["allowed_prefixes"]
51
53
  )
52
54
 
53
- self._valid_command_argument()
54
-
55
- self.config: BaseConfig = config
56
- self.encoding = config.settings["encoding"]
57
- self.cz = factory.committer_factory(self.config)
58
-
59
- def _valid_command_argument(self) -> None:
60
55
  num_exclusive_args_provided = sum(
61
56
  arg is not None
62
- for arg in (self.commit_msg_file, self.commit_msg, self.rev_range)
57
+ for arg in (
58
+ self.commit_msg_file,
59
+ self.commit_msg,
60
+ self.rev_range,
61
+ )
63
62
  )
64
- if num_exclusive_args_provided == 0 and not sys.stdin.isatty():
65
- self.commit_msg = sys.stdin.read()
66
- elif num_exclusive_args_provided != 1:
63
+
64
+ if num_exclusive_args_provided > 1:
67
65
  raise InvalidCommandArgumentError(
68
66
  "Only one of --rev-range, --message, and --commit-msg-file is permitted by check command! "
69
67
  "See 'cz check -h' for more information"
70
68
  )
71
69
 
70
+ if num_exclusive_args_provided == 0 and not sys.stdin.isatty():
71
+ self.commit_msg = sys.stdin.read()
72
+
73
+ self.config: BaseConfig = config
74
+ self.encoding = config.settings["encoding"]
75
+ self.cz = factory.committer_factory(self.config)
76
+
72
77
  def __call__(self) -> None:
73
78
  """Validate if commit messages follows the conventional pattern.
74
79
 
@@ -109,7 +114,10 @@ class Check:
109
114
  return [git.GitCommit(rev="", title="", body=self._filter_comments(msg))]
110
115
 
111
116
  # Get commit messages from git log (--rev-range)
112
- return git.get_commits(end=self.rev_range)
117
+ return git.get_commits(
118
+ git.get_default_branch() if self.use_default_range else None,
119
+ self.rev_range,
120
+ )
113
121
 
114
122
  @staticmethod
115
123
  def _filter_comments(msg: str) -> str:
@@ -134,7 +142,7 @@ class Check:
134
142
  The filtered commit message without comments.
135
143
  """
136
144
 
137
- lines = []
145
+ lines: list[str] = []
138
146
  for line in msg.split("\n"):
139
147
  if "# ------------------------ >8 ------------------------" in line:
140
148
  break