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