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