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