Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4c833a1

Browse files
committed
feat(changelog): add support for single version and version range
Closes #399 #225
1 parent 27b4c54 commit 4c833a1

16 files changed

+503
-22
lines changed

‎commitizen/bump.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def find_increment(
2121
# Most important cases are major and minor.
2222
# Everything else will be considered patch.
2323
select_pattern = re.compile(regex)
24-
increment = None
24+
increment: Optional[str] = None
2525

2626
for commit in commits:
2727
for message in commit.message.split("\n"):

‎commitizen/changelog.py‎

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import re
3030
from collections import OrderedDict, defaultdict
3131
from datetime import date
32-
from typing import Callable, Dict, Iterable, List, Optional
32+
from typing import Callable, Dict, Iterable, List, Optional, Tuple
3333

3434
from jinja2 import Environment, PackageLoader
3535

@@ -281,3 +281,72 @@ def incremental_build(new_content: str, lines: List, metadata: Dict) -> List:
281281
if not isinstance(latest_version_position, int):
282282
output_lines.append(new_content)
283283
return output_lines
284+
285+
286+
def get_smart_tag_range(
287+
tags: List[GitTag], start: str, end: Optional[str] = None
288+
) -> List[GitTag]:
289+
"""Smart because it finds the N+1 tag.
290+
291+
This is because we need to find until the next tag
292+
"""
293+
accumulator = []
294+
keep = False
295+
if not end:
296+
end = start
297+
for index, tag in enumerate(tags):
298+
if tag.name == start:
299+
keep = True
300+
if keep:
301+
accumulator.append(tag)
302+
if tag.name == end:
303+
keep = False
304+
try:
305+
accumulator.append(tags[index + 1])
306+
except IndexError:
307+
pass
308+
break
309+
return accumulator
310+
311+
312+
def get_start_and_end_rev(
313+
tags: List[GitTag], version: str, tag_format: str, create_tag: Callable
314+
) -> Tuple[Optional[str], Optional[str]]:
315+
"""Find the tags for the given version.
316+
317+
`version` may come in different formats:
318+
- `0.1.0..0.4.0`: as a range
319+
- `0.3.0`: as a single version
320+
"""
321+
start: Optional[str] = None
322+
end: Optional[str] = None
323+
324+
try:
325+
start, end = version.split("..")
326+
except ValueError:
327+
end = version
328+
329+
end_tag = create_tag(end, tag_format=tag_format)
330+
331+
start_tag = None
332+
if start:
333+
start_tag = create_tag(start, tag_format=tag_format)
334+
335+
tags_range = get_smart_tag_range(tags, start=end_tag, end=start_tag)
336+
if len(tags_range) == 0:
337+
return None, None
338+
339+
start_rev: Optional[str] = tags_range[-1].name
340+
end_rev = end_tag
341+
342+
# check if it's the first tag created
343+
# and it's also being requested as part of the range
344+
if start_rev == tags[-1].name and start_rev == start_tag:
345+
return None, end_rev
346+
347+
# when they are the same, and it's also the
348+
# first tag crated
349+
if start_rev == end_rev:
350+
return None, end_rev
351+
352+
return start_rev, end_rev

‎commitizen/cli.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@
188188
"useful if the changelog has been manually modified"
189189
),
190190
},
191+
{
192+
"name": "rev_range",
193+
"type": str,
194+
"nargs": "?",
195+
"help": "generates changelog for the given version (e.g: 1.5.3) or version range (e.g: 1.5.3..1.7.9)",
196+
},
191197
{
192198
"name": "--start-rev",
193199
"default": None,

‎commitizen/commands/changelog.py‎

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from operator import itemgetter
44
from typing import Callable, Dict, List, Optional
55

6-
from commitizen import changelog, factory, git, out
6+
from commitizen import bump, changelog, factory, git, out
77
from commitizen.config import BaseConfig
88
from commitizen.exceptions import (
99
DryRunExit,
1010
NoCommitsFoundError,
1111
NoPatternMapError,
1212
NoRevisionError,
1313
NotAGitProjectError,
14+
NotAllowed,
1415
)
1516
from commitizen.git import GitTag
1617

@@ -46,6 +47,10 @@ def __init__(self, config: BaseConfig, args):
4647
self.change_type_order = (
4748
self.config.settings.get("change_type_order") or self.cz.change_type_order
4849
)
50+
self.rev_range = args.get("rev_range")
51+
self.tag_format = args.get("tag_format") or self.config.settings.get(
52+
"tag_format"
53+
)
4954

5055
def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
5156
"""Try to find the 'start_rev'.
@@ -73,6 +78,30 @@ def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
7378
start_rev = tag.name
7479
return start_rev
7580

81+
def write_changelog(
82+
self, changelog_out: str, lines: List[str], changelog_meta: Dict
83+
):
84+
if not isinstance(self.file_name, str):
85+
raise NotAllowed(
86+
"Changelog file name is broken.\n"
87+
"Check the flag `--file-name` in the terminal "
88+
f"or the setting `changelog_file` in {self.config.path}"
89+
)
90+
91+
changelog_hook: Optional[Callable] = self.cz.changelog_hook
92+
with open(self.file_name, "w") as changelog_file:
93+
partial_changelog: Optional[str] = None
94+
if self.incremental:
95+
new_lines = changelog.incremental_build(
96+
changelog_out, lines, changelog_meta
97+
)
98+
changelog_out = "".join(new_lines)
99+
partial_changelog = changelog_out
100+
101+
if changelog_hook:
102+
changelog_out = changelog_hook(changelog_out, partial_changelog)
103+
changelog_file.write(changelog_out)
104+
76105
def __call__(self):
77106
commit_parser = self.cz.commit_parser
78107
changelog_pattern = self.cz.changelog_pattern
@@ -83,23 +112,38 @@ def __call__(self):
83112
changelog_message_builder_hook: Optional[
84113
Callable
85114
] = self.cz.changelog_message_builder_hook
86-
changelog_hook: Optional[Callable] =self.cz.changelog_hook
115+
87116
if not changelog_pattern or not commit_parser:
88117
raise NoPatternMapError(
89118
f"'{self.config.settings['name']}' rule does not support changelog"
90119
)
91120

121+
if self.incremental and self.rev_range:
122+
raise NotAllowed("--incremental cannot be combined with a rev_range")
123+
92124
tags = git.get_tags()
93125
if not tags:
94126
tags = []
95127

128+
end_rev = "HEAD"
129+
96130
if self.incremental:
97131
changelog_meta = changelog.get_metadata(self.file_name)
98132
latest_version = changelog_meta.get("latest_version")
99133
if latest_version:
100134
start_rev = self._find_incremental_rev(latest_version, tags)
101135

102-
commits = git.get_commits(start=start_rev, args="--author-date-order")
136+
if self.rev_range and self.tag_format:
137+
start_rev, end_rev = changelog.get_start_and_end_rev(
138+
tags,
139+
version=self.rev_range,
140+
tag_format=self.tag_format,
141+
create_tag=bump.create_tag,
142+
)
143+
144+
commits = git.get_commits(
145+
start=start_rev, end=end_rev, args="--author-date-order"
146+
)
103147
if not commits:
104148
raise NoCommitsFoundError("No commits found")
105149

@@ -126,15 +170,4 @@ def __call__(self):
126170
with open(self.file_name, "r") as changelog_file:
127171
lines = changelog_file.readlines()
128172

129-
with open(self.file_name, "w") as changelog_file:
130-
partial_changelog: Optional[str] = None
131-
if self.incremental:
132-
new_lines = changelog.incremental_build(
133-
changelog_out, lines, changelog_meta
134-
)
135-
changelog_out = "".join(new_lines)
136-
partial_changelog = changelog_out
137-
138-
if changelog_hook:
139-
changelog_out = changelog_hook(changelog_out, partial_changelog)
140-
changelog_file.write(changelog_out)
173+
self.write_changelog(changelog_out, lines, changelog_meta)

‎commitizen/config/toml_config.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _parse_setting(self, data: Union[bytes, str]) -> None:
4949
name = "cz_conventional_commits"
5050
```
5151
"""
52-
doc = parse(data)# type: ignore
52+
doc = parse(data)
5353
try:
5454
self.settings.update(doc["tool"]["commitizen"]) # type: ignore
5555
except exceptions.NonExistentKey:

‎commitizen/cz/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize
2323
for finder, name, ispkg in pkgutil.iter_modules(path):
2424
try:
2525
if name.startswith("cz_"):
26-
plugins[name] = importlib.import_module(name).discover_this# type: ignore
26+
plugins[name] = importlib.import_module(name).discover_this
2727
except AttributeError as e:
2828
warnings.warn(UserWarning(e.args[0]))
2929
continue

‎commitizen/exceptions.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class ExitCode(enum.IntEnum):
2424
CURRENT_VERSION_NOT_FOUND = 17
2525
INVALID_COMMAND_ARGUMENT = 18
2626
INVALID_CONFIGURATION = 19
27+
NOT_ALLOWED = 20
2728

2829

2930
class CommitizenException(Exception):
@@ -142,3 +143,7 @@ class InvalidCommandArgumentError(CommitizenException):
142143

143144
class InvalidConfigurationError(CommitizenException):
144145
exit_code = ExitCode.INVALID_CONFIGURATION
146+
147+
148+
class NotAllowed(CommitizenException):
149+
exit_code = ExitCode.NOT_ALLOWED

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ isort = "^5.7.0"
7272
# linter
7373
flake8 = "^3.6"
7474
pre-commit = "^2.6.0"
75-
mypy = "0.910"
75+
mypy = "^0.931"
7676
types-PyYAML = "^5.4.3"
7777
types-termcolor = "^0.1.1"
7878
# documentation

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /