/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
7490.65.1 by Jelmer Vernooij
Add functions fo retrieving merge proposal source revision.
135
    def get_source_revision(self):
136
        """Return the latest revision for the source branch."""
137
        from breezy.git.mapping import default_mapping
138
        return default_mapping.revision_id_foreign_to_bzr(
139
            self._pr['head']['sha'].encode('ascii'))
140
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
141
    def get_target_branch_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
142
        return self._branch_from_part(self._pr['base'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
143
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
144
    def get_source_project(self):
145
        return self._pr['head']['repo']['full_name']
146
147
    def get_target_project(self):
148
        return self._pr['base']['repo']['full_name']
149
0.431.44 by Jelmer Vernooij
Support get/set description.
150
    def get_description(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
151
        return self._pr['body']
0.431.44 by Jelmer Vernooij
Support get/set description.
152
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
153
    def get_commit_message(self):
154
        return None
155
7371.4.2 by Jelmer Vernooij
More fixes.
156
    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.
157
        raise errors.UnsupportedOperation(self.set_commit_message, self)
7371.4.2 by Jelmer Vernooij
More fixes.
158
159
    def _patch(self, data):
160
        response = self._gh._api_request(
161
            '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.
162
        if response.status == 422:
163
            raise ValidationFailed(json.loads(response.text))
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
164
        if response.status != 200:
7371.4.2 by Jelmer Vernooij
More fixes.
165
            raise InvalidHttpResponse(self._pr['url'], response.text)
166
        self._pr = json.loads(response.text)
167
0.431.44 by Jelmer Vernooij
Support get/set description.
168
    def set_description(self, description):
7371.4.2 by Jelmer Vernooij
More fixes.
169
        self._patch({
170
            'body': description,
171
            'title': determine_title(description),
172
            })
0.431.44 by Jelmer Vernooij
Support get/set description.
173
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
174
    def is_merged(self):
7381.5.4 by Jelmer Vernooij
'merged' doesn't appear to always be set.
175
        return bool(self._pr.get('merged_at'))
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
176
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
177
    def is_closed(self):
7381.5.4 by Jelmer Vernooij
'merged' doesn't appear to always be set.
178
        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.
179
180
    def reopen(self):
181
        try:
182
            self._patch({'state': 'open'})
183
        except ValidationFailed as e:
184
            raise ReopenFailed(e.error['errors'][0]['message'])
185
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
186
    def close(self):
7371.4.2 by Jelmer Vernooij
More fixes.
187
        self._patch({'state': 'closed'})
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
188
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
189
    def can_be_merged(self):
190
        return self._pr['mergeable']
191
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
192
    def merge(self, commit_message=None):
193
        # https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
194
        data = {}
195
        if commit_message:
196
            data['commit_message'] = commit_messae
197
        response = self._gh._api_request(
198
            'PUT', self._pr['url'] + "/merge", body=json.dumps(data).encode('utf-8'))
199
        if response.status == 422:
200
            raise ValidationFailed(json.loads(response.text))
201
        if response.status != 200:
202
            raise InvalidHttpResponse(self._pr['url'], response.text)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
203
7414.4.2 by Jelmer Vernooij
Fix gitlab / github merged_by fetching.
204
    def get_merged_by(self):
205
        merged_by = self._pr.get('merged_by')
206
        if merged_by is None:
207
            return None
208
        return merged_by['login']
209
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
210
    def get_merged_at(self):
211
        merged_at = self._pr.get('merged_at')
212
        if merged_at is None:
213
            return None
7414.4.4 by Jelmer Vernooij
Use iso8601 module.
214
        import iso8601
215
        return iso8601.parse_date(merged_at)
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
216
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
217
    def post_comment(self, body):
218
        data = {'body': body}
219
        response = self._gh._api_request(
220
            'POST', self._pr['comments_url'], body=json.dumps(data).encode('utf-8'))
221
        if response.status == 422:
222
            raise ValidationFailed(json.loads(response.text))
223
        if response.status != 201:
224
            raise InvalidHttpResponse(self._pr['comments_url'], response.text)
225
        json.loads(response.text)
226
0.431.44 by Jelmer Vernooij
Support get/set description.
227
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
228
def parse_github_url(url):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
229
    (scheme, user, password, host, port, path) = urlutils.parse_url(
230
        url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
231
    if host != GITHUB_HOST:
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
232
        raise NotGitHubUrl(url)
233
    (owner, repo_name) = path.strip('/').split('/')
0.432.12 by Jelmer Vernooij
Fix .git ends.
234
    if repo_name.endswith('.git'):
235
        repo_name = repo_name[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
236
    return owner, repo_name
237
238
239
def parse_github_branch_url(branch):
7441.1.1 by Jelmer Vernooij
Add strip_segment_parameters function.
240
    url = urlutils.strip_segment_parameters(branch.user_url)
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
241
    owner, repo_name = parse_github_url(url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
242
    return owner, repo_name, branch.name
243
244
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
245
def github_url_to_bzr_url(url, branch_name):
7408.2.1 by Jelmer Vernooij
Use standard functions for creating Git URLs.
246
    return git_url_to_bzr_url(url, branch_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
247
248
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
249
def strip_optional(url):
250
    return url.split('{')[0]
251
252
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
253
class GitHub(Hoster):
254
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
255
    name = 'github'
256
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
257
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
258
    supports_merge_proposal_commit_message = False
7490.3.9 by Jelmer Vernooij
Add supports_allow_collaboration flag.
259
    supports_allow_collaboration = True
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
260
    merge_proposal_description_format = 'markdown'
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
261
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
262
    def __repr__(self):
263
        return "GitHub()"
264
7371.4.2 by Jelmer Vernooij
More fixes.
265
    def _api_request(self, method, path, body=None):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
266
        headers = {
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
267
            'Content-Type': 'application/json',
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
268
            'Accept': 'application/vnd.github.v3+json'}
269
        if self._token:
270
            headers['Authorization'] = 'token %s' % self._token
271
        response = self.transport.request(
272
            method, urlutils.join(self.transport.base, path),
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
273
            headers=headers, body=body, retries=3)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
274
        if response.status == 401:
275
            raise GitHubLoginRequired(self)
276
        return response
277
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
278
    def _get_repo(self, owner, repo):
279
        path = 'repos/%s/%s' % (owner, repo)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
280
        response = self._api_request('GET', path)
281
        if response.status == 404:
282
            raise NoSuchProject(path)
283
        if response.status == 200:
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
284
            return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
285
        raise InvalidHttpResponse(path, response.text)
286
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
287
    def _get_repo_pulls(self, path, head=None, state=None):
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
288
        path = path + '?'
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
289
        params = {}
290
        if head is not None:
291
            params['head'] = head
292
        if state is not None:
293
            params['state'] = state
294
        path += ';'.join(['%s=%s' % (k, urlutils.quote(v))
295
                         for k, v in params.items()])
296
        response = self._api_request('GET', path)
297
        if response.status == 404:
298
            raise NoSuchProject(path)
299
        if response.status == 200:
300
            return json.loads(response.text)
301
        raise InvalidHttpResponse(path, response.text)
302
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
303
    def _create_pull(self, path, title, head, base, body=None, labels=None,
7496.1.2 by Jelmer Vernooij
Extra s.
304
                     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.
305
        data = {
306
            'title': title,
307
            'head': head,
308
            'base': base,
7490.2.1 by Jelmer Vernooij
Fix draft option for github merge proposals.
309
            'draft': draft,
7496.1.2 by Jelmer Vernooij
Extra s.
310
            'maintainer_can_modify': maintainer_can_modify,
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
311
        }
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
312
        if labels is not None:
313
            data['labels'] = labels
314
        if assignee is not None:
315
            data['assignee'] = assignee
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
316
        if body:
317
            data['body'] = body
318
319
        response = self._api_request(
320
            'POST', path, body=json.dumps(data).encode('utf-8'))
7428.1.1 by Jelmer Vernooij
Handle 403s for pull request creation.
321
        if response.status == 403:
322
            raise PermissionDenied(path, response.text)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
323
        if response.status != 201:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
324
            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.
325
        return json.loads(response.text)
326
327
    def _get_user_by_email(self, email):
328
        path = 'search/users?q=%s+in:email' % email
329
        response = self._api_request('GET', path)
330
        if response.status != 200:
331
            raise InvalidHttpResponse(path, response.text)
332
        ret = json.loads(response.text)
333
        if ret['total_count'] == 0:
334
            raise KeyError('no user with email %s' % email)
335
        elif ret['total_count'] > 1:
336
            raise ValueError('more than one result for email %s' % email)
337
        return ret['items'][0]
338
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
339
    def _get_user(self, username=None):
340
        if username:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
341
            path = 'users/%s' % username
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
342
        else:
343
            path = 'user'
344
        response = self._api_request('GET', path)
345
        if response.status != 200:
346
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
347
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
348
349
    def _get_organization(self, name):
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
350
        path = 'orgs/%s' % name
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
351
        response = self._api_request('GET', path)
352
        if response.status != 200:
353
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
354
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
355
7408.1.3 by Jelmer Vernooij
Support pagination for github.
356
    def _list_paged(self, path, parameters=None, per_page=None):
357
        if parameters is None:
358
            parameters = {}
359
        else:
360
            parameters = dict(parameters.items())
361
        if per_page:
362
            parameters['per_page'] = str(per_page)
363
        page = 1
364
        i = 0
365
        while path:
366
            parameters['page'] = str(page)
367
            response = self._api_request(
368
                'GET', path + '?' +
369
                ';'.join(['%s=%s' % (k, urlutils.quote(v))
370
                          for (k, v) in parameters.items()]))
371
            if response.status != 200:
372
                raise InvalidHttpResponse(path, response.text)
373
            data = json.loads(response.text)
374
            for entry in data['items']:
375
                i += 1
376
                yield entry
377
            if i >= data['total_count']:
378
                break
379
            page += 1
380
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
381
    def _search_issues(self, query):
382
        path = 'search/issues'
7408.1.3 by Jelmer Vernooij
Support pagination for github.
383
        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.
384
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
385
    def _create_fork(self, path, owner=None):
7410.1.2 by Jelmer Vernooij
Fix fork creation.
386
        if owner and owner != self._current_user['login']:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
387
            path += '?organization=%s' % owner
388
        response = self._api_request('POST', path)
7397.1.2 by Jelmer Vernooij
Fix fork creation on github.
389
        if response.status != 202:
7410.1.2 by Jelmer Vernooij
Fix fork creation.
390
            raise InvalidHttpResponse(path, 'status: %d, %r' % (response.status, response.text))
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
391
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
392
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
393
    @property
394
    def base_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
395
        return WEB_GITHUB_URL
396
397
    def __init__(self, transport):
398
        self._token = retrieve_github_token('https', GITHUB_HOST)
399
        self.transport = transport
400
        self._current_user = self._get_user()
401
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
402
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
403
                        owner=None, revision_id=None, overwrite=False,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
404
                        allow_lossy=True, tag_selector=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
405
        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.
406
        base_repo = self._get_repo(base_owner, base_project)
0.432.3 by Jelmer Vernooij
Publish command works for github.
407
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
408
            owner = self._current_user['login']
0.432.3 by Jelmer Vernooij
Publish command works for github.
409
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
410
            project = base_repo['name']
0.432.3 by Jelmer Vernooij
Publish command works for github.
411
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
412
            remote_repo = self._get_repo(owner, project)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
413
        except NoSuchProject:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
414
            base_repo = self._get_repo(base_owner, base_project)
415
            remote_repo = self._create_fork(base_repo['forks_url'], owner)
0.432.3 by Jelmer Vernooij
Publish command works for github.
416
            note(gettext('Forking new repository %s from %s') %
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
417
                 (remote_repo['html_url'], base_repo['html_url']))
0.432.3 by Jelmer Vernooij
Publish command works for github.
418
        else:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
419
            note(gettext('Reusing existing repository %s') % remote_repo['html_url'])
420
        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.
421
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
422
            push_result = remote_dir.push_branch(
423
                local_branch, revision_id=revision_id, overwrite=overwrite,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
424
                name=name, tag_selector=tag_selector)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
425
        except errors.NoRoundtrippingSupport:
426
            if not allow_lossy:
427
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
428
            push_result = remote_dir.push_branch(
429
                local_branch, revision_id=revision_id,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
430
                overwrite=overwrite, name=name, lossy=True,
431
                tag_selector=tag_selector)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
432
        return push_result.target_branch, github_url_to_bzr_url(
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
433
            remote_repo['html_url'], name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
434
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
435
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
436
        owner, project, branch_name = parse_github_branch_url(branch)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
437
        repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
438
        return github_url_to_bzr_url(repo['ssh_url'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
439
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
440
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
441
        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.
442
        base_repo = self._get_repo(base_owner, base_project)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
443
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
444
            owner = self._current_user['login']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
445
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
446
            project = base_repo['name']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
447
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
448
            remote_repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
449
            full_url = github_url_to_bzr_url(remote_repo['ssh_url'], name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
450
            return _mod_branch.Branch.open(full_url)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
451
        except NoSuchProject:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
452
            raise errors.NotBranchError('%s/%s/%s' % (WEB_GITHUB_URL, owner, project))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
453
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
454
    def get_proposer(self, source_branch, target_branch):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
455
        return GitHubMergeProposalBuilder(self, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
456
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
457
    def iter_proposals(self, source_branch, target_branch, status='open'):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
458
        (source_owner, source_repo_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
459
            parse_github_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
460
        (target_owner, target_repo_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
461
            parse_github_branch_url(target_branch))
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
462
        target_repo = self._get_repo(target_owner, target_repo_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
463
        state = {
464
            'open': 'open',
465
            'merged': 'closed',
466
            'closed': 'closed',
467
            'all': 'all'}
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
468
        pulls = self._get_repo_pulls(
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
469
            strip_optional(target_repo['pulls_url']),
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
470
            head=target_branch_name,
471
            state=state[status])
472
        for pull in pulls:
473
            if (status == 'closed' and pull['merged'] or
474
                    status == 'merged' and not pull['merged']):
475
                continue
476
            if pull['head']['ref'] != source_branch_name:
477
                continue
478
            if pull['head']['repo'] is None:
7268.4.1 by Jelmer Vernooij
Don't attempt to resolve None when repo has gone away.
479
                # Repo has gone the way of the dodo
480
                continue
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
481
            if (pull['head']['repo']['owner']['login'] != source_owner or
482
                    pull['head']['repo']['name'] != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
483
                continue
7371.4.2 by Jelmer Vernooij
More fixes.
484
            yield GitHubMergeProposal(self, pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
485
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
486
    def hosts(self, branch):
487
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
488
            parse_github_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
489
        except NotGitHubUrl:
490
            return False
491
        else:
492
            return True
493
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
494
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
495
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
496
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
497
            parse_github_url(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
498
        except NotGitHubUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
499
            raise UnsupportedHoster(url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
500
        transport = get_transport(
501
            API_GITHUB_URL, possible_transports=possible_transports)
502
        return cls(transport)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
503
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
504
    @classmethod
505
    def iter_instances(cls):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
506
        yield cls(get_transport(API_GITHUB_URL))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
507
0.431.66 by Jelmer Vernooij
Add support for status argument.
508
    def iter_my_proposals(self, status='open'):
509
        query = ['is:pr']
510
        if status == 'open':
511
            query.append('is:open')
512
        elif status == 'closed':
513
            query.append('is:unmerged')
7268.2.1 by Jelmer Vernooij
Don't include open unmerged pull requests in 'closed'.
514
            # Also use "is:closed" otherwise unmerged open pull requests are
515
            # also included.
516
            query.append('is:closed')
0.431.66 by Jelmer Vernooij
Add support for status argument.
517
        elif status == 'merged':
518
            query.append('is:merged')
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
519
        query.append('author:%s' % self._current_user['login'])
7408.1.3 by Jelmer Vernooij
Support pagination for github.
520
        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.
521
            url = issue['pull_request']['url']
522
            response = self._api_request('GET', url)
523
            if response.status != 200:
524
                raise InvalidHttpResponse(url, response.text)
7371.4.2 by Jelmer Vernooij
More fixes.
525
            yield GitHubMergeProposal(self, json.loads(response.text))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
526
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
527
    def get_proposal_by_url(self, url):
528
        raise UnsupportedHoster(url)
529
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
530
    def iter_my_forks(self):
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
531
        response = self._api_request('GET', '/user/repos')
532
        if response.status != 200:
533
            raise InvalidHttpResponse(url, response.text)
534
        for project in json.loads(response.text):
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
535
            if not project['fork']:
536
                continue
537
            yield project['full_name']
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
538
539
    def delete_project(self, path):
540
        path = 'repos/' + path
541
        response = self._api_request('DELETE', path)
542
        if response.status == 404:
543
            raise NoSuchProject(path)
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
544
        if response.status == 204:
545
            return
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
546
        if response.status == 200:
547
            return json.loads(response.text)
548
        raise InvalidHttpResponse(path, response.text)
549
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
550
0.432.2 by Jelmer Vernooij
Publish command sort of works.
551
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
552
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
553
    def __init__(self, gh, source_branch, target_branch):
554
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
555
        self.source_branch = source_branch
556
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
557
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
558
            parse_github_branch_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
559
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
560
            parse_github_branch_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
561
562
    def get_infotext(self):
563
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
564
        info = []
565
        info.append("Merge %s into %s:%s\n" % (
566
            self.source_branch_name, self.target_owner,
567
            self.target_branch_name))
568
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
569
        info.append("Target: %s\n" % self.target_branch.user_url)
570
        return ''.join(info)
571
572
    def get_initial_body(self):
573
        """Get a body for the proposal for the user to modify.
574
575
        :return: a str or None.
576
        """
577
        return None
578
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
579
    def create_proposal(self, description, reviewers=None, labels=None,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
580
                        prerequisite_branch=None, commit_message=None,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
581
                        work_in_progress=False, allow_collaboration=False):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
582
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
583
        if prerequisite_branch is not None:
584
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
585
        # Note that commit_message is ignored, since github doesn't support it.
0.432.7 by Jelmer Vernooij
propose works \o/
586
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
587
        if self.target_repo_name.endswith('.git'):
588
            self.target_repo_name = self.target_repo_name[:-4]
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
589
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
590
        title = determine_title(description)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
591
        target_repo = self.gh._get_repo(
592
            self.target_owner, self.target_repo_name)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
593
        assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
594
        if reviewers:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
595
            assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
596
            for reviewer in reviewers:
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
597
                if '@' in reviewer:
598
                    user = self.gh._get_user_by_email(reviewer)
599
                else:
600
                    user = self.gh._get_user(reviewer)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
601
                assignees.append(user['login'])
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
602
        else:
603
            assignees = None
604
        try:
605
            pull_request = self.gh._create_pull(
606
                strip_optional(target_repo['pulls_url']),
607
                title=title, body=description,
608
                head="%s:%s" % (self.source_owner, self.source_branch_name),
609
                base=self.target_branch_name,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
610
                labels=labels, assignee=assignees,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
611
                draft=work_in_progress,
612
                maintainer_can_modify=allow_collaboration,
613
                )
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
614
        except ValidationFailed:
615
            raise MergeProposalExists(self.source_branch.user_url)
7371.4.2 by Jelmer Vernooij
More fixes.
616
        return GitHubMergeProposal(self.gh, pull_request)