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