/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
7490.65.1 by Jelmer Vernooij
Add functions fo retrieving merge proposal source revision.
218
    def get_source_revision(self):
219
        from breezy.git.mapping import default_mapping
7490.65.2 by Jelmer Vernooij
Fix bugs, add release note.
220
        sha = self._mr['sha']
221
        if sha is None:
222
            return None
223
        return default_mapping.revision_id_foreign_to_bzr(sha.encode('ascii'))
7490.65.1 by Jelmer Vernooij
Add functions fo retrieving merge proposal source revision.
224
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
225
    def get_target_branch_url(self):
226
        return self._branch_url_from_project(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
227
            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.
228
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
229
    def _get_project_name(self, project_id):
230
        source_project = self.gl._get_project(project_id)
231
        return source_project['path_with_namespace']
232
233
    def get_source_project(self):
234
        return self._get_project_name(self._mr['source_project_id'])
235
236
    def get_target_project(self):
237
        return self._get_project_name(self._mr['target_project_id'])
238
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
239
    def is_merged(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
240
        return (self._mr['state'] == 'merged')
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
241
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
242
    def is_closed(self):
243
        return (self._mr['state'] == 'closed')
244
245
    def reopen(self):
7405.2.1 by Jelmer Vernooij
Fix reopen behaviour for gitlab.
246
        return self._update(state_event='reopen')
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
247
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
248
    def close(self):
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
249
        self._update(state_event='close')
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
250
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
251
    def merge(self, commit_message=None):
252
        # https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
253
        self._mr.merge(merge_commit_message=commit_message)
254
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
255
    def can_be_merged(self):
256
        if self._mr['merge_status'] == 'cannot_be_merged':
257
            return False
258
        elif self._mr['merge_status'] == 'can_be_merged':
259
            return True
7490.30.2 by Jelmer Vernooij
Support cannot_be_merged_rechecked
260
        elif self._mr['merge_status'] in (
261
                'unchecked', 'cannot_be_merged_recheck'):
262
            # See https://gitlab.com/gitlab-org/gitlab/-/commit/7517105303c for
263
            # an explanation of the distinction between unchecked and
264
            # cannot_be_merged_recheck
7490.30.1 by Jelmer Vernooij
Support the 'unchecked' value for can_be_merged.
265
            return None
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
266
        else:
267
            raise ValueError(self._mr['merge_status'])
268
7414.4.1 by Jelmer Vernooij
Add a MergeProposal.get_merged_by method.
269
    def get_merged_by(self):
7414.4.2 by Jelmer Vernooij
Fix gitlab / github merged_by fetching.
270
        user = self._mr.get('merged_by')
271
        if user is None:
272
            return None
273
        return user['username']
7414.4.1 by Jelmer Vernooij
Add a MergeProposal.get_merged_by method.
274
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
275
    def get_merged_at(self):
276
        merged_at = self._mr.get('merged_at')
277
        if merged_at is None:
278
            return None
7414.4.4 by Jelmer Vernooij
Use iso8601 module.
279
        import iso8601
280
        return iso8601.parse_date(merged_at)
7414.4.3 by Jelmer Vernooij
Add MergeProposal.get_merged_at.
281
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
282
    def post_comment(self, body):
283
        kwargs = {'body': body}
284
        self.gl._post_merge_request_note(
285
            self._mr['project_id'], self._mr['iid'], kwargs)
286
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
287
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
288
def gitlab_url_to_bzr_url(url, name):
7408.2.1 by Jelmer Vernooij
Use standard functions for creating Git URLs.
289
    return git_url_to_bzr_url(url, branch=name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
290
291
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
292
class GitLab(Hoster):
293
    """GitLab hoster implementation."""
294
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
295
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
296
    supports_merge_proposal_commit_message = False
7490.3.9 by Jelmer Vernooij
Add supports_allow_collaboration flag.
297
    supports_allow_collaboration = True
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
298
    merge_proposal_description_format = 'markdown'
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
299
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
300
    def __repr__(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
301
        return "<GitLab(%r)>" % self.base_url
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
302
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
303
    @property
304
    def base_url(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
305
        return self.transport.base
306
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
307
    @property
308
    def base_hostname(self):
309
        return urlutils.parse_url(self.base_url)[3]
310
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
311
    def _api_request(self, method, path, fields=None, body=None):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
312
        return self.transport.request(
313
            method, urlutils.join(self.base_url, 'api', 'v4', path),
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
314
            headers=self.headers, fields=fields, body=body)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
315
316
    def __init__(self, transport, private_token):
317
        self.transport = transport
318
        self.headers = {"Private-Token": private_token}
319
        self.check()
320
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
321
    def _get_user(self, username):
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
322
        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.
323
        response = self._api_request('GET', path)
324
        if response.status == 404:
325
            raise KeyError('no such user %s' % username)
326
        if response.status == 200:
327
            return json.loads(response.data)
328
        raise errors.InvalidHttpResponse(path, response.text)
329
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
330
    def _get_user_by_email(self, email):
331
        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.
332
        response = self._api_request('GET', path)
333
        if response.status == 404:
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
334
            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.
335
        if response.status == 200:
336
            ret = json.loads(response.data)
337
            if len(ret) != 1:
338
                raise ValueError('unexpected number of results; %r' % ret)
339
            return ret[0]
340
        raise errors.InvalidHttpResponse(path, response.text)
341
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
342
    def _get_project(self, project_name):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
343
        path = 'projects/%s' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
344
        response = self._api_request('GET', path)
345
        if response.status == 404:
346
            raise NoSuchProject(project_name)
347
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
348
            return json.loads(response.data)
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
349
        raise errors.InvalidHttpResponse(path, response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
350
7380.1.2 by Jelmer Vernooij
Review comments.
351
    def _fork_project(self, project_name, timeout=50, interval=5):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
352
        path = 'projects/%s/fork' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
353
        response = self._api_request('POST', path)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
354
        if response.status not in (200, 201):
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
355
            raise errors.InvalidHttpResponse(path, response.text)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
356
        # The response should be valid JSON, but let's ignore it
7397.1.1 by Jelmer Vernooij
Fix project forking.
357
        project = json.loads(response.data)
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
358
        # Spin and wait until import_status for new project
359
        # is complete.
7380.1.2 by Jelmer Vernooij
Review comments.
360
        deadline = time.time() + timeout
7397.1.1 by Jelmer Vernooij
Fix project forking.
361
        while project['import_status'] not in ('finished', 'none'):
7380.1.2 by Jelmer Vernooij
Review comments.
362
            mutter('import status is %s', project['import_status'])
363
            if time.time() > deadline:
364
                raise Exception('timeout waiting for project to become available')
365
            time.sleep(interval)
7397.1.1 by Jelmer Vernooij
Fix project forking.
366
            project = self._get_project(project['path_with_namespace'])
367
        return project
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
368
369
    def _get_logged_in_username(self):
370
        return self._current_user['username']
371
7408.1.1 by Jelmer Vernooij
Use paging to iterate over all gitlab pull requests.
372
    def _list_paged(self, path, parameters=None, per_page=None):
373
        if parameters is None:
374
            parameters = {}
375
        else:
376
            parameters = dict(parameters.items())
377
        if per_page:
7408.1.3 by Jelmer Vernooij
Support pagination for github.
378
            parameters['per_page'] = str(per_page)
7408.1.1 by Jelmer Vernooij
Use paging to iterate over all gitlab pull requests.
379
        page = "1"
380
        while page:
381
            parameters['page'] = page
382
            response = self._api_request(
383
                'GET', path + '?' +
384
                ';'.join(['%s=%s' % item for item in parameters.items()]))
385
            if response.status == 403:
386
                raise errors.PermissionDenied(response.text)
387
            if response.status != 200:
388
                raise errors.InvalidHttpResponse(path, response.text)
389
            page = response.getheader("X-Next-Page")
390
            for entry in json.loads(response.data):
391
                yield entry
392
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
393
    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.
394
        if project is not None:
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
395
            path = 'projects/%s/merge_requests' % urlutils.quote(str(project), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
396
        else:
397
            path = 'merge_requests'
398
        parameters = {}
399
        if state:
400
            parameters['state'] = state
401
        if owner:
402
            parameters['owner_id'] = urlutils.quote(owner, '')
7408.1.2 by Jelmer Vernooij
Set default page size to 50.
403
        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.
404
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
405
    def _get_merge_request(self, project, merge_id):
406
        path = 'projects/%s/merge_requests/%d' % (urlutils.quote(str(project), ''), merge_id)
407
        response = self._api_request('GET', path)
408
        if response.status == 403:
409
            raise errors.PermissionDenied(response.text)
410
        if response.status != 200:
411
            raise errors.InvalidHttpResponse(path, response.text)
412
        return json.loads(response.data)
413
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
414
    def _list_projects(self, owner):
415
        path = 'users/%s/projects' % urlutils.quote(str(owner), '')
416
        parameters = {}
417
        return self._list_paged(path, parameters, per_page=DEFAULT_PAGE_SIZE)
418
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
419
    def _update_merge_request(self, project_id, iid, mr):
420
        path = 'projects/%s/merge_requests/%s' % (
421
            urlutils.quote(str(project_id), ''), iid)
422
        response = self._api_request('PUT', path, fields=mr)
423
        if response.status == 200:
424
            return json.loads(response.data)
425
        raise errors.InvalidHttpResponse(path, response.text)
426
7490.52.1 by Jelmer Vernooij
Add MergeProposal.post_comment.
427
    def _post_merge_request_note(self, project_id, iid, kwargs):
428
        path = 'projects/%s/merge_requests/%s/notes' % (
429
            urlutils.quote(str(project_id), ''), iid)
430
        response = self._api_request('POST', path, fields=kwargs)
431
        if response.status == 201:
432
            json.loads(response.data)
433
            return
434
        raise errors.InvalidHttpResponse(path, response.text)
435
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
436
    def _create_mergerequest(
437
            self, title, source_project_id, target_project_id,
7296.10.3 by Jelmer Vernooij
More fixes.
438
            source_branch_name, target_branch_name, description,
7490.14.1 by Jelmer Vernooij
Various git fixes.
439
            labels=None, allow_collaboration=False):
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
440
        path = 'projects/%s/merge_requests' % source_project_id
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
441
        fields = {
442
            'title': title,
443
            'source_branch': source_branch_name,
444
            'target_branch': target_branch_name,
445
            'target_project_id': target_project_id,
446
            'description': description,
7490.14.1 by Jelmer Vernooij
Various git fixes.
447
            'allow_collaboration': allow_collaboration,
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
448
            }
449
        if labels:
450
            fields['labels'] = labels
7380.1.1 by Jelmer Vernooij
Several more fixes for git merge proposals.
451
        response = self._api_request('POST', path, fields=fields)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
452
        if response.status == 403:
453
            raise errors.PermissionDenied(response.text)
454
        if response.status == 409:
7490.10.1 by Jelmer Vernooij
Fix handling of 409s for gitlab.
455
            raise MergeRequestExists()
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
456
        if response.status != 201:
457
            raise errors.InvalidHttpResponse(path, response.text)
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
458
        return json.loads(response.data)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
459
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
460
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
461
        (host, project_name, branch_name) = parse_gitlab_branch_url(branch)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
462
        project = self._get_project(project_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
463
        return gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
464
            project['ssh_url_to_repo'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
465
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
466
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
467
                        owner=None, revision_id=None, overwrite=False,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
468
                        allow_lossy=True, tag_selector=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
469
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
470
        if owner is None:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
471
            owner = self._get_logged_in_username()
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
472
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
473
            project = self._get_project(base_project)['path']
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
474
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
475
            target_project = self._get_project('%s/%s' % (owner, project))
476
        except NoSuchProject:
477
            target_project = self._fork_project(base_project)
7296.10.3 by Jelmer Vernooij
More fixes.
478
        remote_repo_url = git_url_to_bzr_url(target_project['ssh_url_to_repo'])
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
479
        remote_dir = controldir.ControlDir.open(remote_repo_url)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
480
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
481
            push_result = remote_dir.push_branch(
482
                local_branch, revision_id=revision_id, overwrite=overwrite,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
483
                name=name, tag_selector=tag_selector)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
484
        except errors.NoRoundtrippingSupport:
485
            if not allow_lossy:
486
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
487
            push_result = remote_dir.push_branch(
488
                local_branch, revision_id=revision_id, overwrite=overwrite,
7489.4.2 by Jelmer Vernooij
Plumb through tag_selector.
489
                name=name, lossy=True, tag_selector=tag_selector)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
490
        public_url = gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
491
            target_project['http_url_to_repo'], name)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
492
        return push_result.target_branch, public_url
0.432.4 by Jelmer Vernooij
Some work on gitlab.
493
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
494
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
495
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
496
        if owner is None:
7296.10.3 by Jelmer Vernooij
More fixes.
497
            owner = self._get_logged_in_username()
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
498
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
499
            project = self._get_project(base_project)['path']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
500
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
501
            target_project = self._get_project('%s/%s' % (owner, project))
502
        except NoSuchProject:
503
            raise errors.NotBranchError('%s/%s/%s' % (self.base_url, owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
504
        return _mod_branch.Branch.open(gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
505
            target_project['ssh_url_to_repo'], name))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
506
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
507
    def get_proposer(self, source_branch, target_branch):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
508
        return GitlabMergeProposalBuilder(self, source_branch, target_branch)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
509
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
510
    def iter_proposals(self, source_branch, target_branch, status):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
511
        (source_host, source_project_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
512
            parse_gitlab_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
513
        (target_host, target_project_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
514
            parse_gitlab_branch_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
515
        if source_host != target_host:
516
            raise DifferentGitLabInstances(source_host, target_host)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
517
        source_project = self._get_project(source_project_name)
518
        target_project = self._get_project(target_project_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
519
        state = mp_status_to_status(status)
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
520
        for mr in self._list_merge_requests(
7296.10.3 by Jelmer Vernooij
More fixes.
521
                project=target_project['id'], state=state):
522
            if (mr['source_project_id'] != source_project['id'] or
523
                    mr['source_branch'] != source_branch_name or
524
                    mr['target_project_id'] != target_project['id'] or
525
                    mr['target_branch'] != target_branch_name):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
526
                continue
527
            yield GitLabMergeProposal(self, mr)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
528
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
529
    def hosts(self, branch):
530
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
531
            (host, project, branch_name) = parse_gitlab_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
532
        except NotGitLabUrl:
533
            return False
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
534
        return self.base_hostname == host
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
535
536
    def check(self):
537
        response = self._api_request('GET', 'user')
538
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
539
            self._current_user = json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
540
            return
7296.10.2 by Jelmer Vernooij
More fixes.
541
        if response == 401:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
542
            if json.loads(response.data) == {"message": "401 Unauthorized"}:
7296.10.2 by Jelmer Vernooij
More fixes.
543
                raise GitLabLoginMissing()
544
            else:
545
                raise GitlabLoginError(response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
546
        raise UnsupportedHoster(url)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
547
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
548
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
549
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
550
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
551
            (host, project) = parse_gitlab_url(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
552
        except NotGitLabUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
553
            raise UnsupportedHoster(url)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
554
        transport = get_transport(
555
            'https://%s' % host, possible_transports=possible_transports)
7359.1.2 by Jelmer Vernooij
Some fixes for gitlab API.
556
        credentials = get_credentials_by_url(transport.base)
557
        if credentials is not None:
558
            return cls(transport, credentials.get('private_token'))
559
        raise UnsupportedHoster(url)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
560
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
561
    @classmethod
562
    def iter_instances(cls):
563
        for name, credentials in iter_tokens():
564
            if 'url' not in credentials:
565
                continue
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
566
            yield cls(
567
                get_transport(credentials['url']),
568
                private_token=credentials.get('private_token'))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
569
0.431.66 by Jelmer Vernooij
Add support for status argument.
570
    def iter_my_proposals(self, status='open'):
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
571
        state = mp_status_to_status(status)
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
572
        for mp in self._list_merge_requests(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
573
                owner=self._get_logged_in_username(), state=state):
574
            yield GitLabMergeProposal(self, mp)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
575
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
576
    def iter_my_forks(self):
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
577
        for project in self._list_projects(owner=self._get_logged_in_username()):
578
            base_project = project.get('forked_from_project')
7414.5.2 by Jelmer Vernooij
Change iter_my_projects to iter_my_forks.
579
            if not base_project:
580
                continue
581
            yield project['path_with_namespace']
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
582
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
583
    def get_proposal_by_url(self, url):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
584
        try:
585
            (host, project, merge_id) = parse_gitlab_merge_request_url(url)
586
        except NotGitLabUrl:
587
            raise UnsupportedHoster(url)
7296.9.4 by Jelmer Vernooij
Fix dealing with non-gitlab sites.
588
        except NotMergeRequestUrl as e:
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
589
            if self.base_hostname == e.host:
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
590
                raise
591
            else:
592
                raise UnsupportedHoster(url)
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
593
        if self.base_hostname != host:
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
594
            raise UnsupportedHoster(url)
7360.1.4 by Jelmer Vernooij
Fix retrieval of proposals from gitlab.
595
        project = self._get_project(project)
7490.23.2 by Jelmer Vernooij
More gitlab fixes.
596
        mr = self._get_merge_request(project['path_with_namespace'], merge_id)
597
        return GitLabMergeProposal(self, mr)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
598
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
599
    def delete_project(self, project):
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
600
        path = 'projects/%s' % urlutils.quote(str(project), '')
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
601
        response = self._api_request('DELETE', path)
602
        if response.status == 404:
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
603
            raise NoSuchProject(project)
604
        if response.status != 202:
7414.5.1 by Jelmer Vernooij
Add functions for managing projects.
605
            raise errors.InvalidHttpResponse(path, response.text)
606
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
607
0.432.2 by Jelmer Vernooij
Publish command sort of works.
608
class GitlabMergeProposalBuilder(MergeProposalBuilder):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
609
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
610
    def __init__(self, gl, source_branch, target_branch):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
611
        self.gl = gl
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
612
        self.source_branch = source_branch
613
        (self.source_host, self.source_project_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
614
            parse_gitlab_branch_url(source_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
615
        self.target_branch = target_branch
616
        (self.target_host, self.target_project_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
617
            parse_gitlab_branch_url(target_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
618
        if self.source_host != self.target_host:
619
            raise DifferentGitLabInstances(self.source_host, self.target_host)
620
621
    def get_infotext(self):
622
        """Determine the initial comment for the merge proposal."""
623
        info = []
624
        info.append("Gitlab instance: %s\n" % self.target_host)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
625
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
626
        info.append("Target: %s\n" % self.target_branch.user_url)
627
        return ''.join(info)
628
629
    def get_initial_body(self):
630
        """Get a body for the proposal for the user to modify.
631
632
        :return: a str or None.
633
        """
634
        return None
635
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
636
    def create_proposal(self, description, reviewers=None, labels=None,
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
637
                        prerequisite_branch=None, commit_message=None,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
638
                        work_in_progress=False, allow_collaboration=False):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
639
        """Perform the submission."""
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
640
        # https://docs.gitlab.com/ee/api/merge_requests.html#create-mr
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
641
        if prerequisite_branch is not None:
642
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
643
        # 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.
644
        source_project = self.gl._get_project(self.source_project_name)
645
        target_project = self.gl._get_project(self.target_project_name)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
646
        # TODO(jelmer): Allow setting title explicitly
7445.1.1 by Jelmer Vernooij
Add Hoster.merge_proposal_description_format and common function for determining title.
647
        title = determine_title(description)
7467.3.1 by Jelmer Vernooij
Add a work_in_progress flag.
648
        if work_in_progress:
649
            title = 'WIP: %s' % title
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
650
        # TODO(jelmer): Allow setting milestone field
651
        # TODO(jelmer): Allow setting squash field
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
652
        kwargs = {
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
653
            'title': title,
7296.10.3 by Jelmer Vernooij
More fixes.
654
            'source_project_id': source_project['id'],
655
            'target_project_id': target_project['id'],
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
656
            'source_branch_name': self.source_branch_name,
657
            'target_branch_name': self.target_branch_name,
7490.6.1 by Jelmer Vernooij
Add allow-collaboration flag.
658
            'description': description,
659
            'allow_collaboration': allow_collaboration}
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
660
        if labels:
661
            kwargs['labels'] = ','.join(labels)
7381.5.1 by Jelmer Vernooij
Several more fixes for merge proposals. Add functions for reopening merge proposals.
662
        if reviewers:
663
            kwargs['assignee_ids'] = []
664
            for reviewer in reviewers:
665
                if '@' in reviewer:
666
                    user = self.gl._get_user_by_email(reviewer)
667
                else:
668
                    user = self.gl._get_user(reviewer)
669
                kwargs['assignee_ids'].append(user['id'])
7490.10.1 by Jelmer Vernooij
Fix handling of 409s for gitlab.
670
        try:
671
            merge_request = self.gl._create_mergerequest(**kwargs)
672
        except MergeRequestExists:
7490.25.1 by Jelmer Vernooij
Fix raising of ProposalExists.
673
            raise MergeProposalExists(self.source_branch.user_url)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
674
        return GitLabMergeProposal(self.gl, merge_request)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
675
676
677
def register_gitlab_instance(shortname, url):
678
    """Register a gitlab instance.
679
680
    :param shortname: Short name (e.g. "gitlab")
681
    :param url: URL to the gitlab instance
682
    """
683
    from breezy.bugtracker import (
684
        tracker_registry,
685
        ProjectIntegerBugTracker,
686
        )
687
    tracker_registry.register(
688
        shortname, ProjectIntegerBugTracker(
689
            shortname, url + '/{project}/issues/{id}'))