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