Skip to content

Commit 22cdf20

Browse files
author
Anis
authored
Merge pull request #11 from lumapps/rewrite_release_note
chore(rewrite-existing): rewrite existing release note
2 parents beb6538 + 1cb9804 commit 22cdf20

File tree

8 files changed

+249
-104
lines changed

8 files changed

+249
-104
lines changed

.github/workflows/validate_pr.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@ jobs:
2323
uses: actions/checkout@v3
2424
with:
2525
fetch-depth: 0
26+
- run: |
27+
# create the master branch
28+
git branch ${{ github.base_ref }} origin/${{ github.base_ref }}
29+
# create the pr branch
30+
git branch ${{ github.head_ref }} origin/${{ github.head_ref }}
2631
- name: Generate changelog
2732
id: generate_changelog
2833
uses: ./
2934
with:
3035
github_token: ${{ secrets.GITHUB_TOKEN }}
3136
path_filters: "changelog_generator/"
37+
target: ${{ github.base_ref }}..${{ github.head_ref }}
3238
- uses: kanga333/comment-hider@bbdf5b562fbec24e6f60572d8f712017428b92e0
3339
name: Hide previous comments
3440
with:

action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ inputs:
2323
This can be used to keep only relevant commits by filtering on relevant files.
2424
Example:
2525
path_filters: "src/ doc/ tests/*/qa/*"
26+
target:
27+
description: A rev1..rev2 string to be used to generate the commit list
2628
outputs:
2729
changelog:
2830
description: 'The generated changelog'
@@ -52,7 +54,8 @@ runs:
5254
# generate the change log
5355
CHANGELOG=$(python3 -m changelog_generator \
5456
--tag_prefix ${{ inputs.tag_prefix }} \
55-
--path_filters ${{ inputs.path_filters }}
57+
--path_filters ${{ inputs.path_filters }} \
58+
--target ${{ inputs.target }}
5659
)
5760
5861
# truncate the release note to not bloat the 65536 bytes max limit

changelog_generator/__main__.py

Lines changed: 12 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,7 @@
11
import os
22
from argparse import ArgumentParser
3-
from typing import List, NamedTuple, Sequence
43

5-
from jinja2 import Environment, FileSystemLoader
6-
7-
from changelog_generator.commit import Commit
8-
from changelog_generator.repository_manager import RepositoryManager
9-
10-
11-
class CommitTree(NamedTuple):
12-
commit_type: str
13-
commits: Sequence[Commit]
14-
15-
16-
def render_changelog(
17-
organization: str,
18-
repository: str,
19-
previous_tag: str,
20-
current_tag: str,
21-
commit_trees: Sequence[CommitTree],
22-
) -> str:
23-
template_loader = FileSystemLoader(searchpath=os.path.dirname(__file__))
24-
template_environment = Environment(loader=template_loader)
25-
template = template_environment.get_template("changelog_template.jinja")
26-
27-
return template.render(
28-
organization=organization,
29-
repository=repository,
30-
previous_tag=previous_tag,
31-
current_tag=current_tag,
32-
commit_trees=commit_trees,
33-
)
34-
35-
36-
def get_commit_from_type(
37-
commits: Sequence[Commit], commit_type: str
38-
) -> Sequence[Commit]:
39-
return sorted(
40-
filter(lambda commit: commit.commit_type == commit_type, commits),
41-
key=lambda commit: commit.scope,
42-
)
43-
44-
45-
def get_commit_but_types(
46-
commits: Sequence[Commit], commit_types: Sequence[str]
47-
) -> Sequence[Commit]:
48-
return sorted(
49-
filter(
50-
lambda commit: commit.commit_type
51-
and commit.commit_type not in commit_types,
52-
commits,
53-
),
54-
key=lambda commit: commit.scope,
55-
)
4+
from .generator import generate
565

576

587
def main() -> None:
@@ -69,58 +18,23 @@ def main() -> None:
6918
nargs="*", # optional list
7019
help="Filter commits with this semicolon separated git path regex",
7120
)
21+
parser.add_argument(
22+
"--target",
23+
nargs="?", # optional argument
24+
help="A rev1..rev2 string to be used to generate the commit list",
25+
)
7226
args = parser.parse_args()
7327
#
7428
prefix = args.tag_prefix or os.environ.get("TAG_PREFIX")
7529
filter_paths = args.path_filters
76-
repository = RepositoryManager(uri="./", prefix=prefix, filter_paths=filter_paths)
77-
78-
commits = repository.commits_since_last_tag
79-
trees: List[CommitTree] = []
80-
81-
documentations = CommitTree(
82-
commit_type=":notebook_with_decorative_cover: Documentation",
83-
commits=get_commit_from_type(commits, "docs"),
84-
)
85-
if documentations.commits:
86-
trees.append(documentations)
87-
88-
features = CommitTree(
89-
commit_type=":rocket: Features", commits=get_commit_from_type(commits, "feat")
90-
)
91-
if features.commits:
92-
trees.append(features)
93-
94-
fixes = CommitTree(
95-
commit_type=":bug: Fixes", commits=get_commit_from_type(commits, "fix")
96-
)
97-
if fixes.commits:
98-
trees.append(fixes)
99-
100-
reverts = CommitTree(
101-
commit_type=":scream: Revert", commits=get_commit_from_type(commits, "revert")
102-
)
103-
if reverts.commits:
104-
trees.append(reverts)
105-
106-
others = CommitTree(
107-
commit_type=":nut_and_bolt: Others",
108-
commits=get_commit_but_types(
109-
commits, ["documentations", "feat", "fix", "revert"]
110-
),
111-
)
112-
if others.commits:
113-
trees.append(others)
11430

115-
print(
116-
render_changelog(
117-
organization=repository.organization,
118-
repository=repository.name,
119-
previous_tag=repository.previous_tag,
120-
current_tag=repository.current_tag,
121-
commit_trees=trees,
122-
)
31+
changelog = generate(
32+
repository_path="./",
33+
prefix=prefix,
34+
filter_paths=filter_paths,
35+
target=args.target,
12336
)
37+
print(changelog)
12438

12539

12640
if __name__ == "__main__":

changelog_generator/generator.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import os
2+
from typing import List, NamedTuple, Optional, Sequence
3+
4+
from jinja2 import Environment, FileSystemLoader
5+
6+
from .commit import Commit
7+
from .repository_manager import RepositoryManager
8+
9+
10+
class CommitTree(NamedTuple):
11+
commit_type: str
12+
commits: Sequence[Commit]
13+
14+
15+
def render_changelog(
16+
organization: str,
17+
repository: str,
18+
previous_tag: str,
19+
current_tag: str,
20+
commit_trees: Sequence[CommitTree],
21+
) -> str:
22+
template_loader = FileSystemLoader(searchpath=os.path.dirname(__file__))
23+
template_environment = Environment(loader=template_loader)
24+
template = template_environment.get_template("changelog_template.jinja")
25+
26+
return template.render(
27+
organization=organization,
28+
repository=repository,
29+
previous_tag=previous_tag,
30+
current_tag=current_tag,
31+
commit_trees=commit_trees,
32+
)
33+
34+
35+
def get_commit_trees(commits: Sequence[Commit]) -> List[CommitTree]:
36+
types = {"docs", "feat", "fix", "revert"}
37+
titles = {
38+
"docs": ":notebook_with_decorative_cover: Documentation",
39+
"feat": ":rocket: Features",
40+
"fix": ":bug: Fixes",
41+
"revert": ":scream: Revert",
42+
"others": ":nut_and_bolt: Others",
43+
}
44+
commit_by_type = {}
45+
for commit in commits:
46+
if commit.commit_type in types:
47+
commit_by_type.setdefault(commit.commit_type, []).append(commit)
48+
else:
49+
commit_by_type.setdefault("others", []).append(commit)
50+
51+
return [
52+
CommitTree(commit_type=title, commits=commit_by_type[commit_type])
53+
for commit_type, title in titles.items()
54+
if commit_type in commit_by_type
55+
]
56+
57+
58+
def generate(
59+
repository_path: str,
60+
target: Optional[str] = None,
61+
prefix: Optional[str] = None,
62+
filter_paths: Optional[str] = None,
63+
):
64+
repository = RepositoryManager(
65+
uri=repository_path, prefix=prefix, filter_paths=filter_paths
66+
)
67+
if target:
68+
commits = repository.from_target(target)
69+
previous_tag, current_tag = target.split("..")
70+
else:
71+
commits = repository.commits_since_last_tag
72+
previous_tag, current_tag = repository.previous_tag, repository.current_tag
73+
74+
changelog = render_changelog(
75+
organization=repository.organization,
76+
repository=repository.name,
77+
previous_tag=previous_tag,
78+
current_tag=current_tag,
79+
commit_trees=get_commit_trees(commits),
80+
)
81+
return changelog

changelog_generator/repository_manager.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,17 @@ def commits_since_last_tag(self) -> Sequence[Commit]:
6060
revision = f"{self.previous_tag}..{self.current_tag}"
6161
else:
6262
revision = self.current_tag
63+
return self._get_commits(revision)
6364

65+
def _get_commits(self, revision: str) -> Sequence[Commit]:
6466
options = {"no_merges": True}
6567
if self.filter_paths:
6668
options["paths"] = self.filter_paths
67-
6869
commits = self.repository.iter_commits(revision, **options)
69-
7070
return tuple(
7171
Commit(hexsha=commit.hexsha, summary=commit.summary, message=commit.message)
7272
for commit in commits
7373
)
74+
75+
def from_target(self, target: str) -> Sequence[Commit]:
76+
return self._get_commits(target)

changelog_generator/tag_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,6 @@ def is_release_tag(self, tag: str) -> bool:
9191
return res and res.group("prefix") == self.prefix and not res.group("rc")
9292

9393
def get_tags(self):
94-
return self.repository.git.tag(f"--merged", "HEAD", f"{self.prefix}/*").split(
95-
"\n"
96-
)
94+
return self.repository.git.tag(
95+
"--merged", "origin/master", f"{self.prefix}/*"
96+
).split("\n")

rewrite_all.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from argparse import ArgumentParser
2+
from collections import deque
3+
from itertools import islice
4+
from typing import Iterator, Tuple, TypeVar
5+
6+
from changelog_generator.repository_manager import RepositoryManager
7+
from rewrite_existing import update_release_note
8+
9+
Item = TypeVar("Item")
10+
11+
12+
def sliding_window_iter(it: Iterator[Item], size: int) -> Iterator[Tuple[Item, ...]]:
13+
"""
14+
Get a iterator of sliding sequences
15+
16+
Examples:
17+
it=[1 2 3 4], size=2 -> [(1,2) (2,3) (3,4)]
18+
"""
19+
window = deque(islice(it, size), maxlen=size)
20+
for item in it:
21+
yield tuple(window)
22+
window.append(item)
23+
if window:
24+
yield tuple(window)
25+
26+
27+
def rewrite_all_release_notes_by_prefix():
28+
parser = ArgumentParser()
29+
parser.add_argument(
30+
"repository_path",
31+
help="The path to the repository",
32+
)
33+
parser.add_argument(
34+
"prefix",
35+
help="The prefix to filter the tags",
36+
)
37+
parser.add_argument(
38+
"filter_paths",
39+
nargs="*",
40+
help="A space separated list of path to be used to the commits that edited files within "
41+
"them",
42+
)
43+
args = parser.parse_args()
44+
45+
filter_paths = args.filter_paths
46+
path = args.repository_path
47+
prefix = args.prefix
48+
all_tags_descending = RepositoryManager(
49+
path, prefix=prefix, filter_paths=filter_paths
50+
).tags
51+
52+
for chunk in sliding_window_iter(reversed(all_tags_descending), 2):
53+
n, n1 = chunk
54+
update_release_note(filter_paths, path, f"{n}..{n1}")
55+
56+
57+
if __name__ == "__main__":
58+
rewrite_all_release_notes_by_prefix()

0 commit comments

Comments
 (0)