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