/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
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
24
from .propose import (
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
25
    Hoster,
7268.8.2 by Jelmer Vernooij
Handle GitHub errors.
26
    HosterLoginRequired,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
27
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
28
    MergeProposalBuilder,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
29
    MergeProposalExists,
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
30
    NoSuchProject,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
31
    PrerequisiteBranchUnsupported,
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
32
    ReopenFailed,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
33
    UnsupportedHoster,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
34
    )
35
36
from ... import (
7340.1.1 by Martin
Fix use of config_dir in propose plugin
37
    bedding,
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
38
    branch as _mod_branch,
0.432.3 by Jelmer Vernooij
Publish command works for github.
39
    controldir,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
40
    errors,
41
    hooks,
42
    urlutils,
0.432.3 by Jelmer Vernooij
Publish command works for github.
43
    version_string as breezy_version,
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
44
    )
7340.1.1 by Martin
Fix use of config_dir in propose plugin
45
from ...config import AuthenticationConfig, GlobalStack
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
46
from ...errors import InvalidHttpResponse
0.431.32 by Jelmer Vernooij
Properly resolve git+ssh URLs.
47
from ...git.urls import git_url_to_bzr_url
0.432.3 by Jelmer Vernooij
Publish command works for github.
48
from ...i18n import gettext
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
49
from ...sixish import PY3
0.432.3 by Jelmer Vernooij
Publish command works for github.
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
0.431.44 by Jelmer Vernooij
Support get/set description.
74
def determine_title(description):
75
    return description.splitlines()[0]
76
77
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
78
class ValidationFailed(errors.BzrError):
79
80
    _fmt = "GitHub validation failed: %(error)s"
81
82
    def __init__(self, error):
83
        errors.BzrError.__init__(self)
84
        self.error = error
85
86
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
87
class NotGitHubUrl(errors.BzrError):
88
89
    _fmt = "Not a GitHub URL: %(url)s"
90
91
    def __init__(self, url):
92
        errors.BzrError.__init__(self)
93
        self.url = url
94
95
7268.8.2 by Jelmer Vernooij
Handle GitHub errors.
96
class GitHubLoginRequired(HosterLoginRequired):
97
98
    _fmt = "Action requires GitHub login."
99
100
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
101
def connect_github():
7211.13.7 by Jelmer Vernooij
Fix formatting.
102
    """Connect to GitHub.
103
    """
7296.10.5 by Jelmer Vernooij
use default_user_agent function.
104
    user_agent = default_user_agent()
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
105
    auth = AuthenticationConfig()
106
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
107
    credentials = auth.get_credentials('https', GITHUB_HOST)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
108
    if credentials is not None:
0.432.3 by Jelmer Vernooij
Publish command works for github.
109
        return Github(credentials['user'], credentials['password'],
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
110
                      user_agent=user_agent)
111
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
112
    # TODO(jelmer): token = auth.get_token('https', GITHUB_HOST)
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
113
    if token is not None:
0.431.61 by Jelmer Vernooij
Fix token login.
114
        return Github(token, user_agent=user_agent)
0.431.49 by Jelmer Vernooij
Store GitHub tokens in a magic file, for now.
115
    else:
116
        note('Accessing GitHub anonymously. To log in, run \'brz gh-login\'.')
117
        return Github(user_agent=user_agent)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
118
119
0.431.44 by Jelmer Vernooij
Support get/set description.
120
class GitHubMergeProposal(MergeProposal):
121
7371.4.2 by Jelmer Vernooij
More fixes.
122
    def __init__(self, gh, pr):
123
        self._gh = gh
0.431.44 by Jelmer Vernooij
Support get/set description.
124
        self._pr = pr
125
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
126
    def __repr__(self):
127
        return "<%s at %r>" % (type(self).__name__, self.url)
128
0.431.44 by Jelmer Vernooij
Support get/set description.
129
    @property
130
    def url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
131
        return self._pr['html_url']
0.431.44 by Jelmer Vernooij
Support get/set description.
132
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
133
    def _branch_from_part(self, part):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
134
        if part['repo'] is None:
135
            return None
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
136
        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.
137
138
    def get_source_branch_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
139
        return self._branch_from_part(self._pr['head'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
140
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
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):
151
        self._patch({'title': message})
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
0.431.44 by Jelmer Vernooij
Support get/set description.
190
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
191
def parse_github_url(url):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
192
    (scheme, user, password, host, port, path) = urlutils.parse_url(
193
        url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
194
    if host != GITHUB_HOST:
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
195
        raise NotGitHubUrl(url)
196
    (owner, repo_name) = path.strip('/').split('/')
0.432.12 by Jelmer Vernooij
Fix .git ends.
197
    if repo_name.endswith('.git'):
198
        repo_name = repo_name[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
199
    return owner, repo_name
200
201
202
def parse_github_branch_url(branch):
203
    url = urlutils.split_segment_parameters(branch.user_url)[0]
204
    owner, repo_name = parse_github_url(url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
205
    return owner, repo_name, branch.name
206
207
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
208
def github_url_to_bzr_url(url, branch_name):
209
    if not PY3:
210
        branch_name = branch_name.encode('utf-8')
211
    return urlutils.join_segment_parameters(
7211.13.7 by Jelmer Vernooij
Fix formatting.
212
        git_url_to_bzr_url(url), {"branch": branch_name})
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
213
214
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
215
class GitHub(Hoster):
216
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
217
    name = 'github'
218
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
219
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
220
    supports_merge_proposal_commit_message = False
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
221
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
222
    def __repr__(self):
223
        return "GitHub()"
224
7371.4.2 by Jelmer Vernooij
More fixes.
225
    def _api_request(self, method, path, body=None):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
226
        headers = {
227
            'Accept': 'application/vnd.github.v3+json'}
228
        if self._token:
229
            headers['Authorization'] = 'token %s' % self._token
230
        response = self.transport.request(
231
            method, urlutils.join(self.transport.base, path),
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
232
            headers=headers, body=body, retries=3)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
233
        if response.status == 401:
234
            raise GitHubLoginRequired(self)
235
        return response
236
237
    def _get_repo(self, path):
238
        path = 'repos/' + path
239
        response = self._api_request('GET', path)
240
        if response.status == 404:
241
            raise NoSuchProject(path)
242
        if response.status == 200:
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
243
            return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
244
        raise InvalidHttpResponse(path, response.text)
245
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
246
    def _get_repo_pulls(self, path, head=None, state=None):
247
        path = 'repos/' + path + '/pulls?'
248
        params = {}
249
        if head is not None:
250
            params['head'] = head
251
        if state is not None:
252
            params['state'] = state
253
        path += ';'.join(['%s=%s' % (k, urlutils.quote(v))
254
                         for k, v in params.items()])
255
        response = self._api_request('GET', path)
256
        if response.status == 404:
257
            raise NoSuchProject(path)
258
        if response.status == 200:
259
            return json.loads(response.text)
260
        raise InvalidHttpResponse(path, response.text)
261
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
262
    def _create_pull(self, path, title, head, base, body=None):
263
        path = 'repos/' + path + '/pulls'
264
        data = {
265
            'title': title,
266
            'head': head,
267
            'base': base,
268
        }
269
        if body:
270
            data['body'] = body
271
272
        response = self._api_request(
273
            'POST', path, body=json.dumps(data).encode('utf-8'))
274
        if response.status != 201:
275
            raise InvalidHttpResponse(path, response.text)
276
        return json.loads(response.text)
277
278
    def _get_user_by_email(self, email):
279
        path = 'search/users?q=%s+in:email' % email
280
        response = self._api_request('GET', path)
281
        if response.status != 200:
282
            raise InvalidHttpResponse(path, response.text)
283
        ret = json.loads(response.text)
284
        if ret['total_count'] == 0:
285
            raise KeyError('no user with email %s' % email)
286
        elif ret['total_count'] > 1:
287
            raise ValueError('more than one result for email %s' % email)
288
        return ret['items'][0]
289
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
290
    def _get_user(self, username=None):
291
        if username:
292
            path = 'users/:%s' % username
293
        else:
294
            path = 'user'
295
        response = self._api_request('GET', path)
296
        if response.status != 200:
297
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
298
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
299
300
    def _get_organization(self, name):
301
        path = 'orgs/:%s' % name
302
        response = self._api_request('GET', path)
303
        if response.status != 200:
304
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
305
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
306
7408.1.3 by Jelmer Vernooij
Support pagination for github.
307
    def _list_paged(self, path, parameters=None, per_page=None):
308
        if parameters is None:
309
            parameters = {}
310
        else:
311
            parameters = dict(parameters.items())
312
        if per_page:
313
            parameters['per_page'] = str(per_page)
314
        page = 1
315
        i = 0
316
        while path:
317
            parameters['page'] = str(page)
318
            response = self._api_request(
319
                'GET', path + '?' +
320
                ';'.join(['%s=%s' % (k, urlutils.quote(v))
321
                          for (k, v) in parameters.items()]))
322
            if response.status != 200:
323
                raise InvalidHttpResponse(path, response.text)
324
            data = json.loads(response.text)
325
            for entry in data['items']:
326
                i += 1
327
                yield entry
328
            if i >= data['total_count']:
329
                break
330
            page += 1
331
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
332
    def _search_issues(self, query):
333
        path = 'search/issues'
7408.1.3 by Jelmer Vernooij
Support pagination for github.
334
        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.
335
336
    def _create_fork(self, repo, owner=None):
337
        (orig_owner, orig_repo) = repo.split('/')
338
        path = '/repos/:%s/:%s/forks' % (orig_owner, orig_repo)
339
        if owner:
340
            path += '?organization=%s' % owner
341
        response = self._api_request('POST', path)
7397.1.2 by Jelmer Vernooij
Fix fork creation on github.
342
        if response.status != 202:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
343
            raise InvalidHttpResponse(path, response.text)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
344
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
345
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
346
    @property
347
    def base_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
348
        return WEB_GITHUB_URL
349
350
    def __init__(self, transport):
351
        self._token = retrieve_github_token('https', GITHUB_HOST)
352
        self.transport = transport
353
        self._current_user = self._get_user()
354
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
355
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
356
                        owner=None, revision_id=None, overwrite=False,
357
                        allow_lossy=True):
0.432.12 by Jelmer Vernooij
Fix .git ends.
358
        import github
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
359
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
360
        base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
0.432.3 by Jelmer Vernooij
Publish command works for github.
361
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
362
            owner = self._current_user['login']
0.432.3 by Jelmer Vernooij
Publish command works for github.
363
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
364
            project = base_repo['name']
0.432.3 by Jelmer Vernooij
Publish command works for github.
365
        try:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
366
            remote_repo = self._get_repo('%s/%s' % (owner, project))
0.432.12 by Jelmer Vernooij
Fix .git ends.
367
        except github.UnknownObjectException:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
368
            base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
369
            remote_repo = self._create_fork(base_repo, owner)
0.432.3 by Jelmer Vernooij
Publish command works for github.
370
            note(gettext('Forking new repository %s from %s') %
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
371
                 (remote_repo['html_url'], base_repo['html_url']))
0.432.3 by Jelmer Vernooij
Publish command works for github.
372
        else:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
373
            note(gettext('Reusing existing repository %s') % remote_repo['html_url'])
374
        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.
375
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
376
            push_result = remote_dir.push_branch(
377
                local_branch, revision_id=revision_id, overwrite=overwrite,
378
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
379
        except errors.NoRoundtrippingSupport:
380
            if not allow_lossy:
381
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
382
            push_result = remote_dir.push_branch(
383
                local_branch, revision_id=revision_id,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
384
                overwrite=overwrite, name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
385
        return push_result.target_branch, github_url_to_bzr_url(
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
386
            remote_repo['html_url'], name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
387
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
388
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
389
        owner, project, branch_name = parse_github_branch_url(branch)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
390
        repo = self._get_repo('%s/%s' % (owner, project))
391
        return github_url_to_bzr_url(repo['ssh_url'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
392
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
393
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
394
        import github
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
395
        base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
396
        base_repo = self._get_repo('%s/%s' % (base_owner, base_project))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
397
        if owner is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
398
            owner = self._current_user['login']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
399
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
400
            project = base_repo['name']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
401
        try:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
402
            remote_repo = self._get_repo('%s/%s' % (owner, project))
403
            full_url = github_url_to_bzr_url(remote_repo['ssh_url'], name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
404
            return _mod_branch.Branch.open(full_url)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
405
        except github.UnknownObjectException:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
406
            raise errors.NotBranchError('%s/%s/%s' % (WEB_GITHUB_URL, owner, project))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
407
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
408
    def get_proposer(self, source_branch, target_branch):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
409
        return GitHubMergeProposalBuilder(self, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
410
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
411
    def iter_proposals(self, source_branch, target_branch, status='open'):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
412
        (source_owner, source_repo_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
413
            parse_github_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
414
        (target_owner, target_repo_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
415
            parse_github_branch_url(target_branch))
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
416
        target_repo_path = "%s/%s" % (target_owner, target_repo_name)
417
        target_repo = self._get_repo(target_repo_path)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
418
        state = {
419
            'open': 'open',
420
            'merged': 'closed',
421
            'closed': 'closed',
422
            'all': 'all'}
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
423
        pulls = self._get_repo_pulls(
424
            target_repo_path,
425
            head=target_branch_name,
426
            state=state[status])
427
        for pull in pulls:
428
            if (status == 'closed' and pull['merged'] or
429
                    status == 'merged' and not pull['merged']):
430
                continue
431
            if pull['head']['ref'] != source_branch_name:
432
                continue
433
            if pull['head']['repo'] is None:
7268.4.1 by Jelmer Vernooij
Don't attempt to resolve None when repo has gone away.
434
                # Repo has gone the way of the dodo
435
                continue
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
436
            if (pull['head']['repo']['owner']['login'] != source_owner or
437
                    pull['head']['repo']['name'] != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
438
                continue
7371.4.2 by Jelmer Vernooij
More fixes.
439
            yield GitHubMergeProposal(self, pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
440
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
441
    def hosts(self, branch):
442
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
443
            parse_github_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
444
        except NotGitHubUrl:
445
            return False
446
        else:
447
            return True
448
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
449
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
450
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
451
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
452
            parse_github_url(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
453
        except NotGitHubUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
454
            raise UnsupportedHoster(url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
455
        transport = get_transport(
456
            API_GITHUB_URL, possible_transports=possible_transports)
457
        return cls(transport)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
458
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
459
    @classmethod
460
    def iter_instances(cls):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
461
        yield cls(get_transport(API_GITHUB_URL))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
462
0.431.66 by Jelmer Vernooij
Add support for status argument.
463
    def iter_my_proposals(self, status='open'):
464
        query = ['is:pr']
465
        if status == 'open':
466
            query.append('is:open')
467
        elif status == 'closed':
468
            query.append('is:unmerged')
7268.2.1 by Jelmer Vernooij
Don't include open unmerged pull requests in 'closed'.
469
            # Also use "is:closed" otherwise unmerged open pull requests are
470
            # also included.
471
            query.append('is:closed')
0.431.66 by Jelmer Vernooij
Add support for status argument.
472
        elif status == 'merged':
473
            query.append('is:merged')
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
474
        query.append('author:%s' % self._current_user['login'])
7408.1.3 by Jelmer Vernooij
Support pagination for github.
475
        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.
476
            url = issue['pull_request']['url']
477
            response = self._api_request('GET', url)
478
            if response.status != 200:
479
                raise InvalidHttpResponse(url, response.text)
7371.4.2 by Jelmer Vernooij
More fixes.
480
            yield GitHubMergeProposal(self, json.loads(response.text))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
481
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
482
    def get_proposal_by_url(self, url):
483
        raise UnsupportedHoster(url)
484
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
485
0.432.2 by Jelmer Vernooij
Publish command sort of works.
486
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
487
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
488
    def __init__(self, gh, source_branch, target_branch):
489
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
490
        self.source_branch = source_branch
491
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
492
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
493
            parse_github_branch_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
494
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
495
            parse_github_branch_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
496
497
    def get_infotext(self):
498
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
499
        info = []
500
        info.append("Merge %s into %s:%s\n" % (
501
            self.source_branch_name, self.target_owner,
502
            self.target_branch_name))
503
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
504
        info.append("Target: %s\n" % self.target_branch.user_url)
505
        return ''.join(info)
506
507
    def get_initial_body(self):
508
        """Get a body for the proposal for the user to modify.
509
510
        :return: a str or None.
511
        """
512
        return None
513
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
514
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
515
                        prerequisite_branch=None, commit_message=None):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
516
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
517
        if prerequisite_branch is not None:
518
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
519
        # Note that commit_message is ignored, since github doesn't support it.
0.432.10 by Jelmer Vernooij
More test fixes.
520
        import github
0.432.7 by Jelmer Vernooij
propose works \o/
521
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
522
        if self.target_repo_name.endswith('.git'):
523
            self.target_repo_name = self.target_repo_name[:-4]
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
524
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
525
        title = determine_title(description)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
526
        # TODO(jelmer): Set maintainers_can_modify?
0.432.10 by Jelmer Vernooij
More test fixes.
527
        try:
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
528
            pull_request = self.gh._create_pull(
529
                "%s/%s" % (self.target_owner, self.target_repo_name),
0.432.10 by Jelmer Vernooij
More test fixes.
530
                title=title, body=description,
531
                head="%s:%s" % (self.source_owner, self.source_branch_name),
532
                base=self.target_branch_name)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
533
        except ValidationFailed:
534
            raise MergeProposalExists(self.source_branch.user_url)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
535
        if reviewers:
536
            for reviewer in reviewers:
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
537
                if '@' in reviewer:
538
                    user = self.gh._get_user_by_email(reviewer)
539
                else:
540
                    user = self.gh._get_user(reviewer)
541
                pull_request.assignees.append(user['login'])
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
542
        if labels:
543
            for label in labels:
544
                pull_request.issue.labels.append(label)
7371.4.2 by Jelmer Vernooij
More fixes.
545
        return GitHubMergeProposal(self.gh, pull_request)