/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
1
# Copyright (C) 2010, 2011 Canonical Ltd
0.431.27 by Jelmer Vernooij
Catch 503 errors.
2
# Copyright (C) 2018 Breezy Developers
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
0.434.1 by Jelmer Vernooij
Use absolute_import.
18
"""Support for Launchpad."""
19
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
20
from __future__ import absolute_import
21
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
22
import re
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
23
import shutil
24
import tempfile
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
25
0.431.3 by Jelmer Vernooij
Add a MergeProposal object.
26
from .propose import (
0.432.2 by Jelmer Vernooij
Publish command sort of works.
27
    Hoster,
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
28
    LabelsUnsupported,
0.431.3 by Jelmer Vernooij
Add a MergeProposal object.
29
    MergeProposal,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
30
    MergeProposalBuilder,
0.431.3 by Jelmer Vernooij
Add a MergeProposal object.
31
    MergeProposalExists,
0.431.12 by Jelmer Vernooij
Fix Launchpad probing.
32
    UnsupportedHoster,
0.431.3 by Jelmer Vernooij
Add a MergeProposal object.
33
    )
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
34
35
from ... import (
0.432.2 by Jelmer Vernooij
Publish command sort of works.
36
    branch as _mod_branch,
37
    controldir,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
38
    errors,
39
    hooks,
40
    urlutils,
41
    )
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
42
from ...git.refs import ref_to_branch_name
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
43
from ...lazy_import import lazy_import
44
lazy_import(globals(), """
45
from breezy.plugins.launchpad import (
46
    lp_api,
47
    )
7253 by Jelmer Vernooij
Fix default launchpadlib API URL.
48
49
from launchpadlib import uris
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
50
""")
0.432.2 by Jelmer Vernooij
Publish command sort of works.
51
from ...transport import get_transport
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
52
53
0.431.4 by Jelmer Vernooij
Add basic GitHub support.
54
# TODO(jelmer): Make selection of launchpad staging a configuration option.
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
55
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
56
def status_to_lp_mp_statuses(status):
57
    statuses = []
58
    if status in ('open', 'all'):
59
        statuses.extend([
60
            'Work in progress',
61
            'Needs review',
62
            'Approved',
63
            'Code failed to merge',
64
            'Queued'])
65
    if status in ('closed', 'all'):
66
        statuses.extend(['Rejected', 'Superseded'])
67
    if status in ('merged', 'all'):
68
        statuses.append('Merged')
69
    return statuses
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
70
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
71
72
def plausible_launchpad_url(url):
73
    if url is None:
74
        return False
75
    if url.startswith('lp:'):
76
        return True
77
    regex = re.compile('([a-z]*\+)*(bzr\+ssh|http|ssh|git|https)'
7295.2.1 by Jelmer Vernooij
Fix some issues reported by lgtm.com.
78
                       '://(bazaar|git).*\.launchpad\.net')
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
79
    return bool(regex.match(url))
80
81
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
82
class WebserviceFailure(Exception):
83
84
    def __init__(self, message):
85
        self.message = message
86
87
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
88
def _call_webservice(call, *args, **kwargs):
89
    """Make a call to the webservice, wrapping failures.
90
91
    :param call: The call to make.
92
    :param *args: *args for the call.
93
    :param **kwargs: **kwargs for the call.
94
    :return: The result of calling call(*args, *kwargs).
95
    """
96
    from lazr.restfulclient import errors as restful_errors
97
    try:
98
        return call(*args, **kwargs)
99
    except restful_errors.HTTPError as e:
100
        error_lines = []
101
        for line in e.content.splitlines():
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
102
            if line.startswith(b'Traceback (most recent call last):'):
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
103
                break
104
            error_lines.append(line)
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
105
        raise WebserviceFailure(b''.join(error_lines))
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
106
107
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
108
class LaunchpadMergeProposal(MergeProposal):
109
110
    def __init__(self, mp):
111
        self._mp = mp
112
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
113
    def get_source_branch_url(self):
114
        if self._mp.source_branch:
115
            return self._mp.source_branch.bzr_identity
116
        else:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
117
            branch_name = ref_to_branch_name(
118
                self._mp.source_git_path.encode('utf-8'))
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
119
            return urlutils.join_segment_parameters(
120
                self._mp.source_git_repository.git_identity,
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
121
                {"branch": branch_name})
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
122
123
    def get_target_branch_url(self):
124
        if self._mp.target_branch:
125
            return self._mp.target_branch.bzr_identity
126
        else:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
127
            branch_name = ref_to_branch_name(
128
                self._mp.target_git_path.encode('utf-8'))
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
129
            return urlutils.join_segment_parameters(
130
                self._mp.target_git_repository.git_identity,
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
131
                {"branch": branch_name})
0.431.64 by Jelmer Vernooij
Add get_source_branch_url/get_target_branch_url methods.
132
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
133
    @property
134
    def url(self):
135
        return lp_api.canonical_url(self._mp)
136
137
    def is_merged(self):
138
        return (self._mp.queue_status == 'Merged')
139
0.431.70 by Jelmer Vernooij
Implement get_description/set_description for LaunchpadMergeProposal.
140
    def get_description(self):
141
        return self._mp.description
142
143
    def set_description(self, description):
144
        self._mp.description = description
7290.5.1 by Jelmer Vernooij
Actually send API call to Launchpad when updating description.
145
        self._mp.lp_save()
0.431.70 by Jelmer Vernooij
Implement get_description/set_description for LaunchpadMergeProposal.
146
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
147
    def get_commit_message(self):
148
        return self._mp.commit_message
149
150
    def set_commit_message(self, commit_message):
151
        self._mp.commit_message = commit_message
152
        self._mp.lp_save()
153
7260.2.1 by Jelmer Vernooij
Implement .close on merge proposals.
154
    def close(self):
155
        self._mp.setStatus(status='Rejected')
156
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
157
    def merge(self, commit_message=None):
158
        target_branch = _mod_branch.Branch.open(
159
            self.get_target_branch_url())
160
        source_branch = _mod_branch.Branch.open(
161
            self.get_source_branch_url())
162
        # TODO(jelmer): Ideally this would use a memorytree, but merge doesn't
163
        # support that yet.
164
        # tree = target_branch.create_memorytree()
165
        tmpdir = tempfile.mkdtemp()
166
        try:
167
            tree = target_branch.create_checkout(
168
                to_location=tmpdir, lightweight=True)
169
            tree.merge_from_branch(source_branch)
170
            tree.commit(commit_message or self._mp.commit_message)
171
        finally:
172
            shutil.rmtree(tmpdir)
173
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
174
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
175
class Launchpad(Hoster):
176
    """The Launchpad hosting service."""
177
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
178
    name = 'launchpad'
179
0.431.29 by Jelmer Vernooij
Fix github branch name, add bug URL.
180
    # https://bugs.launchpad.net/launchpad/+bug/397676
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
181
    supports_merge_proposal_labels = False
182
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
183
    supports_merge_proposal_commit_message = True
184
7254.1.2 by Jelmer Vernooij
Fix propose Launchpad.
185
    def __init__(self, staging=False):
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
186
        self._staging = staging
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
187
        if staging:
7253 by Jelmer Vernooij
Fix default launchpadlib API URL.
188
            lp_base_url = uris.STAGING_SERVICE_ROOT
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
189
        else:
7253 by Jelmer Vernooij
Fix default launchpadlib API URL.
190
            lp_base_url = uris.LPNET_SERVICE_ROOT
7240.5.1 by Jelmer Vernooij
Avoid LaunchpadService when connecting to API.
191
        self.launchpad = lp_api.connect_launchpad(lp_base_url)
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
192
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
193
    @property
194
    def base_url(self):
195
        return lp_api.uris.web_root_for_service_root(
196
            str(self.launchpad._root_uri))
197
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
198
    def __repr__(self):
199
        return "Launchpad(staging=%s)" % self._staging
200
201
    def hosts(self, branch):
202
        # TODO(jelmer): staging vs non-staging?
203
        return plausible_launchpad_url(branch.user_url)
204
0.432.2 by Jelmer Vernooij
Publish command sort of works.
205
    @classmethod
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
206
    def probe_from_url(cls, url):
207
        if plausible_launchpad_url(url):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
208
            return Launchpad()
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
209
        raise UnsupportedHoster(url)
0.432.2 by Jelmer Vernooij
Publish command sort of works.
210
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
211
    def _get_lp_git_ref_from_branch(self, branch):
212
        url, params = urlutils.split_segment_parameters(branch.user_url)
213
        (scheme, user, password, host, port, path) = urlutils.parse_url(
214
            url)
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
215
        repo_lp = self.launchpad.git_repositories.getByPath(
216
            path=path.strip('/'))
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
217
        try:
218
            ref_path = params['ref']
219
        except KeyError:
220
            branch_name = params.get('branch', branch.name)
221
            if branch_name:
222
                ref_path = 'refs/heads/%s' % branch_name
223
            else:
224
                ref_path = repo_lp.default_branch
225
        ref_lp = repo_lp.getRefByPath(path=ref_path)
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
226
        return (repo_lp, ref_lp)
227
228
    def _get_lp_bzr_branch_from_branch(self, branch):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
229
        return self.launchpad.branches.getByUrl(
230
            url=urlutils.unescape(branch.user_url))
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
231
0.431.23 by Jelmer Vernooij
Launchpad fixes.
232
    def _get_derived_git_path(self, base_path, owner, project):
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
233
        base_repo = self.launchpad.git_repositories.getByPath(path=base_path)
234
        if project is None:
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
235
            project = urlutils.parse_url(base_repo.git_ssh_url)[-1].strip('/')
236
        if project.startswith('~'):
237
            project = '/'.join(base_path.split('/')[1:])
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
238
        # TODO(jelmer): Surely there is a better way of creating one of these
239
        # URLs?
0.431.23 by Jelmer Vernooij
Launchpad fixes.
240
        return "~%s/%s" % (owner, project)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
241
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
242
    def _publish_git(self, local_branch, base_path, name, owner, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
243
                     revision_id=None, overwrite=False, allow_lossy=True):
0.431.23 by Jelmer Vernooij
Launchpad fixes.
244
        to_path = self._get_derived_git_path(base_path, owner, project)
245
        to_transport = get_transport("git+ssh://git.launchpad.net/" + to_path)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
246
        try:
247
            dir_to = controldir.ControlDir.open_from_transport(to_transport)
248
        except errors.NotBranchError:
249
            # Didn't find anything
250
            dir_to = None
251
252
        if dir_to is None:
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
253
            try:
254
                br_to = local_branch.create_clone_on_transport(
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
255
                    to_transport, revision_id=revision_id, name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
256
            except errors.NoRoundtrippingSupport:
257
                br_to = local_branch.create_clone_on_transport(
7211.13.7 by Jelmer Vernooij
Fix formatting.
258
                    to_transport, revision_id=revision_id, name=name,
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
259
                    lossy=True)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
260
        else:
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
261
            try:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
262
                dir_to = dir_to.push_branch(
263
                    local_branch, revision_id, overwrite=overwrite, name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
264
            except errors.NoRoundtrippingSupport:
265
                if not allow_lossy:
266
                    raise
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
267
                dir_to = dir_to.push_branch(
268
                    local_branch, revision_id, overwrite=overwrite, name=name,
269
                    lossy=True)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
270
            br_to = dir_to.target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
271
        return br_to, (
272
            "https://git.launchpad.net/%s/+ref/%s" % (to_path, name))
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
273
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
274
    def _get_derived_bzr_path(self, base_branch, name, owner, project):
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
275
        if project is None:
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
276
            base_branch_lp = self._get_lp_bzr_branch_from_branch(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
277
            project = '/'.join(base_branch_lp.unique_name.split('/')[1:-1])
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
278
        # TODO(jelmer): Surely there is a better way of creating one of these
279
        # URLs?
0.431.23 by Jelmer Vernooij
Launchpad fixes.
280
        return "~%s/%s/%s" % (owner, project, name)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
281
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
282
    def get_push_url(self, branch):
283
        (vcs, user, password, path, params) = self._split_url(branch.user_url)
284
        if vcs == 'bzr':
285
            branch_lp = self._get_lp_bzr_branch_from_branch(branch)
286
            return branch_lp.bzr_identity
287
        elif vcs == 'git':
288
            return urlutils.join_segment_parameters(
289
                "git+ssh://git.launchpad.net/" + path, params)
290
        else:
291
            raise AssertionError
292
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
293
    def _publish_bzr(self, local_branch, base_branch, name, owner,
294
                     project=None, revision_id=None, overwrite=False,
295
                     allow_lossy=True):
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
296
        to_path = self._get_derived_bzr_path(base_branch, name, owner, project)
0.431.23 by Jelmer Vernooij
Launchpad fixes.
297
        to_transport = get_transport("lp:" + to_path)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
298
        try:
299
            dir_to = controldir.ControlDir.open_from_transport(to_transport)
300
        except errors.NotBranchError:
301
            # Didn't find anything
302
            dir_to = None
303
304
        if dir_to is None:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
305
            br_to = local_branch.create_clone_on_transport(
306
                to_transport, revision_id=revision_id)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
307
        else:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
308
            br_to = dir_to.push_branch(
309
                local_branch, revision_id, overwrite=overwrite).target_branch
0.431.23 by Jelmer Vernooij
Launchpad fixes.
310
        return br_to, ("https://code.launchpad.net/" + to_path)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
311
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
312
    def _split_url(self, url):
313
        url, params = urlutils.split_segment_parameters(url)
314
        (scheme, user, password, host, port, path) = urlutils.parse_url(url)
315
        path = path.strip('/')
316
        if host.startswith('bazaar.'):
317
            vcs = 'bzr'
318
        elif host.startswith('git.'):
319
            vcs = 'git'
320
        else:
321
            raise ValueError("unknown host %s" % host)
322
        return (vcs, user, password, path, params)
323
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
324
    def publish_derived(self, local_branch, base_branch, name, project=None,
325
                        owner=None, revision_id=None, overwrite=False,
326
                        allow_lossy=True):
0.432.2 by Jelmer Vernooij
Publish command sort of works.
327
        """Publish a branch to the site, derived from base_branch.
328
329
        :param base_branch: branch to derive the new branch from
330
        :param new_branch: branch to publish
331
        :param name: Name of the new branch on the remote host
332
        :param project: Optional project name
333
        :param owner: Optional owner
334
        :return: resulting branch
335
        """
336
        if owner is None:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
337
            owner = self.launchpad.me.name
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
338
        (base_vcs, base_user, base_password, base_path,
339
            base_params) = self._split_url(base_branch.user_url)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
340
        # TODO(jelmer): Prevent publishing to development focus
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
341
        if base_vcs == 'bzr':
7211.13.7 by Jelmer Vernooij
Fix formatting.
342
            return self._publish_bzr(
343
                local_branch, base_branch, name, project=project, owner=owner,
344
                revision_id=revision_id, overwrite=overwrite,
345
                allow_lossy=allow_lossy)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
346
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
347
            return self._publish_git(
348
                local_branch, base_path, name, project=project, owner=owner,
349
                revision_id=revision_id, overwrite=overwrite,
350
                allow_lossy=allow_lossy)
0.432.2 by Jelmer Vernooij
Publish command sort of works.
351
        else:
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
352
            raise AssertionError('not a valid Launchpad URL')
0.432.2 by Jelmer Vernooij
Publish command sort of works.
353
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
354
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
355
        if owner is None:
356
            owner = self.launchpad.me.name
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
357
        (base_vcs, base_user, base_password, base_path,
358
            base_params) = self._split_url(base_branch.user_url)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
359
        if base_vcs == 'bzr':
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
360
            to_path = self._get_derived_bzr_path(
361
                base_branch, name, owner, project)
0.431.23 by Jelmer Vernooij
Launchpad fixes.
362
            return _mod_branch.Branch.open("lp:" + to_path)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
363
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
364
            to_path = self._get_derived_git_path(
365
                base_path.strip('/'), owner, project)
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
366
            to_url = urlutils.join_segment_parameters(
7240.4.1 by Jelmer Vernooij
Merge lp:brz-propose.
367
                "git+ssh://git.launchpad.net/" + to_path,
368
                {'branch': name})
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
369
            return _mod_branch.Branch.open(to_url)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
370
        else:
371
            raise AssertionError('not a valid Launchpad URL')
372
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
373
    def iter_proposals(self, source_branch, target_branch, status='open'):
374
        (base_vcs, base_user, base_password, base_path,
375
            base_params) = self._split_url(target_branch.user_url)
376
        statuses = status_to_lp_mp_statuses(status)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
377
        if base_vcs == 'bzr':
7211.13.7 by Jelmer Vernooij
Fix formatting.
378
            target_branch_lp = self.launchpad.branches.getByUrl(
379
                url=target_branch.user_url)
380
            source_branch_lp = self.launchpad.branches.getByUrl(
381
                url=source_branch.user_url)
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
382
            for mp in target_branch_lp.getMergeProposals(status=statuses):
383
                if mp.source_branch_link != source_branch_lp.self_link:
384
                    continue
385
                yield LaunchpadMergeProposal(mp)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
386
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
387
            (source_repo_lp, source_branch_lp) = (
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
388
                self._get_lp_git_ref_from_branch(source_branch))
7211.13.7 by Jelmer Vernooij
Fix formatting.
389
            (target_repo_lp, target_branch_lp) = (
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
390
                self._get_lp_git_ref_from_branch(target_branch))
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
391
            for mp in target_branch_lp.getMergeProposals(status=statuses):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
392
                if (target_branch_lp.path != mp.target_git_path or
7211.13.7 by Jelmer Vernooij
Fix formatting.
393
                        target_repo_lp != mp.target_git_repository or
394
                        source_branch_lp.path != mp.source_git_path or
395
                        source_repo_lp != mp.source_git_repository):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
396
                    continue
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
397
                yield LaunchpadMergeProposal(mp)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
398
        else:
399
            raise AssertionError('not a valid Launchpad URL')
400
0.432.2 by Jelmer Vernooij
Publish command sort of works.
401
    def get_proposer(self, source_branch, target_branch):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
402
        (base_vcs, base_user, base_password, base_path,
403
            base_params) = self._split_url(target_branch.user_url)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
404
        if base_vcs == 'bzr':
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
405
            return LaunchpadBazaarMergeProposalBuilder(
7211.13.7 by Jelmer Vernooij
Fix formatting.
406
                self, source_branch, target_branch)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
407
        elif base_vcs == 'git':
0.431.24 by Jelmer Vernooij
Support git merge proposals.
408
            return LaunchpadGitMergeProposalBuilder(
7211.13.7 by Jelmer Vernooij
Fix formatting.
409
                self, source_branch, target_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
410
        else:
411
            raise AssertionError('not a valid Launchpad URL')
0.432.2 by Jelmer Vernooij
Publish command sort of works.
412
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
413
    @classmethod
414
    def iter_instances(cls):
415
        yield cls()
416
0.431.66 by Jelmer Vernooij
Add support for status argument.
417
    def iter_my_proposals(self, status='open'):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
418
        statuses = status_to_lp_mp_statuses(status)
0.431.66 by Jelmer Vernooij
Add support for status argument.
419
        for mp in self.launchpad.me.getMergeProposals(status=statuses):
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
420
            yield LaunchpadMergeProposal(mp)
421
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
422
    def get_proposal_by_url(self, url):
423
        # Launchpad doesn't have a way to find a merge proposal by URL.
424
        (scheme, user, password, host, port, path) = urlutils.parse_url(
425
            url)
7296.9.2 by Jelmer Vernooij
Fix detection of launchpad hosts.
426
        LAUNCHPAD_CODE_DOMAINS = [
427
            ('code.%s' % domain) for domain in lp_api.LAUNCHPAD_DOMAINS.values()]
428
        if host not in LAUNCHPAD_CODE_DOMAINS:
429
            raise UnsupportedHoster(url)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
430
        # TODO(jelmer): Check if this is a launchpad URL. Otherwise, raise
431
        # UnsupportedHoster
432
        # See https://api.launchpad.net/devel/#branch_merge_proposal
433
        # the syntax is:
434
        # https://api.launchpad.net/devel/~<author.name>/<project.name>/<branch.name>/+merge/<id>
435
        api_url = str(self.launchpad._root_uri) + path
436
        mp = self.launchpad.load(api_url)
437
        return LaunchpadMergeProposal(mp)
438
0.432.2 by Jelmer Vernooij
Publish command sort of works.
439
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
440
class LaunchpadBazaarMergeProposalBuilder(MergeProposalBuilder):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
441
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
442
    def __init__(self, lp_host, source_branch, target_branch,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
443
                 staging=None, approve=None, fixes=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
444
        """Constructor.
445
446
        :param source_branch: The branch to propose for merging.
447
        :param target_branch: The branch to merge into.
448
        :param staging: If True, propose the merge against staging instead of
449
            production.
450
        :param approve: If True, mark the new proposal as approved immediately.
451
            This is useful when a project permits some things to be approved
452
            by the submitter (e.g. merges between release and deployment
453
            branches).
454
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
455
        self.lp_host = lp_host
456
        self.launchpad = lp_host.launchpad
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
457
        self.source_branch = source_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
458
        self.source_branch_lp = self.launchpad.branches.getByUrl(
459
            url=source_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
460
        if target_branch is None:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
461
            self.target_branch_lp = self.source_branch_lp.get_target()
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
462
            self.target_branch = _mod_branch.Branch.open(
463
                self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
464
        else:
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
465
            self.target_branch = target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
466
            self.target_branch_lp = self.launchpad.branches.getByUrl(
467
                url=target_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
468
        self.approve = approve
469
        self.fixes = fixes
470
471
    def get_infotext(self):
472
        """Determine the initial comment for the merge proposal."""
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
473
        info = ["Source: %s\n" % self.source_branch_lp.bzr_identity]
474
        info.append("Target: %s\n" % self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
475
        return ''.join(info)
476
477
    def get_initial_body(self):
478
        """Get a body for the proposal for the user to modify.
479
480
        :return: a str or None.
481
        """
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
482
        if not self.hooks['merge_proposal_body']:
483
            return None
484
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
485
        def list_modified_files():
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
486
            lca_tree = self.source_branch_lp.find_lca_tree(
487
                self.target_branch_lp)
488
            source_tree = self.source_branch.basis_tree()
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
489
            files = modified_files(lca_tree, source_tree)
490
            return list(files)
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
491
        with self.target_branch.lock_read(), \
492
                self.source_branch.lock_read():
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
493
            body = None
494
            for hook in self.hooks['merge_proposal_body']:
495
                body = hook({
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
496
                    'target_branch': self.target_branch_lp.bzr_identity,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
497
                    'modified_files_callback': list_modified_files,
498
                    'old_body': body,
499
                })
500
            return body
501
502
    def check_proposal(self):
503
        """Check that the submission is sensible."""
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
504
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
505
            raise errors.BzrCommandError(
506
                'Source and target branches must be different.')
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
507
        for mp in self.source_branch_lp.landing_targets:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
508
            if mp.queue_status in ('Merged', 'Rejected'):
509
                continue
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
510
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
511
                raise MergeProposalExists(lp_api.canonical_url(mp))
512
513
    def approve_proposal(self, mp):
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
514
        with self.source_branch.lock_read():
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
515
            _call_webservice(
516
                mp.createComment,
517
                vote=u'Approve',
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
518
                subject='',  # Use the default subject.
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
519
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
520
            _call_webservice(mp.setStatus, status=u'Approved',
521
                             revid=self.source_branch.last_revision())
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
522
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
523
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
524
                        prerequisite_branch=None, commit_message=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
525
        """Perform the submission."""
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
526
        if labels:
7295.2.1 by Jelmer Vernooij
Fix some issues reported by lgtm.com.
527
            raise LabelsUnsupported(self)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
528
        if prerequisite_branch is not None:
529
            prereq = self.launchpad.branches.getByUrl(
530
                url=prerequisite_branch.user_url)
531
        else:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
532
            prereq = None
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
533
        if reviewers is None:
534
            reviewers = []
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
535
        try:
536
            mp = _call_webservice(
537
                self.source_branch_lp.createMergeProposal,
538
                target_branch=self.target_branch_lp,
539
                prerequisite_branch=prereq,
0.431.42 by Jelmer Vernooij
Remove unnecessary encode (breaks on Python 3).
540
                initial_comment=description.strip(),
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
541
                commit_message=commit_message,
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
542
                reviewers=[self.launchpad.people[reviewer].self_link
543
                           for reviewer in reviewers],
544
                review_types=[None for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
545
        except WebserviceFailure as e:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
546
            # Urgh.
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
547
            if (b'There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
548
                    b'registered for branch ') in e.message:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
549
                raise MergeProposalExists(self.source_branch.user_url)
550
            raise
551
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
552
        if self.approve:
553
            self.approve_proposal(mp)
554
        if self.fixes:
555
            if self.fixes.startswith('lp:'):
556
                self.fixes = self.fixes[3:]
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
557
            _call_webservice(
558
                mp.linkBug,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
559
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
560
        return LaunchpadMergeProposal(mp)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
561
562
563
class LaunchpadGitMergeProposalBuilder(MergeProposalBuilder):
564
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
565
    def __init__(self, lp_host, source_branch, target_branch,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
566
                 staging=None, approve=None, fixes=None):
567
        """Constructor.
568
569
        :param source_branch: The branch to propose for merging.
570
        :param target_branch: The branch to merge into.
571
        :param staging: If True, propose the merge against staging instead of
572
            production.
573
        :param approve: If True, mark the new proposal as approved immediately.
574
            This is useful when a project permits some things to be approved
575
            by the submitter (e.g. merges between release and deployment
576
            branches).
577
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
578
        self.lp_host = lp_host
579
        self.launchpad = lp_host.launchpad
0.431.24 by Jelmer Vernooij
Support git merge proposals.
580
        self.source_branch = source_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
581
        (self.source_repo_lp,
582
            self.source_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(
583
                source_branch)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
584
        if target_branch is None:
585
            self.target_branch_lp = self.source_branch.get_target()
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
586
            self.target_branch = _mod_branch.Branch.open(
587
                self.target_branch_lp.git_https_url)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
588
        else:
589
            self.target_branch = target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
590
            (self.target_repo_lp, self.target_branch_lp) = (
591
                self.lp_host._get_lp_git_ref_from_branch(target_branch))
0.431.24 by Jelmer Vernooij
Support git merge proposals.
592
        self.approve = approve
593
        self.fixes = fixes
594
595
    def get_infotext(self):
596
        """Determine the initial comment for the merge proposal."""
597
        info = ["Source: %s\n" % self.source_branch.user_url]
598
        info.append("Target: %s\n" % self.target_branch.user_url)
599
        return ''.join(info)
600
601
    def get_initial_body(self):
602
        """Get a body for the proposal for the user to modify.
603
604
        :return: a str or None.
605
        """
606
        if not self.hooks['merge_proposal_body']:
607
            return None
608
609
        def list_modified_files():
610
            lca_tree = self.source_branch_lp.find_lca_tree(
611
                self.target_branch_lp)
612
            source_tree = self.source_branch.basis_tree()
613
            files = modified_files(lca_tree, source_tree)
614
            return list(files)
615
        with self.target_branch.lock_read(), \
616
                self.source_branch.lock_read():
617
            body = None
618
            for hook in self.hooks['merge_proposal_body']:
619
                body = hook({
620
                    'target_branch': self.target_branch,
621
                    'modified_files_callback': list_modified_files,
622
                    'old_body': body,
623
                })
624
            return body
625
626
    def check_proposal(self):
627
        """Check that the submission is sensible."""
628
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
629
            raise errors.BzrCommandError(
630
                'Source and target branches must be different.')
631
        for mp in self.source_branch_lp.landing_targets:
632
            if mp.queue_status in ('Merged', 'Rejected'):
633
                continue
634
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
635
                raise MergeProposalExists(lp_api.canonical_url(mp))
636
637
    def approve_proposal(self, mp):
638
        with self.source_branch.lock_read():
639
            _call_webservice(
640
                mp.createComment,
641
                vote=u'Approve',
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
642
                subject='',  # Use the default subject.
0.431.24 by Jelmer Vernooij
Support git merge proposals.
643
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
644
            _call_webservice(
645
                mp.setStatus, status=u'Approved',
646
                revid=self.source_branch.last_revision())
0.431.24 by Jelmer Vernooij
Support git merge proposals.
647
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
648
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
649
                        prerequisite_branch=None, commit_message=None):
0.431.24 by Jelmer Vernooij
Support git merge proposals.
650
        """Perform the submission."""
651
        if labels:
7295.2.1 by Jelmer Vernooij
Fix some issues reported by lgtm.com.
652
            raise LabelsUnsupported(self)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
653
        if prerequisite_branch is not None:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
654
            (prereq_repo_lp, prereq_branch_lp) = (
655
                self.lp_host._get_lp_git_ref_from_branch(prerequisite_branch))
0.431.24 by Jelmer Vernooij
Support git merge proposals.
656
        else:
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
657
            prereq_branch_lp = None
0.431.24 by Jelmer Vernooij
Support git merge proposals.
658
        if reviewers is None:
659
            reviewers = []
660
        try:
661
            mp = _call_webservice(
662
                self.source_branch_lp.createMergeProposal,
663
                merge_target=self.target_branch_lp,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
664
                merge_prerequisite=prereq_branch_lp,
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
665
                initial_comment=description.strip(),
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
666
                commit_message=commit_message,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
667
                needs_review=True,
668
                reviewers=[self.launchpad.people[reviewer].self_link
669
                           for reviewer in reviewers],
670
                review_types=[None for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
671
        except WebserviceFailure as e:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
672
            # Urgh.
673
            if ('There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
674
                    'registered for branch ') in e.message:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
675
                raise MergeProposalExists(self.source_branch.user_url)
676
            raise
677
        if self.approve:
678
            self.approve_proposal(mp)
679
        if self.fixes:
680
            if self.fixes.startswith('lp:'):
681
                self.fixes = self.fixes[3:]
682
            _call_webservice(
683
                mp.linkBug,
684
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
685
        return LaunchpadMergeProposal(mp)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
686
687
688
def modified_files(old_tree, new_tree):
689
    """Return a list of paths in the new tree with modified contents."""
690
    for f, (op, path), c, v, p, n, (ok, k), e in new_tree.iter_changes(
691
            old_tree):
692
        if c and k == 'file':
693
            yield str(path)