~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Martin Pool
  • Date: 2006-06-20 07:55:43 UTC
  • mfrom: (1798 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1799.
  • Revision ID: mbp@sourcefrog.net-20060620075543-b10f6575d4a4fa32
[merge] bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
# Written by Martin Pool.
3
3
# Modified by Johan Rydberg <jrydberg@gnu.org>
4
4
# Modified by Robert Collins <robert.collins@canonical.com>
 
5
# Modified by Aaron Bentley <aaron.bentley@utoronto.ca>
5
6
#
6
7
# This program is free software; you can redistribute it and/or modify
7
8
# it under the terms of the GNU General Public License as published by
67
68
from cStringIO import StringIO
68
69
import difflib
69
70
from itertools import izip, chain
 
71
import operator
70
72
import os
71
73
import sys
72
74
 
75
77
from bzrlib.errors import FileExists, NoSuchFile, KnitError, \
76
78
        InvalidRevisionId, KnitCorrupt, KnitHeaderError, \
77
79
        RevisionNotPresent, RevisionAlreadyPresent
78
 
from bzrlib.tuned_gzip import *
 
80
from bzrlib.tuned_gzip import GzipFile
79
81
from bzrlib.trace import mutter
80
82
from bzrlib.osutils import contains_whitespace, contains_linebreaks, \
81
83
     sha_strings
89
91
# TODO: Can we put in some kind of value to check that the index and data
90
92
# files belong together?
91
93
 
92
 
# TODO: accomodate binaries, perhaps by storing a byte count
 
94
# TODO: accommodate binaries, perhaps by storing a byte count
93
95
 
94
96
# TODO: function to check whole file
95
97
 
172
174
        intstart intend intcount
173
175
        1..count lines:
174
176
        revid(utf8) newline\n
175
 
        internal represnetation is
 
177
        internal representation is
176
178
        (start, end, count, [1..count tuples (revid, newline)])
177
179
        """
178
180
        result = []
265
267
    stored and retrieved.
266
268
    """
267
269
 
268
 
    def __init__(self, relpath, transport, file_mode=None, access_mode=None, factory=None,
269
 
                 basis_knit=None, delta=True, create=False):
 
270
    def __init__(self, relpath, transport, file_mode=None, access_mode=None, 
 
271
                 factory=None, basis_knit=None, delta=True, create=False):
270
272
        """Construct a knit at location specified by relpath.
271
273
        
272
274
        :param create: If not True, only open an existing knit.
362
364
        :param records: A list of tuples(version_id, options, parents, size).
363
365
        :param data: The data for the records. When it is written, the records
364
366
                     are adjusted to have pos pointing into data by the sum of
365
 
                     the preceeding records sizes.
 
367
                     the preceding records sizes.
366
368
        """
367
369
        # write all the data
368
370
        pos = self._data.add_raw_record(data)
510
512
            diff_hunks.append((op[1], op[2], op[4]-op[3], new_content._lines[op[3]:op[4]]))
511
513
        return diff_hunks
512
514
 
 
515
    def _get_component_versions(self, version_id):
 
516
        basis = self.basis_knit
 
517
        needed_versions = []
 
518
        basis_versions = []
 
519
        cursor = version_id
 
520
 
 
521
        while 1:
 
522
            picked_knit = self
 
523
            if basis and basis._index.has_version(cursor):
 
524
                picked_knit = basis
 
525
                basis_versions.append(cursor)
 
526
            method = picked_knit._index.get_method(cursor)
 
527
            needed_versions.append((method, cursor))
 
528
            if method == 'fulltext':
 
529
                break
 
530
            cursor = picked_knit.get_parents(cursor)[0]
 
531
        return needed_versions, basis_versions
 
532
 
 
533
    def _get_component_positions(self, version_id):
 
534
        needed_versions, basis_versions = \
 
535
            self._get_component_versions(version_id)
 
536
        assert len(basis_versions) == 0
 
537
        positions = []
 
538
        for method, comp_id in needed_versions:
 
539
            data_pos, data_size = self._index.get_position(comp_id)
 
540
            positions.append((method, comp_id, data_pos, data_size))
 
541
        return positions
 
542
 
513
543
    def _get_components(self, version_id):
514
544
        """Return a list of (version_id, method, data) tuples that
515
545
        makes up version specified by version_id of the knit.
533
563
        # basis_revisions is a list of versions that needs to be
534
564
        # fetched but exists in the basis knit.
535
565
 
536
 
        basis = self.basis_knit
537
 
        needed_versions = []
538
 
        basis_versions = []
539
 
        cursor = version_id
540
 
 
541
 
        while 1:
542
 
            picked_knit = self
543
 
            if basis and basis._index.has_version(cursor):
544
 
                picked_knit = basis
545
 
                basis_versions.append(cursor)
546
 
            method = picked_knit._index.get_method(cursor)
547
 
            needed_versions.append((method, cursor))
548
 
            if method == 'fulltext':
549
 
                break
550
 
            cursor = picked_knit.get_parents(cursor)[0]
 
566
        needed_versions, basis_versions = \
 
567
            self._get_component_versions(version_id)
551
568
 
552
569
        components = {}
553
570
        if basis_versions:
 
571
            assert False, "I am broken"
 
572
            basis = self.basis_knit
554
573
            records = []
555
574
            for comp_id in basis_versions:
556
575
                data_pos, data_size = basis._index.get_data_position(comp_id)
557
 
                records.append((piece_id, data_pos, data_size))
 
576
                records.append((comp_id, data_pos, data_size))
558
577
            components.update(basis._data.read_records(records))
559
578
 
560
579
        records = []
603
622
 
604
623
        # digest here is the digest from the last applied component.
605
624
        if sha_strings(content.text()) != digest:
606
 
            import pdb;pdb.set_trace()
607
625
            raise KnitCorrupt(self.filename, 'sha-1 does not match %s' % version_id)
608
626
 
609
627
        return content
717
735
 
718
736
    def _clone_text(self, new_version_id, old_version_id, parents):
719
737
        """See VersionedFile.clone_text()."""
720
 
        # FIXME RBC 20060228 make fast by only inserting an index with null delta.
 
738
        # FIXME RBC 20060228 make fast by only inserting an index with null 
 
739
        # delta.
721
740
        self.add_lines(new_version_id, parents, self.get_lines(old_version_id))
722
741
 
723
742
    def get_lines(self, version_id):
724
743
        """See VersionedFile.get_lines()."""
725
 
        return self._get_content(version_id).text()
 
744
        return self.get_line_list([version_id])[0]
 
745
 
 
746
    def _get_version_components(self, position_map):
 
747
        records = []
 
748
        for version_id, positions in position_map.iteritems():
 
749
            for method, comp_id, position, size in positions:
 
750
                records.append((comp_id, position, size))
 
751
        record_map = self._data.read_records(records)
 
752
 
 
753
        component_map = {}
 
754
        for version_id, positions in position_map.iteritems():
 
755
            components = []
 
756
            for method, comp_id, position, size in positions:
 
757
                data, digest = record_map[comp_id]
 
758
                components.append((comp_id, method, data, digest))
 
759
            component_map[version_id] = components
 
760
        return component_map
 
761
 
 
762
    def get_text(self, version_id):
 
763
        """See VersionedFile.get_text"""
 
764
        return self.get_texts([version_id])[0]
 
765
 
 
766
    def get_texts(self, version_ids):
 
767
        return [''.join(l) for l in self.get_line_list(version_ids)]
 
768
 
 
769
    def get_line_list(self, version_ids):
 
770
        """Return the texts of listed versions as a list of strings."""
 
771
        position_map = {}
 
772
        for version_id in version_ids:
 
773
            if not self.has_version(version_id):
 
774
                raise RevisionNotPresent(version_id, self.filename)
 
775
            position_map[version_id] = \
 
776
                self._get_component_positions(version_id)
 
777
 
 
778
        version_components = self._get_version_components(position_map).items()
 
779
 
 
780
        text_map = {}
 
781
        for version_id, components in version_components:
 
782
            content = None
 
783
            for component_id, method, data, digest in reversed(components):
 
784
                version_idx = self._index.lookup(component_id)
 
785
                if method == 'fulltext':
 
786
                    assert content is None
 
787
                    content = self.factory.parse_fulltext(data, version_idx)
 
788
                elif method == 'line-delta':
 
789
                    delta = self.factory.parse_line_delta(data, version_idx)
 
790
                    content._lines = self._apply_delta(content._lines, delta)
 
791
 
 
792
            if 'no-eol' in self._index.get_options(version_id):
 
793
                line = content._lines[-1][1].rstrip('\n')
 
794
                content._lines[-1] = (content._lines[-1][0], line)
 
795
 
 
796
            # digest here is the digest from the last applied component.
 
797
            if sha_strings(content.text()) != digest:
 
798
                raise KnitCorrupt(self.filename, 
 
799
                                  'sha-1 does not match %s' % version_id)
 
800
 
 
801
            text_map[version_id] = content.text()
 
802
        return [text_map[v] for v in version_ids]
726
803
 
727
804
    def iter_lines_added_or_present_in_versions(self, version_ids=None):
728
805
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
729
806
        if version_ids is None:
730
807
            version_ids = self.versions()
731
 
        # we dont care about inclusions, the caller cares.
 
808
        # we don't care about inclusions, the caller cares.
732
809
        # but we need to setup a list of records to visit.
733
810
        # we need version_id, position, length
734
811
        version_id_records = []
932
1009
 
933
1010
    The index file on disc contains a header, followed by one line per knit
934
1011
    record. The same revision can be present in an index file more than once.
935
 
    The first occurence gets assigned a sequence number starting from 0. 
 
1012
    The first occurrence gets assigned a sequence number starting from 0. 
936
1013
    
937
1014
    The format of a single line is
938
1015
    REVISION_ID FLAGS BYTE_OFFSET LENGTH( PARENT_ID|PARENT_SEQUENCE_ID)* :\n
954
1031
    the end of the file, then the record that is missing it will be ignored by
955
1032
    the parser.
956
1033
 
957
 
    When writing new records to the index file, the data is preceeded by '\n'
 
1034
    When writing new records to the index file, the data is preceded by '\n'
958
1035
    to ensure that records always start on new lines even if the last write was
959
1036
    interrupted. As a result its normal for the last line in the index to be
960
1037
    missing a trailing newline. One can be added with no harmful effects.
991
1068
        self._cache = {}
992
1069
        # position in _history is the 'official' index for a revision
993
1070
        # but the values may have come from a newer entry.
994
 
        # so - wc -l of a knit index is != the number of uniqe names
995
 
        # in the weave.
 
1071
        # so - wc -l of a knit index is != the number of unique names
 
1072
        # in the knit.
996
1073
        self._history = []
997
1074
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
998
1075
        try:
1213
1290
                if parent in self._cache]
1214
1291
 
1215
1292
    def get_parents_with_ghosts(self, version_id):
1216
 
        """Return parents of specified version wth ghosts."""
 
1293
        """Return parents of specified version with ghosts."""
1217
1294
        return self._cache[version_id][4] 
1218
1295
 
1219
1296
    def check_versions_present(self, version_ids):
1373
1450
                needed_records.append((version_id, pos, size))
1374
1451
 
1375
1452
        if len(needed_records):
 
1453
            needed_records.sort(key=operator.itemgetter(1))
1376
1454
            # We take it that the transport optimizes the fetching as good
1377
 
            # as possible (ie, reads continous ranges.)
 
1455
            # as possible (ie, reads continuous ranges.)
1378
1456
            response = self._transport.readv(self._filename,
1379
1457
                [(pos, size) for version_id, pos, size in needed_records])
1380
1458
 
1381
 
            for (record_id, pos, size), (pos, data) in izip(iter(needed_records), response):
 
1459
            for (record_id, pos, size), (pos, data) in \
 
1460
                izip(iter(needed_records), response):
1382
1461
                content, digest = self._parse_record(record_id, data)
1383
1462
                self._records[record_id] = (digest, content)
1384
1463
    
1469
1548
                    # if source has the parent, we must :
1470
1549
                    # * already have it or
1471
1550
                    # * have it scheduled already
1472
 
                    # otherwise we dont care
 
1551
                    # otherwise we don't care
1473
1552
                    assert (self.target.has_version(parent) or
1474
1553
                            parent in copy_set or
1475
1554
                            not self.source.has_version(parent))
1680
1759
            j2lenget = j2len.get
1681
1760
            newj2len = {}
1682
1761
            
1683
 
            # changing b2j.get(a[i], nothing) to a try:Keyerror pair produced the
 
1762
            # changing b2j.get(a[i], nothing) to a try:KeyError pair produced the
1684
1763
            # following improvement
1685
1764
            #     704  0   4650.5320   2620.7410   bzrlib.knit:1336(find_longest_match)
1686
1765
            # +326674  0   1655.1210   1655.1210   +<method 'get' of 'dict' objects>