The branch master has been updated via 216776e13b1673a13fccb525f2ec0c0d821f7525 (commit) via daa4fdc5eba7a9d7c10bee7ece40b9397eb7d8ca (commit) via 55a4f13f049861dfd06476d6d6e4cf1ac8a93c49 (commit) from cbda6bd72e4abbe16a4a260312b1289a0e9e4764 (commit)
- Log ----------------------------------------------------------------- commit 216776e13b1673a13fccb525f2ec0c0d821f7525 Author: Mark J. Cox <m...@awe.com> Date: Wed Feb 5 11:22:24 2020 +0000 Neews python 3.7+ (or do a different date parser) commit daa4fdc5eba7a9d7c10bee7ece40b9397eb7d8ca Author: Mark J. Cox <m...@awe.com> Date: Wed Feb 5 11:20:25 2020 +0000 Style changes undo yapf blank lines, and fix typo function name commit 55a4f13f049861dfd06476d6d6e4cf1ac8a93c49 Author: Mark J. Cox <m...@awe.com> Date: Wed Feb 5 10:49:33 2020 +0000 Add a tool for checking and changing labels ----------------------------------------------------------------------- Summary of changes: .gitignore | 3 + github-approve-label-workflow/README.md | 18 +++ .../github-approve-label-workflow.py | 165 +++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 github-approve-label-workflow/README.md create mode 100644 github-approve-label-workflow/github-approve-label-workflow.py diff --git a/.gitignore b/.gitignore index e3a688e..5c45ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,8 @@ ghpass.txt /OpenSSL-Query/inc /OpenSSL-Query/pm_to_blib +# in case someone forgets and commits their token +github-approve-label-workflow/token.txt + # Generic *~ diff --git a/github-approve-label-workflow/README.md b/github-approve-label-workflow/README.md new file mode 100644 index 0000000..8442b58 --- /dev/null +++ b/github-approve-label-workflow/README.md @@ -0,0 +1,18 @@ +Do we have any open openssl PR requests that have the label +"approval: done" that are over 24 hours old? If so if there +have been no other comments added since then we can automatically +move them to "approval: ready to merge" + +You need a token to make label changes and to ensure you don't +hit rate limiting if you just want a dry run. Get one from +https://github.com/settings/tokens/new select 'repo' only +then put it in token.txt (prefix with the string "token ", i.e. +echo "token 12903413aaaaaa" > token.txt + +to see what it would do: + +python github-approve-label-workflow --debug --token token.txt + +or to also actually change any labels + +python github-approve-label-workflow --debug --token token.txt --commit diff --git a/github-approve-label-workflow/github-approve-label-workflow.py b/github-approve-label-workflow/github-approve-label-workflow.py new file mode 100644 index 0000000..99713f7 --- /dev/null +++ b/github-approve-label-workflow/github-approve-label-workflow.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python +# requires python 3.7+ +# +# Do we have any open PR's that have label "Approval: done" +# that are over 24 hours without any other comments? +# +# get a token.... https://github.com/settings/tokens/new -- just repo is fine +# pop it in token.txt or you'll get a bad API limit +# +# note that we'd use pyGithub but we can't as it doesn't fully handle the timeline objects +# as of Feb 2020 +# +# m...@openssl.org Feb 2020 +# +import requests +import json +from datetime import datetime, timezone +from optparse import OptionParser + +api_url = "https://api.github.com/repos/openssl/openssl" + +def convertdate(date): + # python fromisoformat needs a TZ in hours/minutes + return datetime.fromisoformat(date.replace('Z', '+00:00')) + +# Get all the open pull requests, filtering by approval: done label + +def getpullrequests(): + url = api_url + "/pulls?per_page=100&page=1" # defaults to open + res = requests.get(url, headers=headers) + repos = res.json() + prs = [] + while 'next' in res.links.keys(): + res = requests.get(res.links['next']['url'], headers=headers) + repos.extend(res.json()) + + # Let's filter by label if we're just looking to move things, we can parse + # everything for statistics in another script + + try: + for pr in repos: + if 'labels' in pr: + for label in pr['labels']: + if label['name'] == 'approval: done': + prs.append(pr['number']) + except: + print("failed", repos['message']) + return prs + +# Change the labels on an issue from approval: done to approval: ready to merge + +def movelabeldonetoready(issue): + url = api_url + "/issues/" + str(issue) + "/labels/approval:%20done" + res = requests.delete(url, headers=headers) + if (res.status_code != 200): + print("Error removing label", res.status_code, res.content) + return + url = api_url + "/issues/" + str(issue) + "/labels" + newlabel = {"labels": ["approval: ready to merge"]} + res = requests.post(url, data=json.dumps(newlabel), headers=headers) + if (res.status_code != 200): + print("Error adding label", res.status_code, res.content) + +# Check through an issue and see if it's a candidate for moving + +def checkpr(pr): + url = api_url + "/issues/" + str(pr) + "/timeline?per_page=100&page=1" + res = requests.get(url, headers=headers) + repos = res.json() + while 'next' in res.links.keys(): + res = requests.get(res.links['next']['url'], headers=headers) + repos.extend(res.json()) + + comments = [] + approvallabel = {} + readytomerge = 0 + + for event in repos: + try: + if (event['event'] == "commented"): + comments.append(convertdate(event["updated_at"])) + if debug: + print("debug: commented at ", + convertdate(event["updated_at"])) + if (event['event'] == "committed"): + comments.append(convertdate(event["author"]["date"])) + if debug: + print("debug: created at ", + convertdate(event["author"]["date"])) + elif (event['event'] == "labeled"): + if debug: + print("debug: labelled with ", event['label']['name'], + "at", convertdate(event["created_at"])) + approvallabel[event['label']['name']] = convertdate( + event["created_at"]) + elif (event['event'] == "unlabeled"): + if (debug): + print("debug: unlabelled with ", event['label']['name'], + "at", convertdate(event["created_at"])) + if event['label'][ + 'name'] in approvallabel: # have to do this for if labels got renamed in the middle + del approvallabel[event['label']['name']] + elif (event['event'] == "reviewed" + and event['state'] == "approved"): + if debug: + print("debug: approved at", + convertdate(event['submitted_at'])) + except: + return (repos['message']) + + if 'approval: ready to merge' in approvallabel: + return ("issue already has label approval: ready to merge") + if 'approval: done' not in approvallabel: + return ("issue did not get label approval: done") + approvedone = approvallabel['approval: done'] + + if max(comments) > approvedone: + return ("issue had comments after approval: done label was given") + + now = datetime.now(timezone.utc) + hourssinceapproval = (now - approvedone).total_seconds() / 3600 + if debug: + print("Now: ", now) + print("Last comment: ", max(comments)) + print("Approved since: ", approvedone) + print("hours since approval", hourssinceapproval) + + if (hourssinceapproval < 24): + return ("not yet 24 hours since labelled approval:done hours:" + + str(int(hourssinceapproval))) + + if (options.commit): + print("Moving issue ", pr, " to approval: ready to merge") + movelabeldonetoready(pr) + else: + print("use --commit to actually change the labels") + return ( + "this issue was candidate to move to approval: ready to merge hours:" + + str(int(hourssinceapproval))) + +# main + +parser = OptionParser() +parser.add_option("-d","--debug",action="store_true",help="be noisy",dest="debug") +parser.add_option("-t","--token",help="file containing github authentication token for example 'token 18asdjada...'",dest="token") +parser.add_option("-c","--commit",action="store_true",help="actually change the labels",dest="commit") +(options, args) = parser.parse_args() +if (options.token): + fp = open(options.token, "r") + git_token = fp.readline().strip('\n') +else: + git_token = "" # blank token is fine, but you can't change labels and you hit API rate limiting +debug = options.debug +# since timeline is a preview feature we have to enable access to it with an accept header +headers = { + "Accept": "application/vnd.github.mockingbird-preview", + "Authorization": git_token +} + +if debug: + print("Getting list of PRs") +prs = getpullrequests() +print("There were", len(prs), "open PRs with approval:done ") +for pr in prs: + print(pr, checkpr(pr))