/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
7322.2.1 by Martin
Fix invalid escape sequence warnings in regexps
77
    regex = re.compile(r'([a-z]*\+)*(bzr\+ssh|http|ssh|git|https)'
78
                       r'://(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,
7360.1.3 by Jelmer Vernooij
Fix handling of Git branches on Launchpad.
121
                {"branch": str(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,
7360.1.3 by Jelmer Vernooij
Fix handling of Git branches on Launchpad.
131
                {"branch": str(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
7381.2.1 by Jelmer Vernooij
Implement LaunchpadMergeProposal.can_be_merged.
157
    def can_be_merged(self):
158
        if not self._mp.preview_diff:
159
            # Maybe?
160
            return True
161
        return not bool(self._mp.preview_diff.conflicts)
162
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
163
    def merge(self, commit_message=None):
164
        target_branch = _mod_branch.Branch.open(
165
            self.get_target_branch_url())
166
        source_branch = _mod_branch.Branch.open(
167
            self.get_source_branch_url())
168
        # TODO(jelmer): Ideally this would use a memorytree, but merge doesn't
169
        # support that yet.
170
        # tree = target_branch.create_memorytree()
171
        tmpdir = tempfile.mkdtemp()
172
        try:
173
            tree = target_branch.create_checkout(
174
                to_location=tmpdir, lightweight=True)
175
            tree.merge_from_branch(source_branch)
176
            tree.commit(commit_message or self._mp.commit_message)
177
        finally:
178
            shutil.rmtree(tmpdir)
179
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
180
0.432.1 by Jelmer Vernooij
Initial work on hoster support.
181
class Launchpad(Hoster):
182
    """The Launchpad hosting service."""
183
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
184
    name = 'launchpad'
185
0.431.29 by Jelmer Vernooij
Fix github branch name, add bug URL.
186
    # https://bugs.launchpad.net/launchpad/+bug/397676
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
187
    supports_merge_proposal_labels = False
188
7296.8.2 by Jelmer Vernooij
Add feature flag for commit message.
189
    supports_merge_proposal_commit_message = True
190
7254.1.2 by Jelmer Vernooij
Fix propose Launchpad.
191
    def __init__(self, staging=False):
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
192
        self._staging = staging
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
193
        if staging:
7253 by Jelmer Vernooij
Fix default launchpadlib API URL.
194
            lp_base_url = uris.STAGING_SERVICE_ROOT
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
195
        else:
7253 by Jelmer Vernooij
Fix default launchpadlib API URL.
196
            lp_base_url = uris.LPNET_SERVICE_ROOT
7371.4.4 by Jelmer Vernooij
Pull in more fixes from janitor.
197
        self.launchpad = lp_api.connect_launchpad(lp_base_url, version='devel')
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
198
7260.1.1 by Jelmer Vernooij
Add .base_url property to Hoster.
199
    @property
200
    def base_url(self):
201
        return lp_api.uris.web_root_for_service_root(
202
            str(self.launchpad._root_uri))
203
0.433.1 by Jelmer Vernooij
Add Hoster.hosts.
204
    def __repr__(self):
205
        return "Launchpad(staging=%s)" % self._staging
206
207
    def hosts(self, branch):
208
        # TODO(jelmer): staging vs non-staging?
209
        return plausible_launchpad_url(branch.user_url)
210
0.432.2 by Jelmer Vernooij
Publish command sort of works.
211
    @classmethod
7296.10.1 by Jelmer Vernooij
Initial work making gitlab just directly use ReST.
212
    def probe_from_url(cls, url, possible_transports=None):
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
213
        if plausible_launchpad_url(url):
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
214
            return Launchpad()
7268.12.1 by Jelmer Vernooij
Split out probe_from_url.
215
        raise UnsupportedHoster(url)
0.432.2 by Jelmer Vernooij
Publish command sort of works.
216
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
217
    def _get_lp_git_ref_from_branch(self, branch):
218
        url, params = urlutils.split_segment_parameters(branch.user_url)
219
        (scheme, user, password, host, port, path) = urlutils.parse_url(
220
            url)
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
221
        repo_lp = self.launchpad.git_repositories.getByPath(
222
            path=path.strip('/'))
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
223
        try:
224
            ref_path = params['ref']
225
        except KeyError:
226
            branch_name = params.get('branch', branch.name)
227
            if branch_name:
228
                ref_path = 'refs/heads/%s' % branch_name
229
            else:
230
                ref_path = repo_lp.default_branch
231
        ref_lp = repo_lp.getRefByPath(path=ref_path)
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
232
        return (repo_lp, ref_lp)
233
234
    def _get_lp_bzr_branch_from_branch(self, branch):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
235
        return self.launchpad.branches.getByUrl(
236
            url=urlutils.unescape(branch.user_url))
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
237
0.431.23 by Jelmer Vernooij
Launchpad fixes.
238
    def _get_derived_git_path(self, base_path, owner, project):
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
239
        base_repo = self.launchpad.git_repositories.getByPath(path=base_path)
240
        if project is None:
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
241
            project = urlutils.parse_url(base_repo.git_ssh_url)[-1].strip('/')
242
        if project.startswith('~'):
243
            project = '/'.join(base_path.split('/')[1:])
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
244
        # TODO(jelmer): Surely there is a better way of creating one of these
245
        # URLs?
0.431.23 by Jelmer Vernooij
Launchpad fixes.
246
        return "~%s/%s" % (owner, project)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
247
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
248
    def _publish_git(self, local_branch, base_path, name, owner, project=None,
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
249
                     revision_id=None, overwrite=False, allow_lossy=True):
0.431.23 by Jelmer Vernooij
Launchpad fixes.
250
        to_path = self._get_derived_git_path(base_path, owner, project)
251
        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.
252
        try:
253
            dir_to = controldir.ControlDir.open_from_transport(to_transport)
254
        except errors.NotBranchError:
255
            # Didn't find anything
256
            dir_to = None
257
258
        if dir_to is None:
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
259
            try:
260
                br_to = local_branch.create_clone_on_transport(
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
261
                    to_transport, revision_id=revision_id, name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
262
            except errors.NoRoundtrippingSupport:
263
                br_to = local_branch.create_clone_on_transport(
7211.13.7 by Jelmer Vernooij
Fix formatting.
264
                    to_transport, revision_id=revision_id, name=name,
7233.3.2 by Jelmer Vernooij
Merge lp:brz-propose.
265
                    lossy=True)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
266
        else:
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
267
            try:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
268
                dir_to = dir_to.push_branch(
269
                    local_branch, revision_id, overwrite=overwrite, name=name)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
270
            except errors.NoRoundtrippingSupport:
271
                if not allow_lossy:
272
                    raise
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
273
                dir_to = dir_to.push_branch(
274
                    local_branch, revision_id, overwrite=overwrite, name=name,
275
                    lossy=True)
0.431.51 by Jelmer Vernooij
Allow fallback to lossy by default.
276
            br_to = dir_to.target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
277
        return br_to, (
278
            "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.
279
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
280
    def _get_derived_bzr_path(self, base_branch, name, owner, project):
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
281
        if project is None:
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
282
            base_branch_lp = self._get_lp_bzr_branch_from_branch(base_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
283
            project = '/'.join(base_branch_lp.unique_name.split('/')[1:-1])
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
284
        # TODO(jelmer): Surely there is a better way of creating one of these
285
        # URLs?
0.431.23 by Jelmer Vernooij
Launchpad fixes.
286
        return "~%s/%s/%s" % (owner, project, name)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
287
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
288
    def get_push_url(self, branch):
289
        (vcs, user, password, path, params) = self._split_url(branch.user_url)
290
        if vcs == 'bzr':
291
            branch_lp = self._get_lp_bzr_branch_from_branch(branch)
292
            return branch_lp.bzr_identity
293
        elif vcs == 'git':
294
            return urlutils.join_segment_parameters(
295
                "git+ssh://git.launchpad.net/" + path, params)
296
        else:
297
            raise AssertionError
298
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
299
    def _publish_bzr(self, local_branch, base_branch, name, owner,
300
                     project=None, revision_id=None, overwrite=False,
301
                     allow_lossy=True):
0.431.25 by Jelmer Vernooij
Fix launchpad URL unescaping.
302
        to_path = self._get_derived_bzr_path(base_branch, name, owner, project)
0.431.23 by Jelmer Vernooij
Launchpad fixes.
303
        to_transport = get_transport("lp:" + to_path)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
304
        try:
305
            dir_to = controldir.ControlDir.open_from_transport(to_transport)
306
        except errors.NotBranchError:
307
            # Didn't find anything
308
            dir_to = None
309
310
        if dir_to is None:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
311
            br_to = local_branch.create_clone_on_transport(
312
                to_transport, revision_id=revision_id)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
313
        else:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
314
            br_to = dir_to.push_branch(
315
                local_branch, revision_id, overwrite=overwrite).target_branch
0.431.23 by Jelmer Vernooij
Launchpad fixes.
316
        return br_to, ("https://code.launchpad.net/" + to_path)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
317
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
318
    def _split_url(self, url):
319
        url, params = urlutils.split_segment_parameters(url)
320
        (scheme, user, password, host, port, path) = urlutils.parse_url(url)
321
        path = path.strip('/')
322
        if host.startswith('bazaar.'):
323
            vcs = 'bzr'
324
        elif host.startswith('git.'):
325
            vcs = 'git'
326
        else:
327
            raise ValueError("unknown host %s" % host)
328
        return (vcs, user, password, path, params)
329
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
330
    def publish_derived(self, local_branch, base_branch, name, project=None,
331
                        owner=None, revision_id=None, overwrite=False,
332
                        allow_lossy=True):
0.432.2 by Jelmer Vernooij
Publish command sort of works.
333
        """Publish a branch to the site, derived from base_branch.
334
335
        :param base_branch: branch to derive the new branch from
336
        :param new_branch: branch to publish
337
        :param name: Name of the new branch on the remote host
338
        :param project: Optional project name
339
        :param owner: Optional owner
340
        :return: resulting branch
341
        """
342
        if owner is None:
0.432.9 by Jelmer Vernooij
Drop is_compatible nonesense.
343
            owner = self.launchpad.me.name
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
344
        (base_vcs, base_user, base_password, base_path,
345
            base_params) = self._split_url(base_branch.user_url)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
346
        # TODO(jelmer): Prevent publishing to development focus
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
347
        if base_vcs == 'bzr':
7211.13.7 by Jelmer Vernooij
Fix formatting.
348
            return self._publish_bzr(
349
                local_branch, base_branch, name, project=project, owner=owner,
350
                revision_id=revision_id, overwrite=overwrite,
351
                allow_lossy=allow_lossy)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
352
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
353
            return self._publish_git(
354
                local_branch, base_path, name, project=project, owner=owner,
355
                revision_id=revision_id, overwrite=overwrite,
356
                allow_lossy=allow_lossy)
0.432.2 by Jelmer Vernooij
Publish command sort of works.
357
        else:
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
358
            raise AssertionError('not a valid Launchpad URL')
0.432.2 by Jelmer Vernooij
Publish command sort of works.
359
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
360
    def get_derived_branch(self, base_branch, name, project=None, owner=None):
361
        if owner is None:
362
            owner = self.launchpad.me.name
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
363
        (base_vcs, base_user, base_password, base_path,
364
            base_params) = self._split_url(base_branch.user_url)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
365
        if base_vcs == 'bzr':
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
366
            to_path = self._get_derived_bzr_path(
367
                base_branch, name, owner, project)
0.431.23 by Jelmer Vernooij
Launchpad fixes.
368
            return _mod_branch.Branch.open("lp:" + to_path)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
369
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
370
            to_path = self._get_derived_git_path(
371
                base_path.strip('/'), owner, project)
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
372
            to_url = urlutils.join_segment_parameters(
7240.4.1 by Jelmer Vernooij
Merge lp:brz-propose.
373
                "git+ssh://git.launchpad.net/" + to_path,
374
                {'branch': name})
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
375
            return _mod_branch.Branch.open(to_url)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
376
        else:
377
            raise AssertionError('not a valid Launchpad URL')
378
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
379
    def iter_proposals(self, source_branch, target_branch, status='open'):
380
        (base_vcs, base_user, base_password, base_path,
381
            base_params) = self._split_url(target_branch.user_url)
382
        statuses = status_to_lp_mp_statuses(status)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
383
        if base_vcs == 'bzr':
7211.13.7 by Jelmer Vernooij
Fix formatting.
384
            target_branch_lp = self.launchpad.branches.getByUrl(
385
                url=target_branch.user_url)
386
            source_branch_lp = self.launchpad.branches.getByUrl(
387
                url=source_branch.user_url)
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
388
            for mp in target_branch_lp.getMergeProposals(status=statuses):
389
                if mp.source_branch_link != source_branch_lp.self_link:
390
                    continue
391
                yield LaunchpadMergeProposal(mp)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
392
        elif base_vcs == 'git':
7211.13.7 by Jelmer Vernooij
Fix formatting.
393
            (source_repo_lp, source_branch_lp) = (
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
394
                self._get_lp_git_ref_from_branch(source_branch))
7211.13.7 by Jelmer Vernooij
Fix formatting.
395
            (target_repo_lp, target_branch_lp) = (
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
396
                self._get_lp_git_ref_from_branch(target_branch))
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
397
            for mp in target_branch_lp.getMergeProposals(status=statuses):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
398
                if (target_branch_lp.path != mp.target_git_path or
7211.13.7 by Jelmer Vernooij
Fix formatting.
399
                        target_repo_lp != mp.target_git_repository or
400
                        source_branch_lp.path != mp.source_git_path or
401
                        source_repo_lp != mp.source_git_repository):
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
402
                    continue
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
403
                yield LaunchpadMergeProposal(mp)
0.431.35 by Jelmer Vernooij
Add Hoster.get_proposal.
404
        else:
405
            raise AssertionError('not a valid Launchpad URL')
406
0.432.2 by Jelmer Vernooij
Publish command sort of works.
407
    def get_proposer(self, source_branch, target_branch):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
408
        (base_vcs, base_user, base_password, base_path,
409
            base_params) = self._split_url(target_branch.user_url)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
410
        if base_vcs == 'bzr':
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
411
            return LaunchpadBazaarMergeProposalBuilder(
7211.13.7 by Jelmer Vernooij
Fix formatting.
412
                self, source_branch, target_branch)
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
413
        elif base_vcs == 'git':
0.431.24 by Jelmer Vernooij
Support git merge proposals.
414
            return LaunchpadGitMergeProposalBuilder(
7211.13.7 by Jelmer Vernooij
Fix formatting.
415
                self, source_branch, target_branch)
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
416
        else:
417
            raise AssertionError('not a valid Launchpad URL')
0.432.2 by Jelmer Vernooij
Publish command sort of works.
418
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
419
    @classmethod
420
    def iter_instances(cls):
421
        yield cls()
422
0.431.66 by Jelmer Vernooij
Add support for status argument.
423
    def iter_my_proposals(self, status='open'):
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
424
        statuses = status_to_lp_mp_statuses(status)
0.431.66 by Jelmer Vernooij
Add support for status argument.
425
        for mp in self.launchpad.me.getMergeProposals(status=statuses):
0.431.63 by Jelmer Vernooij
Add 'brz my-proposals' command.
426
            yield LaunchpadMergeProposal(mp)
427
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
428
    def get_proposal_by_url(self, url):
429
        # Launchpad doesn't have a way to find a merge proposal by URL.
430
        (scheme, user, password, host, port, path) = urlutils.parse_url(
431
            url)
7296.9.2 by Jelmer Vernooij
Fix detection of launchpad hosts.
432
        LAUNCHPAD_CODE_DOMAINS = [
433
            ('code.%s' % domain) for domain in lp_api.LAUNCHPAD_DOMAINS.values()]
434
        if host not in LAUNCHPAD_CODE_DOMAINS:
435
            raise UnsupportedHoster(url)
7296.9.1 by Jelmer Vernooij
Add 'brz land' subcommand.
436
        # TODO(jelmer): Check if this is a launchpad URL. Otherwise, raise
437
        # UnsupportedHoster
438
        # See https://api.launchpad.net/devel/#branch_merge_proposal
439
        # the syntax is:
440
        # https://api.launchpad.net/devel/~<author.name>/<project.name>/<branch.name>/+merge/<id>
441
        api_url = str(self.launchpad._root_uri) + path
442
        mp = self.launchpad.load(api_url)
443
        return LaunchpadMergeProposal(mp)
444
0.432.2 by Jelmer Vernooij
Publish command sort of works.
445
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
446
class LaunchpadBazaarMergeProposalBuilder(MergeProposalBuilder):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
447
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
448
    def __init__(self, lp_host, source_branch, target_branch,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
449
                 staging=None, approve=None, fixes=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
450
        """Constructor.
451
452
        :param source_branch: The branch to propose for merging.
453
        :param target_branch: The branch to merge into.
454
        :param staging: If True, propose the merge against staging instead of
455
            production.
456
        :param approve: If True, mark the new proposal as approved immediately.
457
            This is useful when a project permits some things to be approved
458
            by the submitter (e.g. merges between release and deployment
459
            branches).
460
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
461
        self.lp_host = lp_host
462
        self.launchpad = lp_host.launchpad
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
463
        self.source_branch = source_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
464
        self.source_branch_lp = self.launchpad.branches.getByUrl(
465
            url=source_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
466
        if target_branch is None:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
467
            self.target_branch_lp = self.source_branch_lp.get_target()
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
468
            self.target_branch = _mod_branch.Branch.open(
469
                self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
470
        else:
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
471
            self.target_branch = target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
472
            self.target_branch_lp = self.launchpad.branches.getByUrl(
473
                url=target_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
474
        self.approve = approve
475
        self.fixes = fixes
476
477
    def get_infotext(self):
478
        """Determine the initial comment for the merge proposal."""
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
479
        info = ["Source: %s\n" % self.source_branch_lp.bzr_identity]
480
        info.append("Target: %s\n" % self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
481
        return ''.join(info)
482
483
    def get_initial_body(self):
484
        """Get a body for the proposal for the user to modify.
485
486
        :return: a str or None.
487
        """
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
488
        if not self.hooks['merge_proposal_body']:
489
            return None
490
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
491
        def list_modified_files():
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
492
            lca_tree = self.source_branch_lp.find_lca_tree(
493
                self.target_branch_lp)
494
            source_tree = self.source_branch.basis_tree()
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
495
            files = modified_files(lca_tree, source_tree)
496
            return list(files)
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
497
        with self.target_branch.lock_read(), \
498
                self.source_branch.lock_read():
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
499
            body = None
500
            for hook in self.hooks['merge_proposal_body']:
501
                body = hook({
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
502
                    'target_branch': self.target_branch_lp.bzr_identity,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
503
                    'modified_files_callback': list_modified_files,
504
                    'old_body': body,
505
                })
506
            return body
507
508
    def check_proposal(self):
509
        """Check that the submission is sensible."""
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
510
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
511
            raise errors.BzrCommandError(
512
                'Source and target branches must be different.')
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
513
        for mp in self.source_branch_lp.landing_targets:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
514
            if mp.queue_status in ('Merged', 'Rejected'):
515
                continue
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
516
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
517
                raise MergeProposalExists(lp_api.canonical_url(mp))
518
519
    def approve_proposal(self, mp):
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
520
        with self.source_branch.lock_read():
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
521
            _call_webservice(
522
                mp.createComment,
523
                vote=u'Approve',
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
524
                subject='',  # Use the default subject.
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
525
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
526
            _call_webservice(mp.setStatus, status=u'Approved',
527
                             revid=self.source_branch.last_revision())
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
528
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
529
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
530
                        prerequisite_branch=None, commit_message=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
531
        """Perform the submission."""
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
532
        if labels:
7295.2.1 by Jelmer Vernooij
Fix some issues reported by lgtm.com.
533
            raise LabelsUnsupported(self)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
534
        if prerequisite_branch is not None:
535
            prereq = self.launchpad.branches.getByUrl(
536
                url=prerequisite_branch.user_url)
537
        else:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
538
            prereq = None
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
539
        if reviewers is None:
540
            reviewers = []
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
541
        try:
542
            mp = _call_webservice(
543
                self.source_branch_lp.createMergeProposal,
544
                target_branch=self.target_branch_lp,
545
                prerequisite_branch=prereq,
0.431.42 by Jelmer Vernooij
Remove unnecessary encode (breaks on Python 3).
546
                initial_comment=description.strip(),
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
547
                commit_message=commit_message,
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
548
                reviewers=[self.launchpad.people[reviewer].self_link
549
                           for reviewer in reviewers],
7359.1.3 by Jelmer Vernooij
Fix GitHub API interaction.
550
                review_types=['' for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
551
        except WebserviceFailure as e:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
552
            # Urgh.
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
553
            if (b'There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
554
                    b'registered for branch ') in e.message:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
555
                raise MergeProposalExists(self.source_branch.user_url)
556
            raise
557
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
558
        if self.approve:
559
            self.approve_proposal(mp)
560
        if self.fixes:
561
            if self.fixes.startswith('lp:'):
562
                self.fixes = self.fixes[3:]
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
563
            _call_webservice(
564
                mp.linkBug,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
565
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
566
        return LaunchpadMergeProposal(mp)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
567
568
569
class LaunchpadGitMergeProposalBuilder(MergeProposalBuilder):
570
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
571
    def __init__(self, lp_host, source_branch, target_branch,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
572
                 staging=None, approve=None, fixes=None):
573
        """Constructor.
574
575
        :param source_branch: The branch to propose for merging.
576
        :param target_branch: The branch to merge into.
577
        :param staging: If True, propose the merge against staging instead of
578
            production.
579
        :param approve: If True, mark the new proposal as approved immediately.
580
            This is useful when a project permits some things to be approved
581
            by the submitter (e.g. merges between release and deployment
582
            branches).
583
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
584
        self.lp_host = lp_host
585
        self.launchpad = lp_host.launchpad
0.431.24 by Jelmer Vernooij
Support git merge proposals.
586
        self.source_branch = source_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
587
        (self.source_repo_lp,
588
            self.source_branch_lp) = self.lp_host._get_lp_git_ref_from_branch(
589
                source_branch)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
590
        if target_branch is None:
591
            self.target_branch_lp = self.source_branch.get_target()
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
592
            self.target_branch = _mod_branch.Branch.open(
593
                self.target_branch_lp.git_https_url)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
594
        else:
595
            self.target_branch = target_branch
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
596
            (self.target_repo_lp, self.target_branch_lp) = (
597
                self.lp_host._get_lp_git_ref_from_branch(target_branch))
0.431.24 by Jelmer Vernooij
Support git merge proposals.
598
        self.approve = approve
599
        self.fixes = fixes
600
601
    def get_infotext(self):
602
        """Determine the initial comment for the merge proposal."""
603
        info = ["Source: %s\n" % self.source_branch.user_url]
604
        info.append("Target: %s\n" % self.target_branch.user_url)
605
        return ''.join(info)
606
607
    def get_initial_body(self):
608
        """Get a body for the proposal for the user to modify.
609
610
        :return: a str or None.
611
        """
612
        if not self.hooks['merge_proposal_body']:
613
            return None
614
615
        def list_modified_files():
616
            lca_tree = self.source_branch_lp.find_lca_tree(
617
                self.target_branch_lp)
618
            source_tree = self.source_branch.basis_tree()
619
            files = modified_files(lca_tree, source_tree)
620
            return list(files)
621
        with self.target_branch.lock_read(), \
622
                self.source_branch.lock_read():
623
            body = None
624
            for hook in self.hooks['merge_proposal_body']:
625
                body = hook({
626
                    'target_branch': self.target_branch,
627
                    'modified_files_callback': list_modified_files,
628
                    'old_body': body,
629
                })
630
            return body
631
632
    def check_proposal(self):
633
        """Check that the submission is sensible."""
634
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
635
            raise errors.BzrCommandError(
636
                'Source and target branches must be different.')
637
        for mp in self.source_branch_lp.landing_targets:
638
            if mp.queue_status in ('Merged', 'Rejected'):
639
                continue
640
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
641
                raise MergeProposalExists(lp_api.canonical_url(mp))
642
643
    def approve_proposal(self, mp):
644
        with self.source_branch.lock_read():
645
            _call_webservice(
646
                mp.createComment,
647
                vote=u'Approve',
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
648
                subject='',  # Use the default subject.
0.431.24 by Jelmer Vernooij
Support git merge proposals.
649
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
650
            _call_webservice(
651
                mp.setStatus, status=u'Approved',
652
                revid=self.source_branch.last_revision())
0.431.24 by Jelmer Vernooij
Support git merge proposals.
653
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
654
    def create_proposal(self, description, reviewers=None, labels=None,
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
655
                        prerequisite_branch=None, commit_message=None):
0.431.24 by Jelmer Vernooij
Support git merge proposals.
656
        """Perform the submission."""
657
        if labels:
7295.2.1 by Jelmer Vernooij
Fix some issues reported by lgtm.com.
658
            raise LabelsUnsupported(self)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
659
        if prerequisite_branch is not None:
0.431.67 by Jelmer Vernooij
Support multiple merge proposals per branch.
660
            (prereq_repo_lp, prereq_branch_lp) = (
661
                self.lp_host._get_lp_git_ref_from_branch(prerequisite_branch))
0.431.24 by Jelmer Vernooij
Support git merge proposals.
662
        else:
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
663
            prereq_branch_lp = None
0.431.24 by Jelmer Vernooij
Support git merge proposals.
664
        if reviewers is None:
665
            reviewers = []
666
        try:
667
            mp = _call_webservice(
668
                self.source_branch_lp.createMergeProposal,
669
                merge_target=self.target_branch_lp,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
670
                merge_prerequisite=prereq_branch_lp,
0.431.71 by Jelmer Vernooij
Some fixes for Git on Launchpad.
671
                initial_comment=description.strip(),
7296.8.1 by Jelmer Vernooij
Add commit-message option to 'brz propose'.
672
                commit_message=commit_message,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
673
                needs_review=True,
674
                reviewers=[self.launchpad.people[reviewer].self_link
675
                           for reviewer in reviewers],
676
                review_types=[None for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
677
        except WebserviceFailure as e:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
678
            # Urgh.
679
            if ('There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
680
                    'registered for branch ') in e.message:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
681
                raise MergeProposalExists(self.source_branch.user_url)
682
            raise
683
        if self.approve:
684
            self.approve_proposal(mp)
685
        if self.fixes:
686
            if self.fixes.startswith('lp:'):
687
                self.fixes = self.fixes[3:]
688
            _call_webservice(
689
                mp.linkBug,
690
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
691
        return LaunchpadMergeProposal(mp)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
692
693
694
def modified_files(old_tree, new_tree):
695
    """Return a list of paths in the new tree with modified contents."""
7322.1.6 by Jelmer Vernooij
Use the new attributes on TreeChange.
696
    for change in new_tree.iter_changes(old_tree):
697
        if change.changed_content and change.kind[1] == 'file':
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
698
            yield str(path)