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