/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
278
        response = self.transport.request(
279
            method, urlutils.join(self.transport.base, path),
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
280
            headers=headers, body=body, retries=3)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
281
        if response.status == 401:
282
            raise GitHubLoginRequired(self)
283
        return response
284
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
285
    def _get_repo(self, owner, repo):
286
        path = 'repos/%s/%s' % (owner, repo)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
287
        response = self._api_request('GET', path)
288
        if response.status == 404:
289
            raise NoSuchProject(path)
290
        if response.status == 200:
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
291
            return json.loads(response.text)
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
292
        raise UnexpectedHttpStatus(path, response.status)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
293
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
294
    def _get_repo_pulls(self, path, head=None, state=None):
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
295
        path = path + '?'
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
296
        params = {}
297
        if head is not None:
298
            params['head'] = head
299
        if state is not None:
300
            params['state'] = state
301
        path += ';'.join(['%s=%s' % (k, urlutils.quote(v))
302
                         for k, v in params.items()])
303
        response = self._api_request('GET', path)
304
        if response.status == 404:
305
            raise NoSuchProject(path)
306
        if response.status == 200:
307
            return json.loads(response.text)
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
308
        raise UnexpectedHttpStatus(path, response.status)
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
309
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
310
    def _create_pull(self, path, title, head, base, body=None, labels=None,
7496.1.2 by Jelmer Vernooij
Extra s.
311
                     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.
312
        data = {
313
            'title': title,
314
            'head': head,
315
            'base': base,
7490.2.1 by Jelmer Vernooij
Fix draft option for github merge proposals.
316
            'draft': draft,
7496.1.2 by Jelmer Vernooij
Extra s.
317
            'maintainer_can_modify': maintainer_can_modify,
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
318
        }
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
319
        if labels is not None:
320
            data['labels'] = labels
321
        if assignee is not None:
322
            data['assignee'] = assignee
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
323
        if body:
324
            data['body'] = body
325
326
        response = self._api_request(
327
            'POST', path, body=json.dumps(data).encode('utf-8'))
7428.1.1 by Jelmer Vernooij
Handle 403s for pull request creation.
328
        if response.status == 403:
329
            raise PermissionDenied(path, response.text)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
330
        if response.status != 201:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
331
            raise UnexpectedHttpStatus(path, response.status)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
332
        return json.loads(response.text)
333
334
    def _get_user_by_email(self, email):
335
        path = 'search/users?q=%s+in:email' % email
336
        response = self._api_request('GET', path)
337
        if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
338
            raise UnexpectedHttpStatus(path, response.status)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
339
        ret = json.loads(response.text)
340
        if ret['total_count'] == 0:
341
            raise KeyError('no user with email %s' % email)
342
        elif ret['total_count'] > 1:
343
            raise ValueError('more than one result for email %s' % email)
344
        return ret['items'][0]
345
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
346
    def _get_user(self, username=None):
347
        if username:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
348
            path = 'users/%s' % username
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
349
        else:
350
            path = 'user'
351
        response = self._api_request('GET', path)
352
        if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
353
            raise UnexpectedHttpStatus(path, response.status)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
354
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
355
356
    def _get_organization(self, name):
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
357
        path = 'orgs/%s' % name
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
358
        response = self._api_request('GET', path)
359
        if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
360
            raise UnexpectedHttpStatus(path, response.status)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
361
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
362
7408.1.3 by Jelmer Vernooij
Support pagination for github.
363
    def _list_paged(self, path, parameters=None, per_page=None):
364
        if parameters is None:
365
            parameters = {}
366
        else:
367
            parameters = dict(parameters.items())
368
        if per_page:
369
            parameters['per_page'] = str(per_page)
370
        page = 1
371
        i = 0
372
        while path:
373
            parameters['page'] = str(page)
374
            response = self._api_request(
375
                'GET', path + '?' +
376
                ';'.join(['%s=%s' % (k, urlutils.quote(v))
377
                          for (k, v) in parameters.items()]))
378
            if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
379
                raise UnexpectedHttpStatus(path, response.status)
7408.1.3 by Jelmer Vernooij
Support pagination for github.
380
            data = json.loads(response.text)
381
            for entry in data['items']:
382
                i += 1
383
                yield entry
384
            if i >= data['total_count']:
385
                break
386
            page += 1
387
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
388
    def _search_issues(self, query):
389
        path = 'search/issues'
7408.1.3 by Jelmer Vernooij
Support pagination for github.
390
        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.
391
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
392
    def _create_fork(self, path, owner=None):
7490.86.3 by Jelmer Vernooij
Access current user lazily.
393
        if owner and owner != self.current_user['login']:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
394
            path += '?organization=%s' % owner
395
        response = self._api_request('POST', path)
7397.1.2 by Jelmer Vernooij
Fix fork creation on github.
396
        if response.status != 202:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
397
            raise UnexpectedHttpStatus(path, response.status)
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
398
        return json.loads(response.text)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
399
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
400
    @property
401
    def base_url(self):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
402
        return WEB_GITHUB_URL
403
7490.86.3 by Jelmer Vernooij
Access current user lazily.
404
    def __init__(self, transport):
405
        self._token = retrieve_github_token('https', GITHUB_HOST)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
406
        self.transport = transport
7490.86.3 by Jelmer Vernooij
Access current user lazily.
407
        self._current_user = None
408
409
    @property
410
    def current_user(self):
411
        if self._current_user is None:
412
            self._current_user = self._get_user()
413
        return self._current_user
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
414
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
415
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
416
                        owner=None, revision_id=None, overwrite=False,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
417
                        allow_lossy=True, tag_selector=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
418
        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.
419
        base_repo = self._get_repo(base_owner, base_project)
0.432.3 by Jelmer Vernooij
Publish command works for github.
420
        if owner is None:
7490.86.3 by Jelmer Vernooij
Access current user lazily.
421
            owner = self.current_user['login']
0.432.3 by Jelmer Vernooij
Publish command works for github.
422
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
423
            project = base_repo['name']
0.432.3 by Jelmer Vernooij
Publish command works for github.
424
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
425
            remote_repo = self._get_repo(owner, project)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
426
        except NoSuchProject:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
427
            base_repo = self._get_repo(base_owner, base_project)
428
            remote_repo = self._create_fork(base_repo['forks_url'], owner)
0.432.3 by Jelmer Vernooij
Publish command works for github.
429
            note(gettext('Forking new repository %s from %s') %
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
430
                 (remote_repo['html_url'], base_repo['html_url']))
0.432.3 by Jelmer Vernooij
Publish command works for github.
431
        else:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
432
            note(gettext('Reusing existing repository %s') % remote_repo['html_url'])
433
        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.
434
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
435
            push_result = remote_dir.push_branch(
436
                local_branch, revision_id=revision_id, overwrite=overwrite,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
437
                name=name, tag_selector=tag_selector)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
438
        except errors.NoRoundtrippingSupport:
439
            if not allow_lossy:
440
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
441
            push_result = remote_dir.push_branch(
442
                local_branch, revision_id=revision_id,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
443
                overwrite=overwrite, name=name, lossy=True,
444
                tag_selector=tag_selector)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
445
        return push_result.target_branch, github_url_to_bzr_url(
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
446
            remote_repo['html_url'], name)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
447
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
448
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
449
        owner, project, branch_name = parse_github_branch_url(branch)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
450
        repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
451
        return github_url_to_bzr_url(repo['ssh_url'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
452
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
453
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
454
        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.
455
        base_repo = self._get_repo(base_owner, base_project)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
456
        if owner is None:
7490.86.3 by Jelmer Vernooij
Access current user lazily.
457
            owner = self.current_user['login']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
458
        if project is None:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
459
            project = base_repo['name']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
460
        try:
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
461
            remote_repo = self._get_repo(owner, project)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
462
            full_url = github_url_to_bzr_url(remote_repo['ssh_url'], name)
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
463
            return _mod_branch.Branch.open(full_url)
7410.1.1 by Jelmer Vernooij
Remove last imports of the pygithub module.
464
        except NoSuchProject:
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
465
            raise errors.NotBranchError('%s/%s/%s' % (WEB_GITHUB_URL, owner, project))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
466
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
467
    def get_proposer(self, source_branch, target_branch):
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
468
        return GitHubMergeProposalBuilder(self, source_branch, target_branch)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
469
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
470
    def iter_proposals(self, source_branch, target_branch, status='open'):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
471
        (source_owner, source_repo_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
472
            parse_github_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
473
        (target_owner, target_repo_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
474
            parse_github_branch_url(target_branch))
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
475
        target_repo = self._get_repo(target_owner, target_repo_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
476
        state = {
477
            'open': 'open',
478
            'merged': 'closed',
479
            'closed': 'closed',
480
            'all': 'all'}
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
481
        pulls = self._get_repo_pulls(
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
482
            strip_optional(target_repo['pulls_url']),
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
483
            head=target_branch_name,
484
            state=state[status])
485
        for pull in pulls:
486
            if (status == 'closed' and pull['merged'] or
487
                    status == 'merged' and not pull['merged']):
488
                continue
489
            if pull['head']['ref'] != source_branch_name:
490
                continue
491
            if pull['head']['repo'] is None:
7268.4.1 by Jelmer Vernooij
Don't attempt to resolve None when repo has gone away.
492
                # Repo has gone the way of the dodo
493
                continue
7371.4.1 by Jelmer Vernooij
Fix iter_proposals for GitHub.
494
            if (pull['head']['repo']['owner']['login'] != source_owner or
495
                    pull['head']['repo']['name'] != source_repo_name):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
496
                continue
7371.4.2 by Jelmer Vernooij
More fixes.
497
            yield GitHubMergeProposal(self, pull)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
498
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
499
    def hosts(self, branch):
500
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
501
            parse_github_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
502
        except NotGitHubUrl:
503
            return False
504
        else:
505
            return True
506
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
507
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
508
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
509
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
510
            parse_github_url(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
511
        except NotGitHubUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
512
            raise UnsupportedHoster(url)
7296.11.1 by Jelmer Vernooij
Initial work migrating GitHub away from library.
513
        transport = get_transport(
514
            API_GITHUB_URL, possible_transports=possible_transports)
7490.86.3 by Jelmer Vernooij
Access current user lazily.
515
        return cls(transport)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
516
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
517
    @classmethod
518
    def iter_instances(cls):
7490.86.3 by Jelmer Vernooij
Access current user lazily.
519
        yield cls(get_transport(API_GITHUB_URL))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
520
0.431.66 by Jelmer Vernooij
Add support for status argument.
521
    def iter_my_proposals(self, status='open'):
522
        query = ['is:pr']
523
        if status == 'open':
524
            query.append('is:open')
525
        elif status == 'closed':
526
            query.append('is:unmerged')
7268.2.1 by Jelmer Vernooij
Don't include open unmerged pull requests in 'closed'.
527
            # Also use "is:closed" otherwise unmerged open pull requests are
528
            # also included.
529
            query.append('is:closed')
0.431.66 by Jelmer Vernooij
Add support for status argument.
530
        elif status == 'merged':
531
            query.append('is:merged')
7490.86.3 by Jelmer Vernooij
Access current user lazily.
532
        query.append('author:%s' % self.current_user['login'])
7408.1.3 by Jelmer Vernooij
Support pagination for github.
533
        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.
534
            url = issue['pull_request']['url']
535
            response = self._api_request('GET', url)
536
            if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
537
                raise UnexpectedHttpStatus(url, response.status)
7371.4.2 by Jelmer Vernooij
More fixes.
538
            yield GitHubMergeProposal(self, json.loads(response.text))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
539
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
540
    def get_proposal_by_url(self, url):
541
        raise UnsupportedHoster(url)
542
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
543
    def iter_my_forks(self):
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
544
        response = self._api_request('GET', '/user/repos')
545
        if response.status != 200:
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
546
            raise UnexpectedHttpStatus(self.transport.user_url, response.status)
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
547
        for project in json.loads(response.text):
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
548
            if not project['fork']:
549
                continue
550
            yield project['full_name']
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
551
552
    def delete_project(self, path):
553
        path = 'repos/' + path
554
        response = self._api_request('DELETE', path)
555
        if response.status == 404:
556
            raise NoSuchProject(path)
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
557
        if response.status == 204:
558
            return
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
559
        if response.status == 200:
560
            return json.loads(response.text)
7490.90.6 by Jelmer Vernooij
More fixes for hg probing.
561
        raise UnexpectedHttpStatus(path, response.status)
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
562
7490.105.1 by Jelmer Vernooij
Add some hoster metadata fields.
563
    def get_current_user(self):
564
        return self.current_user['login']
565
566
    def get_user_url(self, username):
567
        return urlutils.join(self.base_url, username)
568
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
569
0.432.2 by Jelmer Vernooij
Publish command sort of works.
570
class GitHubMergeProposalBuilder(MergeProposalBuilder):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
571
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
572
    def __init__(self, gh, source_branch, target_branch):
573
        self.gh = gh
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
574
        self.source_branch = source_branch
575
        self.target_branch = target_branch
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
576
        (self.target_owner, self.target_repo_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
577
            parse_github_branch_url(self.target_branch))
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
578
        (self.source_owner, self.source_repo_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
579
            parse_github_branch_url(self.source_branch))
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
580
581
    def get_infotext(self):
582
        """Determine the initial comment for the merge proposal."""
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
583
        info = []
584
        info.append("Merge %s into %s:%s\n" % (
585
            self.source_branch_name, self.target_owner,
586
            self.target_branch_name))
587
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
588
        info.append("Target: %s\n" % self.target_branch.user_url)
589
        return ''.join(info)
590
591
    def get_initial_body(self):
592
        """Get a body for the proposal for the user to modify.
593
594
        :return: a str or None.
595
        """
596
        return None
597
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
598
    def create_proposal(self, description, reviewers=None, labels=None,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
599
                        prerequisite_branch=None, commit_message=None,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
600
                        work_in_progress=False, allow_collaboration=False):
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
601
        """Perform the submission."""
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
602
        if prerequisite_branch is not None:
603
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
604
        # Note that commit_message is ignored, since github doesn't support it.
0.432.7 by Jelmer Vernooij
propose works \o/
605
        # TODO(jelmer): Probe for right repo name
0.432.12 by Jelmer Vernooij
Fix .git ends.
606
        if self.target_repo_name.endswith('.git'):
607
            self.target_repo_name = self.target_repo_name[:-4]
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
608
        # TODO(jelmer): Allow setting title explicitly?
0.431.44 by Jelmer Vernooij
Support get/set description.
609
        title = determine_title(description)
7414.2.1 by Jelmer Vernooij
Support following links in github repositories.
610
        target_repo = self.gh._get_repo(
611
            self.target_owner, self.target_repo_name)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
612
        assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
613
        if reviewers:
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
614
            assignees = []
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
615
            for reviewer in reviewers:
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
616
                if '@' in reviewer:
617
                    user = self.gh._get_user_by_email(reviewer)
618
                else:
619
                    user = self.gh._get_user(reviewer)
7410.1.3 by Jelmer Vernooij
Fix issue handling.
620
                assignees.append(user['login'])
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
621
        else:
622
            assignees = None
623
        try:
624
            pull_request = self.gh._create_pull(
625
                strip_optional(target_repo['pulls_url']),
626
                title=title, body=description,
627
                head="%s:%s" % (self.source_owner, self.source_branch_name),
628
                base=self.target_branch_name,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
629
                labels=labels, assignee=assignees,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
630
                draft=work_in_progress,
631
                maintainer_can_modify=allow_collaboration,
632
                )
7426.1.1 by Jelmer Vernooij
Fix setting of attributes on GitHub pull requests.
633
        except ValidationFailed:
634
            raise MergeProposalExists(self.source_branch.user_url)
7371.4.2 by Jelmer Vernooij
More fixes.
635
        return GitHubMergeProposal(self.gh, pull_request)