source: trunk/release-tools/fetch-pr.py

Last change on this file was 1aa3d08, checked in by meejah <meejah@…>, at 2018-05-29T19:17:32Z

refactor more

  • Property mode set to 100644
File size: 5.5 KB
Line 
1# given a PR number, get all contributers and the summary from
2# GitHub's API
3
4import sys
5import json
6import base64
7
8from twisted.internet.task import react
9from twisted.internet.defer import inlineCallbacks, returnValue
10
11import treq
12
13base_pr_url = "https://api.github.com/repos/tahoe-lafs/tahoe-lafs/pulls/{}"
14ignore_handles = ('codecov-io', )
15
16
17def _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
42def _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
62def _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
75def _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
93def _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
107def _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
139def 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
177if __name__ == "__main__":
178    #react(lambda r: ensureDeferred(main(r)))
179    react(main)
Note: See TracBrowser for help on using the repository browser.