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