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