~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Vincent Ladeuil
  • Date: 2007-09-24 15:01:26 UTC
  • mfrom: (2853 +trunk)
  • mto: (2885.1.1 140432)
  • mto: This revision was merged to the branch mainline in revision 2886.
  • Revision ID: v.ladeuil+lp@free.fr-20070924150126-nll7i0385kisklyj
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
100
100
    RevisionNotPresent,
101
101
    RevisionAlreadyPresent,
102
102
    )
103
 
from bzrlib.tuned_gzip import GzipFile
 
103
from bzrlib.tuned_gzip import GzipFile, bytes_to_gzip
104
104
from bzrlib.osutils import (
105
105
    contains_whitespace,
106
106
    contains_linebreaks,
 
107
    sha_string,
107
108
    sha_strings,
108
109
    )
109
110
from bzrlib.symbol_versioning import DEPRECATED_PARAMETER, deprecated_passed
534
535
        return KnitVersionedFile(name, transport, factory=self.factory,
535
536
                                 delta=self.delta, create=True)
536
537
    
537
 
    def _fix_parents(self, version_id, new_parents):
538
 
        """Fix the parents list for version.
539
 
        
540
 
        This is done by appending a new version to the index
541
 
        with identical data except for the parents list.
542
 
        the parents list must be a superset of the current
543
 
        list.
544
 
        """
545
 
        current_values = self._index._cache[version_id]
546
 
        assert set(current_values[4]).difference(set(new_parents)) == set()
547
 
        self._index.add_version(version_id,
548
 
                                current_values[1],
549
 
                                (None, current_values[2], current_values[3]),
550
 
                                new_parents)
551
 
 
552
538
    def get_data_stream(self, required_versions):
553
539
        """Get a data stream for the specified versions.
554
540
 
835
821
        """See VersionedFile.add_lines_with_ghosts()."""
836
822
        self._check_add(version_id, lines, random_id, check_content)
837
823
        return self._add(version_id, lines, parents, self.delta,
838
 
            parent_texts, None, nostore_sha)
 
824
            parent_texts, None, nostore_sha, random_id)
839
825
 
840
826
    def _add_lines(self, version_id, parents, lines, parent_texts,
841
827
        left_matching_blocks, nostore_sha, random_id, check_content):
843
829
        self._check_add(version_id, lines, random_id, check_content)
844
830
        self._check_versions_present(parents)
845
831
        return self._add(version_id, lines[:], parents, self.delta,
846
 
            parent_texts, left_matching_blocks, nostore_sha)
 
832
            parent_texts, left_matching_blocks, nostore_sha, random_id)
847
833
 
848
834
    def _check_add(self, version_id, lines, random_id, check_content):
849
835
        """check that version_id and lines are safe to add."""
861
847
            self._check_lines_are_lines(lines)
862
848
 
863
849
    def _add(self, version_id, lines, parents, delta, parent_texts,
864
 
             left_matching_blocks, nostore_sha):
 
850
        left_matching_blocks, nostore_sha, random_id):
865
851
        """Add a set of lines on top of version specified by parents.
866
852
 
867
853
        If delta is true, compress the text as a line-delta against
869
855
 
870
856
        Any versions not present will be converted into ghosts.
871
857
        """
872
 
        #  461    0   6546.0390     43.9100   bzrlib.knit:489(_add)
873
 
        # +400    0    889.4890    418.9790   +bzrlib.knit:192(lower_fulltext)
874
 
        # +461    0   1364.8070    108.8030   +bzrlib.knit:996(add_record)
875
 
        # +461    0    193.3940     41.5720   +bzrlib.knit:898(add_version)
876
 
        # +461    0    134.0590     18.3810   +bzrlib.osutils:361(sha_strings)
877
 
        # +461    0     36.3420     15.4540   +bzrlib.knit:146(make)
878
 
        # +1383   0      8.0370      8.0370   +<len>
879
 
        # +61     0     13.5770      7.9190   +bzrlib.knit:199(lower_line_delta)
880
 
        # +61     0    963.3470      7.8740   +bzrlib.knit:427(_get_content)
881
 
        # +61     0    973.9950      5.2950   +bzrlib.knit:136(line_delta)
882
 
        # +61     0   1918.1800      5.2640   +bzrlib.knit:359(_merge_annotations)
 
858
        # first thing, if the content is something we don't need to store, find
 
859
        # that out.
 
860
        line_bytes = ''.join(lines)
 
861
        digest = sha_string(line_bytes)
 
862
        if nostore_sha == digest:
 
863
            raise errors.ExistingContent
883
864
 
884
865
        present_parents = []
885
866
        if parent_texts is None:
894
875
             present_parents[0] != parents[0])):
895
876
            delta = False
896
877
 
897
 
        digest = sha_strings(lines)
898
 
        if nostore_sha == digest:
899
 
            raise errors.ExistingContent
900
 
        text_length = sum(map(len, lines))
 
878
        text_length = len(line_bytes)
901
879
        options = []
902
880
        if lines:
903
881
            if lines[-1][-1] != '\n':
923
901
        if delta:
924
902
            options.append('line-delta')
925
903
            store_lines = self.factory.lower_line_delta(delta_hunks)
 
904
            size, bytes = self._data._record_to_data(version_id, digest,
 
905
                store_lines)
926
906
        else:
927
907
            options.append('fulltext')
 
908
            # get mixed annotation + content and feed it into the
 
909
            # serialiser.
928
910
            store_lines = self.factory.lower_fulltext(content)
 
911
            size, bytes = self._data._record_to_data(version_id, digest,
 
912
                store_lines)
929
913
 
930
 
        access_memo = self._data.add_record(version_id, digest, store_lines)
931
 
        self._index.add_version(version_id, options, access_memo, parents)
 
914
        access_memo = self._data.add_raw_records([size], bytes)[0]
 
915
        self._index.add_versions(
 
916
            ((version_id, options, access_memo, parents),),
 
917
            random_id=random_id)
932
918
        return digest, text_length, content
933
919
 
934
920
    def check(self, progress_bar=None):
1037
1023
            text_map[version_id] = text
1038
1024
        return text_map, final_content
1039
1025
 
 
1026
    @staticmethod
 
1027
    def _apply_delta(lines, delta):
 
1028
        """Apply delta to lines."""
 
1029
        lines = list(lines)
 
1030
        offset = 0
 
1031
        for start, end, count, delta_lines in delta:
 
1032
            lines[offset+start:offset+end] = delta_lines
 
1033
            offset = offset + (start - end) + count
 
1034
        return lines
 
1035
 
1040
1036
    def iter_lines_added_or_present_in_versions(self, version_ids=None, 
1041
1037
                                                pb=None):
1042
1038
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1374
1370
        """Add a version record to the index."""
1375
1371
        self.add_versions(((version_id, options, index_memo, parents),))
1376
1372
 
1377
 
    def add_versions(self, versions):
 
1373
    def add_versions(self, versions, random_id=False):
1378
1374
        """Add multiple versions to the index.
1379
1375
        
1380
1376
        :param versions: a list of tuples:
1381
1377
                         (version_id, options, pos, size, parents).
 
1378
        :param random_id: If True the ids being added were randomly generated
 
1379
            and no check for existence will be performed.
1382
1380
        """
1383
1381
        lines = []
1384
1382
        orig_history = self._history[:]
1714
1712
        """Add a version record to the index."""
1715
1713
        return self.add_versions(((version_id, options, access_memo, parents),))
1716
1714
 
1717
 
    def add_versions(self, versions):
 
1715
    def add_versions(self, versions, random_id=False):
1718
1716
        """Add multiple versions to the index.
1719
1717
        
1720
1718
        This function does not insert data into the Immutable GraphIndex
1724
1722
 
1725
1723
        :param versions: a list of tuples:
1726
1724
                         (version_id, options, pos, size, parents).
 
1725
        :param random_id: If True the ids being added were randomly generated
 
1726
            and no check for existence will be performed.
1727
1727
        """
1728
1728
        if not self._add_callback:
1729
1729
            raise errors.ReadOnlyError(self)
1758
1758
                        "in parentless index.")
1759
1759
                node_refs = ()
1760
1760
            keys[key] = (value, node_refs)
1761
 
        present_nodes = self._get_entries(keys)
1762
 
        for (index, key, value, node_refs) in present_nodes:
1763
 
            if (value, node_refs) != keys[key]:
1764
 
                raise KnitCorrupt(self, "inconsistent details in add_versions"
1765
 
                    ": %s %s" % ((value, node_refs), keys[key]))
1766
 
            del keys[key]
 
1761
        if not random_id:
 
1762
            present_nodes = self._get_entries(keys)
 
1763
            for (index, key, value, node_refs) in present_nodes:
 
1764
                if (value, node_refs) != keys[key]:
 
1765
                    raise KnitCorrupt(self, "inconsistent details in add_versions"
 
1766
                        ": %s %s" % ((value, node_refs), keys[key]))
 
1767
                del keys[key]
1767
1768
        result = []
1768
1769
        if self._parents:
1769
1770
            for key, (value, node_refs) in keys.iteritems():
1983
1984
        
1984
1985
        :return: (len, a StringIO instance with the raw data ready to read.)
1985
1986
        """
1986
 
        sio = StringIO()
1987
 
        data_file = GzipFile(None, mode='wb', fileobj=sio,
1988
 
            compresslevel=Z_DEFAULT_COMPRESSION)
1989
 
 
1990
 
        assert isinstance(version_id, str)
1991
 
        data_file.writelines(chain(
 
1987
        bytes = (''.join(chain(
1992
1988
            ["version %s %d %s\n" % (version_id,
1993
1989
                                     len(lines),
1994
1990
                                     digest)],
1995
1991
            lines,
1996
 
            ["end %s\n" % version_id]))
1997
 
        data_file.close()
1998
 
        length= sio.tell()
1999
 
 
2000
 
        sio.seek(0)
2001
 
        return length, sio
 
1992
            ["end %s\n" % version_id])))
 
1993
        assert bytes.__class__ == str
 
1994
        compressed_bytes = bytes_to_gzip(bytes)
 
1995
        return len(compressed_bytes), compressed_bytes
2002
1996
 
2003
1997
    def add_raw_records(self, sizes, raw_data):
2004
1998
        """Append a prepared record to the data file.
2011
2005
        """
2012
2006
        return self._access.add_raw_records(sizes, raw_data)
2013
2007
        
2014
 
    def add_record(self, version_id, digest, lines):
2015
 
        """Write new text record to disk. 
2016
 
        
2017
 
        Returns index data for retrieving it later, as per add_raw_records.
2018
 
        """
2019
 
        size, sio = self._record_to_data(version_id, digest, lines)
2020
 
        result = self.add_raw_records([size], sio.getvalue())
2021
 
        if self._do_cache:
2022
 
            self._cache[version_id] = sio.getvalue()
2023
 
        return result[0]
2024
 
 
2025
2008
    def _parse_record_header(self, version_id, raw_data):
2026
2009
        """Parse a record header for consistency.
2027
2010
 
2201
2184
    
2202
2185
            self.source_ancestry = set(self.source.get_ancestry(version_ids))
2203
2186
            this_versions = set(self.target._index.get_versions())
 
2187
            # XXX: For efficiency we should not look at the whole index,
 
2188
            #      we only need to consider the referenced revisions - they
 
2189
            #      must all be present, or the method must be full-text.
 
2190
            #      TODO, RBC 20070919
2204
2191
            needed_versions = self.source_ancestry - this_versions
2205
 
            cross_check_versions = self.source_ancestry.intersection(this_versions)
2206
 
            mismatched_versions = set()
2207
 
            for version in cross_check_versions:
2208
 
                # scan to include needed parents.
2209
 
                n1 = set(self.target.get_parents_with_ghosts(version))
2210
 
                n2 = set(self.source.get_parents_with_ghosts(version))
2211
 
                if n1 != n2:
2212
 
                    # FIXME TEST this check for cycles being introduced works
2213
 
                    # the logic is we have a cycle if in our graph we are an
2214
 
                    # ancestor of any of the n2 revisions.
2215
 
                    for parent in n2:
2216
 
                        if parent in n1:
2217
 
                            # safe
2218
 
                            continue
2219
 
                        else:
2220
 
                            parent_ancestors = self.source.get_ancestry(parent)
2221
 
                            if version in parent_ancestors:
2222
 
                                raise errors.GraphCycleError([parent, version])
2223
 
                    # ensure this parent will be available later.
2224
 
                    new_parents = n2.difference(n1)
2225
 
                    needed_versions.update(new_parents.difference(this_versions))
2226
 
                    mismatched_versions.add(version)
2227
2192
    
2228
 
            if not needed_versions and not mismatched_versions:
 
2193
            if not needed_versions:
2229
2194
                return 0
2230
2195
            full_list = topo_sort(self.source.get_graph())
2231
2196
    
2268
2233
                raw_records.append((version_id, options, parents, len(raw_data)))
2269
2234
                raw_datum.append(raw_data)
2270
2235
            self.target._add_raw_records(raw_records, ''.join(raw_datum))
2271
 
 
2272
 
            for version in mismatched_versions:
2273
 
                # FIXME RBC 20060309 is this needed?
2274
 
                n1 = set(self.target.get_parents_with_ghosts(version))
2275
 
                n2 = set(self.source.get_parents_with_ghosts(version))
2276
 
                # write a combined record to our history preserving the current 
2277
 
                # parents as first in the list
2278
 
                new_parents = self.target.get_parents_with_ghosts(version) + list(n2.difference(n1))
2279
 
                self.target.fix_parents(version, new_parents)
2280
2236
            return count
2281
2237
        finally:
2282
2238
            pb.finished()
2317
2273
            self.source_ancestry = set(self.source.get_ancestry(version_ids))
2318
2274
            this_versions = set(self.target._index.get_versions())
2319
2275
            needed_versions = self.source_ancestry - this_versions
2320
 
            cross_check_versions = self.source_ancestry.intersection(this_versions)
2321
 
            mismatched_versions = set()
2322
 
            for version in cross_check_versions:
2323
 
                # scan to include needed parents.
2324
 
                n1 = set(self.target.get_parents_with_ghosts(version))
2325
 
                n2 = set(self.source.get_parents(version))
2326
 
                # if all of n2's parents are in n1, then its fine.
2327
 
                if n2.difference(n1):
2328
 
                    # FIXME TEST this check for cycles being introduced works
2329
 
                    # the logic is we have a cycle if in our graph we are an
2330
 
                    # ancestor of any of the n2 revisions.
2331
 
                    for parent in n2:
2332
 
                        if parent in n1:
2333
 
                            # safe
2334
 
                            continue
2335
 
                        else:
2336
 
                            parent_ancestors = self.source.get_ancestry(parent)
2337
 
                            if version in parent_ancestors:
2338
 
                                raise errors.GraphCycleError([parent, version])
2339
 
                    # ensure this parent will be available later.
2340
 
                    new_parents = n2.difference(n1)
2341
 
                    needed_versions.update(new_parents.difference(this_versions))
2342
 
                    mismatched_versions.add(version)
2343
2276
    
2344
 
            if not needed_versions and not mismatched_versions:
 
2277
            if not needed_versions:
2345
2278
                return 0
2346
2279
            full_list = topo_sort(self.source.get_graph())
2347
2280
    
2361
2294
                self.target.add_lines(
2362
2295
                    version_id, parents, self.source.get_lines(version_id))
2363
2296
                count = count + 1
2364
 
 
2365
 
            for version in mismatched_versions:
2366
 
                # FIXME RBC 20060309 is this needed?
2367
 
                n1 = set(self.target.get_parents_with_ghosts(version))
2368
 
                n2 = set(self.source.get_parents(version))
2369
 
                # write a combined record to our history preserving the current 
2370
 
                # parents as first in the list
2371
 
                new_parents = self.target.get_parents_with_ghosts(version) + list(n2.difference(n1))
2372
 
                self.target.fix_parents(version, new_parents)
2373
2297
            return count
2374
2298
        finally:
2375
2299
            pb.finished()