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