/brz/remove-bazaar

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