commitizen 4.2.2__tar.gz → 4.4.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 (74) hide show
  1. {commitizen-4.2.2 → commitizen-4.4.0}/PKG-INFO +1 -1
  2. commitizen-4.4.0/commitizen/__version__.py +1 -0
  3. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/bump.py +1 -29
  4. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/changelog.py +26 -58
  5. commitizen-4.4.0/commitizen/changelog_formats/asciidoc.py +28 -0
  6. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/changelog_formats/base.py +12 -16
  7. commitizen-4.4.0/commitizen/changelog_formats/markdown.py +29 -0
  8. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/changelog_formats/restructuredtext.py +5 -26
  9. commitizen-4.4.0/commitizen/changelog_formats/textile.py +26 -0
  10. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/bump.py +19 -19
  11. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/changelog.py +18 -21
  12. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/init.py +1 -1
  13. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/defaults.py +15 -13
  14. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/__init__.py +2 -0
  15. commitizen-4.4.0/commitizen/providers/scm_provider.py +28 -0
  16. commitizen-4.4.0/commitizen/providers/uv_provider.py +41 -0
  17. commitizen-4.4.0/commitizen/tags.py +257 -0
  18. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/version_schemes.py +6 -6
  19. {commitizen-4.2.2 → commitizen-4.4.0}/pyproject.toml +12 -13
  20. commitizen-4.2.2/commitizen/__version__.py +0 -1
  21. commitizen-4.2.2/commitizen/changelog_formats/asciidoc.py +0 -39
  22. commitizen-4.2.2/commitizen/changelog_formats/markdown.py +0 -42
  23. commitizen-4.2.2/commitizen/changelog_formats/textile.py +0 -42
  24. commitizen-4.2.2/commitizen/providers/scm_provider.py +0 -80
  25. {commitizen-4.2.2 → commitizen-4.4.0}/LICENSE +0 -0
  26. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/__init__.py +0 -0
  27. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/__main__.py +0 -0
  28. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/changelog_formats/__init__.py +0 -0
  29. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cli.py +0 -0
  30. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cmd.py +0 -0
  31. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/__init__.py +0 -0
  32. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/check.py +0 -0
  33. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/commit.py +0 -0
  34. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/example.py +0 -0
  35. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/info.py +0 -0
  36. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/list_cz.py +0 -0
  37. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/schema.py +0 -0
  38. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/commands/version.py +0 -0
  39. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/config/__init__.py +0 -0
  40. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/config/base_config.py +0 -0
  41. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/config/json_config.py +0 -0
  42. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/config/toml_config.py +0 -0
  43. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/config/yaml_config.py +0 -0
  44. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/__init__.py +0 -0
  45. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/base.py +0 -0
  46. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/conventional_commits/__init__.py +0 -0
  47. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/conventional_commits/conventional_commits.py +0 -0
  48. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/conventional_commits/conventional_commits_info.txt +0 -0
  49. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/customize/__init__.py +0 -0
  50. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/customize/customize.py +0 -0
  51. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/customize/customize_info.txt +0 -0
  52. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/exceptions.py +0 -0
  53. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/jira/__init__.py +0 -0
  54. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/jira/jira.py +0 -0
  55. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/jira/jira_info.txt +0 -0
  56. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/cz/utils.py +0 -0
  57. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/exceptions.py +0 -0
  58. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/factory.py +0 -0
  59. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/git.py +0 -0
  60. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/hooks.py +0 -0
  61. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/out.py +0 -0
  62. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/base_provider.py +0 -0
  63. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/cargo_provider.py +0 -0
  64. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/commitizen_provider.py +0 -0
  65. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/composer_provider.py +0 -0
  66. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/npm_provider.py +0 -0
  67. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/pep621_provider.py +0 -0
  68. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/providers/poetry_provider.py +0 -0
  69. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/py.typed +0 -0
  70. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/templates/CHANGELOG.adoc.j2 +0 -0
  71. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/templates/CHANGELOG.md.j2 +0 -0
  72. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/templates/CHANGELOG.rst.j2 +0 -0
  73. {commitizen-4.2.2 → commitizen-4.4.0}/commitizen/templates/CHANGELOG.textile.j2 +0 -0
  74. {commitizen-4.2.2 → commitizen-4.4.0}/docs/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: commitizen
3
- Version: 4.2.2
3
+ Version: 4.4.0
4
4
  Summary: Python commitizen client tool
5
5
  License: LICENSE
6
6
  Keywords: commitizen,conventional,commits,git
@@ -0,0 +1 @@
1
+ __version__ = "4.4.0"
@@ -11,7 +11,7 @@ from typing import cast
11
11
  from commitizen.defaults import MAJOR, MINOR, PATCH, bump_message, encoding
12
12
  from commitizen.exceptions import CurrentVersionNotFoundError
13
13
  from commitizen.git import GitCommit, smart_open
14
- from commitizen.version_schemes import DEFAULT_SCHEME, Increment, Version, VersionScheme
14
+ from commitizen.version_schemes import Increment, Version
15
15
 
16
16
  VERSION_TYPES = [None, PATCH, MINOR, MAJOR]
17
17
 
@@ -142,34 +142,6 @@ def _version_to_regex(version: str) -> str:
142
142
  return version.replace(".", r"\.").replace("+", r"\+")
143
143
 
144
144
 
145
- def normalize_tag(
146
- version: Version | str,
147
- tag_format: str,
148
- scheme: VersionScheme | None = None,
149
- ) -> str:
150
- """The tag and the software version might be different.
151
-
152
- That's why this function exists.
153
-
154
- Example:
155
- | tag | version (PEP 0440) |
156
- | --- | ------- |
157
- | v0.9.0 | 0.9.0 |
158
- | ver1.0.0 | 1.0.0 |
159
- | ver1.0.0.a0 | 1.0.0a0 |
160
- """
161
- scheme = scheme or DEFAULT_SCHEME
162
- version = scheme(version) if isinstance(version, str) else version
163
-
164
- major, minor, patch = version.release
165
- prerelease = version.prerelease or ""
166
-
167
- t = Template(tag_format)
168
- return t.safe_substitute(
169
- version=version, major=major, minor=minor, patch=patch, prerelease=prerelease
170
- )
171
-
172
-
173
145
  def create_commit_message(
174
146
  current_version: Version | str,
175
147
  new_version: Version | str,
@@ -42,21 +42,13 @@ from jinja2 import (
42
42
  Template,
43
43
  )
44
44
 
45
- from commitizen import out
46
- from commitizen.bump import normalize_tag
47
45
  from commitizen.cz.base import ChangelogReleaseHook
48
- from commitizen.defaults import get_tag_regexes
49
46
  from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
50
47
  from commitizen.git import GitCommit, GitTag
51
- from commitizen.version_schemes import (
52
- DEFAULT_SCHEME,
53
- BaseVersion,
54
- InvalidVersion,
55
- )
48
+ from commitizen.tags import TagRules
56
49
 
57
50
  if TYPE_CHECKING:
58
51
  from commitizen.cz.base import MessageBuilderHook
59
- from commitizen.version_schemes import VersionScheme
60
52
 
61
53
 
62
54
  @dataclass
@@ -69,50 +61,19 @@ class Metadata:
69
61
  unreleased_end: int | None = None
70
62
  latest_version: str | None = None
71
63
  latest_version_position: int | None = None
64
+ latest_version_tag: str | None = None
65
+
66
+ def __post_init__(self):
67
+ if self.latest_version and not self.latest_version_tag:
68
+ # Test syntactic sugar
69
+ # latest version tag is optional if same as latest version
70
+ self.latest_version_tag = self.latest_version
72
71
 
73
72
 
74
73
  def get_commit_tag(commit: GitCommit, tags: list[GitTag]) -> GitTag | None:
75
74
  return next((tag for tag in tags if tag.rev == commit.rev), None)
76
75
 
77
76
 
78
- def tag_included_in_changelog(
79
- tag: GitTag,
80
- used_tags: list,
81
- merge_prerelease: bool,
82
- scheme: VersionScheme = DEFAULT_SCHEME,
83
- ) -> bool:
84
- if tag in used_tags:
85
- return False
86
-
87
- try:
88
- version = scheme(tag.name)
89
- except InvalidVersion:
90
- return False
91
-
92
- if merge_prerelease and version.is_prerelease:
93
- return False
94
-
95
- return True
96
-
97
-
98
- def get_version_tags(
99
- scheme: type[BaseVersion], tags: list[GitTag], tag_format: str
100
- ) -> list[GitTag]:
101
- valid_tags: list[GitTag] = []
102
- TAG_FORMAT_REGEXS = get_tag_regexes(scheme.parser.pattern)
103
- tag_format_regex = tag_format
104
- for pattern, regex in TAG_FORMAT_REGEXS.items():
105
- tag_format_regex = tag_format_regex.replace(pattern, regex)
106
- for tag in tags:
107
- if re.match(tag_format_regex, tag.name):
108
- valid_tags.append(tag)
109
- else:
110
- out.warn(
111
- f"InvalidVersion {tag.name} doesn't match configured tag format {tag_format}"
112
- )
113
- return valid_tags
114
-
115
-
116
77
  def generate_tree_from_commits(
117
78
  commits: list[GitCommit],
118
79
  tags: list[GitTag],
@@ -122,13 +83,13 @@ def generate_tree_from_commits(
122
83
  change_type_map: dict[str, str] | None = None,
123
84
  changelog_message_builder_hook: MessageBuilderHook | None = None,
124
85
  changelog_release_hook: ChangelogReleaseHook | None = None,
125
- merge_prerelease: bool = False,
126
- scheme: VersionScheme = DEFAULT_SCHEME,
86
+ rules: TagRules | None = None,
127
87
  ) -> Iterable[dict]:
128
88
  pat = re.compile(changelog_pattern)
129
89
  map_pat = re.compile(commit_parser, re.MULTILINE)
130
90
  body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL)
131
91
  current_tag: GitTag | None = None
92
+ rules = rules or TagRules()
132
93
 
133
94
  # Check if the latest commit is not tagged
134
95
  if commits:
@@ -148,8 +109,10 @@ def generate_tree_from_commits(
148
109
  for commit in commits:
149
110
  commit_tag = get_commit_tag(commit, tags)
150
111
 
151
- if commit_tag is not None and tag_included_in_changelog(
152
- commit_tag, used_tags, merge_prerelease, scheme=scheme
112
+ if (
113
+ commit_tag
114
+ and commit_tag not in used_tags
115
+ and rules.include_in_changelog(commit_tag)
153
116
  ):
154
117
  used_tags.append(commit_tag)
155
118
  release = {
@@ -343,8 +306,7 @@ def get_smart_tag_range(
343
306
  def get_oldest_and_newest_rev(
344
307
  tags: list[GitTag],
345
308
  version: str,
346
- tag_format: str,
347
- scheme: VersionScheme | None = None,
309
+ rules: TagRules,
348
310
  ) -> tuple[str | None, str | None]:
349
311
  """Find the tags for the given version.
350
312
 
@@ -358,22 +320,28 @@ def get_oldest_and_newest_rev(
358
320
  oldest, newest = version.split("..")
359
321
  except ValueError:
360
322
  newest = version
361
- newest_tag = normalize_tag(newest, tag_format=tag_format, scheme=scheme)
323
+ if not (newest_tag := rules.find_tag_for(tags, newest)):
324
+ raise NoCommitsFoundError("Could not find a valid revision range.")
362
325
 
363
326
  oldest_tag = None
327
+ oldest_tag_name = None
364
328
  if oldest:
365
- oldest_tag = normalize_tag(oldest, tag_format=tag_format, scheme=scheme)
329
+ if not (oldest_tag := rules.find_tag_for(tags, oldest)):
330
+ raise NoCommitsFoundError("Could not find a valid revision range.")
331
+ oldest_tag_name = oldest_tag.name
366
332
 
367
- tags_range = get_smart_tag_range(tags, newest=newest_tag, oldest=oldest_tag)
333
+ tags_range = get_smart_tag_range(
334
+ tags, newest=newest_tag.name, oldest=oldest_tag_name
335
+ )
368
336
  if not tags_range:
369
337
  raise NoCommitsFoundError("Could not find a valid revision range.")
370
338
 
371
339
  oldest_rev: str | None = tags_range[-1].name
372
- newest_rev = newest_tag
340
+ newest_rev = newest_tag.name
373
341
 
374
342
  # check if it's the first tag created
375
343
  # and it's also being requested as part of the range
376
- if oldest_rev == tags[-1].name and oldest_rev == oldest_tag:
344
+ if oldest_rev == tags[-1].name and oldest_rev == oldest_tag_name:
377
345
  return None, newest_rev
378
346
 
379
347
  # when they are the same, and it's also the
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .base import BaseFormat
7
+
8
+ if TYPE_CHECKING:
9
+ from commitizen.tags import VersionTag
10
+
11
+
12
+ class AsciiDoc(BaseFormat):
13
+ extension = "adoc"
14
+
15
+ RE_TITLE = re.compile(r"^(?P<level>=+) (?P<title>.*)$")
16
+
17
+ def parse_version_from_title(self, line: str) -> VersionTag | None:
18
+ m = self.RE_TITLE.match(line)
19
+ if not m:
20
+ return None
21
+ # Capture last match as AsciiDoc use postfixed URL labels
22
+ return self.tag_rules.search_version(m.group("title"), last=True)
23
+
24
+ def parse_title_level(self, line: str) -> int | None:
25
+ m = self.RE_TITLE.match(line)
26
+ if not m:
27
+ return None
28
+ return len(m.group("level"))
@@ -1,14 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- import re
5
4
  from abc import ABCMeta
6
- from re import Pattern
7
5
  from typing import IO, Any, ClassVar
8
6
 
9
7
  from commitizen.changelog import Metadata
10
8
  from commitizen.config.base_config import BaseConfig
11
- from commitizen.defaults import get_tag_regexes
9
+ from commitizen.tags import TagRules, VersionTag
12
10
  from commitizen.version_schemes import get_version_scheme
13
11
 
14
12
  from . import ChangelogFormat
@@ -28,15 +26,12 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
28
26
  self.config = config
29
27
  self.encoding = self.config.settings["encoding"]
30
28
  self.tag_format = self.config.settings["tag_format"]
31
-
32
- @property
33
- def version_parser(self) -> Pattern:
34
- tag_regex: str = self.tag_format
35
- version_regex = get_version_scheme(self.config).parser.pattern
36
- TAG_FORMAT_REGEXS = get_tag_regexes(version_regex)
37
- for pattern, regex in TAG_FORMAT_REGEXS.items():
38
- tag_regex = tag_regex.replace(pattern, regex)
39
- return re.compile(tag_regex)
29
+ self.tag_rules = TagRules(
30
+ scheme=get_version_scheme(self.config.settings),
31
+ tag_format=self.tag_format,
32
+ legacy_tag_formats=self.config.settings["legacy_tag_formats"],
33
+ ignored_tag_formats=self.config.settings["ignored_tag_formats"],
34
+ )
40
35
 
41
36
  def get_metadata(self, filepath: str) -> Metadata:
42
37
  if not os.path.isfile(filepath):
@@ -63,9 +58,10 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
63
58
  meta.unreleased_end = index
64
59
 
65
60
  # Try to find the latest release done
66
- version = self.parse_version_from_title(line)
67
- if version:
68
- meta.latest_version = version
61
+ parsed = self.parse_version_from_title(line)
62
+ if parsed:
63
+ meta.latest_version = parsed.version
64
+ meta.latest_version_tag = parsed.tag
69
65
  meta.latest_version_position = index
70
66
  break # there's no need for more info
71
67
  if meta.unreleased_start is not None and meta.unreleased_end is None:
@@ -73,7 +69,7 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
73
69
 
74
70
  return meta
75
71
 
76
- def parse_version_from_title(self, line: str) -> str | None:
72
+ def parse_version_from_title(self, line: str) -> VersionTag | None:
77
73
  """
78
74
  Extract the version from a title line if any
79
75
  """
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .base import BaseFormat
7
+
8
+ if TYPE_CHECKING:
9
+ from commitizen.tags import VersionTag
10
+
11
+
12
+ class Markdown(BaseFormat):
13
+ extension = "md"
14
+
15
+ alternative_extensions = {"markdown", "mkd"}
16
+
17
+ RE_TITLE = re.compile(r"^(?P<level>#+) (?P<title>.*)$")
18
+
19
+ def parse_version_from_title(self, line: str) -> VersionTag | None:
20
+ m = self.RE_TITLE.match(line)
21
+ if not m:
22
+ return None
23
+ return self.tag_rules.search_version(m.group("title"))
24
+
25
+ def parse_title_level(self, line: str) -> int | None:
26
+ m = self.RE_TITLE.match(line)
27
+ if not m:
28
+ return None
29
+ return len(m.group("level"))
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
3
  import sys
5
4
  from itertools import zip_longest
6
5
  from typing import IO, TYPE_CHECKING, Any, Union
@@ -64,31 +63,11 @@ class RestructuredText(BaseFormat):
64
63
  elif unreleased_title_kind and unreleased_title_kind == kind:
65
64
  meta.unreleased_end = index
66
65
  # Try to find the latest release done
67
- m = re.search(self.version_parser, title)
68
- if m:
69
- matches = m.groupdict()
70
- if "version" in matches:
71
- version = m.group("version")
72
- meta.latest_version = version
73
- meta.latest_version_position = index
74
- break # there's no need for more info
75
- try:
76
- partial_version = (
77
- f"{matches['major']}.{matches['minor']}.{matches['patch']}"
78
- )
79
- if matches.get("prerelease"):
80
- partial_version = (
81
- f"{partial_version}-{matches['prerelease']}"
82
- )
83
- if matches.get("devrelease"):
84
- partial_version = (
85
- f"{partial_version}{matches['devrelease']}"
86
- )
87
- meta.latest_version = partial_version
88
- meta.latest_version_position = index
89
- break
90
- except KeyError:
91
- pass
66
+ if version := self.tag_rules.search_version(title):
67
+ meta.latest_version = version[0]
68
+ meta.latest_version_tag = version[1]
69
+ meta.latest_version_position = index
70
+ break
92
71
  if meta.unreleased_start is not None and meta.unreleased_end is None:
93
72
  meta.unreleased_end = (
94
73
  meta.latest_version_position if meta.latest_version else index + 1
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .base import BaseFormat
7
+
8
+ if TYPE_CHECKING:
9
+ from commitizen.tags import VersionTag
10
+
11
+
12
+ class Textile(BaseFormat):
13
+ extension = "textile"
14
+
15
+ RE_TITLE = re.compile(r"^h(?P<level>\d)\. (?P<title>.*)$")
16
+
17
+ def parse_version_from_title(self, line: str) -> VersionTag | None:
18
+ if not self.RE_TITLE.match(line):
19
+ return None
20
+ return self.tag_rules.search_version(line)
21
+
22
+ def parse_title_level(self, line: str) -> int | None:
23
+ m = self.RE_TITLE.match(line)
24
+ if not m:
25
+ return None
26
+ return int(m.group("level"))
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import warnings
4
4
  from logging import getLogger
5
+ from typing import cast
5
6
 
6
7
  import questionary
7
8
 
@@ -9,6 +10,7 @@ from commitizen import bump, factory, git, hooks, out
9
10
  from commitizen.changelog_formats import get_changelog_format
10
11
  from commitizen.commands.changelog import Changelog
11
12
  from commitizen.config import BaseConfig
13
+ from commitizen.defaults import Settings
12
14
  from commitizen.exceptions import (
13
15
  BumpCommitFailedError,
14
16
  BumpTagFailedError,
@@ -24,6 +26,7 @@ from commitizen.exceptions import (
24
26
  NoVersionSpecifiedError,
25
27
  )
26
28
  from commitizen.providers import get_provider
29
+ from commitizen.tags import TagRules
27
30
  from commitizen.version_schemes import (
28
31
  Increment,
29
32
  InvalidVersion,
@@ -84,7 +87,7 @@ class Bump:
84
87
  )
85
88
  )
86
89
  self.scheme = get_version_scheme(
87
- self.config, arguments["version_scheme"] or deprecated_version_type
90
+ self.config.settings, arguments["version_scheme"] or deprecated_version_type
88
91
  )
89
92
  self.file_name = arguments["file_name"] or self.config.settings.get(
90
93
  "changelog_file"
@@ -98,18 +101,20 @@ class Bump:
98
101
  )
99
102
  self.extras = arguments["extras"]
100
103
 
101
- def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
104
+ def is_initial_tag(
105
+ self, current_tag: git.GitTag | None, is_yes: bool = False
106
+ ) -> bool:
102
107
  """Check if reading the whole git tree up to HEAD is needed."""
103
108
  is_initial = False
104
- if not git.tag_exist(current_tag_version):
109
+ if not current_tag:
105
110
  if is_yes:
106
111
  is_initial = True
107
112
  else:
108
- out.info(f"Tag {current_tag_version} could not be found. ")
113
+ out.info("No tag matching configuration could not be found.")
109
114
  out.info(
110
115
  "Possible causes:\n"
111
116
  "- version in configuration is not the current version\n"
112
- "- tag_format is missing, check them using 'git tag --list'\n"
117
+ "- tag_format or legacy_tag_formats is missing, check them using 'git tag --list'\n"
113
118
  )
114
119
  is_initial = questionary.confirm("Is this the first tag created?").ask()
115
120
  return is_initial
@@ -143,7 +148,6 @@ class Bump:
143
148
  except TypeError:
144
149
  raise NoVersionSpecifiedError()
145
150
 
146
- tag_format: str = self.bump_settings["tag_format"]
147
151
  bump_commit_message: str = self.bump_settings["bump_message"]
148
152
  version_files: list[str] = self.bump_settings["version_files"]
149
153
  major_version_zero: bool = self.bump_settings["major_version_zero"]
@@ -221,13 +225,13 @@ class Bump:
221
225
  or self.changelog_config
222
226
  )
223
227
 
224
- current_tag_version: str = bump.normalize_tag(
225
- current_version,
226
- tag_format=tag_format,
227
- scheme=self.scheme,
228
+ rules = TagRules.from_settings(cast(Settings, self.bump_settings))
229
+ current_tag = rules.find_tag_for(git.get_tags(), current_version)
230
+ current_tag_version = getattr(
231
+ current_tag, "name", rules.normalize_tag(current_version)
228
232
  )
229
233
 
230
- is_initial = self.is_initial_tag(current_tag_version, is_yes)
234
+ is_initial = self.is_initial_tag(current_tag, is_yes)
231
235
 
232
236
  if manual_version:
233
237
  try:
@@ -239,10 +243,10 @@ class Bump:
239
243
  ) from exc
240
244
  else:
241
245
  if increment is None:
242
- if is_initial:
243
- commits = git.get_commits()
246
+ if current_tag:
247
+ commits = git.get_commits(current_tag.name)
244
248
  else:
245
- commits = git.get_commits(current_tag_version)
249
+ commits = git.get_commits()
246
250
 
247
251
  # No commits, there is no need to create an empty tag.
248
252
  # Unless we previously had a prerelease.
@@ -280,11 +284,7 @@ class Bump:
280
284
  exact_increment=increment_mode == "exact",
281
285
  )
282
286
 
283
- new_tag_version = bump.normalize_tag(
284
- new_version,
285
- tag_format=tag_format,
286
- scheme=self.scheme,
287
- )
287
+ new_tag_version = rules.normalize_tag(new_version)
288
288
  message = bump.create_commit_message(
289
289
  current_version, new_version, bump_commit_message
290
290
  )
@@ -6,7 +6,7 @@ from operator import itemgetter
6
6
  from pathlib import Path
7
7
  from typing import Callable, cast
8
8
 
9
- from commitizen import bump, changelog, defaults, factory, git, out
9
+ from commitizen import changelog, defaults, factory, git, out
10
10
  from commitizen.changelog_formats import get_changelog_format
11
11
  from commitizen.config import BaseConfig
12
12
  from commitizen.cz.base import ChangelogReleaseHook, MessageBuilderHook
@@ -20,6 +20,7 @@ from commitizen.exceptions import (
20
20
  NotAllowed,
21
21
  )
22
22
  from commitizen.git import GitTag, smart_open
23
+ from commitizen.tags import TagRules
23
24
  from commitizen.version_schemes import get_version_scheme
24
25
 
25
26
 
@@ -53,7 +54,9 @@ class Changelog:
53
54
  )
54
55
  self.dry_run = args["dry_run"]
55
56
 
56
- self.scheme = get_version_scheme(self.config, args.get("version_scheme"))
57
+ self.scheme = get_version_scheme(
58
+ self.config.settings, args.get("version_scheme")
59
+ )
57
60
 
58
61
  current_version = (
59
62
  args.get("current_version", config.settings.get("version")) or ""
@@ -73,9 +76,14 @@ class Changelog:
73
76
  self.tag_format: str = (
74
77
  args.get("tag_format") or self.config.settings["tag_format"]
75
78
  )
76
- self.merge_prerelease = args.get(
77
- "merge_prerelease"
78
- ) or self.config.settings.get("changelog_merge_prerelease")
79
+ self.tag_rules = TagRules(
80
+ scheme=self.scheme,
81
+ tag_format=self.tag_format,
82
+ legacy_tag_formats=self.config.settings["legacy_tag_formats"],
83
+ ignored_tag_formats=self.config.settings["ignored_tag_formats"],
84
+ merge_prereleases=args.get("merge_prerelease")
85
+ or self.config.settings["changelog_merge_prerelease"],
86
+ )
79
87
 
80
88
  self.template = (
81
89
  args.get("template")
@@ -152,7 +160,6 @@ class Changelog:
152
160
  changelog_release_hook: ChangelogReleaseHook | None = (
153
161
  self.cz.changelog_release_hook
154
162
  )
155
- merge_prerelease = self.merge_prerelease
156
163
 
157
164
  if self.export_template_to:
158
165
  return self.export_template()
@@ -168,28 +175,19 @@ class Changelog:
168
175
  # Don't continue if no `file_name` specified.
169
176
  assert self.file_name
170
177
 
171
- tags = (
172
- changelog.get_version_tags(self.scheme, git.get_tags(), self.tag_format)
173
- or []
174
- )
178
+ tags = self.tag_rules.get_version_tags(git.get_tags(), warn=True)
175
179
  end_rev = ""
176
180
  if self.incremental:
177
181
  changelog_meta = self.changelog_format.get_metadata(self.file_name)
178
182
  if changelog_meta.latest_version:
179
- latest_tag_version: str = bump.normalize_tag(
180
- changelog_meta.latest_version,
181
- tag_format=self.tag_format,
182
- scheme=self.scheme,
183
- )
184
183
  start_rev = self._find_incremental_rev(
185
- strip_local_version(latest_tag_version), tags
184
+ strip_local_version(changelog_meta.latest_version_tag), tags
186
185
  )
187
186
  if self.rev_range:
188
187
  start_rev, end_rev = changelog.get_oldest_and_newest_rev(
189
188
  tags,
190
- version=self.rev_range,
191
- tag_format=self.tag_format,
192
- scheme=self.scheme,
189
+ self.rev_range,
190
+ self.tag_rules,
193
191
  )
194
192
  commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
195
193
  if not commits and (
@@ -205,8 +203,7 @@ class Changelog:
205
203
  change_type_map=change_type_map,
206
204
  changelog_message_builder_hook=changelog_message_builder_hook,
207
205
  changelog_release_hook=changelog_release_hook,
208
- merge_prerelease=merge_prerelease,
209
- scheme=self.scheme,
206
+ rules=self.tag_rules,
210
207
  )
211
208
  if self.change_type_order:
212
209
  tree = changelog.order_changelog_tree(tree, self.change_type_order)
@@ -98,7 +98,7 @@ class Init:
98
98
  version_provider = self._ask_version_provider() # select
99
99
  tag = self._ask_tag() # confirm & select
100
100
  version_scheme = self._ask_version_scheme() # select
101
- version = get_version_scheme(self.config, version_scheme)(tag)
101
+ version = get_version_scheme(self.config.settings, version_scheme)(tag)
102
102
  tag_format = self._ask_tag_format(tag) # confirm & text
103
103
  update_changelog_on_bump = self._ask_update_changelog_on_bump() # confirm
104
104
  major_version_zero = self._ask_major_version_zero(version) # confirm
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import pathlib
4
4
  from collections import OrderedDict
5
- from collections.abc import Iterable, MutableMapping
5
+ from collections.abc import Iterable, MutableMapping, Sequence
6
6
  from typing import Any, TypedDict
7
7
 
8
8
  # Type
@@ -35,6 +35,8 @@ class Settings(TypedDict, total=False):
35
35
  version_scheme: str | None
36
36
  version_type: str | None
37
37
  tag_format: str
38
+ legacy_tag_formats: Sequence[str]
39
+ ignored_tag_formats: Sequence[str]
38
40
  bump_message: str | None
39
41
  retry_after_failure: bool
40
42
  allow_abort: bool
@@ -77,6 +79,8 @@ DEFAULT_SETTINGS: Settings = {
77
79
  "version_provider": "commitizen",
78
80
  "version_scheme": None,
79
81
  "tag_format": "$version", # example v$version
82
+ "legacy_tag_formats": [],
83
+ "ignored_tag_formats": [],
80
84
  "bump_message": None, # bumped v$current_version to $new_version
81
85
  "retry_after_failure": False,
82
86
  "allow_abort": False,
@@ -138,17 +142,15 @@ bump_message = "bump: version $current_version → $new_version"
138
142
  def get_tag_regexes(
139
143
  version_regex: str,
140
144
  ) -> dict[str, str]:
145
+ regexs = {
146
+ "version": version_regex,
147
+ "major": r"(?P<major>\d+)",
148
+ "minor": r"(?P<minor>\d+)",
149
+ "patch": r"(?P<patch>\d+)",
150
+ "prerelease": r"(?P<prerelease>\w+\d+)?",
151
+ "devrelease": r"(?P<devrelease>\.dev\d+)?",
152
+ }
141
153
  return {
142
- "$version": version_regex,
143
- "$major": r"(?P<major>\d+)",
144
- "$minor": r"(?P<minor>\d+)",
145
- "$patch": r"(?P<patch>\d+)",
146
- "$prerelease": r"(?P<prerelease>\w+\d+)?",
147
- "$devrelease": r"(?P<devrelease>\.dev\d+)?",
148
- "${version}": version_regex,
149
- "${major}": r"(?P<major>\d+)",
150
- "${minor}": r"(?P<minor>\d+)",
151
- "${patch}": r"(?P<patch>\d+)",
152
- "${prerelease}": r"(?P<prerelease>\w+\d+)?",
153
- "${devrelease}": r"(?P<devrelease>\.dev\d+)?",
154
+ **{f"${k}": v for k, v in regexs.items()},
155
+ **{f"${{{k}}}": v for k, v in regexs.items()},
154
156
  }