| # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
| |
| """Upload release notes into GitHub releases.""" |
| |
| import json |
| import os |
| import shlex |
| import subprocess |
| import sys |
| import time |
| |
| 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 get_session(): |
| """ |
| Get an authenticated GitHub requests session. |
| """ |
| gh_session = requests.Session() |
| token = os.environ.get("GITHUB_TOKEN", "") |
| if token: |
| gh_session.headers["Authorization"] = f"Bearer {token}" |
| return gh_session |
| |
| def github_paginated(session, url): |
| """ |
| Get all the results from a paginated GitHub url. |
| """ |
| while True: |
| resp = session.get(url) |
| check_ok(resp) |
| yield from resp.json() |
| next_link = resp.links.get("next", None) |
| if not next_link: |
| break |
| url = next_link["url"] |
| |
| 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) |
| releases = { r['tag_name']: r for r in github_paginated(session, url) } |
| return releases |
| |
| RELEASE_BODY_FMT = """\ |
| ## Version {version} \N{EM DASH} {when} |
| |
| {relnote_text} |
| |
| :arrow_right:\xa0 PyPI page: [coverage {version}](https://pypi.org/project/coverage/{version}). |
| :arrow_right:\xa0 To install: `python3 -m pip install coverage=={version}` |
| """ |
| |
| def release_for_relnote(relnote, tag): |
| """ |
| Turn a release note dict into the data needed by GitHub for a release. |
| """ |
| relnote_text = relnote["text"] |
| version = relnote["version"] |
| body = RELEASE_BODY_FMT.format( |
| relnote_text=relnote_text, |
| version=version, |
| when=relnote["when"], |
| ) |
| return { |
| "tag_name": tag, |
| "name": version, |
| "body": body, |
| "draft": False, |
| "prerelease": relnote["prerelease"], |
| } |
| |
| def create_release(session, repo, release_data): |
| """ |
| Create a new GitHub release. |
| """ |
| print(f"Creating {release_data['name']}") |
| resp = session.post(RELEASES_URL.format(repo=repo), json=release_data) |
| check_ok(resp) |
| |
| def update_release(session, url, release_data): |
| """ |
| Update an existing GitHub release. |
| """ |
| print(f"Updating {release_data['name']}") |
| resp = session.patch(url, json=release_data) |
| check_ok(resp) |
| |
| def update_github_releases(json_filename, repo): |
| """ |
| Read the json file, and create or update releases in GitHub. |
| """ |
| gh_session = get_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 = relnote["version"] |
| if not does_tag_exist(tag): |
| tag = f"coverage-{tag}" |
| if not does_tag_exist(tag): |
| continue |
| release_data = release_for_relnote(relnote, tag) |
| exists = tag in releases |
| if not exists: |
| create_release(gh_session, repo, release_data) |
| time.sleep(3) |
| else: |
| release = releases[tag] |
| if release["body"] != release_data["body"]: |
| url = release["url"] |
| update_release(gh_session, url, release_data) |
| time.sleep(3) |
| |
| if __name__ == "__main__": |
| update_github_releases(*sys.argv[1:3]) |