1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
"""Handles creating a release PR"""
from __future__ import annotations
from pathlib import Path
from subprocess import check_call
from git import Commit, Head, Remote, Repo, TagReference
from packaging.version import Version
ROOT_SRC_DIR = Path(__file__).parents[1]
def main(version_str: str) -> None:
version = Version(version_str)
repo = Repo(str(ROOT_SRC_DIR))
if repo.is_dirty():
raise RuntimeError("Current repository is dirty. Please commit any changes and try again.")
upstream, release_branch = create_release_branch(repo, version)
try:
release_commit = release_changelog(repo, version)
tag = tag_release_commit(release_commit, repo, version)
print("push release commit")
repo.git.push(upstream.name, f"{release_branch}:main", "-f")
print("push release tag")
repo.git.push(upstream.name, tag, "-f")
finally:
print("checkout main to new release and delete release branch")
repo.heads.main.checkout()
repo.delete_head(release_branch, force=True)
upstream.fetch()
repo.git.reset("--hard", "upstream/main")
print("All done! ✨ 🍰 ✨")
def create_release_branch(repo: Repo, version: Version) -> tuple[Remote, Head]:
print("create release branch from upstream main")
upstream = get_upstream(repo)
upstream.fetch()
branch_name = f"release-{version}"
release_branch = repo.create_head(branch_name, upstream.refs.main, force=True)
upstream.push(refspec=f"{branch_name}:{branch_name}", force=True)
release_branch.set_tracking_branch(repo.refs[f"{upstream.name}/{branch_name}"])
release_branch.checkout()
return upstream, release_branch
def get_upstream(repo: Repo) -> Remote:
for remote in repo.remotes:
if any(url.endswith("tox-dev/tox.git") for url in remote.urls):
return remote
raise RuntimeError("could not find tox-dev/tox.git remote")
def release_changelog(repo: Repo, version: Version) -> Commit:
print("generate release commit")
check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR))
release_commit = repo.index.commit(f"release {version}")
return release_commit
def tag_release_commit(release_commit, repo, version) -> TagReference:
print("tag release commit")
existing_tags = [x.name for x in repo.tags]
if version in existing_tags:
print(f"delete existing tag {version}")
repo.delete_tag(version)
print(f"create tag {version}")
tag = repo.create_tag(version, ref=release_commit, force=True)
return tag
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(prog="release")
parser.add_argument("--version", required=True)
options = parser.parse_args()
main(options.version)
|