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