summaryrefslogtreecommitdiff
path: root/ci/github_releases.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2020-05-18 09:43:32 -0400
committerNed Batchelder <ned@nedbatchelder.com>2020-05-19 19:33:53 -0400
commitf6d5c35f43c4172348da4efaabf83395ce0b7853 (patch)
treeb13f0436e9db6e613f52d5f0b3eddbb65250a4cb /ci/github_releases.py
parent7f8dd68dd6da360374603f3a352c07713a3b082d (diff)
downloadpython-coveragepy-git-f6d5c35f43c4172348da4efaabf83395ce0b7853.tar.gz
GitHub release automation
Diffstat (limited to 'ci/github_releases.py')
-rw-r--r--ci/github_releases.py142
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