/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
23
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
24
from ... import (
7340.1.1 by Martin
Fix use of config_dir in propose plugin
25
    bedding,
0.431.33 by Jelmer Vernooij
Fix URLs from gitlab.
26
    branch as _mod_branch,
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
27
    controldir,
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
28
    errors,
29
    urlutils,
30
    )
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
31
from ...git.urls import git_url_to_bzr_url
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
32
from ...sixish import PY3
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
33
from ...transport import get_transport
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
34
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
35
from .propose import (
0.432.2 by Jelmer Vernooij
Publish command sort of works.
36
    Hoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
37
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
38
    MergeProposalBuilder,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
39
    MergeProposalExists,
0.431.38 by Jelmer Vernooij
Add NoSuchProject.
40
    NoSuchProject,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
41
    PrerequisiteBranchUnsupported,
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
42
    UnsupportedHoster,
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
43
    )
44
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
45
46
_DEFAULT_FILES = ['/etc/python-gitlab.cfg', '~/.python-gitlab.cfg']
47
48
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
49
def mp_status_to_status(status):
50
    return {
51
        'all': 'all',
52
        'open': 'opened',
53
        'merged': 'merged',
54
        'closed': 'closed'}[status]
55
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
56
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
57
class NotGitLabUrl(errors.BzrError):
58
59
    _fmt = "Not a GitLab URL: %(url)s"
60
61
    def __init__(self, url):
62
        errors.BzrError.__init__(self)
63
        self.url = url
64
65
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
66
class NotMergeRequestUrl(errors.BzrError):
67
68
    _fmt = "Not a merge proposal URL: %(url)s"
69
70
    def __init__(self, host, url):
71
        errors.BzrError.__init__(self)
72
        self.host = host
73
        self.url = url
74
75
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
76
class DifferentGitLabInstances(errors.BzrError):
77
78
    _fmt = ("Can't create merge proposals across GitLab instances: "
79
            "%(source_host)s and %(target_host)s")
80
81
    def __init__(self, source_host, target_host):
82
        self.source_host = source_host
83
        self.target_host = target_host
84
85
0.432.10 by Jelmer Vernooij
More test fixes.
86
class GitLabLoginMissing(errors.BzrError):
87
88
    _fmt = ("Please log into GitLab")
89
90
7296.10.2 by Jelmer Vernooij
More fixes.
91
class GitlabLoginError(errors.BzrError):
92
93
    _fmt = ("Error logging in: %(error)s")
94
95
    def __init__(self, error):
96
        self.error = error
97
98
0.431.59 by Jelmer Vernooij
Add gitlab-login command.
99
def default_config_path():
7340.1.1 by Martin
Fix use of config_dir in propose plugin
100
    return os.path.join(bedding.config_dir(), 'gitlab.conf')
0.431.59 by Jelmer Vernooij
Add gitlab-login command.
101
102
103
def store_gitlab_token(name, url, private_token):
104
    """Store a GitLab token in a configuration file."""
105
    import configparser
106
    config = configparser.ConfigParser()
107
    path = default_config_path()
108
    config.read([path])
109
    config.add_section(name)
110
    config[name]['url'] = url
111
    config[name]['private_token'] = private_token
112
    with open(path, 'w') as f:
113
        config.write(f)
114
115
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
116
def iter_tokens():
117
    import configparser
118
    config = configparser.ConfigParser()
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
119
    config.read(
120
        [os.path.expanduser(p) for p in _DEFAULT_FILES] +
121
        [default_config_path()])
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
122
    for name, section in config.items():
123
        yield name, section
124
125
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
126
def parse_gitlab_url(url):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
127
    (scheme, user, password, host, port, path) = urlutils.parse_url(
128
        url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
129
    if scheme not in ('git+ssh', 'https', 'http'):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
130
        raise NotGitLabUrl(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
131
    if not host:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
132
        raise NotGitLabUrl(url)
0.432.10 by Jelmer Vernooij
More test fixes.
133
    path = path.strip('/')
0.432.11 by Jelmer Vernooij
Fix some tests.
134
    if path.endswith('.git'):
135
        path = path[:-4]
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
136
    return host, path
137
138
139
def parse_gitlab_branch_url(branch):
140
    url = urlutils.split_segment_parameters(branch.user_url)[0]
141
    host, path = parse_gitlab_url(url)
0.432.10 by Jelmer Vernooij
More test fixes.
142
    return host, path, branch.name
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
143
144
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
145
def parse_gitlab_merge_request_url(url):
146
    (scheme, user, password, host, port, path) = urlutils.parse_url(
147
        url)
148
    if scheme not in ('git+ssh', 'https', 'http'):
149
        raise NotGitLabUrl(url)
150
    if not host:
151
        raise NotGitLabUrl(url)
152
    path = path.strip('/')
153
    parts = path.split('/')
154
    if parts[-2] != 'merge_requests':
155
        raise NotMergeRequestUrl(host, url)
156
    return host, '/'.join(parts[:-2]), int(parts[-1])
157
158
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
159
class GitLabMergeProposal(MergeProposal):
160
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
161
    def __init__(self, gl, mr):
162
        self.gl = gl
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
163
        self._mr = mr
164
165
    @property
166
    def url(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
167
        return self._mr['web_url']
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
168
169
    def get_description(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
170
        return self._mr['description']
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
171
172
    def set_description(self, description):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
173
        self._mr['description'] = description
174
        self.gl._update_merge_requests(self._mr)
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
175
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
176
    def get_commit_message(self):
177
        return None
178
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
179
    def _branch_url_from_project(self, project_id, branch_name):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
180
        project = self.gl._get_project(project_id)
7296.10.3 by Jelmer Vernooij
More fixes.
181
        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.
182
183
    def get_source_branch_url(self):
184
        return self._branch_url_from_project(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
185
            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.
186
187
    def get_target_branch_url(self):
188
        return self._branch_url_from_project(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
189
            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.
190
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
191
    def is_merged(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
192
        return (self._mr['state'] == 'merged')
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
193
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
194
    def close(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
195
        self._mr['state_event'] = 'close'
196
        self.gl._update_merge_requests(self._mr)
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
197
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
198
    def merge(self, commit_message=None):
199
        # https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
200
        self._mr.merge(merge_commit_message=commit_message)
201
0.431.39 by Jelmer Vernooij
Extend the merge proposal abstraction a bit.
202
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
203
def gitlab_url_to_bzr_url(url, name):
204
    if not PY3:
205
        name = name.encode('utf-8')
206
    return urlutils.join_segment_parameters(
7211.13.7 by Jelmer Vernooij
Fix formatting.
207
        git_url_to_bzr_url(url), {"branch": name})
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
208
209
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
210
class GitLab(Hoster):
211
    """GitLab hoster implementation."""
212
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
213
    supports_merge_proposal_labels = True
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
214
    supports_merge_proposal_commit_message = False
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
215
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
216
    def __repr__(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
217
        return "<GitLab(%r)>" % self.base_url
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
218
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
219
    @property
220
    def base_url(self):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
221
        return self.transport.base
222
223
    def _api_request(self, method, path):
224
        return self.transport.request(
225
            method, urlutils.join(self.base_url, 'api', 'v4', path),
226
            headers=self.headers)
227
228
    def __init__(self, transport, private_token):
229
        self.transport = transport
230
        self.headers = {"Private-Token": private_token}
231
        self.check()
232
233
    def _get_project(self, project_name):
7296.10.3 by Jelmer Vernooij
More fixes.
234
        path = 'projects/:%s' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
235
        response = self._api_request('GET', path)
236
        if response.status == 404:
237
            raise NoSuchProject(project_name)
238
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
239
            return json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
240
        raise InvalidHttpResponse(path, response.text)
241
242
    def _fork_project(self, project_name):
7296.10.3 by Jelmer Vernooij
More fixes.
243
        path = 'projects/:%s/fork' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
244
        response = self._api_request('POST', path)
245
        if response != 200:
246
            raise InvalidHttpResponse(path, response.text)
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
247
        return json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
248
249
    def _get_logged_in_username(self):
250
        return self._current_user['username']
251
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
252
    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.
253
        if project is not None:
7296.10.3 by Jelmer Vernooij
More fixes.
254
            path = 'projects/:%s/merge_requests' % urlutils.quote(str(project_name), '')
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
255
        else:
256
            path = 'merge_requests'
257
        parameters = {}
258
        if state:
259
            parameters['state'] = state
260
        if owner:
261
            parameters['owner_id'] = urlutils.quote(owner, '')
262
        response = self._api_request(
263
            'GET', path + '?' +
264
            ';'.join(['%s=%s' % item for item in parameters.items()]))
265
        if response.status == 403:
266
            raise errors.PermissionDenied(response.text)
267
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
268
            return json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
269
        raise InvalidHttpResponse(path, response.text)
270
271
    def _create_mergerequest(
272
            self, title, source_project_id, target_project_id,
7296.10.3 by Jelmer Vernooij
More fixes.
273
            source_branch_name, target_branch_name, description,
274
            labels=None):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
275
        path = 'projects/:%s/merge_requests' % source_project_id
276
        response = self._api_request(
277
            'POST', path, fields={
278
                'title': title,
279
                'source_branch': source_branch_name,
280
                'target_branch': target_branch_name,
281
                'target_project_id': target_project_id,
7296.10.3 by Jelmer Vernooij
More fixes.
282
                'description': description,
283
                'labels': labels})
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
284
        if response.status == 403:
285
            raise errors.PermissionDenied(response.text)
286
        if response.status == 409:
287
            raise MergeProposalExists(self.source_branch.user_url)
288
        if response.status == 200:
289
            raise InvalidHttpResponse(path, response.text)
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
290
        return json.loads(response.data)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
291
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
292
    def get_push_url(self, branch):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
293
        (host, project_name, branch_name) = parse_gitlab_branch_url(branch)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
294
        project = self._get_project(project_name)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
295
        return gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
296
            project['ssh_url_to_repo'], branch_name)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
297
0.431.20 by Jelmer Vernooij
publish -> publish_derived.
298
    def publish_derived(self, local_branch, base_branch, name, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
299
                        owner=None, revision_id=None, overwrite=False,
300
                        allow_lossy=True):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
301
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
302
        if owner is None:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
303
            owner = self._get_logged_in_username()
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
304
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
305
            project = self._get_project(base_project)['path']
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
306
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
307
            target_project = self._get_project('%s/%s' % (owner, project))
308
        except NoSuchProject:
309
            target_project = self._fork_project(base_project)
310
            # TODO(jelmer): Spin and wait until import_status for new project
311
            # is complete.
7296.10.3 by Jelmer Vernooij
More fixes.
312
        remote_repo_url = git_url_to_bzr_url(target_project['ssh_url_to_repo'])
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
313
        remote_dir = controldir.ControlDir.open(remote_repo_url)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
314
        try:
7211.13.7 by Jelmer Vernooij
Fix formatting.
315
            push_result = remote_dir.push_branch(
316
                local_branch, revision_id=revision_id, overwrite=overwrite,
317
                name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
318
        except errors.NoRoundtrippingSupport:
319
            if not allow_lossy:
320
                raise
7211.13.7 by Jelmer Vernooij
Fix formatting.
321
            push_result = remote_dir.push_branch(
322
                local_branch, revision_id=revision_id, overwrite=overwrite,
323
                name=name, lossy=True)
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
324
        public_url = gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
325
            target_project['http_url_to_repo'], name)
0.432.5 by Jelmer Vernooij
Fix publishing to gitlab.
326
        return push_result.target_branch, public_url
0.432.4 by Jelmer Vernooij
Some work on gitlab.
327
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
328
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
329
        (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
330
        if owner is None:
7296.10.3 by Jelmer Vernooij
More fixes.
331
            owner = self._get_logged_in_username()
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
332
        if project is None:
7296.10.3 by Jelmer Vernooij
More fixes.
333
            project = self._get_project(base_project)['path']
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
334
        try:
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
335
            target_project = self._get_project('%s/%s' % (owner, project))
336
        except NoSuchProject:
337
            raise errors.NotBranchError('%s/%s/%s' % (self.base_url, owner, project))
0.433.3 by Jelmer Vernooij
Some python 3 compatibility.
338
        return _mod_branch.Branch.open(gitlab_url_to_bzr_url(
7296.10.3 by Jelmer Vernooij
More fixes.
339
            target_project['ssh_url_to_repo'], name))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
340
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
341
    def get_proposer(self, source_branch, target_branch):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
342
        return GitlabMergeProposalBuilder(self, source_branch, target_branch)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
343
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
344
    def iter_proposals(self, source_branch, target_branch, status):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
345
        (source_host, source_project_name, source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
346
            parse_gitlab_branch_url(source_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
347
        (target_host, target_project_name, target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
348
            parse_gitlab_branch_url(target_branch))
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
349
        if source_host != target_host:
350
            raise DifferentGitLabInstances(source_host, target_host)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
351
        source_project = self._get_project(source_project_name)
352
        target_project = self._get_project(target_project_name)
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
353
        state = mp_status_to_status(status)
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
354
        for mr in self.gl._list_merge_requests(
7296.10.3 by Jelmer Vernooij
More fixes.
355
                project=target_project['id'], state=state):
356
            if (mr['source_project_id'] != source_project['id'] or
357
                    mr['source_branch'] != source_branch_name or
358
                    mr['target_project_id'] != target_project['id'] or
359
                    mr['target_branch'] != target_branch_name):
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
360
                continue
361
            yield GitLabMergeProposal(self, mr)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
362
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
363
    def hosts(self, branch):
364
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
365
            (host, project, branch_name) = parse_gitlab_branch_url(branch)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
366
        except NotGitLabUrl:
367
            return False
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
368
        return (self.base_url == ('https://%s' % host))
369
370
    def check(self):
371
        response = self._api_request('GET', 'user')
372
        if response.status == 200:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
373
            self._current_user = json.loads(response.data)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
374
            return
7296.10.2 by Jelmer Vernooij
More fixes.
375
        if response == 401:
7296.10.8 by Jelmer Vernooij
Remove json attribute from Response object, consistent with urllib3 API.
376
            if json.loads(response.data) == {"message": "401 Unauthorized"}:
7296.10.2 by Jelmer Vernooij
More fixes.
377
                raise GitLabLoginMissing()
378
            else:
379
                raise GitlabLoginError(response.text)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
380
        raise UnsupportedHoster(url)
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
381
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
382
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
383
    def probe_from_url(cls, url, possible_transports=None):
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
384
        try:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
385
            (host, project) = parse_gitlab_url(url)
0.431.17 by Jelmer Vernooij
Try harder to avoid detecting any URL as a GitLab URL.
386
        except NotGitLabUrl:
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
387
            raise UnsupportedHoster(url)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
388
        transport = get_transport(
389
            'https://%s' % host, possible_transports=possible_transports)
390
        return cls(transport)
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
391
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
392
    @classmethod
393
    def iter_instances(cls):
394
        for name, credentials in iter_tokens():
395
            if 'url' not in credentials:
396
                continue
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
397
            yield cls(
398
                get_transport(credentials['url']),
399
                private_token=credentials.get('private_token'))
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
400
0.431.66 by Jelmer Vernooij
Add support for status argument.
401
    def iter_my_proposals(self, status='open'):
0.431.68 by Jelmer Vernooij
Add status to other Hosters.
402
        state = mp_status_to_status(status)
7296.10.9 by Jelmer Vernooij
Fix method name spacing.
403
        for mp in self._list_merge_requests(
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
404
                owner=self._get_logged_in_username(), state=state):
405
            yield GitLabMergeProposal(self, mp)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
406
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
407
    def get_proposal_by_url(self, url):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
408
        try:
409
            (host, project, merge_id) = parse_gitlab_merge_request_url(url)
410
        except NotGitLabUrl:
411
            raise UnsupportedHoster(url)
7296.9.4 by Jelmer Vernooij
Fix dealing with non-gitlab sites.
412
        except NotMergeRequestUrl as e:
413
            if self.gl.url == ('https://%s' % e.host):
7296.9.3 by Jelmer Vernooij
Support finding merge proposals by URL on GitLab instances.
414
                raise
415
            else:
416
                raise UnsupportedHoster(url)
417
        if self.gl.url != ('https://%s' % host):
418
            raise UnsupportedHoster(url)
419
        project = self.gl.projects.get(project)
420
        mr = project.mergerequests.get(merge_id)
421
        return GitLabMergeProposal(mr)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
422
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
423
0.432.2 by Jelmer Vernooij
Publish command sort of works.
424
class GitlabMergeProposalBuilder(MergeProposalBuilder):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
425
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
426
    def __init__(self, l, source_branch, target_branch):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
427
        self.gl = gl
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
428
        self.source_branch = source_branch
429
        (self.source_host, self.source_project_name, self.source_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
430
            parse_gitlab_branch_url(source_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
431
        self.target_branch = target_branch
432
        (self.target_host, self.target_project_name, self.target_branch_name) = (
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
433
            parse_gitlab_branch_url(target_branch))
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
434
        if self.source_host != self.target_host:
435
            raise DifferentGitLabInstances(self.source_host, self.target_host)
436
437
    def get_infotext(self):
438
        """Determine the initial comment for the merge proposal."""
439
        info = []
440
        info.append("Gitlab instance: %s\n" % self.target_host)
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
441
        info.append("Source: %s\n" % self.source_branch.user_url)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
442
        info.append("Target: %s\n" % self.target_branch.user_url)
443
        return ''.join(info)
444
445
    def get_initial_body(self):
446
        """Get a body for the proposal for the user to modify.
447
448
        :return: a str or None.
449
        """
450
        return None
451
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
452
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
453
                        prerequisite_branch=None, commit_message=None):
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
454
        """Perform the submission."""
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
455
        # https://docs.gitlab.com/ee/api/merge_requests.html#create-mr
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
456
        if prerequisite_branch is not None:
457
            raise PrerequisiteBranchUnsupported(self)
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
458
        # Note that commit_message is ignored, since Gitlab doesn't support it.
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
459
        # TODO(jelmer): Support reviewers
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
460
        source_project = self.gl._get_project(self.source_project_name)
461
        target_project = self.gl._get_project(self.target_project_name)
0.431.5 by Jelmer Vernooij
Initial work on gitlab support.
462
        # TODO(jelmer): Allow setting title explicitly
463
        title = description.splitlines()[0]
464
        # TODO(jelmer): Allow setting allow_collaboration field
465
        # TODO(jelmer): Allow setting milestone field
466
        # TODO(jelmer): Allow setting squash field
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
467
        kwargs = {
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
468
            'title': title,
7296.10.3 by Jelmer Vernooij
More fixes.
469
            'source_project_id': source_project['id'],
470
            'target_project_id': target_project['id'],
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
471
            'source_branch': self.source_branch_name,
472
            'target_branch': self.target_branch_name,
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
473
            'description': description}
474
        if labels:
475
            kwargs['labels'] = ','.join(labels)
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
476
        merge_request = self.gl._create_mergerequest(**kwargs)
477
        return GitLabMergeProposal(self.gl, merge_request)
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
478
479
480
def register_gitlab_instance(shortname, url):
481
    """Register a gitlab instance.
482
483
    :param shortname: Short name (e.g. "gitlab")
484
    :param url: URL to the gitlab instance
485
    """
486
    from breezy.bugtracker import (
487
        tracker_registry,
488
        ProjectIntegerBugTracker,
489
        )
490
    tracker_registry.register(
491
        shortname, ProjectIntegerBugTracker(
492
            shortname, url + '/{project}/issues/{id}'))