~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
68
68
 
69
69
class ContentFactory(object):
70
70
    """Abstract interface for insertion and retrieval from a VersionedFile.
71
 
    
 
71
 
72
72
    :ivar sha1: None, or the sha1 of the content fulltext.
73
73
    :ivar storage_kind: The native storage kind of this factory. One of
74
74
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
158
158
 
159
159
class AbsentContentFactory(ContentFactory):
160
160
    """A placeholder content factory for unavailable texts.
161
 
    
 
161
 
162
162
    :ivar sha1: None.
163
163
    :ivar storage_kind: 'absent'.
164
164
    :ivar key: The key of this content. Each key is a tuple with a single
200
200
 
201
201
class VersionedFile(object):
202
202
    """Versioned text file storage.
203
 
    
 
203
 
204
204
    A versioned file manages versions of line-based text files,
205
205
    keeping track of the originating version for each line.
206
206
 
244
244
    def insert_record_stream(self, stream):
245
245
        """Insert a record stream into this versioned file.
246
246
 
247
 
        :param stream: A stream of records to insert. 
 
247
        :param stream: A stream of records to insert.
248
248
        :return: None
249
249
        :seealso VersionedFile.get_record_stream:
250
250
        """
269
269
            the data back accurately. (Checking the lines have been split
270
270
            correctly is expensive and extremely unlikely to catch bugs so it
271
271
            is not done at runtime unless check_content is True.)
272
 
        :param parent_texts: An optional dictionary containing the opaque 
 
272
        :param parent_texts: An optional dictionary containing the opaque
273
273
            representations of some or all of the parents of version_id to
274
274
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
275
275
            returned by add_lines or data corruption can be caused.
303
303
        parent_texts=None, nostore_sha=None, random_id=False,
304
304
        check_content=True, left_matching_blocks=None):
305
305
        """Add lines to the versioned file, allowing ghosts to be present.
306
 
        
 
306
 
307
307
        This takes the same parameters as add_lines and returns the same.
308
308
        """
309
309
        self._check_write_ok()
333
333
 
334
334
    def get_format_signature(self):
335
335
        """Get a text description of the data encoding in this file.
336
 
        
 
336
 
337
337
        :since: 0.90
338
338
        """
339
339
        raise NotImplementedError(self.get_format_signature)
460
460
        if isinstance(version_ids, basestring):
461
461
            version_ids = [version_ids]
462
462
        raise NotImplementedError(self.get_ancestry)
463
 
        
 
463
 
464
464
    def get_ancestry_with_ghosts(self, version_ids):
465
465
        """Return a list of all ancestors of given version(s). This
466
466
        will not include the null revision.
467
467
 
468
468
        Must raise RevisionNotPresent if any of the given versions are
469
469
        not present in file history.
470
 
        
 
470
 
471
471
        Ghosts that are known about will be included in ancestry list,
472
472
        but are not explicitly marked.
473
473
        """
474
474
        raise NotImplementedError(self.get_ancestry_with_ghosts)
475
 
    
 
475
 
476
476
    def get_parent_map(self, version_ids):
477
477
        """Get a map of the parents of version_ids.
478
478
 
541
541
        unchanged   Alive in both a and b (possibly created in both)
542
542
        new-a       Created in a
543
543
        new-b       Created in b
544
 
        ghost-a     Killed in a, unborn in b    
 
544
        ghost-a     Killed in a, unborn in b
545
545
        ghost-b     Killed in b, unborn in a
546
546
        irrelevant  Not in either revision
547
547
        """
548
548
        raise NotImplementedError(VersionedFile.plan_merge)
549
 
        
 
549
 
550
550
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
551
551
                    b_marker=TextMerge.B_MARKER):
552
552
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
554
554
 
555
555
class RecordingVersionedFilesDecorator(object):
556
556
    """A minimal versioned files that records calls made on it.
557
 
    
 
557
 
558
558
    Only enough methods have been added to support tests using it to date.
559
559
 
560
560
    :ivar calls: A list of the calls made; can be reset at any time by
563
563
 
564
564
    def __init__(self, backing_vf):
565
565
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
566
 
        
 
566
 
567
567
        :param backing_vf: The versioned file to answer all methods.
568
568
        """
569
569
        self._backing_vf = backing_vf
655
655
 
656
656
    def unmap(self, partition_id):
657
657
        """Map a partitioned storage id back to a key prefix.
658
 
        
 
658
 
659
659
        :param partition_id: The underlying partition id.
660
660
        :return: As much of a key (or prefix) as is derivable from the partition
661
661
            id.
693
693
 
694
694
class PrefixMapper(URLEscapeMapper):
695
695
    """A key mapper that extracts the first component of a key.
696
 
    
 
696
 
697
697
    This mapper is for use with a transport based backend.
698
698
    """
699
699
 
732
732
 
733
733
class HashEscapedPrefixMapper(HashPrefixMapper):
734
734
    """Combines the escaped first component of a key with a hash.
735
 
    
 
735
 
736
736
    This mapper is for use with a transport based backend.
737
737
    """
738
738
 
794
794
        check_content=True):
795
795
        """Add a text to the store.
796
796
 
797
 
        :param key: The key tuple of the text to add.
 
797
        :param key: The key tuple of the text to add. If the last element is
 
798
            None, a CHK string will be generated during the addition.
798
799
        :param parents: The parents key tuples of the text to add.
799
800
        :param lines: A list of lines. Each line must be a bytestring. And all
800
801
            of them except the last must be terminated with \n and contain no
804
805
            the data back accurately. (Checking the lines have been split
805
806
            correctly is expensive and extremely unlikely to catch bugs so it
806
807
            is not done at runtime unless check_content is True.)
807
 
        :param parent_texts: An optional dictionary containing the opaque 
 
808
        :param parent_texts: An optional dictionary containing the opaque
808
809
            representations of some or all of the parents of version_id to
809
810
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
810
811
            returned by add_lines or data corruption can be caused.
943
944
    def insert_record_stream(self, stream):
944
945
        """Insert a record stream into this container.
945
946
 
946
 
        :param stream: A stream of records to insert. 
 
947
        :param stream: A stream of records to insert.
947
948
        :return: None
948
949
        :seealso VersionedFile.get_record_stream:
949
950
        """
1177
1178
    def insert_record_stream(self, stream):
1178
1179
        """Insert a record stream into this container.
1179
1180
 
1180
 
        :param stream: A stream of records to insert. 
 
1181
        :param stream: A stream of records to insert.
1181
1182
        :return: None
1182
1183
        :seealso VersionedFile.get_record_stream:
1183
1184
        """
1340
1341
 
1341
1342
class PlanWeaveMerge(TextMerge):
1342
1343
    """Weave merge that takes a plan as its input.
1343
 
    
 
1344
 
1344
1345
    This exists so that VersionedFile.plan_merge is implementable.
1345
1346
    Most callers will want to use WeaveMerge instead.
1346
1347
    """
1367
1368
                yield(lines_a,)
1368
1369
            else:
1369
1370
                yield (lines_a, lines_b)
1370
 
       
 
1371
 
1371
1372
        # We previously considered either 'unchanged' or 'killed-both' lines
1372
1373
        # to be possible places to resynchronize.  However, assuming agreement
1373
1374
        # on killed-both lines may be too aggressive. -- mbp 20060324
1379
1380
                lines_a = []
1380
1381
                lines_b = []
1381
1382
                ch_a = ch_b = False
1382
 
                
 
1383
 
1383
1384
            if state == 'unchanged':
1384
1385
                if line:
1385
1386
                    yield ([line],)
1412
1413
class WeaveMerge(PlanWeaveMerge):
1413
1414
    """Weave merge that takes a VersionedFile and two versions as its input."""
1414
1415
 
1415
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1416
    def __init__(self, versionedfile, ver_a, ver_b,
1416
1417
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1417
1418
        plan = versionedfile.plan_merge(ver_a, ver_b)
1418
1419
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1419
1420
 
1420
1421
 
1421
1422
class VirtualVersionedFiles(VersionedFiles):
1422
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1423
    """Dummy implementation for VersionedFiles that uses other functions for
1423
1424
    obtaining fulltexts and parent maps.
1424
1425
 
1425
 
    This is always on the bottom of the stack and uses string keys 
 
1426
    This is always on the bottom of the stack and uses string keys
1426
1427
    (rather than tuples) internally.
1427
1428
    """
1428
1429
 
1430
1431
        """Create a VirtualVersionedFiles.
1431
1432
 
1432
1433
        :param get_parent_map: Same signature as Repository.get_parent_map.
1433
 
        :param get_lines: Should return lines for specified key or None if 
 
1434
        :param get_lines: Should return lines for specified key or None if
1434
1435
                          not available.
1435
1436
        """
1436
1437
        super(VirtualVersionedFiles, self).__init__()
1437
1438
        self._get_parent_map = get_parent_map
1438
1439
        self._get_lines = get_lines
1439
 
        
 
1440
 
1440
1441
    def check(self, progressbar=None):
1441
1442
        """See VersionedFiles.check.
1442
1443
 
1484
1485
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1485
1486
        for i, (key,) in enumerate(keys):
1486
1487
            if pb is not None:
1487
 
                pb.update("iterating texts", i, len(keys))
 
1488
                pb.update("Finding changed lines", i, len(keys))
1488
1489
            for l in self._get_lines(key):
1489
1490
                yield (l, key)
1490
1491
 
1534
1535
def fulltext_network_to_record(kind, bytes, line_end):
1535
1536
    """Convert a network fulltext record to record."""
1536
1537
    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
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1538
1539
    key, parents = bencode.bdecode_as_tuple(record_meta)
1539
1540
    if parents == 'nil':
1540
1541
        parents = None
1541
 
    fulltext = record_bytes[line_end+4+meta_len:]
1542
 
    return FulltextContentFactory(key, parents, None, fulltext)
 
1542
    fulltext = bytes[line_end+4+meta_len:]
 
1543
    return [FulltextContentFactory(key, parents, None, fulltext)]
1543
1544
 
1544
1545
 
1545
1546
def _length_prefix(bytes):
1546
1547
    return struct.pack('!L', len(bytes))
1547
1548
 
1548
1549
 
1549
 
def record_to_fulltext_bytes(self, record):
 
1550
def record_to_fulltext_bytes(record):
1550
1551
    if record.parents is None:
1551
1552
        parents = 'nil'
1552
1553
    else:
1555
1556
    record_content = record.get_bytes_as('fulltext')
1556
1557
    return "fulltext\n%s%s%s" % (
1557
1558
        _length_prefix(record_meta), record_meta, record_content)
 
1559
 
 
1560
 
 
1561
def sort_groupcompress(parent_map):
 
1562
    """Sort and group the keys in parent_map into groupcompress order.
 
1563
 
 
1564
    groupcompress is defined (currently) as reverse-topological order, grouped
 
1565
    by the key prefix.
 
1566
 
 
1567
    :return: A sorted-list of keys
 
1568
    """
 
1569
    # gc-optimal ordering is approximately reverse topological,
 
1570
    # properly grouped by file-id.
 
1571
    per_prefix_map = {}
 
1572
    for item in parent_map.iteritems():
 
1573
        key = item[0]
 
1574
        if isinstance(key, str) or len(key) == 1:
 
1575
            prefix = ''
 
1576
        else:
 
1577
            prefix = key[0]
 
1578
        try:
 
1579
            per_prefix_map[prefix].append(item)
 
1580
        except KeyError:
 
1581
            per_prefix_map[prefix] = [item]
 
1582
 
 
1583
    present_keys = []
 
1584
    for prefix in sorted(per_prefix_map):
 
1585
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
1586
    return present_keys