/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
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
21
from ... import (
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
22
    branch as _mod_branch,
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
23
    controldir,
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
24
    errors,
25
    urlutils,
26
    )
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
27
from ...git.urls import git_url_to_bzr_url
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
28
from ...sixish import PY3
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
29
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
30
from .propose import (
0.432.2 by Jelmer Vernooij
Publish command sort of works.
31
    Hoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
32
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
33
    MergeProposalBuilder,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
34
    MergeProposalExists,
0.431.38 by Jelmer Vernooij
Add NoSuchProject.
35
    NoSuchProject,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
36
    PrerequisiteBranchUnsupported,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
37
    UnsupportedHoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
38
    )
39
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
40
def mp_status_to_status(status):
41
    return {
42
        'all': 'all',
43
        'open': 'opened',
44
        'merged': 'merged',
45
        'closed': 'closed'}[status]
46
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
47
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
48
class NotGitLabUrl(errors.BzrError):
49
50
    _fmt = "Not a GitLab URL: %(url)s"
51
52
    def __init__(self, url):
53
        errors.BzrError.__init__(self)
54
        self.url = url
55
56
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
57
class DifferentGitLabInstances(errors.BzrError):
58
59
    _fmt = ("Can't create merge proposals across GitLab instances: "
60
            "%(source_host)s and %(target_host)s")
61
62
    def __init__(self, source_host, target_host):
63
        self.source_host = source_host
64
        self.target_host = target_host
65
66
0.432.10 by Jelmer Vernooij
More test fixes.
67
class GitLabLoginMissing(errors.BzrError):
68
69
    _fmt = ("Please log into GitLab")
70
71
0.431.59 by Jelmer Vernooij
Add gitlab-login command.
72
def default_config_path():
73
    from breezy.config import config_dir
74
    import os
75
    return os.path.join(config_dir(), 'gitlab.conf')
76
77
78
def store_gitlab_token(name, url, private_token):
79
    """Store a GitLab token in a configuration file."""
80
    import configparser
81
    config = configparser.ConfigParser()
82
    path = default_config_path()
83
    config.read([path])
84
    config.add_section(name)
85
    config[name]['url'] = url
86
    config[name]['private_token'] = private_token
87
    with open(path, 'w') as f:
88
        config.write(f)
89
90
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
91
def iter_tokens():
92
    import configparser
93
    from gitlab.config import _DEFAULT_FILES
94
    config = configparser.ConfigParser()
95
    config.read(_DEFAULT_FILES + [default_config_path()])
96
    for name, section in config.items():
97
        yield name, section
98
99
0.432.10 by Jelmer Vernooij
More test fixes.
100
def connect_gitlab(host):
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
101
    from gitlab import Gitlab, GitlabGetError
0.432.10 by Jelmer Vernooij
More test fixes.
102
    url = 'https://%s' % host
7268.3.1 by Jelmer Vernooij
Drop support for getting gitlab authentication from authentication.conf.
103
    for name, section in iter_tokens():
104
        if section.get('url') == url:
105
            return Gitlab(**section)
0.432.10 by Jelmer Vernooij
More test fixes.
106
    else:
7268.3.1 by Jelmer Vernooij
Drop support for getting gitlab authentication from authentication.conf.
107
        try:
108
            return Gitlab(url)
109
        except GitlabGetError:
110
            raise GitLabLoginMissing()
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
111
112
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
113
def parse_gitlab_url(url):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
114
    (scheme, user, password, host, port, path) = urlutils.parse_url(
115
        url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
116
    if scheme not in ('git+ssh', 'https', 'http'):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
117
        raise NotGitLabUrl(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
118
    if not host:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
119
        raise NotGitLabUrl(url)
0.432.10 by Jelmer Vernooij
More test fixes.
120
    path = path.strip('/')
0.432.11 by Jelmer Vernooij
Fix some tests.
121
    if path.endswith('.git'):
122
        path = path[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
123
    return host, path
124
125
126
def parse_gitlab_branch_url(branch):
127
    url = urlutils.split_segment_parameters(branch.user_url)[0]
128
    host, path = parse_gitlab_url(url)
0.432.10 by Jelmer Vernooij
More test fixes.
129
    return host, path, branch.name
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
130
131
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
132
class GitLabMergeProposal(MergeProposal):
133
134
    def __init__(self, mr):
135
        self._mr = mr
136
137
    @property
138
    def url(self):
139
        return self._mr.web_url
140
141
    def get_description(self):
142
        return self._mr.description
143
144
    def set_description(self, description):
145
        self._mr.description = description
7265.1.1 by Jelmer Vernooij
Fix setting of merge proposal descriptions on gitlab.
146
        self._mr.save()
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
147
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
148
    def get_commit_message(self):
149
        return None
150
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
151
    def _branch_url_from_project(self, project_id, branch_name):
152
        project = self._mr.manager.gitlab.projects.get(project_id)
153
        return gitlab_url_to_bzr_url(project.http_url_to_repo, branch_name)
154
155
    def get_source_branch_url(self):
156
        return self._branch_url_from_project(
157
            self._mr.source_project_id, self._mr.source_branch)
158
159
    def get_target_branch_url(self):
160
        return self._branch_url_from_project(
161
            self._mr.target_project_id, self._mr.target_branch)
162
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
163
    def is_merged(self):
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
164
        return (self._mr.state == 'merged')
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
165
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
166
    def close(self):
167
        self._mr.state_event = 'close'
168
        self._mr.save()
169
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
170
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
171
def gitlab_url_to_bzr_url(url, name):
172
    if not PY3:
173
        name = name.encode('utf-8')
174
    return urlutils.join_segment_parameters(
7211.13.7 by Jelmer Vernooij
Fix formatting.
175
        git_url_to_bzr_url(url), {"branch": name})
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
176
177
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
178
class GitLab(Hoster):
179
    """GitLab hoster implementation."""
180
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
181
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
182
    supports_merge_proposal_commit_message = False
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
183
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
184
    def __repr__(self):
185
        return "<GitLab(%r)>" % self.gl.url
186
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
187
    @property
188
    def base_url(self):
189
        return self.gl.url
190
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
191
    def __init__(self, gl):
192
        self.gl = gl
193
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
194
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
195
        (host, project_name, branch_name) = parse_gitlab_branch_url(branch)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
196
        project = self.gl.projects.get(project_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
197
        return gitlab_url_to_bzr_url(
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
198
            project.ssh_url_to_repo, branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
199
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
200
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
201
                        owner=None, revision_id=None, overwrite=False,
202
                        allow_lossy=True):
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
203
        import gitlab
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
204
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.432.10 by Jelmer Vernooij
More test fixes.
205
        self.gl.auth()
0.431.38 by Jelmer Vernooij
Add NoSuchProject.
206
        try:
207
            base_project = self.gl.projects.get(base_project)
208
        except gitlab.GitlabGetError as e:
209
            if e.response_code == 404:
210
                raise NoSuchProject(base_project)
211
            else:
212
                raise
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
213
        if owner is None:
0.432.10 by Jelmer Vernooij
More test fixes.
214
            owner = self.gl.user.username
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
215
        if project is None:
0.431.30 by Jelmer Vernooij
s/name/path.
216
            project = base_project.path
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
217
        try:
0.432.10 by Jelmer Vernooij
More test fixes.
218
            target_project = self.gl.projects.get('%s/%s' % (owner, project))
0.431.30 by Jelmer Vernooij
s/name/path.
219
        except gitlab.GitlabGetError as e:
220
            if e.response_code == 404:
221
                target_project = base_project.forks.create({})
222
            else:
223
                raise
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
224
        remote_repo_url = git_url_to_bzr_url(target_project.ssh_url_to_repo)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
225
        remote_dir = controldir.ControlDir.open(remote_repo_url)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
226
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
227
            push_result = remote_dir.push_branch(
228
                local_branch, revision_id=revision_id, overwrite=overwrite,
229
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
230
        except errors.NoRoundtrippingSupport:
231
            if not allow_lossy:
232
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
233
            push_result = remote_dir.push_branch(
234
                local_branch, revision_id=revision_id, overwrite=overwrite,
235
                name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
236
        public_url = gitlab_url_to_bzr_url(
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
237
            target_project.http_url_to_repo, name)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
238
        return push_result.target_branch, public_url
0.432.4 by Jelmer Vernooij
Some work on gitlab.
239
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
240
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
241
        import gitlab
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
242
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
243
        self.gl.auth()
0.431.38 by Jelmer Vernooij
Add NoSuchProject.
244
        try:
245
            base_project = self.gl.projects.get(base_project)
246
        except gitlab.GitlabGetError as e:
247
            if e.response_code == 404:
248
                raise NoSuchProject(base_project)
249
            else:
250
                raise
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
251
        if owner is None:
252
            owner = self.gl.user.username
253
        if project is None:
0.431.30 by Jelmer Vernooij
s/name/path.
254
            project = base_project.path
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
255
        try:
256
            target_project = self.gl.projects.get('%s/%s' % (owner, project))
257
        except gitlab.GitlabGetError as e:
258
            if e.response_code == 404:
259
                raise errors.NotBranchError('%s/%s/%s' % (self.gl.url, owner, project))
260
            raise
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
261
        return _mod_branch.Branch.open(gitlab_url_to_bzr_url(
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
262
            target_project.ssh_url_to_repo, name))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
263
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
264
    def get_proposer(self, source_branch, target_branch):
265
        return GitlabMergeProposalBuilder(self.gl, source_branch, target_branch)
266
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
267
    def iter_proposals(self, source_branch, target_branch, status):
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
268
        import gitlab
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
269
        (source_host, source_project_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
270
            parse_gitlab_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
271
        (target_host, target_project_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
272
            parse_gitlab_branch_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
273
        if source_host != target_host:
274
            raise DifferentGitLabInstances(source_host, target_host)
275
        self.gl.auth()
276
        source_project = self.gl.projects.get(source_project_name)
277
        target_project = self.gl.projects.get(target_project_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
278
        state = mp_status_to_status(status)
0.431.43 by Jelmer Vernooij
Handle 403s during proposal listing.
279
        try:
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
280
            for mr in target_project.mergerequests.list(state=state):
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
281
                if (mr.source_project_id != source_project.id or
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
282
                        mr.source_branch != source_branch_name or
283
                        mr.target_project_id != target_project.id or
284
                        mr.target_branch != target_branch_name):
0.431.43 by Jelmer Vernooij
Handle 403s during proposal listing.
285
                    continue
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
286
                yield GitLabMergeProposal(mr)
0.431.43 by Jelmer Vernooij
Handle 403s during proposal listing.
287
        except gitlab.GitlabListError as e:
288
            if e.response_code == 403:
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
289
                raise errors.PermissionDenied(e.error_message)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
290
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
291
    def hosts(self, branch):
292
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
293
            (host, project, branch_name) = parse_gitlab_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
294
        except NotGitLabUrl:
295
            return False
296
        return (self.gl.url == ('https://%s' % host))
297
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
298
    @classmethod
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
299
    def probe_from_url(cls, url):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
300
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
301
            (host, project) = parse_gitlab_url(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
302
        except NotGitLabUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
303
            raise UnsupportedHoster(url)
0.432.4 by Jelmer Vernooij
Some work on gitlab.
304
        import gitlab
0.431.43 by Jelmer Vernooij
Handle 403s during proposal listing.
305
        import requests.exceptions
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
306
        try:
0.432.4 by Jelmer Vernooij
Some work on gitlab.
307
            gl = connect_gitlab(host)
0.431.10 by Jelmer Vernooij
Various other fixes.
308
            gl.auth()
0.431.43 by Jelmer Vernooij
Handle 403s during proposal listing.
309
        except requests.exceptions.SSLError:
7211.13.7 by Jelmer Vernooij
Fix formatting.
310
            # Well, I guess it could be..
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
311
            raise UnsupportedHoster(url)
0.432.4 by Jelmer Vernooij
Some work on gitlab.
312
        except gitlab.GitlabGetError:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
313
            raise UnsupportedHoster(url)
0.431.10 by Jelmer Vernooij
Various other fixes.
314
        except gitlab.GitlabHttpError as e:
0.431.27 by Jelmer Vernooij
Catch 503 errors.
315
            if e.response_code in (404, 405, 503):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
316
                raise UnsupportedHoster(url)
0.431.10 by Jelmer Vernooij
Various other fixes.
317
            else:
318
                raise
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
319
        return cls(gl)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
320
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
321
    @classmethod
322
    def iter_instances(cls):
323
        from gitlab import Gitlab
324
        for name, credentials in iter_tokens():
325
            if 'url' not in credentials:
326
                continue
327
            gl = Gitlab(**credentials)
328
            yield cls(gl)
329
0.431.66 by Jelmer Vernooij
Add support for status argument.
330
    def iter_my_proposals(self, status='open'):
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
331
        state = mp_status_to_status(status)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
332
        self.gl.auth()
0.431.66 by Jelmer Vernooij
Add support for status argument.
333
        for mp in self.gl.mergerequests.list(
334
                owner=self.gl.user.username, state=state):
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
335
            yield GitLabMergeProposal(mp)
336
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
337
0.432.2 by Jelmer Vernooij
Publish command sort of works.
338
class GitlabMergeProposalBuilder(MergeProposalBuilder):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
339
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
340
    def __init__(self, gl, source_branch, target_branch):
341
        self.gl = gl
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
342
        self.source_branch = source_branch
343
        (self.source_host, self.source_project_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
344
            parse_gitlab_branch_url(source_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
345
        self.target_branch = target_branch
346
        (self.target_host, self.target_project_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
347
            parse_gitlab_branch_url(target_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
348
        if self.source_host != self.target_host:
349
            raise DifferentGitLabInstances(self.source_host, self.target_host)
350
351
    def get_infotext(self):
352
        """Determine the initial comment for the merge proposal."""
353
        info = []
354
        info.append("Gitlab instance: %s\n" % self.target_host)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
355
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
356
        info.append("Target: %s\n" % self.target_branch.user_url)
357
        return ''.join(info)
358
359
    def get_initial_body(self):
360
        """Get a body for the proposal for the user to modify.
361
362
        :return: a str or None.
363
        """
364
        return None
365
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
366
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
367
                        prerequisite_branch=None, commit_message=None):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
368
        """Perform the submission."""
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
369
        # https://docs.gitlab.com/ee/api/merge_requests.html#create-mr
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
370
        if prerequisite_branch is not None:
371
            raise PrerequisiteBranchUnsupported(self)
0.431.16 by Jelmer Vernooij
gitlab: Report when a merge proposal already exists.
372
        import gitlab
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
373
        # Note that commit_message is ignored, since Gitlab doesn't support it.
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
374
        # TODO(jelmer): Support reviewers
0.432.10 by Jelmer Vernooij
More test fixes.
375
        self.gl.auth()
376
        source_project = self.gl.projects.get(self.source_project_name)
377
        target_project = self.gl.projects.get(self.target_project_name)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
378
        # TODO(jelmer): Allow setting title explicitly
379
        title = description.splitlines()[0]
380
        # TODO(jelmer): Allow setting allow_collaboration field
381
        # TODO(jelmer): Allow setting milestone field
382
        # TODO(jelmer): Allow setting squash field
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
383
        kwargs = {
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
384
            'title': title,
385
            'target_project_id': target_project.id,
386
            'source_branch': self.source_branch_name,
387
            'target_branch': self.target_branch_name,
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
388
            'description': description}
389
        if labels:
390
            kwargs['labels'] = ','.join(labels)
0.431.16 by Jelmer Vernooij
gitlab: Report when a merge proposal already exists.
391
        try:
392
            merge_request = source_project.mergerequests.create(kwargs)
393
        except gitlab.GitlabCreateError as e:
0.431.34 by Jelmer Vernooij
Cope with gitlab 403.
394
            if e.response_code == 403:
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
395
                raise errors.PermissionDenied(e.error_message)
0.431.16 by Jelmer Vernooij
gitlab: Report when a merge proposal already exists.
396
            if e.response_code == 409:
397
                raise MergeProposalExists(self.source_branch.user_url)
398
            raise
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
399
        return GitLabMergeProposal(merge_request)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
400
401
402
def register_gitlab_instance(shortname, url):
403
    """Register a gitlab instance.
404
405
    :param shortname: Short name (e.g. "gitlab")
406
    :param url: URL to the gitlab instance
407
    """
408
    from breezy.bugtracker import (
409
        tracker_registry,
410
        ProjectIntegerBugTracker,
411
        )
412
    tracker_registry.register(
413
        shortname, ProjectIntegerBugTracker(
414
            shortname, url + '/{project}/issues/{id}'))