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