Skip to content

Commit 242f1cb

Browse files
committed
feat(changelog): add support for single version and version range
Closes #399 #225
1 parent 323c72e commit 242f1cb

File tree

8 files changed

+129
-6
lines changed

8 files changed

+129
-6
lines changed

commitizen/bump.py

+1-1
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

+46-1
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,48 @@ 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_tag_range(tags: List[GitTag], start: str, end: Optional[str] = None) -> List[GitTag]:
287+
accumulator = []
288+
keep = False
289+
if not end:
290+
end = start
291+
for index, tag in enumerate(tags):
292+
if tag.name == start:
293+
keep = True
294+
if keep:
295+
accumulator.append(tag)
296+
if tag.name == end:
297+
keep = False
298+
try:
299+
accumulator.append(tags[index + 1])
300+
except IndexError:
301+
pass
302+
break
303+
return accumulator
304+
305+
306+
def get_start_and_end_rev(
307+
tags: List[GitTag], version: str, tag_format: str, create_tag: Callable
308+
) -> Tuple[Optional[str], Optional[str]]:
309+
start: Optional[str] = None
310+
end: Optional[str] = None
311+
312+
try:
313+
start, end = version.split("..")
314+
except ValueError:
315+
end = version
316+
317+
end_tag = create_tag(end, tag_format=tag_format)
318+
319+
start_tag = None
320+
if start:
321+
start_tag = create_tag(start, tag_format=tag_format)
322+
323+
tags = get_tag_range(tags, start=end_tag, end=start_tag)
324+
if len(tags) == 0:
325+
return None, None
326+
start_rev = tags[-1].name
327+
end_rev = end_tag
328+
return start_rev, end_rev

commitizen/cli.py

+6
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 range",
196+
},
191197
{
192198
"name": "--start-rev",
193199
"default": None,

commitizen/commands/changelog.py

+22-2
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'.
@@ -89,17 +94,32 @@ def __call__(self):
8994
f"'{self.config.settings['name']}' rule does not support changelog"
9095
)
9196

97+
if self.incremental and self.rev_range:
98+
raise NotAllowed("--incremental cannot be combined with a rev_range")
99+
92100
tags = git.get_tags()
93101
if not tags:
94102
tags = []
95103

104+
end_rev = "HEAD"
105+
96106
if self.incremental:
97107
changelog_meta = changelog.get_metadata(self.file_name)
98108
latest_version = changelog_meta.get("latest_version")
99109
if latest_version:
100110
start_rev = self._find_incremental_rev(latest_version, tags)
101111

102-
commits = git.get_commits(start=start_rev, args="--author-date-order")
112+
if self.rev_range and self.tag_format:
113+
start_rev, end_rev = changelog.get_start_and_end_rev(
114+
tags,
115+
version=self.rev_range,
116+
tag_format=self.tag_format,
117+
create_tag=bump.create_tag,
118+
)
119+
120+
commits = git.get_commits(
121+
start=start_rev, end=end_rev, args="--author-date-order"
122+
)
103123
if not commits:
104124
raise NoCommitsFoundError("No commits found")
105125

commitizen/exceptions.py

+5
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

scripts/test

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ if [ -d 'venv' ] ; then
55
export PREFIX="venv/bin/"
66
fi
77

8-
${PREFIX}pytest --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen tests/
8+
${PREFIX}pytest -x -s --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen tests/
99
${PREFIX}black commitizen tests --check
1010
${PREFIX}isort --check-only commitizen tests
1111
${PREFIX}flake8 commitizen/ tests/

tests/commands/test_changelog_command.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def test_changelog_multiple_incremental_do_not_add_new_lines(
279279
mocker.patch.object(sys, "argv", testargs)
280280
cli.main()
281281

282-
create_file_and_commit("fix: mama gotta work")
282+
create_file_and_commit("fix: no more explosions")
283283

284284
testargs = ["cz", "changelog", "--incremental"]
285285
mocker.patch.object(sys, "argv", testargs)
@@ -514,3 +514,38 @@ def test_changelog_incremental_with_release_candidate_version(
514514
out = f.read()
515515

516516
file_regression.check(out, extension=".md")
517+
518+
519+
@pytest.mark.usefixtures("tmp_commitizen_project")
520+
def test_changelog_multiple_incremental_do_not_add_new_lines(
521+
mocker, capsys, changelog_path
522+
):
523+
"""Test for bug https://github.com/commitizen-tools/commitizen/issues/192"""
524+
create_file_and_commit("feat: add new output")
525+
526+
testargs = ["cz", "changelog", "--incremental"]
527+
mocker.patch.object(sys, "argv", testargs)
528+
cli.main()
529+
530+
create_file_and_commit("fix: output glitch")
531+
532+
testargs = ["cz", "changelog", "--incremental"]
533+
mocker.patch.object(sys, "argv", testargs)
534+
cli.main()
535+
536+
create_file_and_commit("fix: no more explosions")
537+
538+
testargs = ["cz", "changelog", "--incremental"]
539+
mocker.patch.object(sys, "argv", testargs)
540+
cli.main()
541+
542+
create_file_and_commit("feat: add more stuff")
543+
544+
testargs = ["cz", "changelog", "--incremental"]
545+
mocker.patch.object(sys, "argv", testargs)
546+
cli.main()
547+
548+
with open(changelog_path, "r") as f:
549+
out = f.read()
550+
551+
assert out.startswith("#")

tests/test_changelog.py

+12
Original file line numberDiff line numberDiff line change
@@ -912,3 +912,15 @@ def changelog_message_builder_hook(message: dict, commit: git.GitCommit) -> dict
912912
result = changelog.render_changelog(tree)
913913

914914
assert "[link](github.com/232323232) Commitizen author@cz.dev" in result
915+
916+
917+
def test_get_tag_range_returns_an_extra_for_a_range(tags):
918+
start, end = tags[0], tags[2] # len here is 3, but we expect one more tag as designed
919+
res = changelog.get_tag_range(tags, start.name, end.name)
920+
assert 4 == len(res)
921+
922+
923+
def test_get_tag_range_returns_an_extra_for_a_single_tag(tags):
924+
start = tags[0] # len here is 1, but we expect one more tag as designed
925+
res = changelog.get_tag_range(tags, start.name)
926+
assert 2 == len(res)

0 commit comments

Comments
 (0)