File: release.py

package info (click to toggle)
python-virtualenv 21.2.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,112 kB
  • sloc: python: 10,135; sh: 177; ansic: 61; csh: 53; makefile: 8
file content (90 lines) | stat: -rw-r--r-- 3,350 bytes parent folder | download
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
80
81
82
83
84
85
86
87
88
89
90
"""Handles creating a release."""

from __future__ import annotations

from pathlib import Path
from subprocess import call, check_call

from git import Commit, Remote, Repo, TagReference
from packaging.version import Version

ROOT_SRC_DIR = Path(__file__).resolve().parents[1]
CHANGELOG_DIR = ROOT_SRC_DIR / "docs" / "changelog"


def main(version_str: str, *, push: bool) -> None:
    repo = Repo(str(ROOT_SRC_DIR))
    if repo.is_dirty():
        msg = "Current repository is dirty. Please commit any changes and try again."
        raise RuntimeError(msg)
    remote = get_remote(repo)
    remote.fetch()
    version = resolve_version(version_str, repo)
    print(f"releasing {version}")  # noqa: T201
    release_commit = release_changelog(repo, version)
    tag = tag_release_commit(release_commit, repo, version)
    if push:
        print("push release commit")  # noqa: T201
        repo.git.push(remote.name, "HEAD:main")
        print("push release tag")  # noqa: T201
        repo.git.push(remote.name, tag)
    print("All done! ✨ 🍰 ✨")  # noqa: T201


def resolve_version(version_str: str, repo: Repo) -> Version:
    if version_str not in {"auto", "major", "minor", "patch"}:
        return Version(version_str)
    latest_tag = repo.git.describe("--tags", "--abbrev=0")
    parts = [int(x) for x in latest_tag.split(".")]
    if version_str == "major":
        parts = [parts[0] + 1, 0, 0]
    elif version_str == "minor":
        parts = [parts[0], parts[1] + 1, 0]
    elif version_str == "patch":
        parts[2] += 1
    elif any(CHANGELOG_DIR.glob("*.feature.rst")) or any(CHANGELOG_DIR.glob("*.removal.rst")):
        parts = [parts[0], parts[1] + 1, 0]
    else:
        parts[2] += 1
    return Version(".".join(str(p) for p in parts))


def get_remote(repo: Repo) -> Remote:
    upstream_remote = "pypa/virtualenv"
    urls = set()
    for remote in repo.remotes:
        for url in remote.urls:
            if url.rstrip(".git").endswith(upstream_remote):
                return remote
            urls.add(url)
    msg = f"could not find {upstream_remote} remote, has {urls}"
    raise RuntimeError(msg)


def release_changelog(repo: Repo, version: Version) -> Commit:
    print("generate release commit")  # noqa: T201
    check_call(["towncrier", "build", "--yes", "--version", version.public], cwd=str(ROOT_SRC_DIR))  # noqa: S607
    call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_SRC_DIR))  # noqa: S607
    repo.git.add(".")
    check_call(["pre-commit", "run", "--all-files"], cwd=str(ROOT_SRC_DIR))  # noqa: S607
    return repo.index.commit(f"release {version}")


def tag_release_commit(release_commit: Commit, repo: Repo, version: Version) -> TagReference:
    print("tag release commit")  # noqa: T201
    existing_tags = [x.name for x in repo.tags]
    if version in existing_tags:
        print(f"delete existing tag {version}")  # noqa: T201
        repo.delete_tag(version)
    print(f"create tag {version}")  # noqa: T201
    return repo.create_tag(version, ref=release_commit, force=True)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(prog="release")
    parser.add_argument("--version", default="auto")
    parser.add_argument("--no-push", action="store_true")
    options = parser.parse_args()
    main(options.version, push=not options.no_push)