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