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