Skip to content

Commit 0c219ee

Browse files
committed
Add changelog generation script
Signed-off-by: kramaranya <kramaranya15@gmail.com>
1 parent ac42ba9 commit 0c219ee

File tree

4 files changed

+323
-0
lines changed

4 files changed

+323
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Changelog

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev = [
3939
"kubeflow_trainer_api@git+https://github.com/kubeflow/trainer.git@master#subdirectory=api/python_api",
4040
"ruff>=0.12.2",
4141
"pre-commit>=4.2.0",
42+
"PyGithub>=2.7.0",
4243
]
4344

4445
[project.urls]

scripts/gen-changelog.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import argparse
2+
import sys
3+
4+
try:
5+
from github import Github
6+
except ImportError:
7+
print("Install PyGithub with pip install -e \".[dev]\"")
8+
sys.exit(1)
9+
10+
REPO_NAME = "kubeflow/sdk"
11+
CHANGELOG_FILE = "CHANGELOG.md"
12+
13+
14+
def categorize_pr(title: str) -> str:
15+
title = title.lower().strip()
16+
if title.startswith('feat'):
17+
return 'feat'
18+
elif title.startswith('fix'):
19+
return 'fix'
20+
elif title.startswith('chore'):
21+
return 'chore'
22+
elif title.startswith('revert'):
23+
return 'revert'
24+
else:
25+
return 'misc'
26+
27+
28+
def get_initial_commit(github_repo):
29+
commits = list(github_repo.get_commits())
30+
return commits[-1].sha
31+
32+
33+
def main():
34+
parser = argparse.ArgumentParser(description="Generate changelog for Kubeflow SDK")
35+
parser.add_argument("--token", required=True, help="GitHub Access Token")
36+
parser.add_argument("--version", required=True, help="Target version (e.g. v0.1.0)")
37+
38+
args = parser.parse_args()
39+
40+
current_release = args.version
41+
github_repo = Github(args.token).get_repo(REPO_NAME)
42+
43+
try:
44+
tags = list(github_repo.get_tags())
45+
if not tags:
46+
print("First release - using full history")
47+
previous_release = get_initial_commit(github_repo)
48+
else:
49+
previous_release = tags[0].name
50+
print(f"Generating changelog: {previous_release} → HEAD (for {current_release})")
51+
except Exception as e:
52+
print(f"Error finding previous release: {e}")
53+
sys.exit(1)
54+
55+
comparison = github_repo.compare(previous_release, "HEAD")
56+
commits = list(comparison.commits)
57+
58+
if not commits:
59+
print("No commits found in range")
60+
sys.exit(1)
61+
62+
categories = {
63+
'feat': [],
64+
'fix': [],
65+
'chore': [],
66+
'revert': [],
67+
'misc': []
68+
}
69+
70+
pr_set = set()
71+
for commit in reversed(commits):
72+
for pr in commit.get_pulls():
73+
if pr.number in pr_set:
74+
continue
75+
pr_set.add(pr.number)
76+
77+
category = categorize_pr(pr.title)
78+
pr_entry = (f"- {pr.title} ([#{pr.number}]({pr.html_url})) "
79+
f"by [@{pr.user.login}]({pr.user.html_url})")
80+
categories[category].append(pr_entry)
81+
82+
if not pr_set:
83+
print("No PRs found in range")
84+
sys.exit(1)
85+
86+
release_date = str(commits[-1].commit.author.date).split(" ")[0]
87+
release_url = f"https://github.com/{REPO_NAME}/releases/tag/{current_release}"
88+
89+
changelog_content = [
90+
f"# [{current_release}]({release_url}) ({release_date})\n\n"
91+
]
92+
93+
if categories['feat']:
94+
changelog_content.append("## New Features\n\n")
95+
changelog_content.append("\n".join(categories['feat']) + "\n\n")
96+
97+
if categories['fix']:
98+
changelog_content.append("## Bug Fixes\n\n")
99+
changelog_content.append("\n".join(categories['fix']) + "\n\n")
100+
101+
if categories['chore']:
102+
changelog_content.append("## Maintenance\n\n")
103+
changelog_content.append("\n".join(categories['chore']) + "\n\n")
104+
105+
if categories['revert']:
106+
changelog_content.append("## Reverts\n\n")
107+
changelog_content.append("\n".join(categories['revert']) + "\n\n")
108+
109+
if categories['misc']:
110+
changelog_content.append("## Other Changes\n\n")
111+
changelog_content.append("\n".join(categories['misc']) + "\n\n")
112+
113+
changelog_content.append(f"**Full Changelog**: {comparison.html_url}\n\n")
114+
115+
try:
116+
with open(CHANGELOG_FILE, "r") as f:
117+
existing_content = f.read()
118+
except FileNotFoundError:
119+
existing_content = "# Changelog\n\n"
120+
121+
lines = existing_content.split('\n')
122+
123+
insert_index = 0
124+
for i, line in enumerate(lines):
125+
if line.strip() == "# Changelog":
126+
insert_index = i + 1
127+
while insert_index < len(lines) and not lines[insert_index].strip():
128+
insert_index += 1
129+
break
130+
131+
new_content = ''.join(changelog_content)
132+
new_lines = (
133+
lines[:insert_index] + [''] + new_content.rstrip().split('\n') + [''] + lines[insert_index:]
134+
)
135+
136+
with open(CHANGELOG_FILE, "w") as f:
137+
f.write('\n'.join(new_lines))
138+
139+
print("Changelog has been updated")
140+
print(f"Found {len(pr_set)} PRs for {current_release}")
141+
142+
143+
if __name__ == "__main__":
144+
main()

0 commit comments

Comments
 (0)