1 | # given a PR number, get all contributers and the summary from |
---|
2 | # GitHub's API |
---|
3 | |
---|
4 | import sys |
---|
5 | import json |
---|
6 | import base64 |
---|
7 | |
---|
8 | from twisted.internet.task import react |
---|
9 | from twisted.internet.defer import inlineCallbacks, returnValue |
---|
10 | |
---|
11 | import treq |
---|
12 | |
---|
13 | base_pr_url = "https://api.github.com/repos/tahoe-lafs/tahoe-lafs/pulls/{}" |
---|
14 | ignore_handles = ('codecov-io', ) |
---|
15 | |
---|
16 | |
---|
17 | def _find_pull_request_numbers(): |
---|
18 | """ |
---|
19 | This returns a list of Pull Request numbers that are |
---|
20 | interesting. It first assumes any command-line arguments are PR |
---|
21 | numbers. Failing that, it reads stdin and looks for works starting |
---|
22 | with 'PR' |
---|
23 | """ |
---|
24 | if len(sys.argv) < 2: |
---|
25 | data = sys.stdin.read() |
---|
26 | if not len(data): |
---|
27 | print("put some PR numbers on the command-line") |
---|
28 | raise SystemExit(1) |
---|
29 | else: |
---|
30 | all_prs = set() |
---|
31 | for word in data.split(): |
---|
32 | word = word.strip() |
---|
33 | if word.startswith('PR'): |
---|
34 | all_prs.add(word[2:]) |
---|
35 | all_prs = list(all_prs) |
---|
36 | print("Found {} PRs in stdin text".format(len(all_prs))) |
---|
37 | else: |
---|
38 | all_prs = sys.argv[1:] |
---|
39 | return all_prs |
---|
40 | |
---|
41 | |
---|
42 | def _read_github_token(fname='token'): |
---|
43 | """ |
---|
44 | read a secret github token; a 'token' file contains two lines: |
---|
45 | username, github token. |
---|
46 | |
---|
47 | If the token can't be found, SystemExit is raised |
---|
48 | """ |
---|
49 | try: |
---|
50 | with open(fname, 'r') as f: |
---|
51 | data = f.read().strip() |
---|
52 | username, token = data.split('\n', 1) |
---|
53 | except (IOError, EnvironmentError) as e: |
---|
54 | print("Couldn't open or parse 'token' file: {}".format(e)) |
---|
55 | raise SystemExit(1) |
---|
56 | except ValueError: |
---|
57 | print("'token' should contain two lines: username, github token") |
---|
58 | raise SystemExit(1) |
---|
59 | return username, token |
---|
60 | |
---|
61 | |
---|
62 | def _initialize_headers(username, token): |
---|
63 | """ |
---|
64 | Create the base headers for all requests. |
---|
65 | |
---|
66 | :return dict: the headers dict |
---|
67 | """ |
---|
68 | return { |
---|
69 | "User-Agent": "treq", |
---|
70 | "Authorization": "Basic {}".format(base64.b64encode("{}:{}".format(username, token))), |
---|
71 | } |
---|
72 | |
---|
73 | |
---|
74 | @inlineCallbacks |
---|
75 | def _report_authors(data, headers): |
---|
76 | print("Commits:") |
---|
77 | commits_resp = yield treq.get(data['commits_url'], headers=headers) |
---|
78 | commits_data = yield commits_resp.text() |
---|
79 | commits = json.loads(commits_data) |
---|
80 | authors = set() |
---|
81 | for commit in commits: |
---|
82 | if commit['author'] is None: |
---|
83 | print(" {}: no author!".format(commit['sha'])) |
---|
84 | else: |
---|
85 | author = commit['author']['login'] |
---|
86 | print(" {}: {}".format(commit['sha'], author)) |
---|
87 | if author not in ignore_handles: |
---|
88 | authors.add(author) |
---|
89 | returnValue(authors) |
---|
90 | |
---|
91 | |
---|
92 | @inlineCallbacks |
---|
93 | def _report_helpers(data, headers): |
---|
94 | helpers = set() |
---|
95 | print("Comments:") |
---|
96 | comments_resp = yield treq.get(data['comments_url'], headers=headers) |
---|
97 | comments_data = yield comments_resp.text() |
---|
98 | comments = json.loads(comments_data) |
---|
99 | for comment in comments: |
---|
100 | author = comment['user']['login'] |
---|
101 | if author not in ignore_handles: |
---|
102 | helpers.add(author) |
---|
103 | print(" {}: {}".format(author, comment['body'].replace('\n', ' ')[:60])) |
---|
104 | returnValue(helpers) |
---|
105 | |
---|
106 | @inlineCallbacks |
---|
107 | def _request_pr_information(username, token, headers, all_prs): |
---|
108 | """ |
---|
109 | Download PR information from GitHub. |
---|
110 | |
---|
111 | :return dict: mapping PRs to a 2-tuple of "contributers" and |
---|
112 | "helpers" to the PR. Contributers are nicks of people who |
---|
113 | commited to the PR, and "helpers" either reviewed or commented |
---|
114 | on the PR. |
---|
115 | """ |
---|
116 | pr_info = dict() |
---|
117 | |
---|
118 | for pr in all_prs: |
---|
119 | print("Fetching PR{}".format(pr)) |
---|
120 | resp = yield treq.get( |
---|
121 | base_pr_url.format(pr), |
---|
122 | headers=headers, |
---|
123 | ) |
---|
124 | raw_data = yield resp.text() |
---|
125 | data = json.loads(raw_data) |
---|
126 | |
---|
127 | code_handles = yield _report_authors(data, headers) |
---|
128 | help_handles = yield _report_helpers(data, headers) |
---|
129 | |
---|
130 | pr_info[pr] = ( |
---|
131 | code_handles, |
---|
132 | help_handles - help_handles.intersection(code_handles), |
---|
133 | ) |
---|
134 | returnValue(pr_info) |
---|
135 | |
---|
136 | |
---|
137 | #async def main(reactor): |
---|
138 | @inlineCallbacks |
---|
139 | def main(reactor): |
---|
140 | """ |
---|
141 | Fetch Pull Request (PR) information from GitHub. |
---|
142 | |
---|
143 | Either pass a list of PR numbers on the command-line, or pipe text |
---|
144 | containing references like: "There is a PR123 somewhere" from |
---|
145 | which instances of "PRxxx" are extrated. From GitHub's API we get |
---|
146 | all author information and anyone who disucced the PR and print a |
---|
147 | summary afterwards. |
---|
148 | |
---|
149 | You need a 'token' file containing two lines: your username, and |
---|
150 | access token (get this from the GitHub Web UI). |
---|
151 | """ |
---|
152 | |
---|
153 | username, token = _read_github_token() |
---|
154 | pr_info = yield _request_pr_information( |
---|
155 | username, token, |
---|
156 | _initialize_headers(username, token), |
---|
157 | _find_pull_request_numbers(), |
---|
158 | ) |
---|
159 | |
---|
160 | unique_handles = set() |
---|
161 | for pr, (code_handles, help_handles) in sorted(pr_info.items()): |
---|
162 | coders = ', '.join('`{}`_'.format(c) for c in code_handles) |
---|
163 | helpers = ', '.join('`{}`_'.format(c) for c in help_handles) |
---|
164 | if helpers: |
---|
165 | print("`PR{}`_: {} (with {})".format(pr, coders, helpers)) |
---|
166 | else: |
---|
167 | print("`PR{}`_: {}".format(pr, coders)) |
---|
168 | for h in code_handles.union(help_handles): |
---|
169 | unique_handles.add(h) |
---|
170 | |
---|
171 | for pr in sorted(pr_info.keys()): |
---|
172 | print(".. _PR{}: https://github.com/tahoe-lafs/tahoe-lafs/pull/{}".format(pr, pr)) |
---|
173 | for h in sorted(unique_handles): |
---|
174 | print(".. _{}: https://github.com/{}".format(h, h)) |
---|
175 | |
---|
176 | |
---|
177 | if __name__ == "__main__": |
---|
178 | #react(lambda r: ensureDeferred(main(r))) |
---|
179 | react(main) |
---|