/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
354
    def iter_my_proposals(self):
355
        for mp in self.launchpad.me.getMergeProposals():
356
            yield LaunchpadMergeProposal(mp)
357
0.432.2 by Jelmer Vernooij
Publish command sort of works.
358
359
def connect_launchpad(lp_instance='production'):
360
    service = lp_registration.LaunchpadService(lp_instance=lp_instance)
0.431.15 by Jelmer Vernooij
Initial work on support for git branches in launchpad.
361
    return lp_api.login(service, version='devel')
0.432.2 by Jelmer Vernooij
Publish command sort of works.
362
363
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
364
class LaunchpadBazaarMergeProposalBuilder(MergeProposalBuilder):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
365
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
366
    def __init__(self, lp_host, source_branch, target_branch, message=None,
0.432.2 by Jelmer Vernooij
Publish command sort of works.
367
                 staging=None, approve=None, fixes=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
368
        """Constructor.
369
370
        :param source_branch: The branch to propose for merging.
371
        :param target_branch: The branch to merge into.
372
        :param message: The commit message to use.  (May be None.)
373
        :param staging: If True, propose the merge against staging instead of
374
            production.
375
        :param approve: If True, mark the new proposal as approved immediately.
376
            This is useful when a project permits some things to be approved
377
            by the submitter (e.g. merges between release and deployment
378
            branches).
379
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
380
        self.lp_host = lp_host
381
        self.launchpad = lp_host.launchpad
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
382
        self.source_branch = source_branch
383
        self.source_branch_lp = self.launchpad.branches.getByUrl(url=source_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
384
        if target_branch is None:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
385
            self.target_branch_lp = self.source_branch_lp.get_target()
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
386
            self.target_branch = _mod_branch.Branch.open(self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
387
        else:
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
388
            self.target_branch = target_branch
389
            self.target_branch_lp = self.launchpad.branches.getByUrl(url=target_branch.user_url)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
390
        self.commit_message = message
391
        self.approve = approve
392
        self.fixes = fixes
393
394
    def get_infotext(self):
395
        """Determine the initial comment for the merge proposal."""
396
        if self.commit_message is not None:
397
            return self.commit_message.strip().encode('utf-8')
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
398
        info = ["Source: %s\n" % self.source_branch_lp.bzr_identity]
399
        info.append("Target: %s\n" % self.target_branch_lp.bzr_identity)
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
400
        return ''.join(info)
401
402
    def get_initial_body(self):
403
        """Get a body for the proposal for the user to modify.
404
405
        :return: a str or None.
406
        """
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
407
        if not self.hooks['merge_proposal_body']:
408
            return None
409
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
410
        def list_modified_files():
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
411
            lca_tree = self.source_branch_lp.find_lca_tree(
412
                self.target_branch_lp)
413
            source_tree = self.source_branch.basis_tree()
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
414
            files = modified_files(lca_tree, source_tree)
415
            return list(files)
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
416
        with self.target_branch.lock_read(), \
417
                self.source_branch.lock_read():
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
418
            body = None
419
            for hook in self.hooks['merge_proposal_body']:
420
                body = hook({
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
421
                    'target_branch': self.target_branch_lp.bzr_identity,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
422
                    'modified_files_callback': list_modified_files,
423
                    'old_body': body,
424
                })
425
            return body
426
427
    def check_proposal(self):
428
        """Check that the submission is sensible."""
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
429
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
430
            raise errors.BzrCommandError(
431
                'Source and target branches must be different.')
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
432
        for mp in self.source_branch_lp.landing_targets:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
433
            if mp.queue_status in ('Merged', 'Rejected'):
434
                continue
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
435
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
436
                raise MergeProposalExists(lp_api.canonical_url(mp))
437
438
    def approve_proposal(self, mp):
0.431.11 by Jelmer Vernooij
Fix launchpad handling.
439
        with self.source_branch.lock_read():
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
440
            _call_webservice(
441
                mp.createComment,
442
                vote=u'Approve',
443
                subject='', # Use the default subject.
444
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
445
            _call_webservice(mp.setStatus, status=u'Approved',
446
                             revid=self.source_branch.last_revision())
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
447
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
448
    def create_proposal(self, description, reviewers=None, labels=None,
449
                        prerequisite_branch=None):
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
450
        """Perform the submission."""
0.431.13 by Jelmer Vernooij
Add support for labels on merge proposals.
451
        if labels:
452
            raise LabelsUnsupported()
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
453
        if prerequisite_branch is not None:
454
            prereq = self.launchpad.branches.getByUrl(
455
                url=prerequisite_branch.user_url)
456
        else:
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
457
            prereq = None
0.431.6 by Jelmer Vernooij
Initial gitlab support works.
458
        if reviewers is None:
459
            reviewers = []
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
460
        try:
461
            mp = _call_webservice(
462
                self.source_branch_lp.createMergeProposal,
463
                target_branch=self.target_branch_lp,
464
                prerequisite_branch=prereq,
0.431.42 by Jelmer Vernooij
Remove unnecessary encode (breaks on Python 3).
465
                initial_comment=description.strip(),
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
466
                commit_message=self.commit_message,
467
                reviewers=[self.launchpad.people[reviewer].self_link
468
                           for reviewer in reviewers],
469
                review_types=[None for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
470
        except WebserviceFailure as e:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
471
            # Urgh.
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
472
            if (b'There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
473
                    b'registered for branch ') in e.message:
0.431.28 by Jelmer Vernooij
Implement Hoster.get_push_url.
474
                raise MergeProposalExists(self.source_branch.user_url)
475
            raise
476
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
477
        if self.approve:
478
            self.approve_proposal(mp)
479
        if self.fixes:
480
            if self.fixes.startswith('lp:'):
481
                self.fixes = self.fixes[3:]
0.431.22 by Jelmer Vernooij
Add Hoster.get_derived_branch.
482
            _call_webservice(
483
                mp.linkBug,
0.431.2 by Jelmer Vernooij
Add launchpad implementation.
484
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
485
        return LaunchpadMergeProposal(mp)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
486
487
488
class LaunchpadGitMergeProposalBuilder(MergeProposalBuilder):
489
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
490
    def __init__(self, lp_host, source_branch, target_branch, message=None,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
491
                 staging=None, approve=None, fixes=None):
492
        """Constructor.
493
494
        :param source_branch: The branch to propose for merging.
495
        :param target_branch: The branch to merge into.
496
        :param message: The commit message to use.  (May be None.)
497
        :param staging: If True, propose the merge against staging instead of
498
            production.
499
        :param approve: If True, mark the new proposal as approved immediately.
500
            This is useful when a project permits some things to be approved
501
            by the submitter (e.g. merges between release and deployment
502
            branches).
503
        """
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
504
        self.lp_host = lp_host
505
        self.launchpad = lp_host.launchpad
0.431.24 by Jelmer Vernooij
Support git merge proposals.
506
        self.source_branch = source_branch
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
507
        (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.
508
        if target_branch is None:
509
            self.target_branch_lp = self.source_branch.get_target()
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
510
            self.target_branch = _mod_branch.Branch.open(self.target_branch_lp.git_https_url)
0.431.24 by Jelmer Vernooij
Support git merge proposals.
511
        else:
512
            self.target_branch = target_branch
0.431.26 by Jelmer Vernooij
Fix handling of proposals for Launchapd Git URLs.
513
            (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.
514
        self.commit_message = message
515
        self.approve = approve
516
        self.fixes = fixes
517
518
    def get_infotext(self):
519
        """Determine the initial comment for the merge proposal."""
520
        if self.commit_message is not None:
521
            return self.commit_message.strip().encode('utf-8')
522
        info = ["Source: %s\n" % self.source_branch.user_url]
523
        info.append("Target: %s\n" % self.target_branch.user_url)
524
        return ''.join(info)
525
526
    def get_initial_body(self):
527
        """Get a body for the proposal for the user to modify.
528
529
        :return: a str or None.
530
        """
531
        if not self.hooks['merge_proposal_body']:
532
            return None
533
534
        def list_modified_files():
535
            lca_tree = self.source_branch_lp.find_lca_tree(
536
                self.target_branch_lp)
537
            source_tree = self.source_branch.basis_tree()
538
            files = modified_files(lca_tree, source_tree)
539
            return list(files)
540
        with self.target_branch.lock_read(), \
541
                self.source_branch.lock_read():
542
            body = None
543
            for hook in self.hooks['merge_proposal_body']:
544
                body = hook({
545
                    'target_branch': self.target_branch,
546
                    'modified_files_callback': list_modified_files,
547
                    'old_body': body,
548
                })
549
            return body
550
551
    def check_proposal(self):
552
        """Check that the submission is sensible."""
553
        if self.source_branch_lp.self_link == self.target_branch_lp.self_link:
554
            raise errors.BzrCommandError(
555
                'Source and target branches must be different.')
556
        for mp in self.source_branch_lp.landing_targets:
557
            if mp.queue_status in ('Merged', 'Rejected'):
558
                continue
559
            if mp.target_branch.self_link == self.target_branch_lp.self_link:
560
                raise MergeProposalExists(lp_api.canonical_url(mp))
561
562
    def approve_proposal(self, mp):
563
        with self.source_branch.lock_read():
564
            _call_webservice(
565
                mp.createComment,
566
                vote=u'Approve',
567
                subject='', # Use the default subject.
568
                content=u"Rubberstamp! Proposer approves of own proposal.")
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
569
            _call_webservice(
570
                mp.setStatus, status=u'Approved',
571
                revid=self.source_branch.last_revision())
0.431.24 by Jelmer Vernooij
Support git merge proposals.
572
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
573
    def create_proposal(self, description, reviewers=None, labels=None,
574
                        prerequisite_branch=None):
0.431.24 by Jelmer Vernooij
Support git merge proposals.
575
        """Perform the submission."""
576
        if labels:
577
            raise LabelsUnsupported()
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
578
        if prerequisite_branch is not None:
579
            (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.
580
        else:
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
581
            prereq_branch_lp = None
0.431.24 by Jelmer Vernooij
Support git merge proposals.
582
        if reviewers is None:
583
            reviewers = []
584
        try:
585
            mp = _call_webservice(
586
                self.source_branch_lp.createMergeProposal,
587
                merge_target=self.target_branch_lp,
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
588
                merge_prerequisite=prereq_branch_lp,
0.431.24 by Jelmer Vernooij
Support git merge proposals.
589
                initial_comment=description.strip().encode('utf-8'),
590
                commit_message=self.commit_message,
591
                needs_review=True,
592
                reviewers=[self.launchpad.people[reviewer].self_link
593
                           for reviewer in reviewers],
594
                review_types=[None for reviewer in reviewers])
0.431.58 by Jelmer Vernooij
Fix python3 compatibility.
595
        except WebserviceFailure as e:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
596
            # Urgh.
597
            if ('There is already a branch merge proposal '
7211.13.7 by Jelmer Vernooij
Fix formatting.
598
                    'registered for branch ') in e.message:
0.431.24 by Jelmer Vernooij
Support git merge proposals.
599
                raise MergeProposalExists(self.source_branch.user_url)
600
            raise
601
        if self.approve:
602
            self.approve_proposal(mp)
603
        if self.fixes:
604
            if self.fixes.startswith('lp:'):
605
                self.fixes = self.fixes[3:]
606
            _call_webservice(
607
                mp.linkBug,
608
                bug=self.launchpad.bugs[int(self.fixes)])
0.431.46 by Jelmer Vernooij
Add MergeProposal.is_merged.
609
        return LaunchpadMergeProposal(mp)
0.431.56 by Jelmer Vernooij
Add support for prerequisite branches.
610
611
612
def modified_files(old_tree, new_tree):
613
    """Return a list of paths in the new tree with modified contents."""
614
    for f, (op, path), c, v, p, n, (ok, k), e in new_tree.iter_changes(
615
            old_tree):
616
        if c and k == 'file':
617
            yield str(path)