~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-10-24 12:49:17 UTC
  • mfrom: (2935.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071024124917-xb75eckyxx6vkrlg
Makefile fixes - hooks.html generation & allow python to be overridden (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 by Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
22
 
 
23
 
from copy import deepcopy
24
 
from unittest import TestSuite
25
 
 
26
 
 
27
 
import bzrlib.errors as errors
 
22
from bzrlib.lazy_import import lazy_import
 
23
lazy_import(globals(), """
 
24
 
 
25
from bzrlib import (
 
26
    errors,
 
27
    osutils,
 
28
    multiparent,
 
29
    tsort,
 
30
    revision,
 
31
    ui,
 
32
    )
 
33
from bzrlib.transport.memory import MemoryTransport
 
34
""")
 
35
 
 
36
from cStringIO import StringIO
 
37
 
28
38
from bzrlib.inter import InterObject
29
39
from bzrlib.textmerge import TextMerge
30
 
from bzrlib.transport.memory import MemoryTransport
31
 
from bzrlib.tsort import topo_sort
32
 
from bzrlib import ui
33
 
from bzrlib.symbol_versioning import (deprecated_function,
34
 
        deprecated_method,
35
 
        zero_eight,
36
 
        )
37
40
 
38
41
 
39
42
class VersionedFile(object):
54
57
        self.finished = False
55
58
        self._access_mode = access_mode
56
59
 
 
60
    @staticmethod
 
61
    def check_not_reserved_id(version_id):
 
62
        revision.check_not_reserved_id(version_id)
 
63
 
57
64
    def copy_to(self, name, transport):
58
65
        """Copy this versioned file to name on transport."""
59
66
        raise NotImplementedError(self.copy_to)
60
67
 
61
 
    @deprecated_method(zero_eight)
62
 
    def names(self):
63
 
        """Return a list of all the versions in this versioned file.
64
 
 
65
 
        Please use versionedfile.versions() now.
66
 
        """
67
 
        return self.versions()
68
 
 
69
68
    def versions(self):
70
69
        """Return a unsorted list of versions."""
71
70
        raise NotImplementedError(self.versions)
78
77
        """Returns whether version is present."""
79
78
        raise NotImplementedError(self.has_version)
80
79
 
81
 
    def add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
82
 
        """Add a text to the versioned file via a pregenerated delta.
83
 
 
84
 
        :param version_id: The version id being added.
85
 
        :param parents: The parents of the version_id.
86
 
        :param delta_parent: The parent this delta was created against.
87
 
        :param sha1: The sha1 of the full text.
88
 
        :param delta: The delta instructions. See get_delta for details.
89
 
        """
90
 
        self._check_write_ok()
91
 
        if self.has_version(version_id):
92
 
            raise errors.RevisionAlreadyPresent(version_id, self)
93
 
        return self._add_delta(version_id, parents, delta_parent, sha1, noeol, delta)
94
 
 
95
 
    def _add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
96
 
        """Class specific routine to add a delta.
97
 
 
98
 
        This generic version simply applies the delta to the delta_parent and
99
 
        then inserts it.
100
 
        """
101
 
        # strip annotation from delta
102
 
        new_delta = []
103
 
        for start, stop, delta_len, delta_lines in delta:
104
 
            new_delta.append((start, stop, delta_len, [text for origin, text in delta_lines]))
105
 
        if delta_parent is not None:
106
 
            parent_full = self.get_lines(delta_parent)
107
 
        else:
108
 
            parent_full = []
109
 
        new_full = self._apply_delta(parent_full, new_delta)
110
 
        # its impossible to have noeol on an empty file
111
 
        if noeol and new_full[-1][-1] == '\n':
112
 
            new_full[-1] = new_full[-1][:-1]
113
 
        self.add_lines(version_id, parents, new_full)
114
 
 
115
 
    def add_lines(self, version_id, parents, lines, parent_texts=None):
 
80
    def add_lines(self, version_id, parents, lines, parent_texts=None,
 
81
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
82
        check_content=True):
116
83
        """Add a single text on top of the versioned file.
117
84
 
118
85
        Must raise RevisionAlreadyPresent if the new version is
120
87
 
121
88
        Must raise RevisionNotPresent if any of the given parents are
122
89
        not present in file history.
 
90
 
 
91
        :param lines: A list of lines. Each line must be a bytestring. And all
 
92
            of them except the last must be terminated with \n and contain no
 
93
            other \n's. The last line may either contain no \n's or a single
 
94
            terminated \n. If the lines list does meet this constraint the add
 
95
            routine may error or may succeed - but you will be unable to read
 
96
            the data back accurately. (Checking the lines have been split
 
97
            correctly is expensive and extremely unlikely to catch bugs so it
 
98
            is not done at runtime unless check_content is True.)
123
99
        :param parent_texts: An optional dictionary containing the opaque 
124
 
             representations of some or all of the parents of 
125
 
             version_id to allow delta optimisations. 
126
 
             VERY IMPORTANT: the texts must be those returned
127
 
             by add_lines or data corruption can be caused.
128
 
        :return: An opaque representation of the inserted version which can be
129
 
                 provided back to future add_lines calls in the parent_texts
130
 
                 dictionary.
 
100
            representations of some or all of the parents of version_id to
 
101
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
 
102
            returned by add_lines or data corruption can be caused.
 
103
        :param left_matching_blocks: a hint about which areas are common
 
104
            between the text and its left-hand-parent.  The format is
 
105
            the SequenceMatcher.get_matching_blocks format.
 
106
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
107
            the versioned file if the digest of the lines matches this.
 
108
        :param random_id: If True a random id has been selected rather than
 
109
            an id determined by some deterministic process such as a converter
 
110
            from a foreign VCS. When True the backend may choose not to check
 
111
            for uniqueness of the resulting key within the versioned file, so
 
112
            this should only be done when the result is expected to be unique
 
113
            anyway.
 
114
        :param check_content: If True, the lines supplied are verified to be
 
115
            bytestrings that are correctly formed lines.
 
116
        :return: The text sha1, the number of bytes in the text, and an opaque
 
117
                 representation of the inserted version which can be provided
 
118
                 back to future add_lines calls in the parent_texts dictionary.
131
119
        """
132
120
        self._check_write_ok()
133
 
        return self._add_lines(version_id, parents, lines, parent_texts)
 
121
        return self._add_lines(version_id, parents, lines, parent_texts,
 
122
            left_matching_blocks, nostore_sha, random_id, check_content)
134
123
 
135
 
    def _add_lines(self, version_id, parents, lines, parent_texts):
 
124
    def _add_lines(self, version_id, parents, lines, parent_texts,
 
125
        left_matching_blocks, nostore_sha, random_id, check_content):
136
126
        """Helper to do the class specific add_lines."""
137
127
        raise NotImplementedError(self.add_lines)
138
128
 
139
129
    def add_lines_with_ghosts(self, version_id, parents, lines,
140
 
                              parent_texts=None):
 
130
        parent_texts=None, nostore_sha=None, random_id=False,
 
131
        check_content=True):
141
132
        """Add lines to the versioned file, allowing ghosts to be present.
142
133
        
143
 
        This takes the same parameters as add_lines.
 
134
        This takes the same parameters as add_lines and returns the same.
144
135
        """
145
136
        self._check_write_ok()
146
137
        return self._add_lines_with_ghosts(version_id, parents, lines,
147
 
                                           parent_texts)
 
138
            parent_texts, nostore_sha, random_id, check_content)
148
139
 
149
 
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts):
 
140
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
 
141
        nostore_sha, random_id, check_content):
150
142
        """Helper to do class specific add_lines_with_ghosts."""
151
143
        raise NotImplementedError(self.add_lines_with_ghosts)
152
144
 
211
203
        """
212
204
        raise NotImplementedError(self.create_empty)
213
205
 
214
 
    def fix_parents(self, version, new_parents):
215
 
        """Fix the parents list for version.
216
 
        
217
 
        This is done by appending a new version to the index
218
 
        with identical data except for the parents list.
219
 
        the parents list must be a superset of the current
220
 
        list.
221
 
        """
222
 
        self._check_write_ok()
223
 
        return self._fix_parents(version, new_parents)
224
 
 
225
 
    def _fix_parents(self, version, new_parents):
226
 
        """Helper for fix_parents."""
227
 
        raise NotImplementedError(self.fix_parents)
228
 
 
229
 
    def get_delta(self, version):
230
 
        """Get a delta for constructing version from some other version.
231
 
        
232
 
        :return: (delta_parent, sha1, noeol, delta)
233
 
        Where delta_parent is a version id or None to indicate no parent.
234
 
        """
235
 
        raise NotImplementedError(self.get_delta)
236
 
 
237
 
    def get_deltas(self, versions):
238
 
        """Get multiple deltas at once for constructing versions.
239
 
        
240
 
        :return: dict(version_id:(delta_parent, sha1, noeol, delta))
241
 
        Where delta_parent is a version id or None to indicate no parent, and
242
 
        version_id is the version_id created by that delta.
243
 
        """
244
 
        result = {}
245
 
        for version in versions:
246
 
            result[version] = self.get_delta(version)
247
 
        return result
 
206
    def get_format_signature(self):
 
207
        """Get a text description of the data encoding in this file.
 
208
        
 
209
        :since: 0.90
 
210
        """
 
211
        raise NotImplementedError(self.get_format_signature)
 
212
 
 
213
    def make_mpdiffs(self, version_ids):
 
214
        """Create multiparent diffs for specified versions."""
 
215
        knit_versions = set()
 
216
        for version_id in version_ids:
 
217
            knit_versions.add(version_id)
 
218
            knit_versions.update(self.get_parents(version_id))
 
219
        lines = dict(zip(knit_versions,
 
220
            self._get_lf_split_line_list(knit_versions)))
 
221
        diffs = []
 
222
        for version_id in version_ids:
 
223
            target = lines[version_id]
 
224
            parents = [lines[p] for p in self.get_parents(version_id)]
 
225
            if len(parents) > 0:
 
226
                left_parent_blocks = self._extract_blocks(version_id,
 
227
                                                          parents[0], target)
 
228
            else:
 
229
                left_parent_blocks = None
 
230
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
 
231
                         left_parent_blocks))
 
232
        return diffs
 
233
 
 
234
    def _extract_blocks(self, version_id, source, target):
 
235
        return None
 
236
 
 
237
    def add_mpdiffs(self, records):
 
238
        """Add mpdiffs to this VersionedFile.
 
239
 
 
240
        Records should be iterables of version, parents, expected_sha1,
 
241
        mpdiff. mpdiff should be a MultiParent instance.
 
242
        """
 
243
        # Does this need to call self._check_write_ok()? (IanC 20070919)
 
244
        vf_parents = {}
 
245
        mpvf = multiparent.MultiMemoryVersionedFile()
 
246
        versions = []
 
247
        for version, parent_ids, expected_sha1, mpdiff in records:
 
248
            versions.append(version)
 
249
            mpvf.add_diff(mpdiff, version, parent_ids)
 
250
        needed_parents = set()
 
251
        for version, parent_ids, expected_sha1, mpdiff in records:
 
252
            needed_parents.update(p for p in parent_ids
 
253
                                  if not mpvf.has_version(p))
 
254
        for parent_id, lines in zip(needed_parents,
 
255
                                 self._get_lf_split_line_list(needed_parents)):
 
256
            mpvf.add_version(lines, parent_id, [])
 
257
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
 
258
            zip(records, mpvf.get_line_list(versions)):
 
259
            if len(parent_ids) == 1:
 
260
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
 
261
                    mpvf.get_diff(parent_ids[0]).num_lines()))
 
262
            else:
 
263
                left_matching_blocks = None
 
264
            _, _, version_text = self.add_lines(version, parent_ids, lines,
 
265
                vf_parents, left_matching_blocks=left_matching_blocks)
 
266
            vf_parents[version] = version_text
 
267
        for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
 
268
             zip(records, self.get_sha1s(versions)):
 
269
            if expected_sha1 != sha1:
 
270
                raise errors.VersionedFileInvalidChecksum(version)
248
271
 
249
272
    def get_sha1(self, version_id):
250
273
        """Get the stored sha1 sum for the given revision.
251
274
        
252
 
        :param name: The name of the version to lookup
 
275
        :param version_id: The name of the version to lookup
253
276
        """
254
277
        raise NotImplementedError(self.get_sha1)
255
278
 
 
279
    def get_sha1s(self, version_ids):
 
280
        """Get the stored sha1 sums for the given revisions.
 
281
 
 
282
        :param version_ids: The names of the versions to lookup
 
283
        :return: a list of sha1s in order according to the version_ids
 
284
        """
 
285
        raise NotImplementedError(self.get_sha1s)
 
286
 
256
287
    def get_suffixes(self):
257
288
        """Return the file suffixes associated with this versioned file."""
258
289
        raise NotImplementedError(self.get_suffixes)
282
313
        """
283
314
        raise NotImplementedError(self.get_lines)
284
315
 
285
 
    def get_ancestry(self, version_ids):
 
316
    def _get_lf_split_line_list(self, version_ids):
 
317
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
 
318
 
 
319
    def get_ancestry(self, version_ids, topo_sorted=True):
286
320
        """Return a list of all ancestors of given version(s). This
287
321
        will not include the null revision.
288
322
 
 
323
        This list will not be topologically sorted if topo_sorted=False is
 
324
        passed.
 
325
 
289
326
        Must raise RevisionNotPresent if any of the given versions are
290
327
        not present in file history."""
291
328
        if isinstance(version_ids, basestring):
311
348
        :param version_ids: Versions to select.
312
349
                            None means retrieve all versions.
313
350
        """
 
351
        if version_ids is None:
 
352
            return dict(self.iter_parents(self.versions()))
314
353
        result = {}
315
 
        if version_ids is None:
316
 
            for version in self.versions():
317
 
                result[version] = self.get_parents(version)
318
 
        else:
319
 
            pending = set(version_ids)
320
 
            while pending:
321
 
                version = pending.pop()
322
 
                if version in result:
323
 
                    continue
324
 
                parents = self.get_parents(version)
 
354
        pending = set(version_ids)
 
355
        while pending:
 
356
            this_iteration = pending
 
357
            pending = set()
 
358
            for version, parents in self.iter_parents(this_iteration):
 
359
                result[version] = parents
325
360
                for parent in parents:
326
361
                    if parent in result:
327
362
                        continue
328
363
                    pending.add(parent)
329
 
                result[version] = parents
330
364
        return result
331
365
 
332
366
    def get_graph_with_ghosts(self):
337
371
        """
338
372
        raise NotImplementedError(self.get_graph_with_ghosts)
339
373
 
340
 
    @deprecated_method(zero_eight)
341
 
    def parent_names(self, version):
342
 
        """Return version names for parents of a version.
343
 
        
344
 
        See get_parents for the current api.
345
 
        """
346
 
        return self.get_parents(version)
347
 
 
348
374
    def get_parents(self, version_id):
349
375
        """Return version names for parents of a version.
350
376
 
368
394
        """Yield list of (version-id, line) pairs for the specified
369
395
        version.
370
396
 
371
 
        Must raise RevisionNotPresent if any of the given versions are
 
397
        Must raise RevisionNotPresent if the given version is
372
398
        not present in file history.
373
399
        """
374
400
        raise NotImplementedError(self.annotate_iter)
376
402
    def annotate(self, version_id):
377
403
        return list(self.annotate_iter(version_id))
378
404
 
379
 
    def _apply_delta(self, lines, delta):
380
 
        """Apply delta to lines."""
381
 
        lines = list(lines)
382
 
        offset = 0
383
 
        for start, end, count, delta_lines in delta:
384
 
            lines[offset+start:offset+end] = delta_lines
385
 
            offset = offset + (start - end) + count
386
 
        return lines
387
 
 
388
405
    def join(self, other, pb=None, msg=None, version_ids=None,
389
406
             ignore_missing=False):
390
407
        """Integrate versions from other into this versioned file.
393
410
        incorporated into this versioned file.
394
411
 
395
412
        Must raise RevisionNotPresent if any of the specified versions
396
 
        are not present in the other files history unless ignore_missing
397
 
        is supplied when they are silently skipped.
 
413
        are not present in the other file's history unless ignore_missing
 
414
        is supplied in which case they are silently skipped.
398
415
        """
399
416
        self._check_write_ok()
400
417
        return InterVersionedFile.get(other, self).join(
403
420
            version_ids,
404
421
            ignore_missing)
405
422
 
406
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None):
 
423
    def iter_lines_added_or_present_in_versions(self, version_ids=None, 
 
424
                                                pb=None):
407
425
        """Iterate over the lines in the versioned file from version_ids.
408
426
 
409
427
        This may return lines from other versions, and does not return the
412
430
        thinks is relevant, but given that such hints are just guesses,
413
431
        its better not to have it if we don't need it.
414
432
 
 
433
        If a progress bar is supplied, it may be used to indicate progress.
 
434
        The caller is responsible for cleaning up progress bars (because this
 
435
        is an iterator).
 
436
 
415
437
        NOTES: Lines are normalised: they will all have \n terminators.
416
438
               Lines are returned in arbitrary order.
417
439
        """
418
440
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
419
441
 
 
442
    def iter_parents(self, version_ids):
 
443
        """Iterate through the parents for many version ids.
 
444
 
 
445
        :param version_ids: An iterable yielding version_ids.
 
446
        :return: An iterator that yields (version_id, parents). Requested 
 
447
            version_ids not present in the versioned file are simply skipped.
 
448
            The order is undefined, allowing for different optimisations in
 
449
            the underlying implementation.
 
450
        """
 
451
        for version_id in version_ids:
 
452
            try:
 
453
                yield version_id, tuple(self.get_parents(version_id))
 
454
            except errors.RevisionNotPresent:
 
455
                pass
 
456
 
420
457
    def transaction_finished(self):
421
458
        """The transaction that this file was opened in has finished.
422
459
 
425
462
        """
426
463
        self.finished = True
427
464
 
428
 
    @deprecated_method(zero_eight)
429
 
    def walk(self, version_ids=None):
430
 
        """Walk the versioned file as a weave-like structure, for
431
 
        versions relative to version_ids.  Yields sequence of (lineno,
432
 
        insert, deletes, text) for each relevant line.
433
 
 
434
 
        Must raise RevisionNotPresent if any of the specified versions
435
 
        are not present in the file history.
436
 
 
437
 
        :param version_ids: the version_ids to walk with respect to. If not
438
 
                            supplied the entire weave-like structure is walked.
439
 
 
440
 
        walk is deprecated in favour of iter_lines_added_or_present_in_versions
441
 
        """
442
 
        raise NotImplementedError(self.walk)
443
 
 
444
 
    @deprecated_method(zero_eight)
445
 
    def iter_names(self):
446
 
        """Walk the names list."""
447
 
        return iter(self.versions())
448
 
 
449
465
    def plan_merge(self, ver_a, ver_b):
450
466
        """Return pseudo-annotation indicating how the two versions merge.
451
467
 
468
484
        """
469
485
        raise NotImplementedError(VersionedFile.plan_merge)
470
486
        
471
 
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER, 
 
487
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
472
488
                    b_marker=TextMerge.B_MARKER):
473
489
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
474
490
 
538
554
 
539
555
 
540
556
class WeaveMerge(PlanWeaveMerge):
541
 
    """Weave merge that takes a VersionedFile and two versions as its input"""
 
557
    """Weave merge that takes a VersionedFile and two versions as its input."""
542
558
 
543
559
    def __init__(self, versionedfile, ver_a, ver_b, 
544
560
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
547
563
 
548
564
 
549
565
class InterVersionedFile(InterObject):
550
 
    """This class represents operations taking place between two versionedfiles..
 
566
    """This class represents operations taking place between two VersionedFiles.
551
567
 
552
568
    Its instances have methods like join, and contain
553
569
    references to the source and target versionedfiles these operations can be 
558
574
    InterVersionedFile.get(other).method_name(parameters).
559
575
    """
560
576
 
561
 
    _optimisers = set()
 
577
    _optimisers = []
562
578
    """The available optimised InterVersionedFile types."""
563
579
 
564
580
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
568
584
        incorporated into this versioned file.
569
585
 
570
586
        Must raise RevisionNotPresent if any of the specified versions
571
 
        are not present in the other files history unless ignore_missing is 
572
 
        supplied when they are silently skipped.
 
587
        are not present in the other file's history unless ignore_missing is 
 
588
        supplied in which case they are silently skipped.
573
589
        """
574
590
        # the default join: 
575
591
        # - if the target is empty, just add all the versions from 
585
601
            target = temp_source
586
602
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
587
603
        graph = self.source.get_graph(version_ids)
588
 
        order = topo_sort(graph.items())
 
604
        order = tsort.topo_sort(graph.items())
589
605
        pb = ui.ui_factory.nested_progress_bar()
590
606
        parent_texts = {}
591
607
        try:
602
618
            # TODO: remove parent texts when they are not relevant any more for 
603
619
            # memory pressure reduction. RBC 20060313
604
620
            # pb.update('Converting versioned data', 0, len(order))
605
 
            # deltas = self.source.get_deltas(order)
 
621
            total = len(order)
606
622
            for index, version in enumerate(order):
607
 
                pb.update('Converting versioned data', index, len(order))
608
 
                parent_text = target.add_lines(version,
 
623
                pb.update('Converting versioned data', index, total)
 
624
                _, _, parent_text = target.add_lines(version,
609
625
                                               self.source.get_parents(version),
610
626
                                               self.source.get_lines(version),
611
627
                                               parent_texts=parent_texts)
612
628
                parent_texts[version] = parent_text
613
 
                #delta_parent, sha1, noeol, delta = deltas[version]
614
 
                #target.add_delta(version,
615
 
                #                 self.source.get_parents(version),
616
 
                #                 delta_parent,
617
 
                #                 sha1,
618
 
                #                 noeol,
619
 
                #                 delta)
620
 
                #target.get_lines(version)
621
629
            
622
630
            # this should hit the native code path for target
623
631
            if target is not self.target:
626
634
                                        msg,
627
635
                                        version_ids,
628
636
                                        ignore_missing)
 
637
            else:
 
638
                return total
629
639
        finally:
630
640
            pb.finished()
631
641
 
655
665
                    else:
656
666
                        new_version_ids.add(version)
657
667
                return new_version_ids
658
 
 
659
 
 
660
 
class InterVersionedFileTestProviderAdapter(object):
661
 
    """A tool to generate a suite testing multiple inter versioned-file classes.
662
 
 
663
 
    This is done by copying the test once for each InterVersionedFile provider
664
 
    and injecting the transport_server, transport_readonly_server,
665
 
    versionedfile_factory and versionedfile_factory_to classes into each copy.
666
 
    Each copy is also given a new id() to make it easy to identify.
667
 
    """
668
 
 
669
 
    def __init__(self, transport_server, transport_readonly_server, formats):
670
 
        self._transport_server = transport_server
671
 
        self._transport_readonly_server = transport_readonly_server
672
 
        self._formats = formats
673
 
    
674
 
    def adapt(self, test):
675
 
        result = TestSuite()
676
 
        for (interversionedfile_class,
677
 
             versionedfile_factory,
678
 
             versionedfile_factory_to) in self._formats:
679
 
            new_test = deepcopy(test)
680
 
            new_test.transport_server = self._transport_server
681
 
            new_test.transport_readonly_server = self._transport_readonly_server
682
 
            new_test.interversionedfile_class = interversionedfile_class
683
 
            new_test.versionedfile_factory = versionedfile_factory
684
 
            new_test.versionedfile_factory_to = versionedfile_factory_to
685
 
            def make_new_test_id():
686
 
                new_id = "%s(%s)" % (new_test.id(), interversionedfile_class.__name__)
687
 
                return lambda: new_id
688
 
            new_test.id = make_new_test_id()
689
 
            result.addTest(new_test)
690
 
        return result
691
 
 
692
 
    @staticmethod
693
 
    def default_test_list():
694
 
        """Generate the default list of interversionedfile permutations to test."""
695
 
        from bzrlib.weave import WeaveFile
696
 
        from bzrlib.knit import KnitVersionedFile
697
 
        result = []
698
 
        # test the fallback InterVersionedFile from annotated knits to weave
699
 
        result.append((InterVersionedFile, 
700
 
                       KnitVersionedFile,
701
 
                       WeaveFile))
702
 
        for optimiser in InterVersionedFile._optimisers:
703
 
            result.append((optimiser,
704
 
                           optimiser._matching_file_from_factory,
705
 
                           optimiser._matching_file_to_factory
706
 
                           ))
707
 
        # if there are specific combinations we want to use, we can add them 
708
 
        # here.
709
 
        return result