/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
1
# Copyright (C) 2018 Breezy Developers
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
0.434.1 by Jelmer Vernooij
Use absolute_import.
17
"""Support for GitHub."""
18
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
19
import json
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
20
import os
21
7408.3.1 by Jelmer Vernooij
Move propose module into core.
22
from ...propose import (
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
23
    determine_title,
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
24
    Hoster,
7268.8.2 by Jelmer Vernooij
Handle GitHub errors.
25
    HosterLoginRequired,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
26
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
27
    MergeProposalBuilder,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
28
    MergeProposalExists,
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
29
    NoSuchProject,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
30
    PrerequisiteBranchUnsupported,
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
31
    ReopenFailed,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
32
    UnsupportedHoster,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
33
    )
34
35
from ... import (
7340.1.1 by Martin
Fix use of config_dir in propose plugin
36
    bedding,
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
37
    branch as _mod_branch,
0.432.3 by Jelmer Vernooij
Publish command works for github.
38
    controldir,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
39
    errors,
40
    hooks,
41
    urlutils,
0.432.3 by Jelmer Vernooij
Publish command works for github.
42
    version_string as breezy_version,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
43
    )
7340.1.1 by Martin
Fix use of config_dir in propose plugin
44
from ...config import AuthenticationConfig, GlobalStack
7428.1.1 by Jelmer Vernooij
Handle 403s for pull request creation.
45
from ...errors import InvalidHttpResponse, PermissionDenied
0.431.32 by Jelmer Vernooij
Properly resolve git+ssh URLs.
46
from ...git.urls import git_url_to_bzr_url
0.432.3 by Jelmer Vernooij
Publish command works for github.
47
from ...i18n import gettext
48
from ...trace import note
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
49
from ...transport import get_transport
7296.10.5 by Jelmer Vernooij
use default_user_agent function.
50
from ...transport.http import default_user_agent
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
51
52
53
GITHUB_HOST = 'github.com'
54
WEB_GITHUB_URL = 'https://github.com'
55
API_GITHUB_URL = 'https://api.github.com'
7408.1.3 by Jelmer Vernooij
Support pagination for github.
56
DEFAULT_PER_PAGE = 50
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
57
58
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
59
def store_github_token(scheme, host, token):
7340.1.1 by Martin
Fix use of config_dir in propose plugin
60
    with open(os.path.join(bedding.config_dir(), 'github.conf'), 'w') as f:
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
61
        f.write(token)
62
63
64
def retrieve_github_token(scheme, host):
7340.1.1 by Martin
Fix use of config_dir in propose plugin
65
    path = os.path.join(bedding.config_dir(), 'github.conf')
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
66
    if not os.path.exists(path):
67
        return None
0.435.1 by Jelmer Vernooij
Fix reading github credentials.
68
    with open(path, 'r') as f:
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
69
        return f.read().strip()
70
71
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
72
class ValidationFailed(errors.BzrError):
73
74
    _fmt = "GitHub validation failed: %(error)s"
75
76
    def __init__(self, error):
77
        errors.BzrError.__init__(self)
78
        self.error = error
79
80
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
81
class NotGitHubUrl(errors.BzrError):
82
83
    _fmt = "Not a GitHub URL: %(url)s"
84
85
    def __init__(self, url):
86
        errors.BzrError.__init__(self)
87
        self.url = url
88
89
7268.8.2 by Jelmer Vernooij
Handle GitHub errors.
90
class GitHubLoginRequired(HosterLoginRequired):
91
92
    _fmt = "Action requires GitHub login."
93
94
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
95
def connect_github():
7211.13.7 by Jelmer Vernooij
Fix formatting.
96
    """Connect to GitHub.
97
    """
7296.10.5 by Jelmer Vernooij
use default_user_agent function.
98
    user_agent = default_user_agent()
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
99
    auth = AuthenticationConfig()
100
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
101
    credentials = auth.get_credentials('https', GITHUB_HOST)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
102
    if credentials is not None:
0.432.3 by Jelmer Vernooij
Publish command works for github.
103
        return Github(credentials['user'], credentials['password'],
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
104
                      user_agent=user_agent)
105
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
106
    # TODO(jelmer): token = auth.get_token('https', GITHUB_HOST)
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
107
    if token is not None:
0.431.61 by Jelmer Vernooij
Fix token login.
108
        return Github(token, user_agent=user_agent)
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
109
    else:
110
        note('Accessing GitHub anonymously. To log in, run \'brz gh-login\'.')
111
        return Github(user_agent=user_agent)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
112
113
0.431.44 by Jelmer Vernooij
Support get/set description.
114
class GitHubMergeProposal(MergeProposal):
115
7371.4.2 by Jelmer Vernooij
More fixes.
116
    def __init__(self, gh, pr):
117
        self._gh = gh
0.431.44 by Jelmer Vernooij
Support get/set description.
118
        self._pr = pr
119
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
120
    def __repr__(self):
121
        return "<%s at %r>" % (type(self).__name__, self.url)
122
0.431.44 by Jelmer Vernooij
Support get/set description.
123
    @property
124
    def url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
125
        return self._pr['html_url']
0.431.44 by Jelmer Vernooij
Support get/set description.
126
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
127
    def _branch_from_part(self, part):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
128
        if part['repo'] is None:
129
            return None
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
130
        return github_url_to_bzr_url(part['repo']['html_url'], part['ref'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
131
132
    def get_source_branch_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
133
        return self._branch_from_part(self._pr['head'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
134
135
    def get_target_branch_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
136
        return self._branch_from_part(self._pr['base'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
137
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
138
    def get_source_project(self):
139
        return self._pr['head']['repo']['full_name']
140
141
    def get_target_project(self):
142
        return self._pr['base']['repo']['full_name']
143
0.431.44 by Jelmer Vernooij
Support get/set description.
144
    def get_description(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
145
        return self._pr['body']
0.431.44 by Jelmer Vernooij
Support get/set description.
146
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
147
    def get_commit_message(self):
148
        return None
149
7371.4.2 by Jelmer Vernooij
More fixes.
150
    def set_commit_message(self, message):
7443.1.1 by Jelmer Vernooij
Don't attempt to set the title when updating the commit message in GitHub.
151
        raise errors.UnsupportedOperation(self.set_commit_message, self)
7371.4.2 by Jelmer Vernooij
More fixes.
152
153
    def _patch(self, data):
154
        response = self._gh._api_request(
155
            'PATCH', self._pr['url'], body=json.dumps(data).encode('utf-8'))
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
156
        if response.status == 422:
157
            raise ValidationFailed(json.loads(response.text))
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
158
        if response.status != 200:
7371.4.2 by Jelmer Vernooij
More fixes.
159
            raise InvalidHttpResponse(self._pr['url'], response.text)
160
        self._pr = json.loads(response.text)
161
0.431.44 by Jelmer Vernooij
Support get/set description.
162
    def set_description(self, description):
7371.4.2 by Jelmer Vernooij
More fixes.
163
        self._patch({
164
            'body': description,
165
            'title': determine_title(description),
166
            })
0.431.44 by Jelmer Vernooij
Support get/set description.
167
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
168
    def is_merged(self):
7381.5.4 by Jelmer Vernooij
'merged' doesn't appear to always be set.
169
        return bool(self._pr.get('merged_at'))
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
170
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
171
    def is_closed(self):
7381.5.4 by Jelmer Vernooij
'merged' doesn't appear to always be set.
172
        return self._pr['state'] == 'closed' and not bool(self._pr.get('merged_at'))
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
173
174
    def reopen(self):
175
        try:
176
            self._patch({'state': 'open'})
177
        except ValidationFailed as e:
178
            raise ReopenFailed(e.error['errors'][0]['message'])
179
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
180
    def close(self):
7371.4.2 by Jelmer Vernooij
More fixes.
181
        self._patch({'state': 'closed'})
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
182
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
183
    def can_be_merged(self):
184
        return self._pr['mergeable']
185
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
186
    def merge(self, commit_message=None):
187
        # https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
188
        data = {}
189
        if commit_message:
190
            data['commit_message'] = commit_messae
191
        response = self._gh._api_request(
192
            'PUT', self._pr['url'] + "/merge", body=json.dumps(data).encode('utf-8'))
193
        if response.status == 422:
194
            raise ValidationFailed(json.loads(response.text))
195
        if response.status != 200:
196
            raise InvalidHttpResponse(self._pr['url'], response.text)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
197
7414.4.2 by Jelmer Vernooij
Fix gitlab / github merged_by fetching.
198
    def get_merged_by(self):
199
        merged_by = self._pr.get('merged_by')
200
        if merged_by is None:
201
            return None
202
        return merged_by['login']
203
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
204
    def get_merged_at(self):
205
        merged_at = self._pr.get('merged_at')
206
        if merged_at is None:
207
            return None
7414.4.4 by Jelmer Vernooij
Use iso8601 module.
208
        import iso8601
209
        return iso8601.parse_date(merged_at)
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
210
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
211
    def post_comment(self, body):
212
        data = {'body': body}
213
        response = self._gh._api_request(
214
            'POST', self._pr['comments_url'], body=json.dumps(data).encode('utf-8'))
215
        if response.status == 422:
216
            raise ValidationFailed(json.loads(response.text))
217
        if response.status != 201:
218
            raise InvalidHttpResponse(self._pr['comments_url'], response.text)
219
        json.loads(response.text)
220
0.431.44 by Jelmer Vernooij
Support get/set description.
221
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
222
def parse_github_url(url):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
223
    (scheme, user, password, host, port, path) = urlutils.parse_url(
224
        url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
225
    if host != GITHUB_HOST:
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
226
        raise NotGitHubUrl(url)
227
    (owner, repo_name) = path.strip('/').split('/')
0.432.12 by Jelmer Vernooij
Fix .git ends.
228
    if repo_name.endswith('.git'):
229
        repo_name = repo_name[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
230
    return owner, repo_name
231
232
233
def parse_github_branch_url(branch):
7441.1.1 by Jelmer Vernooij
Add strip_segment_parameters function.
234
    url = urlutils.strip_segment_parameters(branch.user_url)
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
235
    owner, repo_name = parse_github_url(url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
236
    return owner, repo_name, branch.name
237
238
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
239
def github_url_to_bzr_url(url, branch_name):
7408.2.1 by Jelmer Vernooij
Use standard functions for creating Git URLs.
240
    return git_url_to_bzr_url(url, branch_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
241
242
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
243
def strip_optional(url):
244
    return url.split('{')[0]
245
246
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
247
class GitHub(Hoster):
248
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
249
    name = 'github'
250
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
251
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
252
    supports_merge_proposal_commit_message = False
7490.3.9 by Jelmer Vernooij
Add supports_allow_collaboration flag.
253
    supports_allow_collaboration = True
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
254
    merge_proposal_description_format = 'markdown'
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
255
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
256
    def __repr__(self):
257
        return "GitHub()"
258
7371.4.2 by Jelmer Vernooij
More fixes.
259
    def _api_request(self, method, path, body=None):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
260
        headers = {
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
261
            'Content-Type': 'application/json',
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
262
            'Accept': 'application/vnd.github.v3+json'}
263
        if self._token:
264
            headers['Authorization'] = 'token %s' % self._token
265
        response = self.transport.request(
266
            method, urlutils.join(self.transport.base, path),
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
267
            headers=headers, body=body, retries=3)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
268
        if response.status == 401:
269
            raise GitHubLoginRequired(self)
270
        return response
271
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
272
    def _get_repo(self, owner, repo):
273
        path = 'repos/%s/%s' % (owner, repo)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
274
        response = self._api_request('GET', path)
275
        if response.status == 404:
276
            raise NoSuchProject(path)
277
        if response.status == 200:
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
278
            return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
279
        raise InvalidHttpResponse(path, response.text)
280
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
281
    def _get_repo_pulls(self, path, head=None, state=None):
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
282
        path = path + '?'
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
283
        params = {}
284
        if head is not None:
285
            params['head'] = head
286
        if state is not None:
287
            params['state'] = state
288
        path += ';'.join(['%s=%s' % (k, urlutils.quote(v))
289
                         for k, v in params.items()])
290
        response = self._api_request('GET', path)
291
        if response.status == 404:
292
            raise NoSuchProject(path)
293
        if response.status == 200:
294
            return json.loads(response.text)
295
        raise InvalidHttpResponse(path, response.text)
296
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
297
    def _create_pull(self, path, title, head, base, body=None, labels=None,
7496.1.2 by Jelmer Vernooij
Extra s.
298
                     assignee=None, draft=False, maintainer_can_modify=False):
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
299
        data = {
300
            'title': title,
301
            'head': head,
302
            'base': base,
7490.2.1 by Jelmer Vernooij
Fix draft option for github merge proposals.
303
            'draft': draft,
7496.1.2 by Jelmer Vernooij
Extra s.
304
            'maintainer_can_modify': maintainer_can_modify,
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
305
        }
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
306
        if labels is not None:
307
            data['labels'] = labels
308
        if assignee is not None:
309
            data['assignee'] = assignee
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
310
        if body:
311
            data['body'] = body
312
313
        response = self._api_request(
314
            'POST', path, body=json.dumps(data).encode('utf-8'))
7428.1.1 by Jelmer Vernooij
Handle 403s for pull request creation.
315
        if response.status == 403:
316
            raise PermissionDenied(path, response.text)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
317
        if response.status != 201:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
318
            raise InvalidHttpResponse(path, 'req is invalid %d %r: %r' % (response.status, data, response.text))
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
319
        return json.loads(response.text)
320
321
    def _get_user_by_email(self, email):
322
        path = 'search/users?q=%s+in:email' % email
323
        response = self._api_request('GET', path)
324
        if response.status != 200:
325
            raise InvalidHttpResponse(path, response.text)
326
        ret = json.loads(response.text)
327
        if ret['total_count'] == 0:
328
            raise KeyError('no user with email %s' % email)
329
        elif ret['total_count'] > 1:
330
            raise ValueError('more than one result for email %s' % email)
331
        return ret['items'][0]
332
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
333
    def _get_user(self, username=None):
334
        if username:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
335
            path = 'users/%s' % username
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
336
        else:
337
            path = 'user'
338
        response = self._api_request('GET', path)
339
        if response.status != 200:
340
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
341
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
342
343
    def _get_organization(self, name):
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
344
        path = 'orgs/%s' % name
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
345
        response = self._api_request('GET', path)
346
        if response.status != 200:
347
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
348
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
349
7408.1.3 by Jelmer Vernooij
Support pagination for github.
350
    def _list_paged(self, path, parameters=None, per_page=None):
351
        if parameters is None:
352
            parameters = {}
353
        else:
354
            parameters = dict(parameters.items())
355
        if per_page:
356
            parameters['per_page'] = str(per_page)
357
        page = 1
358
        i = 0
359
        while path:
360
            parameters['page'] = str(page)
361
            response = self._api_request(
362
                'GET', path + '?' +
363
                ';'.join(['%s=%s' % (k, urlutils.quote(v))
364
                          for (k, v) in parameters.items()]))
365
            if response.status != 200:
366
                raise InvalidHttpResponse(path, response.text)
367
            data = json.loads(response.text)
368
            for entry in data['items']:
369
                i += 1
370
                yield entry
371
            if i >= data['total_count']:
372
                break
373
            page += 1
374
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
375
    def _search_issues(self, query):
376
        path = 'search/issues'
7408.1.3 by Jelmer Vernooij
Support pagination for github.
377
        return self._list_paged(path, {'q': query}, per_page=DEFAULT_PER_PAGE)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
378
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
379
    def _create_fork(self, path, owner=None):
7410.1.2 by Jelmer Vernooij
Fix fork creation.
380
        if owner and owner != self._current_user['login']:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
381
            path += '?organization=%s' % owner
382
        response = self._api_request('POST', path)
7397.1.2 by Jelmer Vernooij
Fix fork creation on github.
383
        if response.status != 202:
7410.1.2 by Jelmer Vernooij
Fix fork creation.
384
            raise InvalidHttpResponse(path, 'status: %d, %r' % (response.status, response.text))
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
385
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
386
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
387
    @property
388
    def base_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
389
        return WEB_GITHUB_URL
390
391
    def __init__(self, transport):
392
        self._token = retrieve_github_token('https', GITHUB_HOST)
393
        self.transport = transport
394
        self._current_user = self._get_user()
395
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
396
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
397
                        owner=None, revision_id=None, overwrite=False,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
398
                        allow_lossy=True, tag_selector=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
399
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
400
        base_repo = self._get_repo(base_owner, base_project)
0.432.3 by Jelmer Vernooij
Publish command works for github.
401
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
402
            owner = self._current_user['login']
0.432.3 by Jelmer Vernooij
Publish command works for github.
403
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
404
            project = base_repo['name']
0.432.3 by Jelmer Vernooij
Publish command works for github.
405
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
406
            remote_repo = self._get_repo(owner, project)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
407
        except NoSuchProject:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
408
            base_repo = self._get_repo(base_owner, base_project)
409
            remote_repo = self._create_fork(base_repo['forks_url'], owner)
0.432.3 by Jelmer Vernooij
Publish command works for github.
410
            note(gettext('Forking new repository %s from %s') %
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
411
                 (remote_repo['html_url'], base_repo['html_url']))
0.432.3 by Jelmer Vernooij
Publish command works for github.
412
        else:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
413
            note(gettext('Reusing existing repository %s') % remote_repo['html_url'])
414
        remote_dir = controldir.ControlDir.open(git_url_to_bzr_url(remote_repo['ssh_url']))
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
415
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
416
            push_result = remote_dir.push_branch(
417
                local_branch, revision_id=revision_id, overwrite=overwrite,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
418
                name=name, tag_selector=tag_selector)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
419
        except errors.NoRoundtrippingSupport:
420
            if not allow_lossy:
421
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
422
            push_result = remote_dir.push_branch(
423
                local_branch, revision_id=revision_id,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
424
                overwrite=overwrite, name=name, lossy=True,
425
                tag_selector=tag_selector)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
426
        return push_result.target_branch, github_url_to_bzr_url(
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
427
            remote_repo['html_url'], name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
428
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
429
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
430
        owner, project, branch_name = parse_github_branch_url(branch)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
431
        repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
432
        return github_url_to_bzr_url(repo['ssh_url'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
433
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
434
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
435
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
436
        base_repo = self._get_repo(base_owner, base_project)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
437
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
438
            owner = self._current_user['login']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
439
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
440
            project = base_repo['name']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
441
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
442
            remote_repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
443
            full_url = github_url_to_bzr_url(remote_repo['ssh_url'], name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
444
            return _mod_branch.Branch.open(full_url)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
445
        except NoSuchProject:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
446
            raise errors.NotBranchError('%s/%s/%s' % (WEB_GITHUB_URL, owner, project))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
447
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
448
    def get_proposer(self, source_branch, target_branch):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
449
        return GitHubMergeProposalBuilder(self, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
450
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
451
    def iter_proposals(self, source_branch, target_branch, status='open'):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
452
        (source_owner, source_repo_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
453
            parse_github_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
454
        (target_owner, target_repo_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
455
            parse_github_branch_url(target_branch))
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
456
        target_repo = self._get_repo(target_owner, target_repo_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
457
        state = {
458
            'open': 'open',
459
            'merged': 'closed',
460
            'closed': 'closed',
461
            'all': 'all'}
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
462
        pulls = self._get_repo_pulls(
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
463
            strip_optional(target_repo['pulls_url']),
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
464
            head=target_branch_name,
465
            state=state[status])
466
        for pull in pulls:
467
            if (status == 'closed' and pull['merged'] or
468
                    status == 'merged' and not pull['merged']):
469
                continue
470
            if pull['head']['ref'] != source_branch_name:
471
                continue
472
            if pull['head']['repo'] is None:
7268.4.1 by Jelmer Vernooij
Don't attempt to resolve None when repo has gone away.
473
                # Repo has gone the way of the dodo
474
                continue
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
475
            if (pull['head']['repo']['owner']['login'] != source_owner or
476
                    pull['head']['repo']['name'] != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
477
                continue
7371.4.2 by Jelmer Vernooij
More fixes.
478
            yield GitHubMergeProposal(self, pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
479
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
480
    def hosts(self, branch):
481
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
482
            parse_github_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
483
        except NotGitHubUrl:
484
            return False
485
        else:
486
            return True
487
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
488
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
489
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
490
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
491
            parse_github_url(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
492
        except NotGitHubUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
493
            raise UnsupportedHoster(url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
494
        transport = get_transport(
495
            API_GITHUB_URL, possible_transports=possible_transports)
496
        return cls(transport)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
497
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
498
    @classmethod
499
    def iter_instances(cls):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
500
        yield cls(get_transport(API_GITHUB_URL))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
501
0.431.66 by Jelmer Vernooij
Add support for status argument.
502
    def iter_my_proposals(self, status='open'):
503
        query = ['is:pr']
504
        if status == 'open':
505
            query.append('is:open')
506
        elif status == 'closed':
507
            query.append('is:unmerged')
7268.2.1 by Jelmer Vernooij
Don't include open unmerged pull requests in 'closed'.
508
            # Also use "is:closed" otherwise unmerged open pull requests are
509
            # also included.
510
            query.append('is:closed')
0.431.66 by Jelmer Vernooij
Add support for status argument.
511
        elif status == 'merged':
512
            query.append('is:merged')
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
513
        query.append('author:%s' % self._current_user['login'])
7408.1.3 by Jelmer Vernooij
Support pagination for github.
514
        for issue in self._search_issues(query=' '.join(query)):
7360.1.5 by Jelmer Vernooij
Use authenticated request when requesting pull requests from GitHub; authenticated connections have a much lower rate limit.
515
            url = issue['pull_request']['url']
516
            response = self._api_request('GET', url)
517
            if response.status != 200:
518
                raise InvalidHttpResponse(url, response.text)
7371.4.2 by Jelmer Vernooij
More fixes.
519
            yield GitHubMergeProposal(self, json.loads(response.text))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
520
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
521
    def get_proposal_by_url(self, url):
522
        raise UnsupportedHoster(url)
523
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
524
    def iter_my_forks(self):
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
525
        response = self._api_request('GET', '/user/repos')
526
        if response.status != 200:
527
            raise InvalidHttpResponse(url, response.text)
528
        for project in json.loads(response.text):
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
529
            if not project['fork']:
530
                continue
531
            yield project['full_name']
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
532
533
    def delete_project(self, path):
534
        path = 'repos/' + path
535
        response = self._api_request('DELETE', path)
536
        if response.status == 404:
537
            raise NoSuchProject(path)
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
538
        if response.status == 204:
539
            return
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
540
        if response.status == 200:
541
            return json.loads(response.text)
542
        raise InvalidHttpResponse(path, response.text)
543
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
544
0.432.2 by Jelmer Vernooij
Publish command sort of works.
545
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
546
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
547
    def __init__(self, gh, source_branch, target_branch):
548
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
549
        self.source_branch = source_branch
550
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
551
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
552
            parse_github_branch_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
553
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
554
            parse_github_branch_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
555
556
    def get_infotext(self):
557
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
558
        info = []
559
        info.append("Merge %s into %s:%s\n" % (
560
            self.source_branch_name, self.target_owner,
561
            self.target_branch_name))
562
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
563
        info.append("Target: %s\n" % self.target_branch.user_url)
564
        return ''.join(info)
565
566
    def get_initial_body(self):
567
        """Get a body for the proposal for the user to modify.
568
569
        :return: a str or None.
570
        """
571
        return None
572
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
573
    def create_proposal(self, description, reviewers=None, labels=None,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
574
                        prerequisite_branch=None, commit_message=None,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
575
                        work_in_progress=False, allow_collaboration=False):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
576
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
577
        if prerequisite_branch is not None:
578
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
579
        # Note that commit_message is ignored, since github doesn't support it.
0.432.7 by Jelmer Vernooij
propose works \o/
580
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
581
        if self.target_repo_name.endswith('.git'):
582
            self.target_repo_name = self.target_repo_name[:-4]
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
583
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
584
        title = determine_title(description)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
585
        target_repo = self.gh._get_repo(
586
            self.target_owner, self.target_repo_name)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
587
        assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
588
        if reviewers:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
589
            assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
590
            for reviewer in reviewers:
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
591
                if '@' in reviewer:
592
                    user = self.gh._get_user_by_email(reviewer)
593
                else:
594
                    user = self.gh._get_user(reviewer)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
595
                assignees.append(user['login'])
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
596
        else:
597
            assignees = None
598
        try:
599
            pull_request = self.gh._create_pull(
600
                strip_optional(target_repo['pulls_url']),
601
                title=title, body=description,
602
                head="%s:%s" % (self.source_owner, self.source_branch_name),
603
                base=self.target_branch_name,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
604
                labels=labels, assignee=assignees,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
605
                draft=work_in_progress,
606
                maintainer_can_modify=allow_collaboration,
607
                )
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
608
        except ValidationFailed:
609
            raise MergeProposalExists(self.source_branch.user_url)
7371.4.2 by Jelmer Vernooij
More fixes.
610
        return GitHubMergeProposal(self.gh, pull_request)