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