/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.431.5 by Jelmer Vernooij
Initial work on gitlab 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 GitLab."""
18
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
19
from __future__ import absolute_import
20
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
21
import json
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
22
import os
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
23
import time
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
24
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
25
from ... import (
7340.1.1 by Martin
Fix use of config_dir in propose plugin
26
    bedding,
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
27
    branch as _mod_branch,
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
28
    controldir,
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
29
    errors,
30
    urlutils,
31
    )
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
32
from ...git.urls import git_url_to_bzr_url
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
33
from ...sixish import PY3
7380.1.2 by Jelmer Vernooij
Review comments.
34
from ...trace import mutter
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
35
from ...transport import get_transport
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
36
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
37
from .propose import (
0.432.2 by Jelmer Vernooij
Publish command sort of works.
38
    Hoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
39
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
40
    MergeProposalBuilder,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
41
    MergeProposalExists,
0.431.38 by Jelmer Vernooij
Add NoSuchProject.
42
    NoSuchProject,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
43
    PrerequisiteBranchUnsupported,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
44
    UnsupportedHoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
45
    )
46
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
47
48
_DEFAULT_FILES = ['/etc/python-gitlab.cfg', '~/.python-gitlab.cfg']
49
50
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
51
def mp_status_to_status(status):
52
    return {
53
        'all': 'all',
54
        'open': 'opened',
55
        'merged': 'merged',
56
        'closed': 'closed'}[status]
57
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
58
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
59
class NotGitLabUrl(errors.BzrError):
60
61
    _fmt = "Not a GitLab URL: %(url)s"
62
63
    def __init__(self, url):
64
        errors.BzrError.__init__(self)
65
        self.url = url
66
67
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
68
class NotMergeRequestUrl(errors.BzrError):
69
70
    _fmt = "Not a merge proposal URL: %(url)s"
71
72
    def __init__(self, host, url):
73
        errors.BzrError.__init__(self)
74
        self.host = host
75
        self.url = url
76
77
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
78
class DifferentGitLabInstances(errors.BzrError):
79
80
    _fmt = ("Can't create merge proposals across GitLab instances: "
81
            "%(source_host)s and %(target_host)s")
82
83
    def __init__(self, source_host, target_host):
84
        self.source_host = source_host
85
        self.target_host = target_host
86
87
0.432.10 by Jelmer Vernooij
More test fixes.
88
class GitLabLoginMissing(errors.BzrError):
89
90
    _fmt = ("Please log into GitLab")
91
92
7296.10.2 by Jelmer Vernooij
More fixes.
93
class GitlabLoginError(errors.BzrError):
94
95
    _fmt = ("Error logging in: %(error)s")
96
97
    def __init__(self, error):
98
        self.error = error
99
100
0.431.59 by Jelmer Vernooij
Add gitlab-login command.
101
def default_config_path():
7340.1.1 by Martin
Fix use of config_dir in propose plugin
102
    return os.path.join(bedding.config_dir(), 'gitlab.conf')
0.431.59 by Jelmer Vernooij
Add gitlab-login command.
103
104
105
def store_gitlab_token(name, url, private_token):
106
    """Store a GitLab token in a configuration file."""
107
    import configparser
108
    config = configparser.ConfigParser()
109
    path = default_config_path()
110
    config.read([path])
111
    config.add_section(name)
112
    config[name]['url'] = url
113
    config[name]['private_token'] = private_token
114
    with open(path, 'w') as f:
115
        config.write(f)
116
117
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
118
def iter_tokens():
119
    import configparser
120
    config = configparser.ConfigParser()
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
121
    config.read(
122
        [os.path.expanduser(p) for p in _DEFAULT_FILES] +
123
        [default_config_path()])
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
124
    for name, section in config.items():
125
        yield name, section
126
127
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
128
def get_credentials_by_url(url):
129
    for name, credentials in iter_tokens():
130
        if 'url' not in credentials:
131
            continue
132
        if credentials['url'].rstrip('/') == url.rstrip('/'):
133
            return credentials
134
    else:
135
        return None
136
137
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
138
def parse_gitlab_url(url):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
139
    (scheme, user, password, host, port, path) = urlutils.parse_url(
140
        url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
141
    if scheme not in ('git+ssh', 'https', 'http'):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
142
        raise NotGitLabUrl(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
143
    if not host:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
144
        raise NotGitLabUrl(url)
0.432.10 by Jelmer Vernooij
More test fixes.
145
    path = path.strip('/')
0.432.11 by Jelmer Vernooij
Fix some tests.
146
    if path.endswith('.git'):
147
        path = path[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
148
    return host, path
149
150
151
def parse_gitlab_branch_url(branch):
152
    url = urlutils.split_segment_parameters(branch.user_url)[0]
153
    host, path = parse_gitlab_url(url)
0.432.10 by Jelmer Vernooij
More test fixes.
154
    return host, path, branch.name
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
155
156
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
157
def parse_gitlab_merge_request_url(url):
158
    (scheme, user, password, host, port, path) = urlutils.parse_url(
159
        url)
160
    if scheme not in ('git+ssh', 'https', 'http'):
161
        raise NotGitLabUrl(url)
162
    if not host:
163
        raise NotGitLabUrl(url)
164
    path = path.strip('/')
165
    parts = path.split('/')
166
    if parts[-2] != 'merge_requests':
167
        raise NotMergeRequestUrl(host, url)
168
    return host, '/'.join(parts[:-2]), int(parts[-1])
169
170
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
171
class GitLabMergeProposal(MergeProposal):
172
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
173
    def __init__(self, gl, mr):
174
        self.gl = gl
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
175
        self._mr = mr
176
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
177
    def _update(self, **kwargs):
178
        self.gl._update_merge_request(self._mr['project_id'], self._mr['iid'], kwargs)
179
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
180
    def __repr__(self):
181
        return "<%s at %r>" % (type(self).__name__, self._mr['web_url'])
182
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
183
    @property
184
    def url(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
185
        return self._mr['web_url']
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
186
187
    def get_description(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
188
        return self._mr['description']
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
189
190
    def set_description(self, description):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
191
        self._update(description=description, title=description.splitlines()[0])
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
192
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
193
    def get_commit_message(self):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
194
        return self._mr.get('merge_commit_message')
195
196
    def set_commit_message(self, message):
197
        raise errors.UnsupportedOperation(self.set_commit_message, self)
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
198
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
199
    def _branch_url_from_project(self, project_id, branch_name):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
200
        if project_id is None:
201
            return None
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
202
        project = self.gl._get_project(project_id)
7296.10.3 by Jelmer Vernooij
More fixes.
203
        return gitlab_url_to_bzr_url(project['http_url_to_repo'], branch_name)
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
204
205
    def get_source_branch_url(self):
206
        return self._branch_url_from_project(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
207
            self._mr['source_project_id'], self._mr['source_branch'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
208
209
    def get_target_branch_url(self):
210
        return self._branch_url_from_project(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
211
            self._mr['target_project_id'], self._mr['target_branch'])
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
212
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
213
    def is_merged(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
214
        return (self._mr['state'] == 'merged')
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
215
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
216
    def is_closed(self):
217
        return (self._mr['state'] == 'closed')
218
219
    def reopen(self):
220
        return self._update(state_event='open')
221
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
222
    def close(self):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
223
        self._update(state_event='close')
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
224
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
225
    def merge(self, commit_message=None):
226
        # https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
227
        self._mr.merge(merge_commit_message=commit_message)
228
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
229
    def can_be_merged(self):
230
        if self._mr['merge_status'] == 'cannot_be_merged':
231
            return False
232
        elif self._mr['merge_status'] == 'can_be_merged':
233
            return True
234
        else:
235
            raise ValueError(self._mr['merge_status'])
236
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
237
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
238
def gitlab_url_to_bzr_url(url, name):
239
    if not PY3:
240
        name = name.encode('utf-8')
241
    return urlutils.join_segment_parameters(
7211.13.7 by Jelmer Vernooij
Fix formatting.
242
        git_url_to_bzr_url(url), {"branch": name})
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
243
244
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
245
class GitLab(Hoster):
246
    """GitLab hoster implementation."""
247
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
248
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
249
    supports_merge_proposal_commit_message = False
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
250
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
251
    def __repr__(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
252
        return "<GitLab(%r)>" % self.base_url
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
253
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
254
    @property
255
    def base_url(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
256
        return self.transport.base
257
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
258
    def _api_request(self, method, path, fields=None):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
259
        return self.transport.request(
260
            method, urlutils.join(self.base_url, 'api', 'v4', path),
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
261
            headers=self.headers, fields=fields)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
262
263
    def __init__(self, transport, private_token):
264
        self.transport = transport
265
        self.headers = {"Private-Token": private_token}
266
        self.check()
267
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
268
    def _get_user(self, username):
269
        path = 'users/%s' % urlutils.quote(str(project_name), '')
270
        response = self._api_request('GET', path)
271
        if response.status == 404:
272
            raise KeyError('no such user %s' % username)
273
        if response.status == 200:
274
            return json.loads(response.data)
275
        raise errors.InvalidHttpResponse(path, response.text)
276
277
    def _get_user_by_email(self, username):
278
        path = 'users?search=%s' % urlutils.quote(str(project_name), '')
279
        response = self._api_request('GET', path)
280
        if response.status == 404:
281
            raise KeyError('no such user %s' % username)
282
        if response.status == 200:
283
            ret = json.loads(response.data)
284
            if len(ret) != 1:
285
                raise ValueError('unexpected number of results; %r' % ret)
286
            return ret[0]
287
        raise errors.InvalidHttpResponse(path, response.text)
288
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
289
    def _get_project(self, project_name):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
290
        path = 'projects/%s' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
291
        response = self._api_request('GET', path)
292
        if response.status == 404:
293
            raise NoSuchProject(project_name)
294
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
295
            return json.loads(response.data)
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
296
        raise errors.InvalidHttpResponse(path, response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
297
7380.1.2 by Jelmer Vernooij
Review comments.
298
    def _fork_project(self, project_name, timeout=50, interval=5):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
299
        path = 'projects/%s/fork' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
300
        response = self._api_request('POST', path)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
301
        if response.status not in (200, 201):
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
302
            raise errors.InvalidHttpResponse(path, response.text)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
303
        # The response should be valid JSON, but let's ignore it
7397.1.1 by Jelmer Vernooij
Fix project forking.
304
        project = json.loads(response.data)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
305
        # Spin and wait until import_status for new project
306
        # is complete.
7380.1.2 by Jelmer Vernooij
Review comments.
307
        deadline = time.time() + timeout
7397.1.1 by Jelmer Vernooij
Fix project forking.
308
        while project['import_status'] not in ('finished', 'none'):
7380.1.2 by Jelmer Vernooij
Review comments.
309
            mutter('import status is %s', project['import_status'])
310
            if time.time() > deadline:
311
                raise Exception('timeout waiting for project to become available')
312
            time.sleep(interval)
7397.1.1 by Jelmer Vernooij
Fix project forking.
313
            project = self._get_project(project['path_with_namespace'])
314
        return project
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
315
316
    def _get_logged_in_username(self):
317
        return self._current_user['username']
318
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
319
    def _list_merge_requests(self, owner=None, project=None, state=None):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
320
        if project is not None:
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
321
            path = 'projects/%s/merge_requests' % urlutils.quote(str(project), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
322
        else:
323
            path = 'merge_requests'
324
        parameters = {}
325
        if state:
326
            parameters['state'] = state
327
        if owner:
328
            parameters['owner_id'] = urlutils.quote(owner, '')
329
        response = self._api_request(
330
            'GET', path + '?' +
331
            ';'.join(['%s=%s' % item for item in parameters.items()]))
332
        if response.status == 403:
333
            raise errors.PermissionDenied(response.text)
334
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
335
            return json.loads(response.data)
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
336
        raise errors.InvalidHttpResponse(path, response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
337
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
338
    def _update_merge_request(self, project_id, iid, mr):
339
        path = 'projects/%s/merge_requests/%s' % (
340
            urlutils.quote(str(project_id), ''), iid)
341
        response = self._api_request('PUT', path, fields=mr)
342
        if response.status == 200:
343
            return json.loads(response.data)
344
        raise errors.InvalidHttpResponse(path, response.text)
345
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
346
    def _create_mergerequest(
347
            self, title, source_project_id, target_project_id,
7296.10.3 by Jelmer Vernooij
More fixes.
348
            source_branch_name, target_branch_name, description,
349
            labels=None):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
350
        path = 'projects/%s/merge_requests' % source_project_id
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
351
        fields = {
352
            'title': title,
353
            'source_branch': source_branch_name,
354
            'target_branch': target_branch_name,
355
            'target_project_id': target_project_id,
356
            'description': description,
357
            }
358
        if labels:
359
            fields['labels'] = labels
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
360
        response = self._api_request('POST', path, fields=fields)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
361
        if response.status == 403:
362
            raise errors.PermissionDenied(response.text)
363
        if response.status == 409:
364
            raise MergeProposalExists(self.source_branch.user_url)
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
365
        if response.status != 201:
366
            raise errors.InvalidHttpResponse(path, response.text)
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
367
        return json.loads(response.data)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
368
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
369
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
370
        (host, project_name, branch_name) = parse_gitlab_branch_url(branch)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
371
        project = self._get_project(project_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
372
        return gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
373
            project['ssh_url_to_repo'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
374
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
375
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
376
                        owner=None, revision_id=None, overwrite=False,
377
                        allow_lossy=True):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
378
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
379
        if owner is None:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
380
            owner = self._get_logged_in_username()
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
381
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
382
            project = self._get_project(base_project)['path']
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
383
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
384
            target_project = self._get_project('%s/%s' % (owner, project))
385
        except NoSuchProject:
386
            target_project = self._fork_project(base_project)
7296.10.3 by Jelmer Vernooij
More fixes.
387
        remote_repo_url = git_url_to_bzr_url(target_project['ssh_url_to_repo'])
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
388
        remote_dir = controldir.ControlDir.open(remote_repo_url)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
389
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
390
            push_result = remote_dir.push_branch(
391
                local_branch, revision_id=revision_id, overwrite=overwrite,
392
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
393
        except errors.NoRoundtrippingSupport:
394
            if not allow_lossy:
395
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
396
            push_result = remote_dir.push_branch(
397
                local_branch, revision_id=revision_id, overwrite=overwrite,
398
                name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
399
        public_url = gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
400
            target_project['http_url_to_repo'], name)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
401
        return push_result.target_branch, public_url
0.432.4 by Jelmer Vernooij
Some work on gitlab.
402
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
403
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
404
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
405
        if owner is None:
7296.10.3 by Jelmer Vernooij
More fixes.
406
            owner = self._get_logged_in_username()
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
407
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
408
            project = self._get_project(base_project)['path']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
409
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
410
            target_project = self._get_project('%s/%s' % (owner, project))
411
        except NoSuchProject:
412
            raise errors.NotBranchError('%s/%s/%s' % (self.base_url, owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
413
        return _mod_branch.Branch.open(gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
414
            target_project['ssh_url_to_repo'], name))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
415
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
416
    def get_proposer(self, source_branch, target_branch):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
417
        return GitlabMergeProposalBuilder(self, source_branch, target_branch)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
418
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
419
    def iter_proposals(self, source_branch, target_branch, status):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
420
        (source_host, source_project_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
421
            parse_gitlab_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
422
        (target_host, target_project_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
423
            parse_gitlab_branch_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
424
        if source_host != target_host:
425
            raise DifferentGitLabInstances(source_host, target_host)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
426
        source_project = self._get_project(source_project_name)
427
        target_project = self._get_project(target_project_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
428
        state = mp_status_to_status(status)
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
429
        for mr in self._list_merge_requests(
7296.10.3 by Jelmer Vernooij
More fixes.
430
                project=target_project['id'], state=state):
431
            if (mr['source_project_id'] != source_project['id'] or
432
                    mr['source_branch'] != source_branch_name or
433
                    mr['target_project_id'] != target_project['id'] or
434
                    mr['target_branch'] != target_branch_name):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
435
                continue
436
            yield GitLabMergeProposal(self, mr)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
437
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
438
    def hosts(self, branch):
439
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
440
            (host, project, branch_name) = parse_gitlab_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
441
        except NotGitLabUrl:
442
            return False
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
443
        return (self.base_url == ('https://%s' % host))
444
445
    def check(self):
446
        response = self._api_request('GET', 'user')
447
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
448
            self._current_user = json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
449
            return
7296.10.2 by Jelmer Vernooij
More fixes.
450
        if response == 401:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
451
            if json.loads(response.data) == {"message": "401 Unauthorized"}:
7296.10.2 by Jelmer Vernooij
More fixes.
452
                raise GitLabLoginMissing()
453
            else:
454
                raise GitlabLoginError(response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
455
        raise UnsupportedHoster(url)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
456
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
457
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
458
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
459
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
460
            (host, project) = parse_gitlab_url(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
461
        except NotGitLabUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
462
            raise UnsupportedHoster(url)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
463
        transport = get_transport(
464
            'https://%s' % host, possible_transports=possible_transports)
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
465
        credentials = get_credentials_by_url(transport.base)
466
        if credentials is not None:
467
            return cls(transport, credentials.get('private_token'))
468
        raise UnsupportedHoster(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
469
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
470
    @classmethod
471
    def iter_instances(cls):
472
        for name, credentials in iter_tokens():
473
            if 'url' not in credentials:
474
                continue
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
475
            yield cls(
476
                get_transport(credentials['url']),
477
                private_token=credentials.get('private_token'))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
478
0.431.66 by Jelmer Vernooij
Add support for status argument.
479
    def iter_my_proposals(self, status='open'):
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
480
        state = mp_status_to_status(status)
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
481
        for mp in self._list_merge_requests(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
482
                owner=self._get_logged_in_username(), state=state):
483
            yield GitLabMergeProposal(self, mp)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
484
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
485
    def get_proposal_by_url(self, url):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
486
        try:
487
            (host, project, merge_id) = parse_gitlab_merge_request_url(url)
488
        except NotGitLabUrl:
489
            raise UnsupportedHoster(url)
7296.9.4 by Jelmer Vernooij
Fix dealing with non-gitlab sites.
490
        except NotMergeRequestUrl as e:
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
491
            if self.base_url == ('https://%s' % e.host):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
492
                raise
493
            else:
494
                raise UnsupportedHoster(url)
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
495
        if self.base_url != ('https://%s' % host):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
496
            raise UnsupportedHoster(url)
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
497
        project = self._get_project(project)
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
498
        mr = project.mergerequests.get(merge_id)
499
        return GitLabMergeProposal(mr)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
500
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
501
0.432.2 by Jelmer Vernooij
Publish command sort of works.
502
class GitlabMergeProposalBuilder(MergeProposalBuilder):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
503
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
504
    def __init__(self, gl, source_branch, target_branch):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
505
        self.gl = gl
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
506
        self.source_branch = source_branch
507
        (self.source_host, self.source_project_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
508
            parse_gitlab_branch_url(source_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
509
        self.target_branch = target_branch
510
        (self.target_host, self.target_project_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
511
            parse_gitlab_branch_url(target_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
512
        if self.source_host != self.target_host:
513
            raise DifferentGitLabInstances(self.source_host, self.target_host)
514
515
    def get_infotext(self):
516
        """Determine the initial comment for the merge proposal."""
517
        info = []
518
        info.append("Gitlab instance: %s\n" % self.target_host)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
519
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
520
        info.append("Target: %s\n" % self.target_branch.user_url)
521
        return ''.join(info)
522
523
    def get_initial_body(self):
524
        """Get a body for the proposal for the user to modify.
525
526
        :return: a str or None.
527
        """
528
        return None
529
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
530
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
531
                        prerequisite_branch=None, commit_message=None):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
532
        """Perform the submission."""
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
533
        # https://docs.gitlab.com/ee/api/merge_requests.html#create-mr
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
534
        if prerequisite_branch is not None:
535
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
536
        # Note that commit_message is ignored, since Gitlab doesn't support it.
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
537
        source_project = self.gl._get_project(self.source_project_name)
538
        target_project = self.gl._get_project(self.target_project_name)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
539
        # TODO(jelmer): Allow setting title explicitly
540
        title = description.splitlines()[0]
541
        # TODO(jelmer): Allow setting allow_collaboration field
542
        # TODO(jelmer): Allow setting milestone field
543
        # TODO(jelmer): Allow setting squash field
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
544
        kwargs = {
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
545
            'title': title,
7296.10.3 by Jelmer Vernooij
More fixes.
546
            'source_project_id': source_project['id'],
547
            'target_project_id': target_project['id'],
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
548
            'source_branch_name': self.source_branch_name,
549
            'target_branch_name': self.target_branch_name,
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
550
            'description': description}
551
        if labels:
552
            kwargs['labels'] = ','.join(labels)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
553
        if reviewers:
554
            kwargs['assignee_ids'] = []
555
            for reviewer in reviewers:
556
                if '@' in reviewer:
557
                    user = self.gl._get_user_by_email(reviewer)
558
                else:
559
                    user = self.gl._get_user(reviewer)
560
                kwargs['assignee_ids'].append(user['id'])
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
561
        merge_request = self.gl._create_mergerequest(**kwargs)
562
        return GitLabMergeProposal(self.gl, merge_request)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
563
564
565
def register_gitlab_instance(shortname, url):
566
    """Register a gitlab instance.
567
568
    :param shortname: Short name (e.g. "gitlab")
569
    :param url: URL to the gitlab instance
570
    """
571
    from breezy.bugtracker import (
572
        tracker_registry,
573
        ProjectIntegerBugTracker,
574
        )
575
    tracker_registry.register(
576
        shortname, ProjectIntegerBugTracker(
577
            shortname, url + '/{project}/issues/{id}'))