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