/brz/remove-bazaar

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