forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbug_bash.py
executable file
·187 lines (146 loc) · 5.7 KB
/
bug_bash.py
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()