/brz/remove-bazaar

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