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