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