/brz/remove-bazaar

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