/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/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2009-02-25 14:36:59 UTC
  • mfrom: (4048 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4049.
  • Revision ID: jelmer@samba.org-20090225143659-vx6cbqtmyicuzfyf
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from copy import copy
23
23
from cStringIO import StringIO
24
24
import os
 
25
import struct
25
26
from zlib import adler32
26
27
 
27
28
from bzrlib.lazy_import import lazy_import
31
32
from bzrlib import (
32
33
    errors,
33
34
    index,
 
35
    knit,
34
36
    osutils,
35
37
    multiparent,
36
38
    tsort,
44
46
from bzrlib.registry import Registry
45
47
from bzrlib.symbol_versioning import *
46
48
from bzrlib.textmerge import TextMerge
 
49
from bzrlib.util import bencode
47
50
 
48
51
 
49
52
adapter_registry = Registry()
65
68
 
66
69
class ContentFactory(object):
67
70
    """Abstract interface for insertion and retrieval from a VersionedFile.
68
 
    
 
71
 
69
72
    :ivar sha1: None, or the sha1 of the content fulltext.
70
73
    :ivar storage_kind: The native storage kind of this factory. One of
71
74
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
155
158
 
156
159
class AbsentContentFactory(ContentFactory):
157
160
    """A placeholder content factory for unavailable texts.
158
 
    
 
161
 
159
162
    :ivar sha1: None.
160
163
    :ivar storage_kind: 'absent'.
161
164
    :ivar key: The key of this content. Each key is a tuple with a single
197
200
 
198
201
class VersionedFile(object):
199
202
    """Versioned text file storage.
200
 
    
 
203
 
201
204
    A versioned file manages versions of line-based text files,
202
205
    keeping track of the originating version for each line.
203
206
 
241
244
    def insert_record_stream(self, stream):
242
245
        """Insert a record stream into this versioned file.
243
246
 
244
 
        :param stream: A stream of records to insert. 
 
247
        :param stream: A stream of records to insert.
245
248
        :return: None
246
249
        :seealso VersionedFile.get_record_stream:
247
250
        """
266
269
            the data back accurately. (Checking the lines have been split
267
270
            correctly is expensive and extremely unlikely to catch bugs so it
268
271
            is not done at runtime unless check_content is True.)
269
 
        :param parent_texts: An optional dictionary containing the opaque 
 
272
        :param parent_texts: An optional dictionary containing the opaque
270
273
            representations of some or all of the parents of version_id to
271
274
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
272
275
            returned by add_lines or data corruption can be caused.
300
303
        parent_texts=None, nostore_sha=None, random_id=False,
301
304
        check_content=True, left_matching_blocks=None):
302
305
        """Add lines to the versioned file, allowing ghosts to be present.
303
 
        
 
306
 
304
307
        This takes the same parameters as add_lines and returns the same.
305
308
        """
306
309
        self._check_write_ok()
330
333
 
331
334
    def get_format_signature(self):
332
335
        """Get a text description of the data encoding in this file.
333
 
        
 
336
 
334
337
        :since: 0.90
335
338
        """
336
339
        raise NotImplementedError(self.get_format_signature)
457
460
        if isinstance(version_ids, basestring):
458
461
            version_ids = [version_ids]
459
462
        raise NotImplementedError(self.get_ancestry)
460
 
        
 
463
 
461
464
    def get_ancestry_with_ghosts(self, version_ids):
462
465
        """Return a list of all ancestors of given version(s). This
463
466
        will not include the null revision.
464
467
 
465
468
        Must raise RevisionNotPresent if any of the given versions are
466
469
        not present in file history.
467
 
        
 
470
 
468
471
        Ghosts that are known about will be included in ancestry list,
469
472
        but are not explicitly marked.
470
473
        """
471
474
        raise NotImplementedError(self.get_ancestry_with_ghosts)
472
 
    
 
475
 
473
476
    def get_parent_map(self, version_ids):
474
477
        """Get a map of the parents of version_ids.
475
478
 
538
541
        unchanged   Alive in both a and b (possibly created in both)
539
542
        new-a       Created in a
540
543
        new-b       Created in b
541
 
        ghost-a     Killed in a, unborn in b    
 
544
        ghost-a     Killed in a, unborn in b
542
545
        ghost-b     Killed in b, unborn in a
543
546
        irrelevant  Not in either revision
544
547
        """
545
548
        raise NotImplementedError(VersionedFile.plan_merge)
546
 
        
 
549
 
547
550
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
548
551
                    b_marker=TextMerge.B_MARKER):
549
552
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
551
554
 
552
555
class RecordingVersionedFilesDecorator(object):
553
556
    """A minimal versioned files that records calls made on it.
554
 
    
 
557
 
555
558
    Only enough methods have been added to support tests using it to date.
556
559
 
557
560
    :ivar calls: A list of the calls made; can be reset at any time by
560
563
 
561
564
    def __init__(self, backing_vf):
562
565
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
563
 
        
 
566
 
564
567
        :param backing_vf: The versioned file to answer all methods.
565
568
        """
566
569
        self._backing_vf = backing_vf
652
655
 
653
656
    def unmap(self, partition_id):
654
657
        """Map a partitioned storage id back to a key prefix.
655
 
        
 
658
 
656
659
        :param partition_id: The underlying partition id.
657
660
        :return: As much of a key (or prefix) as is derivable from the partition
658
661
            id.
690
693
 
691
694
class PrefixMapper(URLEscapeMapper):
692
695
    """A key mapper that extracts the first component of a key.
693
 
    
 
696
 
694
697
    This mapper is for use with a transport based backend.
695
698
    """
696
699
 
729
732
 
730
733
class HashEscapedPrefixMapper(HashPrefixMapper):
731
734
    """Combines the escaped first component of a key with a hash.
732
 
    
 
735
 
733
736
    This mapper is for use with a transport based backend.
734
737
    """
735
738
 
801
804
            the data back accurately. (Checking the lines have been split
802
805
            correctly is expensive and extremely unlikely to catch bugs so it
803
806
            is not done at runtime unless check_content is True.)
804
 
        :param parent_texts: An optional dictionary containing the opaque 
 
807
        :param parent_texts: An optional dictionary containing the opaque
805
808
            representations of some or all of the parents of version_id to
806
809
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
807
810
            returned by add_lines or data corruption can be caused.
925
928
 
926
929
    has_key = index._has_key_from_parent_map
927
930
 
 
931
    def get_missing_compression_parent_keys(self):
 
932
        """Return an iterable of keys of missing compression parents.
 
933
 
 
934
        Check this after calling insert_record_stream to find out if there are
 
935
        any missing compression parents.  If there are, the records that
 
936
        depend on them are not able to be inserted safely. The precise
 
937
        behaviour depends on the concrete VersionedFiles class in use.
 
938
 
 
939
        Classes that do not support this will raise NotImplementedError.
 
940
        """
 
941
        raise NotImplementedError(self.get_missing_compression_parent_keys)
 
942
 
928
943
    def insert_record_stream(self, stream):
929
944
        """Insert a record stream into this container.
930
945
 
931
 
        :param stream: A stream of records to insert. 
 
946
        :param stream: A stream of records to insert.
932
947
        :return: None
933
948
        :seealso VersionedFile.get_record_stream:
934
949
        """
1162
1177
    def insert_record_stream(self, stream):
1163
1178
        """Insert a record stream into this container.
1164
1179
 
1165
 
        :param stream: A stream of records to insert. 
 
1180
        :param stream: A stream of records to insert.
1166
1181
        :return: None
1167
1182
        :seealso VersionedFile.get_record_stream:
1168
1183
        """
1325
1340
 
1326
1341
class PlanWeaveMerge(TextMerge):
1327
1342
    """Weave merge that takes a plan as its input.
1328
 
    
 
1343
 
1329
1344
    This exists so that VersionedFile.plan_merge is implementable.
1330
1345
    Most callers will want to use WeaveMerge instead.
1331
1346
    """
1352
1367
                yield(lines_a,)
1353
1368
            else:
1354
1369
                yield (lines_a, lines_b)
1355
 
       
 
1370
 
1356
1371
        # We previously considered either 'unchanged' or 'killed-both' lines
1357
1372
        # to be possible places to resynchronize.  However, assuming agreement
1358
1373
        # on killed-both lines may be too aggressive. -- mbp 20060324
1364
1379
                lines_a = []
1365
1380
                lines_b = []
1366
1381
                ch_a = ch_b = False
1367
 
                
 
1382
 
1368
1383
            if state == 'unchanged':
1369
1384
                if line:
1370
1385
                    yield ([line],)
1397
1412
class WeaveMerge(PlanWeaveMerge):
1398
1413
    """Weave merge that takes a VersionedFile and two versions as its input."""
1399
1414
 
1400
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1415
    def __init__(self, versionedfile, ver_a, ver_b,
1401
1416
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1402
1417
        plan = versionedfile.plan_merge(ver_a, ver_b)
1403
1418
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1404
1419
 
1405
1420
 
1406
1421
class VirtualVersionedFiles(VersionedFiles):
1407
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1422
    """Dummy implementation for VersionedFiles that uses other functions for
1408
1423
    obtaining fulltexts and parent maps.
1409
1424
 
1410
 
    This is always on the bottom of the stack and uses string keys 
 
1425
    This is always on the bottom of the stack and uses string keys
1411
1426
    (rather than tuples) internally.
1412
1427
    """
1413
1428
 
1415
1430
        """Create a VirtualVersionedFiles.
1416
1431
 
1417
1432
        :param get_parent_map: Same signature as Repository.get_parent_map.
1418
 
        :param get_lines: Should return lines for specified key or None if 
 
1433
        :param get_lines: Should return lines for specified key or None if
1419
1434
                          not available.
1420
1435
        """
1421
1436
        super(VirtualVersionedFiles, self).__init__()
1422
1437
        self._get_parent_map = get_parent_map
1423
1438
        self._get_lines = get_lines
1424
 
        
 
1439
 
1425
1440
    def check(self, progressbar=None):
1426
1441
        """See VersionedFiles.check.
1427
1442
 
1472
1487
                pb.update("iterating texts", i, len(keys))
1473
1488
            for l in self._get_lines(key):
1474
1489
                yield (l, key)
 
1490
 
 
1491
 
 
1492
def network_bytes_to_kind_and_offset(network_bytes):
 
1493
    """Strip of a record kind from the front of network_bytes.
 
1494
 
 
1495
    :param network_bytes: The bytes of a record.
 
1496
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
 
1497
    """
 
1498
    line_end = network_bytes.find('\n')
 
1499
    storage_kind = network_bytes[:line_end]
 
1500
    return storage_kind, line_end + 1
 
1501
 
 
1502
 
 
1503
class NetworkRecordStream(object):
 
1504
    """A record_stream which reconstitures a serialised stream."""
 
1505
 
 
1506
    def __init__(self, bytes_iterator):
 
1507
        """Create a NetworkRecordStream.
 
1508
 
 
1509
        :param bytes_iterator: An iterator of bytes. Each item in this
 
1510
            iterator should have been obtained from a record_streams'
 
1511
            record.get_bytes_as(record.storage_kind) call.
 
1512
        """
 
1513
        self._bytes_iterator = bytes_iterator
 
1514
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
 
1515
            'knit-delta-gz':knit.knit_network_to_record,
 
1516
            'knit-annotated-ft-gz':knit.knit_network_to_record,
 
1517
            'knit-annotated-delta-gz':knit.knit_network_to_record,
 
1518
            'knit-delta-closure':knit.knit_delta_closure_to_records,
 
1519
            'fulltext':fulltext_network_to_record,
 
1520
            }
 
1521
 
 
1522
    def read(self):
 
1523
        """Read the stream.
 
1524
 
 
1525
        :return: An iterator as per VersionedFiles.get_record_stream().
 
1526
        """
 
1527
        for bytes in self._bytes_iterator:
 
1528
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
1529
            for record in self._kind_factory[storage_kind](
 
1530
                storage_kind, bytes, line_end):
 
1531
                yield record
 
1532
 
 
1533
 
 
1534
def fulltext_network_to_record(kind, bytes, line_end):
 
1535
    """Convert a network fulltext record to record."""
 
1536
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1537
    record_meta = record_bytes[line_end+4:line_end+4+meta_len]
 
1538
    key, parents = bencode.bdecode_as_tuple(record_meta)
 
1539
    if parents == 'nil':
 
1540
        parents = None
 
1541
    fulltext = record_bytes[line_end+4+meta_len:]
 
1542
    return FulltextContentFactory(key, parents, None, fulltext)
 
1543
 
 
1544
 
 
1545
def _length_prefix(bytes):
 
1546
    return struct.pack('!L', len(bytes))
 
1547
 
 
1548
 
 
1549
def record_to_fulltext_bytes(self, record):
 
1550
    if record.parents is None:
 
1551
        parents = 'nil'
 
1552
    else:
 
1553
        parents = record.parents
 
1554
    record_meta = bencode.bencode((record.key, parents))
 
1555
    record_content = record.get_bytes_as('fulltext')
 
1556
    return "fulltext\n%s%s%s" % (
 
1557
        _length_prefix(record_meta), record_meta, record_content)