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