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