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