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