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