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