Loading...
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #!/usr/bin/env python3 # Copyright (c) 2021, Facebook # # SPDX-License-Identifier: Apache-2.0 """Query the Top-Ten Bug Bashers This script will query the top-ten Bug Bashers in a specified date window. Usage: ./scripts/bug-bash.py -t ~/.ghtoken -b 2021-07-26 -e 2021-08-07 GITHUB_TOKEN="..." ./scripts/bug-bash.py -b 2021-07-26 -e 2021-08-07 """ import argparse from datetime import datetime, timedelta import operator import os # Requires PyGithub from github import Github def parse_args(): parser = argparse.ArgumentParser(allow_abbrev=False) parser.add_argument('-a', '--all', dest='all', help='Show all bugs squashed', action='store_true') parser.add_argument('-t', '--token', dest='tokenfile', help='File containing GitHub token (alternatively, use GITHUB_TOKEN env variable)', metavar='FILE') parser.add_argument('-s', '--start', dest='start', help='start date (YYYY-mm-dd)', metavar='START_DATE', type=valid_date_type, required=True) parser.add_argument('-e', '--end', dest='end', help='end date (YYYY-mm-dd)', metavar='END_DATE', type=valid_date_type, required=True) args = parser.parse_args() if args.end < args.start: raise ValueError( 'end date {} is before start date {}'.format(args.end, args.start)) if args.tokenfile: with open(args.tokenfile, 'r') as file: token = file.read() token = token.strip() else: if 'GITHUB_TOKEN' not in os.environ: raise ValueError('No credentials specified') token = os.environ['GITHUB_TOKEN'] setattr(args, 'token', token) return args class BugBashTally(object): def __init__(self, gh, start_date, end_date): """Create a BugBashTally object with the provided Github object, start datetime object, and end datetime object""" self._gh = gh self._repo = gh.get_repo('zephyrproject-rtos/zephyr') self._start_date = start_date self._end_date = end_date self._issues = [] self._pulls = [] def get_tally(self): """Return a dict with (key = user, value = score)""" tally = dict() for p in self.get_pulls(): user = p.user.login tally[user] = tally.get(user, 0) + 1 return tally def get_rev_tally(self): """Return a dict with (key = score, value = list<user>) sorted in descending order""" # there may be ties! rev_tally = dict() for user, score in self.get_tally().items(): if score not in rev_tally: rev_tally[score] = [user] else: rev_tally[score].append(user) # sort in descending order by score rev_tally = dict( sorted(rev_tally.items(), key=operator.itemgetter(0), reverse=True)) return rev_tally def get_top_ten(self): """Return a dict with (key = score, value = user) sorted in descending order""" top_ten = [] for score, users in self.get_rev_tally().items(): # do not sort users by login - hopefully fair-ish for user in users: if len(top_ten) == 10: return top_ten top_ten.append(tuple([score, user])) return top_ten def get_pulls(self): """Return GitHub pull requests that squash bugs in the provided date window""" if self._pulls: return self._pulls self.get_issues() return self._pulls def get_issues(self): """Return GitHub issues representing bugs in the provided date window""" if self._issues: return self._issues cutoff = self._end_date + timedelta(1) issues = self._repo.get_issues(state='closed', labels=[ 'bug'], since=self._start_date) for i in issues: # the PyGithub API and v3 REST API do not facilitate 'until' # or 'end date' :-/ if i.closed_at < self._start_date or i.closed_at > cutoff: continue ipr = i.pull_request if ipr is None: # ignore issues without a linked pull request continue prid = int(ipr.html_url.split('/')[-1]) pr = self._repo.get_pull(prid) if not pr.merged: # pull requests that were not merged do not count continue self._pulls.append(pr) self._issues.append(i) return self._issues # https://gist.github.com/monkut/e60eea811ef085a6540f def valid_date_type(arg_date_str): """custom argparse *date* type for user dates values given from the command line""" try: return datetime.strptime(arg_date_str, "%Y-%m-%d") except ValueError: msg = "Given Date ({0}) not valid! Expected format, YYYY-MM-DD!".format(arg_date_str) raise argparse.ArgumentTypeError(msg) def print_top_ten(top_ten): """Print the top-ten bug bashers""" for score, user in top_ten: # print tab-separated value, to allow for ./script ... > foo.csv print('{}\t{}'.format(score, user)) def main(): args = parse_args() bbt = BugBashTally(Github(args.token), args.start, args.end) if args.all: # print one issue per line issues = bbt.get_issues() pulls = bbt.get_pulls() n = len(issues) m = len(pulls) assert n == m for i in range(0, n): print('{}\t{}\t{}'.format( issues[i].number, pulls[i].user.login, pulls[i].title)) else: # print the top ten print_top_ten(bbt.get_top_ten()) if __name__ == '__main__': main() |