/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

Add simple tests and docstrings for GraphWalker.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
from bzrlib.lazy_import import lazy_import
18
 
lazy_import(globals(), """
19
 
from bzrlib import (
20
 
    debug,
21
 
    )
22
 
from bzrlib.store import revision
23
 
from bzrlib.store.revision.knit import KnitRevisionStore
24
 
""")
25
 
from bzrlib import (
26
 
    bzrdir,
27
 
    deprecated_graph,
28
 
    errors,
29
 
    knit,
30
 
    lockable_files,
31
 
    lockdir,
32
 
    osutils,
33
 
    symbol_versioning,
34
 
    transactions,
35
 
    xml5,
36
 
    xml6,
37
 
    xml7,
38
 
    )
39
 
 
40
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
41
 
from bzrlib.repository import (
42
 
    CommitBuilder,
43
 
    MetaDirRepository,
44
 
    MetaDirRepositoryFormat,
45
 
    RepositoryFormat,
46
 
    RootCommitBuilder,
47
 
    )
48
 
import bzrlib.revision as _mod_revision
49
 
from bzrlib.store.versioned import VersionedFileStore
50
 
from bzrlib.trace import mutter, mutter_callsite
51
 
from bzrlib.util import bencode
52
 
 
53
 
 
54
 
class _KnitParentsProvider(object):
55
 
 
56
 
    def __init__(self, knit):
57
 
        self._knit = knit
58
 
 
59
 
    def __repr__(self):
60
 
        return 'KnitParentsProvider(%r)' % self._knit
61
 
 
62
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
63
 
    def get_parents(self, revision_ids):
64
 
        """See graph._StackedParentsProvider.get_parents"""
65
 
        parent_map = self.get_parent_map(revision_ids)
66
 
        return [parent_map.get(r, None) for r in revision_ids]
67
 
 
68
 
    def get_parent_map(self, keys):
69
 
        """See graph._StackedParentsProvider.get_parent_map"""
70
 
        parent_map = {}
71
 
        for revision_id in keys:
72
 
            if revision_id == _mod_revision.NULL_REVISION:
73
 
                parent_map[revision_id] = []
74
 
            else:
75
 
                try:
76
 
                    parents = self._knit.get_parents_with_ghosts(revision_id)
77
 
                except errors.RevisionNotPresent:
78
 
                    pass
79
 
                else:
80
 
                    if len(parents) == 0:
81
 
                        parents = [_mod_revision.NULL_REVISION]
82
 
                parent_map[revision_id] = parents
83
 
        return parent_map
84
 
 
85
 
 
86
 
class KnitRepository(MetaDirRepository):
87
 
    """Knit format repository."""
88
 
 
89
 
    # These attributes are inherited from the Repository base class. Setting
90
 
    # them to None ensures that if the constructor is changed to not initialize
91
 
    # them, or a subclass fails to call the constructor, that an error will
92
 
    # occur rather than the system working but generating incorrect data.
93
 
    _commit_builder_class = None
94
 
    _serializer = None
95
 
 
96
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
97
 
        control_store, text_store, _commit_builder_class, _serializer):
98
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
99
 
            _revision_store, control_store, text_store)
100
 
        self._commit_builder_class = _commit_builder_class
101
 
        self._serializer = _serializer
102
 
        self._reconcile_fixes_text_parents = True
103
 
 
104
 
    def _warn_if_deprecated(self):
105
 
        # This class isn't deprecated
106
 
        pass
107
 
 
108
 
    def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
109
 
        return inv_vf.add_lines_with_ghosts(revid, parents, lines,
110
 
            check_content=check_content)[0]
111
 
 
112
 
    @needs_read_lock
113
 
    def _all_revision_ids(self):
114
 
        """See Repository.all_revision_ids()."""
115
 
        # Knits get the revision graph from the index of the revision knit, so
116
 
        # it's always possible even if they're on an unlistable transport.
117
 
        return self._revision_store.all_revision_ids(self.get_transaction())
118
 
 
119
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
120
 
        """Find file_id(s) which are involved in the changes between revisions.
121
 
 
122
 
        This determines the set of revisions which are involved, and then
123
 
        finds all file ids affected by those revisions.
124
 
        """
125
 
        vf = self._get_revision_vf()
126
 
        from_set = set(vf.get_ancestry(from_revid))
127
 
        to_set = set(vf.get_ancestry(to_revid))
128
 
        changed = to_set.difference(from_set)
129
 
        return self._fileid_involved_by_set(changed)
130
 
 
131
 
    def fileid_involved(self, last_revid=None):
132
 
        """Find all file_ids modified in the ancestry of last_revid.
133
 
 
134
 
        :param last_revid: If None, last_revision() will be used.
135
 
        """
136
 
        if not last_revid:
137
 
            changed = set(self.all_revision_ids())
138
 
        else:
139
 
            changed = set(self.get_ancestry(last_revid))
140
 
        if None in changed:
141
 
            changed.remove(None)
142
 
        return self._fileid_involved_by_set(changed)
143
 
 
144
 
    @needs_read_lock
145
 
    def get_ancestry(self, revision_id, topo_sorted=True):
146
 
        """Return a list of revision-ids integrated by a revision.
147
 
        
148
 
        This is topologically sorted, unless 'topo_sorted' is specified as
149
 
        False.
150
 
        """
151
 
        if _mod_revision.is_null(revision_id):
152
 
            return [None]
153
 
        vf = self._get_revision_vf()
154
 
        try:
155
 
            return [None] + vf.get_ancestry(revision_id, topo_sorted)
156
 
        except errors.RevisionNotPresent:
157
 
            raise errors.NoSuchRevision(self, revision_id)
158
 
 
159
 
    @needs_read_lock
160
 
    def get_data_stream(self, revision_ids):
161
 
        """See Repository.get_data_stream."""
162
 
        item_keys = self.item_keys_introduced_by(revision_ids)
163
 
        for knit_kind, file_id, versions in item_keys:
164
 
            name = (knit_kind,)
165
 
            if knit_kind == 'file':
166
 
                name = ('file', file_id)
167
 
                knit = self.weave_store.get_weave_or_empty(
168
 
                    file_id, self.get_transaction())
169
 
            elif knit_kind == 'inventory':
170
 
                knit = self.get_inventory_weave()
171
 
            elif knit_kind == 'revisions':
172
 
                knit = self._revision_store.get_revision_file(
173
 
                    self.get_transaction())
174
 
            elif knit_kind == 'signatures':
175
 
                knit = self._revision_store.get_signature_file(
176
 
                    self.get_transaction())
177
 
            else:
178
 
                raise AssertionError('Unknown knit kind %r' % (knit_kind,))
179
 
            yield name, _get_stream_as_bytes(knit, versions)
180
 
 
181
 
    @needs_read_lock
182
 
    def get_revision(self, revision_id):
183
 
        """Return the Revision object for a named revision"""
184
 
        revision_id = osutils.safe_revision_id(revision_id)
185
 
        return self.get_revision_reconcile(revision_id)
186
 
 
187
 
    @needs_read_lock
188
 
    def get_revision_graph(self, revision_id=None):
189
 
        """Return a dictionary containing the revision graph.
190
 
 
191
 
        :param revision_id: The revision_id to get a graph from. If None, then
192
 
        the entire revision graph is returned. This is a deprecated mode of
193
 
        operation and will be removed in the future.
194
 
        :return: a dictionary of revision_id->revision_parents_list.
195
 
        """
196
 
        if 'evil' in debug.debug_flags:
197
 
            mutter_callsite(3,
198
 
                "get_revision_graph scales with size of history.")
199
 
        # special case NULL_REVISION
200
 
        if revision_id == _mod_revision.NULL_REVISION:
201
 
            return {}
202
 
        a_weave = self._get_revision_vf()
203
 
        if revision_id is None:
204
 
            return a_weave.get_graph()
205
 
        if revision_id not in a_weave:
206
 
            raise errors.NoSuchRevision(self, revision_id)
207
 
        else:
208
 
            # add what can be reached from revision_id
209
 
            return a_weave.get_graph([revision_id])
210
 
 
211
 
    @needs_read_lock
212
 
    def get_revision_graph_with_ghosts(self, revision_ids=None):
213
 
        """Return a graph of the revisions with ghosts marked as applicable.
214
 
 
215
 
        :param revision_ids: an iterable of revisions to graph or None for all.
216
 
        :return: a Graph object with the graph reachable from revision_ids.
217
 
        """
218
 
        if 'evil' in debug.debug_flags:
219
 
            mutter_callsite(3,
220
 
                "get_revision_graph_with_ghosts scales with size of history.")
221
 
        result = deprecated_graph.Graph()
222
 
        vf = self._get_revision_vf()
223
 
        versions = set(vf.versions())
224
 
        if not revision_ids:
225
 
            pending = set(self.all_revision_ids())
226
 
            required = set([])
227
 
        else:
228
 
            pending = set(revision_ids)
229
 
            # special case NULL_REVISION
230
 
            if _mod_revision.NULL_REVISION in pending:
231
 
                pending.remove(_mod_revision.NULL_REVISION)
232
 
            required = set(pending)
233
 
        done = set([])
234
 
        while len(pending):
235
 
            revision_id = pending.pop()
236
 
            if not revision_id in versions:
237
 
                if revision_id in required:
238
 
                    raise errors.NoSuchRevision(self, revision_id)
239
 
                # a ghost
240
 
                result.add_ghost(revision_id)
241
 
                # mark it as done so we don't try for it again.
242
 
                done.add(revision_id)
243
 
                continue
244
 
            parent_ids = vf.get_parents_with_ghosts(revision_id)
245
 
            for parent_id in parent_ids:
246
 
                # is this queued or done ?
247
 
                if (parent_id not in pending and
248
 
                    parent_id not in done):
249
 
                    # no, queue it.
250
 
                    pending.add(parent_id)
251
 
            result.add_node(revision_id, parent_ids)
252
 
            done.add(revision_id)
253
 
        return result
254
 
 
255
 
    def _get_revision_vf(self):
256
 
        """:return: a versioned file containing the revisions."""
257
 
        vf = self._revision_store.get_revision_file(self.get_transaction())
258
 
        return vf
259
 
 
260
 
    def _get_history_vf(self):
261
 
        """Get a versionedfile whose history graph reflects all revisions.
262
 
 
263
 
        For knit repositories, this is the revision knit.
264
 
        """
265
 
        return self._get_revision_vf()
266
 
 
267
 
    @needs_write_lock
268
 
    def reconcile(self, other=None, thorough=False):
269
 
        """Reconcile this repository."""
270
 
        from bzrlib.reconcile import KnitReconciler
271
 
        reconciler = KnitReconciler(self, thorough=thorough)
272
 
        reconciler.reconcile()
273
 
        return reconciler
274
 
    
275
 
    def revision_parents(self, revision_id):
276
 
        return self._get_revision_vf().get_parents(revision_id)
277
 
 
278
 
    def _make_parents_provider(self):
279
 
        return _KnitParentsProvider(self._get_revision_vf())
280
 
 
281
 
    def _find_inconsistent_revision_parents(self):
282
 
        """Find revisions with different parent lists in the revision object
283
 
        and in the index graph.
284
 
 
285
 
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
286
 
            parents-in-revision).
287
 
        """
288
 
        assert self.is_locked()
289
 
        vf = self._get_revision_vf()
290
 
        for index_version in vf.versions():
291
 
            parents_according_to_index = tuple(vf.get_parents_with_ghosts(
292
 
                index_version))
293
 
            revision = self.get_revision(index_version)
294
 
            parents_according_to_revision = tuple(revision.parent_ids)
295
 
            if parents_according_to_index != parents_according_to_revision:
296
 
                yield (index_version, parents_according_to_index,
297
 
                    parents_according_to_revision)
298
 
 
299
 
    def _check_for_inconsistent_revision_parents(self):
300
 
        inconsistencies = list(self._find_inconsistent_revision_parents())
301
 
        if inconsistencies:
302
 
            raise errors.BzrCheckError(
303
 
                "Revision knit has inconsistent parents.")
304
 
 
305
 
    def revision_graph_can_have_wrong_parents(self):
306
 
        # The revision.kndx could potentially claim a revision has a different
307
 
        # parent to the revision text.
308
 
        return True
309
 
 
310
 
 
311
 
class RepositoryFormatKnit(MetaDirRepositoryFormat):
312
 
    """Bzr repository knit format (generalized). 
313
 
 
314
 
    This repository format has:
315
 
     - knits for file texts and inventory
316
 
     - hash subdirectory based stores.
317
 
     - knits for revisions and signatures
318
 
     - TextStores for revisions and signatures.
319
 
     - a format marker of its own
320
 
     - an optional 'shared-storage' flag
321
 
     - an optional 'no-working-trees' flag
322
 
     - a LockDir lock
323
 
    """
324
 
 
325
 
    # Set this attribute in derived classes to control the repository class
326
 
    # created by open and initialize.
327
 
    repository_class = None
328
 
    # Set this attribute in derived classes to control the
329
 
    # _commit_builder_class that the repository objects will have passed to
330
 
    # their constructor.
331
 
    _commit_builder_class = None
332
 
    # Set this attribute in derived clases to control the _serializer that the
333
 
    # repository objects will have passed to their constructor.
334
 
    _serializer = xml5.serializer_v5
335
 
    # Knit based repositories handle ghosts reasonably well.
336
 
    supports_ghosts = True
337
 
 
338
 
    def _get_control_store(self, repo_transport, control_files):
339
 
        """Return the control store for this repository."""
340
 
        return VersionedFileStore(
341
 
            repo_transport,
342
 
            prefixed=False,
343
 
            file_mode=control_files._file_mode,
344
 
            versionedfile_class=knit.KnitVersionedFile,
345
 
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
346
 
            )
347
 
 
348
 
    def _get_revision_store(self, repo_transport, control_files):
349
 
        """See RepositoryFormat._get_revision_store()."""
350
 
        versioned_file_store = VersionedFileStore(
351
 
            repo_transport,
352
 
            file_mode=control_files._file_mode,
353
 
            prefixed=False,
354
 
            precious=True,
355
 
            versionedfile_class=knit.KnitVersionedFile,
356
 
            versionedfile_kwargs={'delta':False,
357
 
                                  'factory':knit.KnitPlainFactory(),
358
 
                                 },
359
 
            escaped=True,
360
 
            )
361
 
        return KnitRevisionStore(versioned_file_store)
362
 
 
363
 
    def _get_text_store(self, transport, control_files):
364
 
        """See RepositoryFormat._get_text_store()."""
365
 
        return self._get_versioned_file_store('knits',
366
 
                                  transport,
367
 
                                  control_files,
368
 
                                  versionedfile_class=knit.KnitVersionedFile,
369
 
                                  versionedfile_kwargs={
370
 
                                      'create_parent_dir':True,
371
 
                                      'delay_create':True,
372
 
                                      'dir_mode':control_files._dir_mode,
373
 
                                  },
374
 
                                  escaped=True)
375
 
 
376
 
    def initialize(self, a_bzrdir, shared=False):
377
 
        """Create a knit format 1 repository.
378
 
 
379
 
        :param a_bzrdir: bzrdir to contain the new repository; must already
380
 
            be initialized.
381
 
        :param shared: If true the repository will be initialized as a shared
382
 
                       repository.
383
 
        """
384
 
        mutter('creating repository in %s.', a_bzrdir.transport.base)
385
 
        dirs = ['knits']
386
 
        files = []
387
 
        utf8_files = [('format', self.get_format_string())]
388
 
        
389
 
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
390
 
        repo_transport = a_bzrdir.get_repository_transport(None)
391
 
        control_files = lockable_files.LockableFiles(repo_transport,
392
 
                                'lock', lockdir.LockDir)
393
 
        control_store = self._get_control_store(repo_transport, control_files)
394
 
        transaction = transactions.WriteTransaction()
395
 
        # trigger a write of the inventory store.
396
 
        control_store.get_weave_or_empty('inventory', transaction)
397
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
398
 
        # the revision id here is irrelevant: it will not be stored, and cannot
399
 
        # already exist.
400
 
        _revision_store.has_revision_id('A', transaction)
401
 
        _revision_store.get_signature_file(transaction)
402
 
        return self.open(a_bzrdir=a_bzrdir, _found=True)
403
 
 
404
 
    def open(self, a_bzrdir, _found=False, _override_transport=None):
405
 
        """See RepositoryFormat.open().
406
 
        
407
 
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
408
 
                                    repository at a slightly different url
409
 
                                    than normal. I.e. during 'upgrade'.
410
 
        """
411
 
        if not _found:
412
 
            format = RepositoryFormat.find_format(a_bzrdir)
413
 
            assert format.__class__ ==  self.__class__
414
 
        if _override_transport is not None:
415
 
            repo_transport = _override_transport
416
 
        else:
417
 
            repo_transport = a_bzrdir.get_repository_transport(None)
418
 
        control_files = lockable_files.LockableFiles(repo_transport,
419
 
                                'lock', lockdir.LockDir)
420
 
        text_store = self._get_text_store(repo_transport, control_files)
421
 
        control_store = self._get_control_store(repo_transport, control_files)
422
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
423
 
        return self.repository_class(_format=self,
424
 
                              a_bzrdir=a_bzrdir,
425
 
                              control_files=control_files,
426
 
                              _revision_store=_revision_store,
427
 
                              control_store=control_store,
428
 
                              text_store=text_store,
429
 
                              _commit_builder_class=self._commit_builder_class,
430
 
                              _serializer=self._serializer)
431
 
 
432
 
 
433
 
class RepositoryFormatKnit1(RepositoryFormatKnit):
434
 
    """Bzr repository knit format 1.
435
 
 
436
 
    This repository format has:
437
 
     - knits for file texts and inventory
438
 
     - hash subdirectory based stores.
439
 
     - knits for revisions and signatures
440
 
     - TextStores for revisions and signatures.
441
 
     - a format marker of its own
442
 
     - an optional 'shared-storage' flag
443
 
     - an optional 'no-working-trees' flag
444
 
     - a LockDir lock
445
 
 
446
 
    This format was introduced in bzr 0.8.
447
 
    """
448
 
 
449
 
    repository_class = KnitRepository
450
 
    _commit_builder_class = CommitBuilder
451
 
    _serializer = xml5.serializer_v5
452
 
 
453
 
    def __ne__(self, other):
454
 
        return self.__class__ is not other.__class__
455
 
 
456
 
    def get_format_string(self):
457
 
        """See RepositoryFormat.get_format_string()."""
458
 
        return "Bazaar-NG Knit Repository Format 1"
459
 
 
460
 
    def get_format_description(self):
461
 
        """See RepositoryFormat.get_format_description()."""
462
 
        return "Knit repository format 1"
463
 
 
464
 
    def check_conversion_target(self, target_format):
465
 
        pass
466
 
 
467
 
 
468
 
class RepositoryFormatKnit3(RepositoryFormatKnit):
469
 
    """Bzr repository knit format 3.
470
 
 
471
 
    This repository format has:
472
 
     - knits for file texts and inventory
473
 
     - hash subdirectory based stores.
474
 
     - knits for revisions and signatures
475
 
     - TextStores for revisions and signatures.
476
 
     - a format marker of its own
477
 
     - an optional 'shared-storage' flag
478
 
     - an optional 'no-working-trees' flag
479
 
     - a LockDir lock
480
 
     - support for recording full info about the tree root
481
 
     - support for recording tree-references
482
 
    """
483
 
 
484
 
    repository_class = KnitRepository
485
 
    _commit_builder_class = RootCommitBuilder
486
 
    rich_root_data = True
487
 
    supports_tree_reference = True
488
 
    _serializer = xml7.serializer_v7
489
 
 
490
 
    def _get_matching_bzrdir(self):
491
 
        return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
492
 
 
493
 
    def _ignore_setting_bzrdir(self, format):
494
 
        pass
495
 
 
496
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
497
 
 
498
 
    def check_conversion_target(self, target_format):
499
 
        if not target_format.rich_root_data:
500
 
            raise errors.BadConversionTarget(
501
 
                'Does not support rich root data.', target_format)
502
 
        if not getattr(target_format, 'supports_tree_reference', False):
503
 
            raise errors.BadConversionTarget(
504
 
                'Does not support nested trees', target_format)
505
 
            
506
 
    def get_format_string(self):
507
 
        """See RepositoryFormat.get_format_string()."""
508
 
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
509
 
 
510
 
    def get_format_description(self):
511
 
        """See RepositoryFormat.get_format_description()."""
512
 
        return "Knit repository format 3"
513
 
 
514
 
 
515
 
class RepositoryFormatKnit4(RepositoryFormatKnit):
516
 
    """Bzr repository knit format 4.
517
 
 
518
 
    This repository format has everything in format 3, except for
519
 
    tree-references:
520
 
     - knits for file texts and inventory
521
 
     - hash subdirectory based stores.
522
 
     - knits for revisions and signatures
523
 
     - TextStores for revisions and signatures.
524
 
     - a format marker of its own
525
 
     - an optional 'shared-storage' flag
526
 
     - an optional 'no-working-trees' flag
527
 
     - a LockDir lock
528
 
     - support for recording full info about the tree root
529
 
    """
530
 
 
531
 
    repository_class = KnitRepository
532
 
    _commit_builder_class = RootCommitBuilder
533
 
    rich_root_data = True
534
 
    supports_tree_reference = False
535
 
    _serializer = xml6.serializer_v6
536
 
 
537
 
    def _get_matching_bzrdir(self):
538
 
        return bzrdir.format_registry.make_bzrdir('rich-root')
539
 
 
540
 
    def _ignore_setting_bzrdir(self, format):
541
 
        pass
542
 
 
543
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
544
 
 
545
 
    def check_conversion_target(self, target_format):
546
 
        if not target_format.rich_root_data:
547
 
            raise errors.BadConversionTarget(
548
 
                'Does not support rich root data.', target_format)
549
 
 
550
 
    def get_format_string(self):
551
 
        """See RepositoryFormat.get_format_string()."""
552
 
        return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
553
 
 
554
 
    def get_format_description(self):
555
 
        """See RepositoryFormat.get_format_description()."""
556
 
        return "Knit repository format 4"
557
 
 
558
 
 
559
 
def _get_stream_as_bytes(knit, required_versions):
560
 
    """Generate a serialised data stream.
561
 
 
562
 
    The format is a bencoding of a list.  The first element of the list is a
563
 
    string of the format signature, then each subsequent element is a list
564
 
    corresponding to a record.  Those lists contain:
565
 
 
566
 
      * a version id
567
 
      * a list of options
568
 
      * a list of parents
569
 
      * the bytes
570
 
 
571
 
    :returns: a bencoded list.
572
 
    """
573
 
    knit_stream = knit.get_data_stream(required_versions)
574
 
    format_signature, data_list, callable = knit_stream
575
 
    data = []
576
 
    data.append(format_signature)
577
 
    for version, options, length, parents in data_list:
578
 
        data.append([version, options, parents, callable(length)])
579
 
    return bencode.bencode(data)