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