/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/propose/github.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2019-06-15 15:50:25 UTC
  • mfrom: (7296.11.1 custom-github)
  • Revision ID: breezy.the.bot@gmail.com-20190615155025-edm8b2lkn1mzqwtq
Use local REST implementation to access GitHub API.

Merged from https://code.launchpad.net/~jelmer/brz/custom-github/+merge/367669

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    MergeProposal,
27
27
    MergeProposalBuilder,
28
28
    MergeProposalExists,
 
29
    NoSuchProject,
29
30
    PrerequisiteBranchUnsupported,
30
31
    UnsupportedHoster,
31
32
    )
39
40
    version_string as breezy_version,
40
41
    )
41
42
from ...config import AuthenticationConfig, GlobalStack, config_dir
 
43
from ...errors import InvalidHttpResponse
42
44
from ...git.urls import git_url_to_bzr_url
43
45
from ...i18n import gettext
44
46
from ...sixish import PY3
45
47
from ...trace import note
 
48
from ...transport import get_transport
46
49
from ...transport.http import default_user_agent
47
 
from ...lazy_import import lazy_import
48
 
lazy_import(globals(), """
49
 
from github import Github
50
 
""")
 
50
 
 
51
 
 
52
GITHUB_HOST = 'github.com'
 
53
WEB_GITHUB_URL = 'https://github.com'
 
54
API_GITHUB_URL = 'https://api.github.com'
51
55
 
52
56
 
53
57
def store_github_token(scheme, host, token):
87
91
    user_agent = default_user_agent()
88
92
    auth = AuthenticationConfig()
89
93
 
90
 
    credentials = auth.get_credentials('https', 'github.com')
 
94
    credentials = auth.get_credentials('https', GITHUB_HOST)
91
95
    if credentials is not None:
92
96
        return Github(credentials['user'], credentials['password'],
93
97
                      user_agent=user_agent)
94
98
 
95
 
    # TODO(jelmer): token = auth.get_token('https', 'github.com')
96
 
    token = retrieve_github_token('https', 'github.com')
 
99
    # TODO(jelmer): token = auth.get_token('https', GITHUB_HOST)
97
100
    if token is not None:
98
101
        return Github(token, user_agent=user_agent)
99
102
    else:
108
111
 
109
112
    @property
110
113
    def url(self):
111
 
        return self._pr.html_url
 
114
        return self._pr['html_url']
112
115
 
113
116
    def _branch_from_part(self, part):
114
 
        return github_url_to_bzr_url(part.repo.html_url, part.ref)
 
117
        return github_url_to_bzr_url(part['repo']['html_url'], part['ref'])
115
118
 
116
119
    def get_source_branch_url(self):
117
 
        return self._branch_from_part(self._pr.head)
 
120
        return self._branch_from_part(self._pr['head'])
118
121
 
119
122
    def get_target_branch_url(self):
120
 
        return self._branch_from_part(self._pr.base)
 
123
        return self._branch_from_part(self._pr['base'])
121
124
 
122
125
    def get_description(self):
123
 
        return self._pr.body
 
126
        return self._pr['body']
124
127
 
125
128
    def get_commit_message(self):
126
129
        return None
129
132
        self._pr.edit(body=description, title=determine_title(description))
130
133
 
131
134
    def is_merged(self):
132
 
        return self._pr.merged
 
135
        return self._pr['merged']
133
136
 
134
137
    def close(self):
135
138
        self._pr.edit(state='closed')
142
145
def parse_github_url(url):
143
146
    (scheme, user, password, host, port, path) = urlutils.parse_url(
144
147
        url)
145
 
    if host != 'github.com':
 
148
    if host != GITHUB_HOST:
146
149
        raise NotGitHubUrl(url)
147
150
    (owner, repo_name) = path.strip('/').split('/')
148
151
    if repo_name.endswith('.git'):
163
166
        git_url_to_bzr_url(url), {"branch": branch_name})
164
167
 
165
168
 
166
 
def convert_github_error(fn):
167
 
    def convert(self, *args, **kwargs):
168
 
        import github
169
 
        try:
170
 
            return fn(self, *args, **kwargs)
171
 
        except github.GithubException as e:
172
 
            if e.args[0] == 401:
173
 
                raise GitHubLoginRequired(self)
174
 
            raise
175
 
    return convert
176
 
 
177
 
 
178
169
class GitHub(Hoster):
179
170
 
180
171
    name = 'github'
185
176
    def __repr__(self):
186
177
        return "GitHub()"
187
178
 
 
179
    def _api_request(self, method, path):
 
180
        headers = {
 
181
            'Accept': 'application/vnd.github.v3+json'}
 
182
        if self._token:
 
183
            headers['Authorization'] = 'token %s' % self._token
 
184
        response = self.transport.request(
 
185
            method, urlutils.join(self.transport.base, path),
 
186
            headers=headers)
 
187
        if response.status == 401:
 
188
            raise GitHubLoginRequired(self)
 
189
        return response
 
190
 
 
191
    def _get_repo(self, path):
 
192
        path = 'repos/' + path
 
193
        response = self._api_request('GET', path)
 
194
        if response.status == 404:
 
195
            raise NoSuchProject(path)
 
196
        if response.status == 200:
 
197
            return response.json
 
198
        raise InvalidHttpResponse(path, response.text)
 
199
 
 
200
    def _get_user(self, username=None):
 
201
        if username:
 
202
            path = 'users/:%s' % username
 
203
        else:
 
204
            path = 'user'
 
205
        response = self._api_request('GET', path)
 
206
        if response.status != 200:
 
207
            raise InvalidHttpResponse(path, response.text)
 
208
        return response.json
 
209
 
 
210
    def _get_organization(self, name):
 
211
        path = 'orgs/:%s' % name
 
212
        response = self._api_request('GET', path)
 
213
        if response.status != 200:
 
214
            raise InvalidHttpResponse(path, response.text)
 
215
        return response.json
 
216
 
 
217
    def _search_issues(self, query):
 
218
        path = 'search/issues'
 
219
        response = self._api_request(
 
220
            'GET', path + '?q=' + urlutils.quote(query))
 
221
        if response.status != 200:
 
222
            raise InvalidHttpResponse(path, response.text)
 
223
        return response.json
 
224
 
 
225
    def _create_fork(self, repo, owner=None):
 
226
        (orig_owner, orig_repo) = repo.split('/')
 
227
        path = '/repos/:%s/:%s/forks' % (orig_owner, orig_repo)
 
228
        if owner:
 
229
            path += '?organization=%s' % owner
 
230
        response = self._api_request('POST', path)
 
231
        if response != 202:
 
232
            raise InvalidHttpResponse(path, response.text)
 
233
        return response.json
 
234
 
188
235
    @property
189
236
    def base_url(self):
190
 
        # TODO(jelmer): Can we get the default URL from the Python API package
191
 
        # somehow?
192
 
        return "https://github.com"
193
 
 
194
 
    def __init__(self):
195
 
        self.gh = connect_github()
196
 
 
197
 
    @convert_github_error
 
237
        return WEB_GITHUB_URL
 
238
 
 
239
    def __init__(self, transport):
 
240
        self._token = retrieve_github_token('https', GITHUB_HOST)
 
241
        self.transport = transport
 
242
        self._current_user = self._get_user()
 
243
 
198
244
    def publish_derived(self, local_branch, base_branch, name, project=None,
199
245
                        owner=None, revision_id=None, overwrite=False,
200
246
                        allow_lossy=True):
201
247
        import github
202
248
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
203
 
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
 
249
        base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
204
250
        if owner is None:
205
 
            owner = self.gh.get_user().login
 
251
            owner = self._current_user['login']
206
252
        if project is None:
207
 
            project = base_repo.name
 
253
            project = base_repo['name']
208
254
        try:
209
 
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
210
 
            remote_repo.id
 
255
            remote_repo = self._get_repo('%s/%s' % (owner, project))
211
256
        except github.UnknownObjectException:
212
 
            base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
213
 
            if owner == self.gh.get_user().login:
214
 
                owner_obj = self.gh.get_user()
215
 
            else:
216
 
                owner_obj = self.gh.get_organization(owner)
217
 
            remote_repo = owner_obj.create_fork(base_repo)
 
257
            base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
 
258
            remote_repo = self._create_fork(base_repo, owner)
218
259
            note(gettext('Forking new repository %s from %s') %
219
 
                 (remote_repo.html_url, base_repo.html_url))
 
260
                 (remote_repo['html_url'], base_repo['html_url']))
220
261
        else:
221
 
            note(gettext('Reusing existing repository %s') % remote_repo.html_url)
222
 
        remote_dir = controldir.ControlDir.open(git_url_to_bzr_url(remote_repo.ssh_url))
 
262
            note(gettext('Reusing existing repository %s') % remote_repo['html_url'])
 
263
        remote_dir = controldir.ControlDir.open(git_url_to_bzr_url(remote_repo['ssh_url']))
223
264
        try:
224
265
            push_result = remote_dir.push_branch(
225
266
                local_branch, revision_id=revision_id, overwrite=overwrite,
231
272
                local_branch, revision_id=revision_id,
232
273
                overwrite=overwrite, name=name, lossy=True)
233
274
        return push_result.target_branch, github_url_to_bzr_url(
234
 
            remote_repo.html_url, name)
 
275
            remote_repo['html_url'], name)
235
276
 
236
 
    @convert_github_error
237
277
    def get_push_url(self, branch):
238
278
        owner, project, branch_name = parse_github_branch_url(branch)
239
 
        repo = self.gh.get_repo('%s/%s' % (owner, project))
240
 
        return github_url_to_bzr_url(repo.ssh_url, branch_name)
 
279
        repo = self._get_repo('%s/%s' % (owner, project))
 
280
        return github_url_to_bzr_url(repo['ssh_url'], branch_name)
241
281
 
242
 
    @convert_github_error
243
282
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
244
283
        import github
245
284
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
246
 
        base_repo = self.gh.get_repo('%s/%s' % (base_owner, base_project))
 
285
        base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
247
286
        if owner is None:
248
 
            owner = self.gh.get_user().login
 
287
            owner = self._current_user['login']
249
288
        if project is None:
250
 
            project = base_repo.name
 
289
            project = base_repo['name']
251
290
        try:
252
 
            remote_repo = self.gh.get_repo('%s/%s' % (owner, project))
253
 
            full_url = github_url_to_bzr_url(remote_repo.ssh_url, name)
 
291
            remote_repo = self._get_repo('%s/%s' % (owner, project))
 
292
            full_url = github_url_to_bzr_url(remote_repo['ssh_url'], name)
254
293
            return _mod_branch.Branch.open(full_url)
255
294
        except github.UnknownObjectException:
256
 
            raise errors.NotBranchError('https://github.com/%s/%s' % (owner, project))
 
295
            raise errors.NotBranchError('%s/%s/%s' % (WEB_GITHUB_URL, owner, project))
257
296
 
258
 
    @convert_github_error
259
297
    def get_proposer(self, source_branch, target_branch):
260
 
        return GitHubMergeProposalBuilder(self.gh, source_branch, target_branch)
 
298
        return GitHubMergeProposalBuilder(self, source_branch, target_branch)
261
299
 
262
 
    @convert_github_error
263
300
    def iter_proposals(self, source_branch, target_branch, status='open'):
264
301
        (source_owner, source_repo_name, source_branch_name) = (
265
302
            parse_github_branch_url(source_branch))
266
303
        (target_owner, target_repo_name, target_branch_name) = (
267
304
            parse_github_branch_url(target_branch))
268
 
        target_repo = self.gh.get_repo(
 
305
        target_repo = self._get_repo(
269
306
            "%s/%s" % (target_owner, target_repo_name))
270
307
        state = {
271
308
            'open': 'open',
302
339
            parse_github_url(url)
303
340
        except NotGitHubUrl:
304
341
            raise UnsupportedHoster(url)
305
 
        return cls()
 
342
        transport = get_transport(
 
343
            API_GITHUB_URL, possible_transports=possible_transports)
 
344
        return cls(transport)
306
345
 
307
346
    @classmethod
308
347
    def iter_instances(cls):
309
 
        yield cls()
 
348
        yield cls(get_transport(API_GITHUB_URL))
310
349
 
311
 
    @convert_github_error
312
350
    def iter_my_proposals(self, status='open'):
313
351
        query = ['is:pr']
314
352
        if status == 'open':
320
358
            query.append('is:closed')
321
359
        elif status == 'merged':
322
360
            query.append('is:merged')
323
 
        query.append('author:%s' % self.gh.get_user().login)
324
 
        for issue in self.gh.search_issues(query=' '.join(query)):
325
 
            yield GitHubMergeProposal(issue.as_pull_request())
 
361
        query.append('author:%s' % self._current_user['login'])
 
362
        for issue in self._search_issues(query=' '.join(query))['items']:
 
363
            yield GitHubMergeProposal(
 
364
                self.transport.request('GET', issue['pull_request']['url']).json)
326
365
 
327
366
    @convert_github_error
328
367
    def get_proposal_by_url(self, url):
367
406
        # TODO(jelmer): Probe for right repo name
368
407
        if self.target_repo_name.endswith('.git'):
369
408
            self.target_repo_name = self.target_repo_name[:-4]
370
 
        target_repo = self.gh.get_repo("%s/%s" % (self.target_owner, self.target_repo_name))
 
409
        target_repo = self.gh._get_repo("%s/%s" % (self.target_owner, self.target_repo_name))
371
410
        # TODO(jelmer): Allow setting title explicitly?
372
411
        title = determine_title(description)
373
412
        # TOOD(jelmer): Set maintainers_can_modify?
383
422
        if reviewers:
384
423
            for reviewer in reviewers:
385
424
                pull_request.assignees.append(
386
 
                    self.gh.get_user(reviewer))
 
425
                    self.gh._get_user(reviewer)['login'])
387
426
        if labels:
388
427
            for label in labels:
389
428
                pull_request.issue.labels.append(label)