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