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