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