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 dda193f

Browse files
committed
feat(changelog): add incremental flag
1 parent e0a1b49 commit dda193f

File tree

12 files changed

+543
-91
lines changed

12 files changed

+543
-91
lines changed

‎commitizen/changelog.py‎

Lines changed: 118 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
7. generate changelog
1919
2020
Extra:
21-
- Generate full or partial changelog
22-
- Include in tree from file all the extra comments added manually
21+
- [x] Generate full or partial changelog
22+
- [x] Include in tree from file all the extra comments added manually
23+
- [ ] Add unreleased value
24+
- [ ] hook after message is parsed (add extra information like hyperlinks)
25+
- [ ] hook after changelog is generated (api calls)
26+
- [ ] add support for change_type maps
2327
"""
2428
import re
2529
from collections import defaultdict
@@ -29,7 +33,7 @@
2933
from jinja2 import Template
3034

3135
from commitizen import defaults
32-
from commitizen.git import GitCommit, GitProtocol, GitTag
36+
from commitizen.git import GitCommit, GitTag
3337

3438
CATEGORIES = [
3539
("fix", "fix"),
@@ -55,30 +59,25 @@ def transform_change_type(change_type: str) -> str:
5559
raise ValueError(f"Could not match a change_type with {change_type}")
5660

5761

58-
def get_commit_tag(commit: GitProtocol, tags: List[GitProtocol]) -> Optional[GitTag]:
62+
def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]:
5963
""""""
60-
try:
61-
tag_index = tags.index(commit)
62-
except ValueError:
63-
return None
64-
else:
65-
tag = tags[tag_index]
66-
return tag
64+
return next((tag for tag in tags if tag.rev == commit.rev), None)
6765

6866

6967
def generate_tree_from_commits(
7068
commits: List[GitCommit],
7169
tags: List[GitTag],
7270
commit_parser: str,
7371
changelog_pattern: str = defaults.bump_pattern,
72+
unreleased_version: Optional[str] = None,
7473
) -> Iterable[Dict]:
7574
pat = re.compile(changelog_pattern)
7675
map_pat = re.compile(commit_parser)
7776
# Check if the latest commit is not tagged
7877
latest_commit = commits[0]
7978
current_tag: Optional[GitTag] = get_commit_tag(latest_commit, tags)
8079

81-
current_tag_name: str = "Unreleased"
80+
current_tag_name: str = unreleased_versionor"Unreleased"
8281
current_tag_date: str = ""
8382
if current_tag is not None and current_tag.name:
8483
current_tag_name = current_tag.name
@@ -109,6 +108,7 @@ def generate_tree_from_commits(
109108
message = map_pat.match(commit.message)
110109
message_body = map_pat.match(commit.body)
111110
if message:
111+
# TODO: add a post hook coming from a rule (CzBase)
112112
parsed_message: Dict = message.groupdict()
113113
# change_type becomes optional by providing None
114114
change_type = parsed_message.pop("change_type", None)
@@ -132,3 +132,109 @@ def render_changelog(tree: Iterable) -> str:
132132
jinja_template = Template(template_file, trim_blocks=True)
133133
changelog: str = jinja_template.render(tree=tree)
134134
return changelog
135+
136+
137+
def parse_version_from_markdown(value: str) -> Optional[str]:
138+
if not value.startswith("#"):
139+
return None
140+
m = re.search(defaults.version_parser, value)
141+
if not m:
142+
return None
143+
return m.groupdict().get("version")
144+
145+
146+
def parse_title_type_of_line(value: str) -> Optional[str]:
147+
md_title_parser = r"^(?P<title>#+)"
148+
m = re.search(md_title_parser, value)
149+
if not m:
150+
return None
151+
return m.groupdict().get("title")
152+
153+
154+
def get_metadata(filepath: str) -> Dict:
155+
unreleased_start: Optional[int] = None
156+
unreleased_end: Optional[int] = None
157+
unreleased_title: Optional[str] = None
158+
latest_version: Optional[str] = None
159+
latest_version_position: Optional[int] = None
160+
with open(filepath, "r") as changelog_file:
161+
for index, line in enumerate(changelog_file):
162+
line = line.strip().lower()
163+
164+
unreleased: Optional[str] = None
165+
if "unreleased" in line:
166+
unreleased = parse_title_type_of_line(line)
167+
# Try to find beginning and end lines of the unreleased block
168+
if unreleased:
169+
unreleased_start = index
170+
unreleased_title = unreleased
171+
continue
172+
elif (
173+
isinstance(unreleased_title, str)
174+
and parse_title_type_of_line(line) == unreleased_title
175+
):
176+
unreleased_end = index
177+
178+
# Try to find the latest release done
179+
version = parse_version_from_markdown(line)
180+
if version:
181+
latest_version = version
182+
latest_version_position = index
183+
break # there's no need for more info
184+
if unreleased_start is not None and unreleased_end is None:
185+
unreleased_end = index
186+
return {
187+
"unreleased_start": unreleased_start,
188+
"unreleased_end": unreleased_end,
189+
"latest_version": latest_version,
190+
"latest_version_position": latest_version_position,
191+
}
192+
193+
194+
def incremental_build(new_content: str, lines: List, metadata: Dict) -> List:
195+
"""Takes the original lines and updates with new_content.
196+
197+
The metadata holds information enough to remove the old unreleased and
198+
where to place the new content
199+
200+
Arguments:
201+
lines -- the lines from the changelog
202+
new_content -- this should be placed somewhere in the lines
203+
metadata -- information about the changelog
204+
205+
Returns:
206+
List -- updated lines
207+
"""
208+
unreleased_start = metadata.get("unreleased_start")
209+
unreleased_end = metadata.get("unreleased_end")
210+
latest_version_position = metadata.get("latest_version_position")
211+
skip = False
212+
output_lines: List = []
213+
for index, line in enumerate(lines):
214+
if index == unreleased_start:
215+
skip = True
216+
elif index == unreleased_end:
217+
skip = False
218+
if (
219+
latest_version_position is None
220+
or isinstance(latest_version_position, int)
221+
and isinstance(unreleased_end, int)
222+
and latest_version_position > unreleased_end
223+
):
224+
continue
225+
226+
if skip:
227+
continue
228+
229+
if (
230+
isinstance(latest_version_position, int)
231+
and index == latest_version_position
232+
):
233+
234+
output_lines.append(new_content)
235+
output_lines.append("\n")
236+
237+
output_lines.append(line)
238+
if not isinstance(latest_version_position, int):
239+
output_lines.append(new_content)
240+
return output_lines

‎commitizen/cli.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@
8686
"action": "store_true",
8787
"help": "bump version in the files from the config",
8888
},
89+
{
90+
"name": ["--changelog", "-ch"],
91+
"action": "store_true",
92+
"default": False,
93+
"help": "generate the changelog for the newest version",
94+
},
8995
{
9096
"name": "--yes",
9197
"action": "store_true",
@@ -131,6 +137,17 @@
131137
"default": False,
132138
"help": "show changelog to stdout",
133139
},
140+
{
141+
"name": "--file-name",
142+
"help": "file name of changelog (default: 'CHANGELOG.md')",
143+
},
144+
{
145+
"name": "--unreleased-version",
146+
"help": (
147+
"set the value for the new version (use the tag value), "
148+
"instead of using unreleased"
149+
),
150+
},
134151
{
135152
"name": "--incremental",
136153
"action": "store_true",
@@ -140,10 +157,6 @@
140157
"useful if the changelog has been manually modified"
141158
),
142159
},
143-
{
144-
"name": "--file-name",
145-
"help": "file name of changelog (default: 'CHANGELOG.md')",
146-
},
147160
{
148161
"name": "--start-rev",
149162
"default": None,

‎commitizen/commands/bump.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from packaging.version import Version
55

66
from commitizen import bump, factory, git, out
7+
from commitizen.commands.changelog import Changelog
78
from commitizen.config import BaseConfig
89
from commitizen.error_codes import (
910
COMMIT_FAILED,
@@ -29,6 +30,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
2930
},
3031
}
3132
self.cz = factory.commiter_factory(self.config)
33+
self.changelog = arguments["changelog"]
3234

3335
def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
3436
"""Check if reading the whole git tree up to HEAD is needed."""
@@ -131,6 +133,17 @@ def __call__(self): # noqa: C901
131133
if is_files_only:
132134
raise SystemExit()
133135

136+
if self.changelog:
137+
changelog = Changelog(
138+
self.config,
139+
{
140+
"unreleased_version": new_tag_version,
141+
"incremental": True,
142+
"dry_run": dry_run,
143+
},
144+
)
145+
changelog()
146+
134147
self.config.set_key("version", new_version.public)
135148
c = git.commit(message, args="-a")
136149
if c.err:

0 commit comments

Comments
(0)

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