2021-11-09 14:53:02 +03:00
|
|
|
import datetime as dt
|
2020-09-04 18:26:24 +03:00
|
|
|
import os
|
2021-11-18 12:36:42 +03:00
|
|
|
import re
|
2024-10-17 23:38:22 +03:00
|
|
|
import subprocess
|
2022-07-27 01:37:02 +03:00
|
|
|
from collections.abc import Iterable
|
2020-09-04 18:26:24 +03:00
|
|
|
from pathlib import Path
|
2021-11-09 14:53:02 +03:00
|
|
|
|
2021-11-18 12:36:42 +03:00
|
|
|
import git
|
2021-11-16 15:54:05 +03:00
|
|
|
import github.PullRequest
|
2021-11-18 12:36:42 +03:00
|
|
|
import github.Repository
|
|
|
|
from github import Github
|
2020-09-04 18:26:24 +03:00
|
|
|
from jinja2 import Template
|
|
|
|
|
|
|
|
CURRENT_FILE = Path(__file__)
|
|
|
|
ROOT = CURRENT_FILE.parents[1]
|
2021-11-18 12:36:42 +03:00
|
|
|
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
|
|
|
GITHUB_REPO = os.getenv("GITHUB_REPOSITORY")
|
|
|
|
GIT_BRANCH = os.getenv("GITHUB_REF_NAME")
|
2020-09-04 18:26:24 +03:00
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
"""
|
|
|
|
Script entry point.
|
|
|
|
"""
|
2021-11-18 12:36:42 +03:00
|
|
|
# Generate changelog for PRs merged yesterday
|
|
|
|
merged_date = dt.date.today() - dt.timedelta(days=1)
|
|
|
|
repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO)
|
|
|
|
merged_pulls = list(iter_pulls(repo, merged_date))
|
|
|
|
print(f"Merged pull requests: {merged_pulls}")
|
2020-09-04 18:26:24 +03:00
|
|
|
if not merged_pulls:
|
|
|
|
print("Nothing was merged, existing.")
|
|
|
|
return
|
|
|
|
|
|
|
|
# Group pull requests by type of change
|
|
|
|
grouped_pulls = group_pulls_by_change_type(merged_pulls)
|
2023-09-30 13:20:49 +03:00
|
|
|
if not any(grouped_pulls.values()):
|
|
|
|
print("Pull requests merged aren't worth a changelog mention.")
|
|
|
|
return
|
2020-09-04 18:26:24 +03:00
|
|
|
|
|
|
|
# Generate portion of markdown
|
2021-11-18 12:36:42 +03:00
|
|
|
release_changes_summary = generate_md(grouped_pulls)
|
|
|
|
print(f"Summary of changes: {release_changes_summary}")
|
2020-09-04 18:26:24 +03:00
|
|
|
|
|
|
|
# Update CHANGELOG.md file
|
2021-11-18 12:36:42 +03:00
|
|
|
release = f"{merged_date:%Y.%m.%d}"
|
|
|
|
changelog_path = ROOT / "CHANGELOG.md"
|
|
|
|
write_changelog(changelog_path, release, release_changes_summary)
|
|
|
|
print(f"Wrote {changelog_path}")
|
|
|
|
|
|
|
|
# Update version
|
2024-09-06 15:05:26 +03:00
|
|
|
setup_py_path = ROOT / "pyproject.toml"
|
2021-11-18 12:36:42 +03:00
|
|
|
update_version(setup_py_path, release)
|
|
|
|
print(f"Updated version in {setup_py_path}")
|
|
|
|
|
2024-10-17 23:38:22 +03:00
|
|
|
# Run uv lock
|
|
|
|
subprocess.run(["uv", "lock"], cwd=ROOT)
|
|
|
|
|
2021-11-18 12:36:42 +03:00
|
|
|
# Commit changes, create tag and push
|
|
|
|
update_git_repo([changelog_path, setup_py_path], release)
|
|
|
|
|
|
|
|
# Create GitHub release
|
|
|
|
github_release = repo.create_git_release(
|
|
|
|
tag=release,
|
|
|
|
name=release,
|
|
|
|
message=release_changes_summary,
|
2020-09-04 18:26:24 +03:00
|
|
|
)
|
2021-11-18 12:36:42 +03:00
|
|
|
print(f"Created release on GitHub {github_release}")
|
2020-09-04 18:26:24 +03:00
|
|
|
|
|
|
|
|
2021-11-18 12:36:42 +03:00
|
|
|
def iter_pulls(
|
|
|
|
repo: github.Repository.Repository,
|
|
|
|
merged_date: dt.date,
|
|
|
|
) -> Iterable[github.PullRequest.PullRequest]:
|
2020-09-04 18:26:24 +03:00
|
|
|
"""Fetch merged pull requests at the date we're interested in."""
|
|
|
|
recent_pulls = repo.get_pulls(
|
2021-11-18 12:36:42 +03:00
|
|
|
state="closed",
|
|
|
|
sort="updated",
|
|
|
|
direction="desc",
|
2020-09-04 18:26:24 +03:00
|
|
|
).get_page(0)
|
|
|
|
for pull in recent_pulls:
|
2021-11-18 12:36:42 +03:00
|
|
|
if pull.merged and pull.merged_at.date() == merged_date:
|
2020-09-04 18:26:24 +03:00
|
|
|
yield pull
|
|
|
|
|
|
|
|
|
2021-11-16 15:54:05 +03:00
|
|
|
def group_pulls_by_change_type(
|
|
|
|
pull_requests_list: list[github.PullRequest.PullRequest],
|
|
|
|
) -> dict[str, list[github.PullRequest.PullRequest]]:
|
2020-09-04 18:26:24 +03:00
|
|
|
"""Group pull request by change type."""
|
|
|
|
grouped_pulls = {
|
|
|
|
"Changed": [],
|
|
|
|
"Fixed": [],
|
2023-02-05 13:03:22 +03:00
|
|
|
"Documentation": [],
|
2020-09-04 18:26:24 +03:00
|
|
|
"Updated": [],
|
|
|
|
}
|
|
|
|
for pull in pull_requests_list:
|
2021-11-26 18:41:50 +03:00
|
|
|
label_names = {label.name for label in pull.labels}
|
2023-02-05 13:03:22 +03:00
|
|
|
if "project infrastructure" in label_names:
|
|
|
|
# Don't mention it in the changelog
|
|
|
|
continue
|
2020-09-04 18:26:24 +03:00
|
|
|
if "update" in label_names:
|
|
|
|
group_name = "Updated"
|
|
|
|
elif "bug" in label_names:
|
|
|
|
group_name = "Fixed"
|
2023-02-05 13:03:22 +03:00
|
|
|
elif "docs" in label_names:
|
|
|
|
group_name = "Documentation"
|
2020-09-04 18:26:24 +03:00
|
|
|
else:
|
|
|
|
group_name = "Changed"
|
|
|
|
grouped_pulls[group_name].append(pull)
|
|
|
|
return grouped_pulls
|
|
|
|
|
|
|
|
|
2021-11-16 15:54:05 +03:00
|
|
|
def generate_md(grouped_pulls: dict[str, list[github.PullRequest.PullRequest]]) -> str:
|
2020-09-04 18:26:24 +03:00
|
|
|
"""Generate markdown file from Jinja template."""
|
|
|
|
changelog_template = ROOT / ".github" / "changelog-template.md"
|
|
|
|
template = Template(changelog_template.read_text(), autoescape=True)
|
2021-11-18 12:36:42 +03:00
|
|
|
return template.render(grouped_pulls=grouped_pulls)
|
|
|
|
|
|
|
|
|
|
|
|
def write_changelog(file_path: Path, release: str, content: str) -> None:
|
|
|
|
"""Write Release details to the changelog file."""
|
|
|
|
content = f"## {release}\n{content}"
|
|
|
|
old_content = file_path.read_text()
|
|
|
|
updated_content = old_content.replace(
|
|
|
|
"<!-- GENERATOR_PLACEHOLDER -->",
|
|
|
|
f"<!-- GENERATOR_PLACEHOLDER -->\n\n{content}",
|
|
|
|
)
|
|
|
|
file_path.write_text(updated_content)
|
|
|
|
|
|
|
|
|
|
|
|
def update_version(file_path: Path, release: str) -> None:
|
2024-09-06 18:30:55 +03:00
|
|
|
"""Update template version in pyproject.toml."""
|
2021-11-18 12:36:42 +03:00
|
|
|
old_content = file_path.read_text()
|
|
|
|
updated_content = re.sub(
|
|
|
|
r'\nversion = "\d+\.\d+\.\d+"\n',
|
|
|
|
f'\nversion = "{release}"\n',
|
|
|
|
old_content,
|
|
|
|
)
|
|
|
|
file_path.write_text(updated_content)
|
|
|
|
|
|
|
|
|
|
|
|
def update_git_repo(paths: list[Path], release: str) -> None:
|
|
|
|
"""Commit, tag changes in git repo and push to origin."""
|
|
|
|
repo = git.Repo(ROOT)
|
|
|
|
for path in paths:
|
|
|
|
repo.git.add(path)
|
|
|
|
message = f"Release {release}"
|
|
|
|
|
|
|
|
user = repo.git.config("--get", "user.name")
|
|
|
|
email = repo.git.config("--get", "user.email")
|
|
|
|
|
|
|
|
repo.git.commit(
|
|
|
|
m=message,
|
|
|
|
author=f"{user} <{email}>",
|
|
|
|
)
|
|
|
|
repo.git.tag("-a", release, m=message)
|
|
|
|
server = f"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git"
|
|
|
|
print(f"Pushing changes to {GIT_BRANCH} branch of {GITHUB_REPO}")
|
|
|
|
repo.git.push(server, GIT_BRANCH)
|
|
|
|
repo.git.push("--tags", server, GIT_BRANCH)
|
2020-09-04 18:26:24 +03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2021-11-09 13:32:08 +03:00
|
|
|
if GITHUB_REPO is None:
|
2023-04-15 17:43:04 +03:00
|
|
|
raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY")
|
2021-11-18 12:36:42 +03:00
|
|
|
if GIT_BRANCH is None:
|
2023-04-15 17:43:04 +03:00
|
|
|
raise RuntimeError("No git branch set, please set the GITHUB_REF_NAME environment variable")
|
2020-09-04 18:26:24 +03:00
|
|
|
main()
|