diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2020-05-18 09:43:32 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2020-05-19 19:33:53 -0400 |
commit | f6d5c35f43c4172348da4efaabf83395ce0b7853 (patch) | |
tree | b13f0436e9db6e613f52d5f0b3eddbb65250a4cb /ci/github_releases.py | |
parent | 7f8dd68dd6da360374603f3a352c07713a3b082d (diff) | |
download | python-coveragepy-git-f6d5c35f43c4172348da4efaabf83395ce0b7853.tar.gz |
GitHub release automation
Diffstat (limited to 'ci/github_releases.py')
-rw-r--r-- | ci/github_releases.py | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/ci/github_releases.py b/ci/github_releases.py new file mode 100644 index 00000000..408a2ab8 --- /dev/null +++ b/ci/github_releases.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Upload release notes into GitHub releases. +""" + +import json +import shlex +import subprocess +import sys + +import pkg_resources +import requests + + +RELEASES_URL = "https://api.github.com/repos/{repo}/releases" + +def run_command(cmd): + """ + Run a command line (with no shell). + + Returns a tuple: + bool: true if the command succeeded. + str: the output of the command. + + """ + proc = subprocess.run( + shlex.split(cmd), + shell=False, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + output = proc.stdout.decode("utf-8") + succeeded = proc.returncode == 0 + return succeeded, output + +def does_tag_exist(tag_name): + """ + Does `tag_name` exist as a tag in git? + """ + return run_command(f"git rev-parse --verify {tag_name}")[0] + +def check_ok(resp): + """ + Check that the Requests response object was successful. + + Raise an exception if not. + """ + if not resp: + print(f"text: {resp.text!r}") + resp.raise_for_status() + +def github_paginated(session, url): + """ + Get all the results from a paginated GitHub url. + """ + while True: + print(f"GETTING: {url}") + resp = session.get(url) + check_ok(resp) + yield from resp.json() + if 'Link' not in resp.headers: + break + links = resp.headers['link'].split(",") + next_link = next((link for link in links if 'rel="next"' in link), None) + if not next_link: + break + url = next_link.split(";")[0].strip(" <>") + +def get_releases(session, repo): + """ + Get all the releases from a name/project repo. + + Returns: + A dict mapping tag names to release dictionaries. + """ + url = RELEASES_URL.format(repo=repo) + "?per_page=100" + releases = { r['tag_name']: r for r in github_paginated(session, url) } + return releases + +def release_for_relnote(relnote): + """ + Turn a release note dict into the data needed by GitHub for a release. + """ + tag = f"coverage-{relnote['version']}" + return { + "tag_name": tag, + "name": tag, + "body": relnote["text"], + "draft": False, + "prerelease": relnote["prerelease"], + } + +def create_release(session, repo, relnote): + """ + Create a new GitHub release. + """ + print(f"Creating {relnote['version']}") + data = release_for_relnote(relnote) + resp = session.post(RELEASES_URL.format(repo=repo), json=data) + check_ok(resp) + +def update_release(session, url, relnote): + """ + Update an existing GitHub release. + """ + print(f"Updating {relnote['version']}") + data = release_for_relnote(relnote) + resp = session.patch(url, json=data) + check_ok(resp) + +def update_github_releases(json_filename, repo): + """ + Read the json file, and create or update releases in GitHub. + """ + gh_session = requests.Session() + releases = get_releases(gh_session, repo) + if 0: # if you need to delete all the releases! + for release in releases.values(): + print(release["tag_name"]) + resp = gh_session.delete(release["url"]) + check_ok(resp) + return + + with open(json_filename) as jf: + relnotes = json.load(jf) + relnotes.sort(key=lambda rel: pkg_resources.parse_version(rel["version"])) + for relnote in relnotes: + tag = "coverage-" + relnote["version"] + if not does_tag_exist(tag): + continue + exists = tag in releases + if not exists: + create_release(gh_session, repo, relnote) + else: + release = releases[tag] + if release["body"] != relnote["text"]: + url = release["url"] + update_release(gh_session, url, relnote) + +if __name__ == "__main__": + update_github_releases(*sys.argv[1:]) # pylint: disable=no-value-for-parameter |