|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | """ |
3 | | -Script that outputs a changelog for the repository in the current directory and its submodules. |
| 3 | +Script that generates a changelog for the repository and its submodules, and outputs it in the current directory. |
| 4 | +
|
| 5 | +NOTE: This script can be downloaded as-is and run from your repository. |
| 6 | +
|
| 7 | +Repos using this script: |
| 8 | + - ActivityWatch/activitywatch |
| 9 | + - ErikBjare/gptme |
4 | 10 |
|
5 | 11 | Manual actions needed to clean up for changelog: |
6 | 12 | - Reorder modules in a logical order (aw-webui, aw-server, aw-server-rust, aw-watcher-window, aw-watcher-afk, ...) |
|
14 | 20 | from collections import defaultdict |
15 | 21 | from collections.abc import Collection |
16 | 22 | from dataclasses import dataclass |
| 23 | +from pathlib import Path |
17 | 24 | from subprocess import PIPE, STDOUT |
18 | 25 | from subprocess import run as _run |
19 | 26 | from time import sleep |
|
30 | 37 | logger = logging.getLogger(__name__) |
31 | 38 |
|
32 | 39 |
|
33 | | -def main(): |
34 | | - prev_release = run("git describe --tags --abbrev=0").strip() |
35 | | - next_release = "master" |
| 40 | +script_dir = Path(__file__).parent.resolve() |
36 | 41 |
|
| 42 | + |
| 43 | +def main(): |
37 | 44 | parser = argparse.ArgumentParser(description="Generate changelog from git history") |
38 | 45 |
|
| 46 | + # repo info |
39 | 47 | parser.add_argument("--org", default="ActivityWatch", help="GitHub organization") |
40 | 48 | parser.add_argument("--repo", default="activitywatch", help="GitHub repository") |
41 | 49 | parser.add_argument( |
42 | 50 | "--project-title", default="ActivityWatch", help="Project title" |
43 | 51 | ) |
44 | 52 |
|
| 53 | + # settings |
| 54 | + last_tag = run("git describe --tags --abbrev=0").strip() # get latest tag |
| 55 | + branch = run("git rev-parse --abbrev-ref HEAD").strip() # get current branch name |
45 | 56 | parser.add_argument( |
46 | | - "--range", default=f"{prev_release}...{next_release}", help="Git commit range" |
| 57 | + "--range", default=f"{last_tag}...{branch}", help="Git commit range" |
47 | 58 | ) |
48 | 59 | parser.add_argument("--path", default=".", help="Path to git repo") |
| 60 | + |
| 61 | + # output |
49 | 62 | parser.add_argument( |
50 | 63 | "--output", default="changelog.md", help="Path to output changelog" |
51 | 64 | ) |
52 | | - args = parser.parse_args() |
53 | 65 |
|
| 66 | + # parse args |
| 67 | + args = parser.parse_args() |
54 | 68 | since, until = args.range.split("...", 1) |
55 | 69 |
|
56 | 70 | # preferred output order for submodules |
@@ -164,9 +178,9 @@ def wrap_details(title, body, wraplines=5): |
164 | 178 | wrap = body.strip().count("\n") > wraplines |
165 | 179 | if wrap: |
166 | 180 | out += "\n<details><summary>Click to expand</summary>\n<p>" |
167 | | - out += f"\n{body.rstrip()}\n\n" |
| 181 | + out += f"\n{body.rstrip()}" |
168 | 182 | if wrap: |
169 | | - out += "</p>\n</details>" |
| 183 | + out += "\n\n</p>\n</details>" |
170 | 184 | return out |
171 | 185 |
|
172 | 186 |
|
@@ -223,7 +237,7 @@ def summary_repo( |
223 | 237 | if "Misc" in name or "Fixes" in name: |
224 | 238 | out += wrap_details(title, entries) |
225 | 239 | else: |
226 | | - out += f"\n\n### {title}" |
| 240 | + out += f"\n\n### {title}\n" |
227 | 241 | out += entries |
228 | 242 |
|
229 | 243 | # NOTE: For now, these TODOs can be manually fixed for each changelog. |
@@ -252,13 +266,20 @@ def summary_repo( |
252 | 266 |
|
253 | 267 | subrepos[name] = summary_repo( |
254 | 268 | org, |
255 | | - repo, |
| 269 | + name, |
256 | 270 | f"{path}/{name}", |
257 | 271 | commit_range, |
258 | 272 | filter_types=filter_types, |
259 | 273 | repo_order=repo_order, |
260 | 274 | ) |
261 | 275 |
|
| 276 | + # filter out subrepos with no commits (single line after stripping whitespace) |
| 277 | + subrepos = { |
| 278 | + name: output |
| 279 | + for name, output in subrepos.items() |
| 280 | + if len(output.strip().splitlines()) > 1 |
| 281 | + } |
| 282 | + |
262 | 283 | # pick subrepos in repo_order, and remove from dict |
263 | 284 | for name in repo_order: |
264 | 285 | if name in subrepos: |
@@ -380,6 +401,9 @@ def build( |
380 | 401 |
|
381 | 402 | output += output_contributors.strip() + "\n\n" |
382 | 403 | output += output_changelog.strip() + "\n\n" |
| 404 | + output += ( |
| 405 | + f"**Full Changelog**: https://github.com/{org}/{repo}/compare/{since}...{tag}" |
| 406 | + ) |
383 | 407 |
|
384 | 408 | if repo == "activitywatch": |
385 | 409 | output = output.replace("# activitywatch", "# activitywatch (bundle repo)") |
@@ -444,7 +468,7 @@ def get_all_contributors() -> set[str]: |
444 | 468 | logger.info("Getting all contributors") |
445 | 469 |
|
446 | 470 | # We will commit this file, to act as a cache (preventing us from querying GitHub API every time) |
447 | | - filename = "scripts/changelog_contributors.csv" |
| 471 | + filename = script_dir / "changelog_contributors.csv" |
448 | 472 |
|
449 | 473 | # mapping from username to one or more emails |
450 | 474 | usernames: Dict[str, set] = defaultdict(set) |
@@ -502,7 +526,7 @@ def get_twitter_of_ghusers(ghusers: Collection[str]): |
502 | 526 | logger.info("Getting twitter of GitHub usernames") |
503 | 527 |
|
504 | 528 | # We will commit this file, to act as a cache (preventing us from querying GitHub API every time) |
505 | | - filename = "scripts/changelog_contributors_twitter.csv" |
| 529 | + filename = script_dir / "changelog_contributors_twitter.csv" |
506 | 530 |
|
507 | 531 | twitter = {} |
508 | 532 |
|
|
0 commit comments