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