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