diff --git a/CHANGELOG.md b/CHANGELOG.md index c9e58b70b1eb..4bbfe730777e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,7 +147,7 @@ Meanwhile, we did a bunch of improvements to our manual. If you had trouble runn * Switch pre-release action to ncipollo/release-action [#1466](https://github.com/rerun-io/rerun/pull/1466) * Disallow some methods and types via Clippy[#1411](https://github.com/rerun-io/rerun/pull/1411) -#### Other not user facing refactors +#### Other non-user-facing refactors * Fix: don't create a dummy LogDb when opening the Rerun Menu [#1440](https://github.com/rerun-io/rerun/pull/1440) * `re_renderer` * `Draw Phases` in preparation of executing `Renderer` several times on different targets [#1419](https://github.com/rerun-io/rerun/pull/1419) diff --git a/RELEASES.md b/RELEASES.md index 4265fe487b17..22de63cf3eab 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -53,9 +53,11 @@ Copy this checklist to the the PR description, go through it from top to bottom, * [ ] `./scripts/publish_crates.sh --dry-run` * [ ] Bump version number in root `Cargo.toml`. * [ ] Update `CHANGELOG.md` with the new version number and the summary and the gif - * Go through https://github.com/rerun-io/rerun/compare/latest...HEAD and manually add any important PR descriptions to the `CHANGELOG.md`, with a link to the PR (which should have a deeper explanation). - * You can use git log to quickly generate a list of commit headlines, use `git fetch --tags --force && git log --pretty=format:%s latest..HEAD` (fetch with `--force` is necessary to update the `latest` tag) - * [ ]Β Make sure to it includes instructions for handling any breaking changes + * [ ] Run `pip install GitPython && scripts/generate_changelog.py` + * [ ] Edit PR descriptions/labels to improve the generated changelog + * [ ] Copy-paste the results into `CHANGELOG.md`. + * [ ] Editorialize the changelog if necessary + * [ ]Β Make sure the changelog includes instructions for handling any breaking changes * [ ] Get the PR reviewed * [ ] Check that CI is green * [ ] Publish the crates (see below) diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py new file mode 100755 index 000000000000..eff040b54c48 --- /dev/null +++ b/scripts/generate_changelog.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +""" +Summarizes recent PRs based on their GitHub labels. + +The result can be copy-pasted into CHANGELOG.md, though it often needs some manual editing too. +""" + +import re +import sys +from typing import Any, List, Optional, Tuple + +import requests +from git import Repo # pip install GitPython +from tqdm import tqdm + + +def get_github_token() -> str: + import os + + token = os.environ.get("GH_ACCESS_TOKEN", "") + if token != "": + return token + + home_dir = os.path.expanduser("~") + token_file = os.path.join(home_dir, ".githubtoken") + + try: + with open(token_file, "r") as f: + token = f.read().strip() + return token + except Exception: + pass + + print("ERROR: expected a GitHub token in the environment variable GH_ACCESS_TOKEN or in ~/.githubtoken") + sys.exit(1) + + +OWNER = "rerun-io" +REPO = "rerun" +COMMIT_RANGE = "latest..HEAD" +INCLUDE_LABELS = False # It adds quite a bit of visual noise + + +def pr_title_labels(pr_number: int) -> Tuple[Optional[str], List[str]]: + url = f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}" + gh_access_token = get_github_token() + headers = {"Authorization": f"Token {gh_access_token}"} + response = requests.get(url, headers=headers) + json = response.json() + + # Check if the request was successful (status code 200) + if response.status_code == 200: + labels = [label["name"] for label in json["labels"]] + return (json["title"], labels) + else: + print(f"ERROR: {response.status_code} - {json['message']}") + return (None, []) + + +def commit_title_pr_number(commit: Any) -> Tuple[str, Optional[int]]: + match = re.match(r"(.*) \(#(\d+)\)", commit.summary) + if match: + return (str(match.group(1)), int(match.group(2))) + else: + return (commit.summary, None) + + +def print_section(title: str, items: List[str]) -> None: + if 0 < len(items): + print(f"#### {title}") + for line in items: + print(f"- {line}") + print() + + +repo = Repo(".") +commits = list(repo.iter_commits(COMMIT_RANGE)) +commits.reverse() # Most recent last + +# Sections: +analytics = [] +enhancement = [] +bugs = [] +dev_experience = [] +docs = [] +examples = [] +misc = [] +performance = [] +python = [] +renderer = [] +rfc = [] +rust = [] +ui = [] +viewer = [] +web = [] + +for commit in tqdm(commits, desc="Processing commits"): + (title, pr_number) = commit_title_pr_number(commit) + if pr_number is None: + # Someone committed straight to main: + summary = f"{title} [{commit.hexsha}](https://github.com/{OWNER}/{REPO}/commit/{commit.hexsha})" + misc.append(summary) + else: + (pr_title, labels) = pr_title_labels(pr_number) + title = pr_title or title # We prefer the PR title if available + summary = f"{title} [#{pr_number}](https://github.com/{OWNER}/{REPO}/pull/{pr_number})" + + if INCLUDE_LABELS and 0 < len(labels): + summary += f" ({', '.join(labels)})" + + added = False + + if labels == ["β›΄ release"]: + # Ignore release PRs + continue + + # Some PRs can show up underm multiple sections: + if "🐍 python API" in labels: + python.append(summary) + added = True + if "πŸ¦€ rust SDK" in labels: + rust.append(summary) + added = True + if "πŸ“Š analytics" in labels: + analytics.append(summary) + added = True + + if not added: + # Put the remaining PRs under just one section: + if "πŸͺ³ bug" in labels or "πŸ’£ crash" in labels: + bugs.append(summary) + elif "πŸ“‰ performance" in labels: + performance.append(summary) + elif "examples" in labels: + examples.append(summary) + elif "πŸ“– documentation" in labels: + docs.append(summary) + elif "ui" in labels: + ui.append(summary) + elif "πŸ“Ί re_viewer" in labels: + viewer.append(summary) + elif "πŸ”Ί re_renderer" in labels: + renderer.append(summary) + elif "πŸ•ΈοΈ web" in labels: + web.append(summary) + elif "enhancement" in labels: + enhancement.append(summary) + elif "πŸ§‘β€πŸ’» dev experience" in labels: + dev_experience.append(summary) + elif "πŸ’¬ discussion" in labels: + rfc.append(summary) + elif not added: + misc.append(summary) + +print() +# Most interesting first: +print_section("🐍 Python SDK", python) +print_section("πŸ¦€ Rust SDK", rust) +print_section("πŸͺ³ Bug Fixes", bugs) +print_section("πŸš€ Performance Improvements", performance) +print_section("πŸ§‘β€πŸ« Examples", examples) +print_section("πŸ“š Docs", docs) +print_section("πŸ–Ό UI Improvements", ui) +print_section("πŸ€·β€β™‚οΈ Other Viewer Improvements", viewer) +print_section("πŸ•ΈοΈ Web", web) +print_section("🎨 Renderer Improvements", renderer) +print_section("✨ Other Enhancement", enhancement) +print_section("πŸ“ˆ Analytics", analytics) +print_section("πŸ—£ Merged RFCs", rfc) +print_section("πŸ§‘β€πŸ’» Dev-experience", dev_experience) +print_section("πŸ€·β€β™‚οΈ Other", misc)