Skip to content

Commit 6b67ec6

Browse files
committed
scripts: adapted build_changelog.py to work for any repo
1 parent 9afdb51 commit 6b67ec6

File tree

1 file changed

+121
-74
lines changed

1 file changed

+121
-74
lines changed

scripts/build_changelog.py

Lines changed: 121 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,51 @@
2929
logging.basicConfig(level=logging.DEBUG)
3030
logger = logging.getLogger(__name__)
3131

32-
# preferred repository order
33-
repo_order = [
34-
"activitywatch",
35-
"aw-server",
36-
"aw-server-rust",
37-
"aw-webui",
38-
"aw-watcher-afk",
39-
"aw-watcher-window",
40-
"aw-qt",
41-
"aw-core",
42-
"aw-client",
43-
]
32+
33+
def main():
34+
prev_release = run("git describe --tags --abbrev=0").strip()
35+
next_release = "master"
36+
37+
parser = argparse.ArgumentParser(description="Generate changelog from git history")
38+
39+
parser.add_argument("--org", default="ActivityWatch", help="GitHub organization")
40+
parser.add_argument("--repo", default="activitywatch", help="GitHub repository")
41+
parser.add_argument(
42+
"--project-title", default="ActivityWatch", help="Project title"
43+
)
44+
45+
parser.add_argument(
46+
"--range", default=f"{prev_release}...{next_release}", help="Git commit range"
47+
)
48+
parser.add_argument("--path", default=".", help="Path to git repo")
49+
parser.add_argument(
50+
"--output", default="changelog.md", help="Path to output changelog"
51+
)
52+
args = parser.parse_args()
53+
54+
since, until = args.range.split("...", 1)
55+
56+
# preferred output order for submodules
57+
repo_order = [
58+
"activitywatch",
59+
"aw-server",
60+
"aw-server-rust",
61+
"aw-webui",
62+
"aw-watcher-afk",
63+
"aw-watcher-window",
64+
"aw-qt",
65+
"aw-core",
66+
"aw-client",
67+
]
68+
69+
build(
70+
args.org,
71+
args.repo,
72+
args.project_title,
73+
commit_range=(since, until),
74+
output_path=args.output,
75+
repo_order=repo_order,
76+
)
4477

4578

4679
class CommitMsg:
@@ -53,25 +86,26 @@ class CommitMsg:
5386
class Commit:
5487
id: str
5588
msg: str
89+
org: str
5690
repo: str
5791

5892
@property
5993
def msg_processed(self) -> str:
6094
"""Generates links from commit and issue references (like 0c14d77, #123) to correct repo and such"""
6195
s = self.msg
6296
s = re.sub(
63-
r"[^(-]https://github.com/ActivityWatch/([\-\w\d]+)/(issues|pulls)/(\d+)",
64-
r"[#\3](https://github.com/ActivityWatch/\1/issues/\3)",
97+
rf"[^(-]https://github.com/{self.org}/([\-\w\d]+)/(issues|pulls)/(\d+)",
98+
rf"[#\3](https://github.com/{self.org}/\1/issues/\3)",
6599
s,
66100
)
67101
s = re.sub(
68102
r"#(\d+)",
69-
rf"[#\1](https://github.com/ActivityWatch/{self.repo}/issues/\1)",
103+
rf"[#\1](https://github.com/{self.org}/{self.repo}/issues/\1)",
70104
s,
71105
)
72106
s = re.sub(
73107
r"[\s\(][0-9a-f]{7}[\s\)]",
74-
rf"[`\0`](https://github.com/ActivityWatch/{self.repo}/issues/\0)",
108+
rf"[`\0`](https://github.com/{self.org}/{self.repo}/issues/\0)",
75109
s,
76110
)
77111
return s
@@ -101,7 +135,7 @@ def type_str(self) -> str:
101135
return f"{_type}" + (f"({subtype})" if subtype else "")
102136

103137
def format(self) -> str:
104-
commit_link = commit_linkify(self.id, self.repo) if self.id else ""
138+
commit_link = commit_linkify(self.id, self.org, self.repo) if self.id else ""
105139

106140
return f"{self.msg_processed}" + (f" ({commit_link})" if commit_link else "")
107141

@@ -116,59 +150,63 @@ def run(cmd, cwd=".") -> str:
116150
return p.stdout
117151

118152

119-
def pr_linkify(prid: str, repo: str) -> str:
120-
return f"[#{prid}](https://github.com/ActivityWatch/{repo}/pulls/{prid})"
153+
def pr_linkify(prid: str, org: str, repo: str) -> str:
154+
return f"[#{prid}](https://github.com/{org}/{repo}/pulls/{prid})"
121155

122156

123-
def commit_linkify(commitid: str, repo: str) -> str:
124-
return f"[`{commitid}`](https://github.com/ActivityWatch/{repo}/commit/{commitid})"
157+
def commit_linkify(commitid: str, org: str, repo: str) -> str:
158+
return f"[`{commitid}`](https://github.com/{org}/{repo}/commit/{commitid})"
125159

126160

127161
def wrap_details(title, body, wraplines=5):
128162
"""Wrap lines into a <details> element if body is longer than `wraplines`"""
129163
out = f"\n\n### {title}"
130-
if body.count("\n") > wraplines:
131-
out += "\n<details><summary>Click to expand</summary>"
132-
out += f"\n<p>\n\n{body.rstrip()}\n\n</p>\n"
133-
if body.count("\n") > wraplines:
134-
out += "</details>"
164+
wrap = body.strip().count("\n") > wraplines
165+
if wrap:
166+
out += "\n<details><summary>Click to expand</summary>\n<p>"
167+
out += f"\n{body.rstrip()}\n\n"
168+
if wrap:
169+
out += "</p>\n</details>"
135170
return out
136171

137172

138173
contributor_emails = set()
139174

140175

141-
def summary_repo(path: str, commitrange: str, filter_types: List[str]) -> str:
142-
if commitrange.endswith("0000000"):
176+
def summary_repo(
177+
org: str,
178+
repo: str,
179+
path: str,
180+
commit_range: Tuple[str, str],
181+
filter_types: List[str],
182+
repo_order: List[str],
183+
) -> str:
184+
if commit_range[1] == "0000000":
143185
# Happens when a submodule has been removed
144186
return ""
145-
if commitrange.startswith("0000000"):
187+
if commit_range[0] == "0000000":
146188
# Happens when a submodule has been added
147-
commitrange = "" # no range = all commits
189+
commit_range = ("", "") # no range = all commits for new submodule
148190

149-
dirname = run("bash -c 'basename $(pwd)'", cwd=path).strip()
150-
out = f"\n## 📦 {dirname}"
191+
out = f"\n## 📦 {repo}"
151192

152193
feats = ""
153194
fixes = ""
154195
misc = ""
155196

156197
# pretty format is modified version of: https://stackoverflow.com/a/1441062/965332
157198
summary_bundle = run(
158-
f"git log {commitrange} --no-decorate --pretty=format:'%h%x09%an%x09%ae%x09%s'",
199+
f"git log {'...'.join(commit_range) if any(commit_range) else ''} --no-decorate --pretty=format:'%h%x09%an%x09%ae%x09%s'",
159200
cwd=path,
160201
)
202+
print(f"Found {len(summary_bundle.splitlines())} commits in {repo}")
161203
for line in summary_bundle.split("\n"):
162204
if line:
163205
_id, _author, email, msg = line.split("\t")
164206
# will add author email to contributor list
165207
# the `contributor_emails` is global and collected later
166208
contributor_emails.add(email)
167-
commit = Commit(
168-
id=_id,
169-
msg=msg,
170-
repo=dirname,
171-
)
209+
commit = Commit(id=_id, msg=msg, org=org, repo=repo)
172210

173211
entry = f"\n - {commit.format()}"
174212
if commit.type == "feat":
@@ -192,7 +230,7 @@ def summary_repo(path: str, commitrange: str, filter_types: List[str]) -> str:
192230
# TODO: Fix issue where subsubmodules can appear twice (like aw-webui)
193231
# TODO: Use specific order (aw-webui should be one of the first, for example)
194232
summary_subrepos = run(
195-
f"git submodule summary --cached {commitrange.split('...')[0]}", cwd=path
233+
f"git submodule summary --cached {commit_range[0]}", cwd=path
196234
)
197235
subrepos = {}
198236
for header, *_ in [s.split("\n") for s in summary_subrepos.split("\n\n")]:
@@ -204,15 +242,21 @@ def summary_repo(path: str, commitrange: str, filter_types: List[str]) -> str:
204242
# Submodule may have been deleted
205243
continue
206244

207-
_, name, commitrange, count = header.split(" ")
245+
_, name, crange, count = header.split(" ")
246+
commit_range = tuple(crange.split("...", 1)) # type: ignore
208247
count = count.strip().lstrip("(").rstrip("):")
209248
logger.info(
210-
f"Found {name}, looking up range: {commitrange} ({count} commits)"
249+
f"Found {name}, looking up range: {commit_range} ({count} commits)"
211250
)
212251
name = name.strip(".").strip("/")
213252

214253
subrepos[name] = summary_repo(
215-
f"{path}/{name}", commitrange, filter_types=filter_types
254+
org,
255+
repo,
256+
f"{path}/{name}",
257+
commit_range,
258+
filter_types=filter_types,
259+
repo_order=repo_order,
216260
)
217261

218262
# pick subrepos in repo_order, and remove from dict
@@ -266,35 +310,33 @@ def remove_duplicates(s: List[str], minlen=10, only_sections=True) -> List[str]:
266310
return out
267311

268312

269-
def build(filter_types=["build", "ci", "tests", "test"]):
270-
prev_release = run("git describe --tags --abbrev=0").strip()
271-
next_release = "master"
272-
273-
parser = argparse.ArgumentParser(description="Generate changelog from git history")
274-
parser.add_argument(
275-
"--range", default=f"{prev_release}...{next_release}", help="Git commit range"
276-
)
277-
parser.add_argument("--path", default=".", help="Path to git repo")
278-
parser.add_argument(
279-
"--output", default="changelog.md", help="Path to output changelog"
280-
)
281-
args = parser.parse_args()
282-
283-
since, until = args.range.split("...")
284-
tag = until
285-
313+
def build(
314+
org: str,
315+
repo: str,
316+
project_name: str,
317+
commit_range: Tuple[str, str],
318+
output_path: str,
319+
repo_order: List[str],
320+
filter_types=["build", "ci", "tests", "test"],
321+
):
286322
# provides a commit summary for the repo and subrepos, recursively looking up subrepos
287323
# NOTE: this must be done *before* `get_all_contributors` is called,
288324
# as the latter relies on summary_repo looking up all users and storing in a global.
289325
logger.info("Generating commit summary")
326+
since, tag = commit_range
290327
output_changelog = summary_repo(
291-
".", commitrange=args.range, filter_types=filter_types
328+
org,
329+
repo,
330+
".",
331+
commit_range=commit_range,
332+
filter_types=filter_types,
333+
repo_order=repo_order,
292334
)
293335

294336
output_changelog = f"""
295337
# Changelog
296338
297-
Changes since {since}
339+
Changes since {since}:
298340
299341
{output_changelog}
300342
""".strip()
@@ -316,29 +358,34 @@ def build(filter_types=["build", "ci", "tests", "test"]):
316358

317359
# Header starts here
318360
logger.info("Building final output")
319-
output = f"These are the release notes for ActivityWatch version {tag}.".strip()
320-
output += "\n\n"
321-
output += "**New to ActivityWatch?** Check out the [website](https://activitywatch.net) and the [README](https://github.com/ActivityWatch/activitywatch/blob/master/README.md)."
361+
output = f"These are the release notes for {project_name} version {tag}.".strip()
322362
output += "\n\n"
323-
output += """# Installation
363+
364+
# hardcoded for now
365+
if repo == "activitywatch":
366+
output += "**New to ActivityWatch?** Check out the [website](https://activitywatch.net) and the [README](https://github.com/ActivityWatch/activitywatch/blob/master/README.md)."
367+
output += "\n\n"
368+
output += """# Installation
324369
325370
See the [getting started guide in the documentation](https://docs.activitywatch.net/en/latest/getting-started.html).
326-
""".strip()
327-
output += "\n\n"
328-
output += f"""# Downloads
371+
""".strip()
372+
output += "\n\n"
373+
output += f"""# Downloads
329374
330375
- [**Windows**](https://github.com/ActivityWatch/activitywatch/releases/download/{tag}/activitywatch-{tag}-windows-x86_64-setup.exe) (.exe, installer)
331376
- [**macOS**](https://github.com/ActivityWatch/activitywatch/releases/download/{tag}/activitywatch-{tag}-macos-x86_64.dmg) (.dmg)
332377
- [**Linux**](https://github.com/ActivityWatch/activitywatch/releases/download/{tag}/activitywatch-{tag}-linux-x86_64.zip) (.zip)
333-
""".strip()
334-
output += "\n\n"
378+
""".strip()
379+
output += "\n\n"
380+
335381
output += output_contributors.strip() + "\n\n"
336382
output += output_changelog.strip() + "\n\n"
337383

338-
output = output.replace("# activitywatch", "# activitywatch (bundle repo)")
339-
with open(args.output, "w") as f:
384+
if repo == "activitywatch":
385+
output = output.replace("# activitywatch", "# activitywatch (bundle repo)")
386+
with open(output_path, "w") as f:
340387
f.write(output)
341-
print(f"Wrote {len(output.splitlines())} lines to {args.output}")
388+
print(f"Wrote {len(output.splitlines())} lines to {output_path}")
342389

343390

344391
def _resolve_email(email: str) -> Optional[str]:
@@ -494,4 +541,4 @@ def get_twitter_of_ghusers(ghusers: Collection[str]):
494541

495542

496543
if __name__ == "__main__":
497-
build()
544+
main()

0 commit comments

Comments
 (0)