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