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