~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

Modify test_tsort_partial to accept multiple valid orderings.

This test previously checked for an exact match on the result of
tsort.topo_sort, while only a partial ordering is garantueed.  The current
implementation of topo_sort indeed returns the graph in lexicographical order,
but this depends on the order in which dict.popitem() pops and that is a
Python implementation detail not to be relied on.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
56
56
from itertools import izip
57
57
import operator
58
58
import os
 
59
import sys
59
60
 
60
61
from bzrlib.lazy_import import lazy_import
61
62
lazy_import(globals(), """
62
 
import gzip
63
 
 
64
63
from bzrlib import (
 
64
    annotate,
65
65
    debug,
66
66
    diff,
67
67
    graph as _mod_graph,
68
68
    index as _mod_index,
 
69
    lru_cache,
69
70
    pack,
70
 
    patiencediff,
71
 
    static_tuple,
 
71
    progress,
72
72
    trace,
73
73
    tsort,
74
74
    tuned_gzip,
75
 
    ui,
76
75
    )
77
 
 
78
 
from bzrlib.repofmt import pack_repo
79
 
from bzrlib.i18n import gettext
80
76
""")
81
77
from bzrlib import (
82
 
    annotate,
83
78
    errors,
84
79
    osutils,
 
80
    patiencediff,
85
81
    )
86
82
from bzrlib.errors import (
 
83
    FileExists,
87
84
    NoSuchFile,
 
85
    KnitError,
88
86
    InvalidRevisionId,
89
87
    KnitCorrupt,
90
88
    KnitHeaderError,
91
89
    RevisionNotPresent,
 
90
    RevisionAlreadyPresent,
92
91
    SHA1KnitCorrupt,
93
92
    )
94
93
from bzrlib.osutils import (
95
94
    contains_whitespace,
 
95
    contains_linebreaks,
96
96
    sha_string,
97
97
    sha_strings,
98
98
    split_lines,
99
99
    )
100
100
from bzrlib.versionedfile import (
101
 
    _KeyRefs,
102
101
    AbsentContentFactory,
103
102
    adapter_registry,
104
103
    ConstantMapper,
105
104
    ContentFactory,
 
105
    ChunkedContentFactory,
106
106
    sort_groupcompress,
107
 
    VersionedFilesWithFallbacks,
 
107
    VersionedFile,
 
108
    VersionedFiles,
108
109
    )
109
110
 
110
111
 
409
410
class KnitContent(object):
410
411
    """Content of a knit version to which deltas can be applied.
411
412
 
412
 
    This is always stored in memory as a list of lines with \\n at the end,
 
413
    This is always stored in memory as a list of lines with \n at the end,
413
414
    plus a flag saying if the final ending is really there or not, because that
414
415
    corresponds to the on-disk knit representation.
415
416
    """
802
803
        writer.begin()
803
804
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
804
805
            deltas=delta, add_callback=graph_index.add_nodes)
805
 
        access = pack_repo._DirectPackAccess({})
 
806
        access = _DirectPackAccess({})
806
807
        access.set_writer(writer, graph_index, (transport, 'newpack'))
807
808
        result = KnitVersionedFiles(index, access,
808
809
            max_delta_chain=max_delta_chain)
846
847
                in all_build_index_memos.itervalues()])
847
848
 
848
849
 
849
 
class KnitVersionedFiles(VersionedFilesWithFallbacks):
 
850
class KnitVersionedFiles(VersionedFiles):
850
851
    """Storage for many versioned files using knit compression.
851
852
 
852
853
    Backend storage is managed by indices and data objects.
879
880
            self._factory = KnitAnnotateFactory()
880
881
        else:
881
882
            self._factory = KnitPlainFactory()
882
 
        self._immediate_fallback_vfs = []
 
883
        self._fallback_vfs = []
883
884
        self._reload_func = reload_func
884
885
 
885
886
    def __repr__(self):
888
889
            self._index,
889
890
            self._access)
890
891
 
891
 
    def without_fallbacks(self):
892
 
        """Return a clone of this object without any fallbacks configured."""
893
 
        return KnitVersionedFiles(self._index, self._access,
894
 
            self._max_delta_chain, self._factory.annotated,
895
 
            self._reload_func)
896
 
 
897
892
    def add_fallback_versioned_files(self, a_versioned_files):
898
893
        """Add a source of texts for texts not present in this knit.
899
894
 
900
895
        :param a_versioned_files: A VersionedFiles object.
901
896
        """
902
 
        self._immediate_fallback_vfs.append(a_versioned_files)
 
897
        self._fallback_vfs.append(a_versioned_files)
903
898
 
904
899
    def add_lines(self, key, parents, lines, parent_texts=None,
905
900
        left_matching_blocks=None, nostore_sha=None, random_id=False,
1050
1045
    def get_annotator(self):
1051
1046
        return _KnitAnnotator(self)
1052
1047
 
1053
 
    def check(self, progress_bar=None, keys=None):
 
1048
    def check(self, progress_bar=None):
1054
1049
        """See VersionedFiles.check()."""
1055
 
        if keys is None:
1056
 
            return self._logical_check()
1057
 
        else:
1058
 
            # At the moment, check does not extra work over get_record_stream
1059
 
            return self.get_record_stream(keys, 'unordered', True)
1060
 
 
1061
 
    def _logical_check(self):
1062
1050
        # This doesn't actually test extraction of everything, but that will
1063
1051
        # impact 'bzr check' substantially, and needs to be integrated with
1064
1052
        # care. However, it does check for the obvious problem of a delta with
1072
1060
                    raise errors.KnitCorrupt(self,
1073
1061
                        "Missing basis parent %s for %s" % (
1074
1062
                        compression_parent, key))
1075
 
        for fallback_vfs in self._immediate_fallback_vfs:
 
1063
        for fallback_vfs in self._fallback_vfs:
1076
1064
            fallback_vfs.check()
1077
1065
 
1078
1066
    def _check_add(self, key, lines, random_id, check_content):
1156
1144
 
1157
1145
        A dict of key to (record_details, index_memo, next, parents) is
1158
1146
        returned.
1159
 
 
1160
 
        * method is the way referenced data should be applied.
1161
 
        * index_memo is the handle to pass to the data access to actually get
1162
 
          the data
1163
 
        * next is the build-parent of the version, or None for fulltexts.
1164
 
        * parents is the version_ids of the parents of this version
1165
 
 
1166
 
        :param allow_missing: If True do not raise an error on a missing
1167
 
            component, just ignore it.
 
1147
        method is the way referenced data should be applied.
 
1148
        index_memo is the handle to pass to the data access to actually get the
 
1149
            data
 
1150
        next is the build-parent of the version, or None for fulltexts.
 
1151
        parents is the version_ids of the parents of this version
 
1152
 
 
1153
        :param allow_missing: If True do not raise an error on a missing component,
 
1154
            just ignore it.
1168
1155
        """
1169
1156
        component_data = {}
1170
1157
        pending_components = keys
1216
1203
            and so on.
1217
1204
        """
1218
1205
        result = {}
1219
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1206
        sources = [self._index] + self._fallback_vfs
1220
1207
        source_results = []
1221
1208
        missing = set(keys)
1222
1209
        for source in sources:
1232
1219
        """Produce a dictionary of knit records.
1233
1220
 
1234
1221
        :return: {key:(record, record_details, digest, next)}
1235
 
 
1236
 
            * record: data returned from read_records (a KnitContentobject)
1237
 
            * record_details: opaque information to pass to parse_record
1238
 
            * digest: SHA1 digest of the full text after all steps are done
1239
 
            * next: build-parent of the version, i.e. the leftmost ancestor.
 
1222
            record
 
1223
                data returned from read_records (a KnitContentobject)
 
1224
            record_details
 
1225
                opaque information to pass to parse_record
 
1226
            digest
 
1227
                SHA1 digest of the full text after all steps are done
 
1228
            next
 
1229
                build-parent of the version, i.e. the leftmost ancestor.
1240
1230
                Will be None if the record is not a delta.
1241
 
 
1242
1231
        :param keys: The keys to build a map for
1243
1232
        :param allow_missing: If some records are missing, rather than
1244
1233
            error, just return the data that could be generated.
1509
1498
                if source is parent_maps[0]:
1510
1499
                    # this KnitVersionedFiles
1511
1500
                    records = [(key, positions[key][1]) for key in keys]
1512
 
                    for key, raw_data in self._read_records_iter_unchecked(records):
 
1501
                    for key, raw_data, sha1 in self._read_records_iter_raw(records):
1513
1502
                        (record_details, index_memo, _) = positions[key]
1514
1503
                        yield KnitContentFactory(key, global_map[key],
1515
 
                            record_details, None, raw_data, self._factory.annotated, None)
 
1504
                            record_details, sha1, raw_data, self._factory.annotated, None)
1516
1505
                else:
1517
 
                    vf = self._immediate_fallback_vfs[parent_maps.index(source) - 1]
 
1506
                    vf = self._fallback_vfs[parent_maps.index(source) - 1]
1518
1507
                    for record in vf.get_record_stream(keys, ordering,
1519
1508
                        include_delta_closure):
1520
1509
                        yield record
1530
1519
            # record entry 2 is the 'digest'.
1531
1520
            result[key] = details[2]
1532
1521
        missing.difference_update(set(result))
1533
 
        for source in self._immediate_fallback_vfs:
 
1522
        for source in self._fallback_vfs:
1534
1523
            if not missing:
1535
1524
                break
1536
1525
            new_result = source.get_sha1s(missing)
1587
1576
        # key = basis_parent, value = index entry to add
1588
1577
        buffered_index_entries = {}
1589
1578
        for record in stream:
1590
 
            kind = record.storage_kind
1591
 
            if kind.startswith('knit-') and kind.endswith('-gz'):
1592
 
                # Check that the ID in the header of the raw knit bytes matches
1593
 
                # the record metadata.
1594
 
                raw_data = record._raw_record
1595
 
                df, rec = self._parse_record_header(record.key, raw_data)
1596
 
                df.close()
1597
1579
            buffered = False
1598
1580
            parents = record.parents
1599
1581
            if record.storage_kind in delta_types:
1607
1589
                raise RevisionNotPresent([record.key], self)
1608
1590
            elif ((record.storage_kind in knit_types)
1609
1591
                  and (compression_parent is None
1610
 
                       or not self._immediate_fallback_vfs
 
1592
                       or not self._fallback_vfs
1611
1593
                       or self._index.has_key(compression_parent)
1612
1594
                       or not self.has_key(compression_parent))):
1613
1595
                # we can insert the knit record literally if either it has no
1701
1683
            # There were index entries buffered at the end of the stream,
1702
1684
            # So these need to be added (if the index supports holding such
1703
1685
            # entries for later insertion)
1704
 
            all_entries = []
1705
1686
            for key in buffered_index_entries:
1706
1687
                index_entries = buffered_index_entries[key]
1707
 
                all_entries.extend(index_entries)
1708
 
            self._index.add_records(
1709
 
                all_entries, missing_compression_parents=True)
 
1688
                self._index.add_records(index_entries,
 
1689
                    missing_compression_parents=True)
1710
1690
 
1711
1691
    def get_missing_compression_parent_keys(self):
1712
1692
        """Return an iterable of keys of missing compression parents.
1745
1725
        :return: An iterator over (line, key).
1746
1726
        """
1747
1727
        if pb is None:
1748
 
            pb = ui.ui_factory.nested_progress_bar()
 
1728
            pb = progress.DummyProgress()
1749
1729
        keys = set(keys)
1750
1730
        total = len(keys)
1751
1731
        done = False
1761
1741
                        key_records.append((key, details[0]))
1762
1742
                records_iter = enumerate(self._read_records_iter(key_records))
1763
1743
                for (key_idx, (key, data, sha_value)) in records_iter:
1764
 
                    pb.update(gettext('Walking content'), key_idx, total)
 
1744
                    pb.update('Walking content', key_idx, total)
1765
1745
                    compression_parent = build_details[key][1]
1766
1746
                    if compression_parent is None:
1767
1747
                        # fulltext
1785
1765
        # vfs, and hope to find them there.  Note that if the keys are found
1786
1766
        # but had no changes or no content, the fallback may not return
1787
1767
        # anything.
1788
 
        if keys and not self._immediate_fallback_vfs:
 
1768
        if keys and not self._fallback_vfs:
1789
1769
            # XXX: strictly the second parameter is meant to be the file id
1790
1770
            # but it's not easily accessible here.
1791
1771
            raise RevisionNotPresent(keys, repr(self))
1792
 
        for source in self._immediate_fallback_vfs:
 
1772
        for source in self._fallback_vfs:
1793
1773
            if not keys:
1794
1774
                break
1795
1775
            source_keys = set()
1797
1777
                source_keys.add(key)
1798
1778
                yield line, key
1799
1779
            keys.difference_update(source_keys)
1800
 
        pb.update(gettext('Walking content'), total, total)
 
1780
        pb.update('Walking content', total, total)
1801
1781
 
1802
1782
    def _make_line_delta(self, delta_seq, new_content):
1803
1783
        """Generate a line delta from delta_seq and new_content."""
1868
1848
        :return: the header and the decompressor stream.
1869
1849
                 as (stream, header_record)
1870
1850
        """
1871
 
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
 
1851
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
1872
1852
        try:
1873
1853
            # Current serialise
1874
1854
            rec = self._check_header(key, df.readline())
1883
1863
        # 4168 calls in 2880 217 internal
1884
1864
        # 4168 calls to _parse_record_header in 2121
1885
1865
        # 4168 calls to readlines in 330
1886
 
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(data))
 
1866
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(data))
1887
1867
        try:
1888
1868
            record_contents = df.readlines()
1889
1869
        except Exception, e:
1911
1891
        The result will be returned in whatever is the fastest to read.
1912
1892
        Not by the order requested. Also, multiple requests for the same
1913
1893
        record will only yield 1 response.
1914
 
 
1915
1894
        :param records: A list of (key, access_memo) entries
1916
1895
        :return: Yields (key, contents, digest) in the order
1917
1896
                 read, not the order requested
1975
1954
        :param key: The key of the record. Currently keys are always serialised
1976
1955
            using just the trailing component.
1977
1956
        :param dense_lines: The bytes of lines but in a denser form. For
1978
 
            instance, if lines is a list of 1000 bytestrings each ending in
1979
 
            \\n, dense_lines may be a list with one line in it, containing all
1980
 
            the 1000's lines and their \\n's. Using dense_lines if it is
1981
 
            already known is a win because the string join to create bytes in
1982
 
            this function spends less time resizing the final string.
 
1957
            instance, if lines is a list of 1000 bytestrings each ending in \n,
 
1958
            dense_lines may be a list with one line in it, containing all the
 
1959
            1000's lines and their \n's. Using dense_lines if it is already
 
1960
            known is a win because the string join to create bytes in this
 
1961
            function spends less time resizing the final string.
1983
1962
        :return: (len, a StringIO instance with the raw data ready to read.)
1984
1963
        """
1985
1964
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
2005
1984
        """See VersionedFiles.keys."""
2006
1985
        if 'evil' in debug.debug_flags:
2007
1986
            trace.mutter_callsite(2, "keys scales with size of history")
2008
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1987
        sources = [self._index] + self._fallback_vfs
2009
1988
        result = set()
2010
1989
        for source in sources:
2011
1990
            result.update(source.keys())
2051
2030
 
2052
2031
        missing_keys = set(nonlocal_keys)
2053
2032
        # Read from remote versioned file instances and provide to our caller.
2054
 
        for source in self.vf._immediate_fallback_vfs:
 
2033
        for source in self.vf._fallback_vfs:
2055
2034
            if not missing_keys:
2056
2035
                break
2057
2036
            # Loop over fallback repositories asking them for texts - ignore
2359
2338
    FLAGS is a comma separated list of flags about the record. Values include
2360
2339
        no-eol, line-delta, fulltext.
2361
2340
    BYTE_OFFSET is the ascii representation of the byte offset in the data file
2362
 
        that the compressed data starts at.
 
2341
        that the the compressed data starts at.
2363
2342
    LENGTH is the ascii representation of the length of the data file.
2364
2343
    PARENT_ID a utf-8 revision id prefixed by a '.' that is a parent of
2365
2344
        REVISION_ID.
2574
2553
        except KeyError:
2575
2554
            raise RevisionNotPresent(key, self)
2576
2555
 
2577
 
    def find_ancestry(self, keys):
2578
 
        """See CombinedGraphIndex.find_ancestry()"""
2579
 
        prefixes = set(key[:-1] for key in keys)
2580
 
        self._load_prefixes(prefixes)
2581
 
        result = {}
2582
 
        parent_map = {}
2583
 
        missing_keys = set()
2584
 
        pending_keys = list(keys)
2585
 
        # This assumes that keys will not reference parents in a different
2586
 
        # prefix, which is accurate so far.
2587
 
        while pending_keys:
2588
 
            key = pending_keys.pop()
2589
 
            if key in parent_map:
2590
 
                continue
2591
 
            prefix = key[:-1]
2592
 
            try:
2593
 
                suffix_parents = self._kndx_cache[prefix][0][key[-1]][4]
2594
 
            except KeyError:
2595
 
                missing_keys.add(key)
2596
 
            else:
2597
 
                parent_keys = tuple([prefix + (suffix,)
2598
 
                                     for suffix in suffix_parents])
2599
 
                parent_map[key] = parent_keys
2600
 
                pending_keys.extend([p for p in parent_keys
2601
 
                                        if p not in parent_map])
2602
 
        return parent_map, missing_keys
2603
 
 
2604
2556
    def get_parent_map(self, keys):
2605
2557
        """Get a map of the parents of keys.
2606
2558
 
2776
2728
        return key[:-1], key[-1]
2777
2729
 
2778
2730
 
 
2731
class _KeyRefs(object):
 
2732
 
 
2733
    def __init__(self):
 
2734
        # dict mapping 'key' to 'set of keys referring to that key'
 
2735
        self.refs = {}
 
2736
 
 
2737
    def add_references(self, key, refs):
 
2738
        # Record the new references
 
2739
        for referenced in refs:
 
2740
            try:
 
2741
                needed_by = self.refs[referenced]
 
2742
            except KeyError:
 
2743
                needed_by = self.refs[referenced] = set()
 
2744
            needed_by.add(key)
 
2745
        # Discard references satisfied by the new key
 
2746
        self.add_key(key)
 
2747
 
 
2748
    def get_unsatisfied_refs(self):
 
2749
        return self.refs.iterkeys()
 
2750
 
 
2751
    def add_key(self, key):
 
2752
        try:
 
2753
            del self.refs[key]
 
2754
        except KeyError:
 
2755
            # No keys depended on this key.  That's ok.
 
2756
            pass
 
2757
 
 
2758
    def add_keys(self, keys):
 
2759
        for key in keys:
 
2760
            self.add_key(key)
 
2761
 
 
2762
    def get_referrers(self):
 
2763
        result = set()
 
2764
        for referrers in self.refs.itervalues():
 
2765
            result.update(referrers)
 
2766
        return result
 
2767
 
 
2768
 
2779
2769
class _KnitGraphIndex(object):
2780
2770
    """A KnitVersionedFiles index layered on GraphIndex."""
2781
2771
 
2878
2868
        if not random_id:
2879
2869
            present_nodes = self._get_entries(keys)
2880
2870
            for (index, key, value, node_refs) in present_nodes:
2881
 
                parents = node_refs[:1]
2882
 
                # Sometimes these are passed as a list rather than a tuple
2883
 
                passed = static_tuple.as_tuples(keys[key])
2884
 
                passed_parents = passed[1][:1]
2885
2871
                if (value[0] != keys[key][0][0] or
2886
 
                    parents != passed_parents):
2887
 
                    node_refs = static_tuple.as_tuples(node_refs)
 
2872
                    node_refs[:1] != keys[key][1][:1]):
2888
2873
                    raise KnitCorrupt(self, "inconsistent details in add_records"
2889
 
                        ": %s %s" % ((value, node_refs), passed))
 
2874
                        ": %s %s" % ((value, node_refs), keys[key]))
2890
2875
                del keys[key]
2891
2876
        result = []
2892
2877
        if self._parents:
2940
2925
        # If updating this, you should also update
2941
2926
        # groupcompress._GCGraphIndex.get_missing_parents
2942
2927
        # We may have false positives, so filter those out.
2943
 
        self._key_dependencies.satisfy_refs_for_keys(
 
2928
        self._key_dependencies.add_keys(
2944
2929
            self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
2945
2930
        return frozenset(self._key_dependencies.get_unsatisfied_refs())
2946
2931
 
3057
3042
            options.append('no-eol')
3058
3043
        return options
3059
3044
 
3060
 
    def find_ancestry(self, keys):
3061
 
        """See CombinedGraphIndex.find_ancestry()"""
3062
 
        return self._graph_index.find_ancestry(keys, 0)
3063
 
 
3064
3045
    def get_parent_map(self, keys):
3065
3046
        """Get a map of the parents of keys.
3066
3047
 
3210
3191
                yield data
3211
3192
 
3212
3193
 
 
3194
class _DirectPackAccess(object):
 
3195
    """Access to data in one or more packs with less translation."""
 
3196
 
 
3197
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
 
3198
        """Create a _DirectPackAccess object.
 
3199
 
 
3200
        :param index_to_packs: A dict mapping index objects to the transport
 
3201
            and file names for obtaining data.
 
3202
        :param reload_func: A function to call if we determine that the pack
 
3203
            files have moved and we need to reload our caches. See
 
3204
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
 
3205
        """
 
3206
        self._container_writer = None
 
3207
        self._write_index = None
 
3208
        self._indices = index_to_packs
 
3209
        self._reload_func = reload_func
 
3210
        self._flush_func = flush_func
 
3211
 
 
3212
    def add_raw_records(self, key_sizes, raw_data):
 
3213
        """Add raw knit bytes to a storage area.
 
3214
 
 
3215
        The data is spooled to the container writer in one bytes-record per
 
3216
        raw data item.
 
3217
 
 
3218
        :param sizes: An iterable of tuples containing the key and size of each
 
3219
            raw data segment.
 
3220
        :param raw_data: A bytestring containing the data.
 
3221
        :return: A list of memos to retrieve the record later. Each memo is an
 
3222
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
 
3223
            length), where the index field is the write_index object supplied
 
3224
            to the PackAccess object.
 
3225
        """
 
3226
        if type(raw_data) is not str:
 
3227
            raise AssertionError(
 
3228
                'data must be plain bytes was %s' % type(raw_data))
 
3229
        result = []
 
3230
        offset = 0
 
3231
        for key, size in key_sizes:
 
3232
            p_offset, p_length = self._container_writer.add_bytes_record(
 
3233
                raw_data[offset:offset+size], [])
 
3234
            offset += size
 
3235
            result.append((self._write_index, p_offset, p_length))
 
3236
        return result
 
3237
 
 
3238
    def flush(self):
 
3239
        """Flush pending writes on this access object.
 
3240
 
 
3241
        This will flush any buffered writes to a NewPack.
 
3242
        """
 
3243
        if self._flush_func is not None:
 
3244
            self._flush_func()
 
3245
            
 
3246
    def get_raw_records(self, memos_for_retrieval):
 
3247
        """Get the raw bytes for a records.
 
3248
 
 
3249
        :param memos_for_retrieval: An iterable containing the (index, pos,
 
3250
            length) memo for retrieving the bytes. The Pack access method
 
3251
            looks up the pack to use for a given record in its index_to_pack
 
3252
            map.
 
3253
        :return: An iterator over the bytes of the records.
 
3254
        """
 
3255
        # first pass, group into same-index requests
 
3256
        request_lists = []
 
3257
        current_index = None
 
3258
        for (index, offset, length) in memos_for_retrieval:
 
3259
            if current_index == index:
 
3260
                current_list.append((offset, length))
 
3261
            else:
 
3262
                if current_index is not None:
 
3263
                    request_lists.append((current_index, current_list))
 
3264
                current_index = index
 
3265
                current_list = [(offset, length)]
 
3266
        # handle the last entry
 
3267
        if current_index is not None:
 
3268
            request_lists.append((current_index, current_list))
 
3269
        for index, offsets in request_lists:
 
3270
            try:
 
3271
                transport, path = self._indices[index]
 
3272
            except KeyError:
 
3273
                # A KeyError here indicates that someone has triggered an index
 
3274
                # reload, and this index has gone missing, we need to start
 
3275
                # over.
 
3276
                if self._reload_func is None:
 
3277
                    # If we don't have a _reload_func there is nothing that can
 
3278
                    # be done
 
3279
                    raise
 
3280
                raise errors.RetryWithNewPacks(index,
 
3281
                                               reload_occurred=True,
 
3282
                                               exc_info=sys.exc_info())
 
3283
            try:
 
3284
                reader = pack.make_readv_reader(transport, path, offsets)
 
3285
                for names, read_func in reader.iter_records():
 
3286
                    yield read_func(None)
 
3287
            except errors.NoSuchFile:
 
3288
                # A NoSuchFile error indicates that a pack file has gone
 
3289
                # missing on disk, we need to trigger a reload, and start over.
 
3290
                if self._reload_func is None:
 
3291
                    raise
 
3292
                raise errors.RetryWithNewPacks(transport.abspath(path),
 
3293
                                               reload_occurred=False,
 
3294
                                               exc_info=sys.exc_info())
 
3295
 
 
3296
    def set_writer(self, writer, index, transport_packname):
 
3297
        """Set a writer to use for adding data."""
 
3298
        if index is not None:
 
3299
            self._indices[index] = transport_packname
 
3300
        self._container_writer = writer
 
3301
        self._write_index = index
 
3302
 
 
3303
    def reload_or_raise(self, retry_exc):
 
3304
        """Try calling the reload function, or re-raise the original exception.
 
3305
 
 
3306
        This should be called after _DirectPackAccess raises a
 
3307
        RetryWithNewPacks exception. This function will handle the common logic
 
3308
        of determining when the error is fatal versus being temporary.
 
3309
        It will also make sure that the original exception is raised, rather
 
3310
        than the RetryWithNewPacks exception.
 
3311
 
 
3312
        If this function returns, then the calling function should retry
 
3313
        whatever operation was being performed. Otherwise an exception will
 
3314
        be raised.
 
3315
 
 
3316
        :param retry_exc: A RetryWithNewPacks exception.
 
3317
        """
 
3318
        is_error = False
 
3319
        if self._reload_func is None:
 
3320
            is_error = True
 
3321
        elif not self._reload_func():
 
3322
            # The reload claimed that nothing changed
 
3323
            if not retry_exc.reload_occurred:
 
3324
                # If there wasn't an earlier reload, then we really were
 
3325
                # expecting to find changes. We didn't find them, so this is a
 
3326
                # hard error
 
3327
                is_error = True
 
3328
        if is_error:
 
3329
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
 
3330
            raise exc_class, exc_value, exc_traceback
 
3331
 
 
3332
 
 
3333
# Deprecated, use PatienceSequenceMatcher instead
 
3334
KnitSequenceMatcher = patiencediff.PatienceSequenceMatcher
 
3335
 
 
3336
 
3213
3337
def annotate_knit(knit, revision_id):
3214
3338
    """Annotate a knit with no cached annotations.
3215
3339
 
3313
3437
        return records, ann_keys
3314
3438
 
3315
3439
    def _get_needed_texts(self, key, pb=None):
3316
 
        # if True or len(self._vf._immediate_fallback_vfs) > 0:
3317
 
        if len(self._vf._immediate_fallback_vfs) > 0:
 
3440
        # if True or len(self._vf._fallback_vfs) > 0:
 
3441
        if len(self._vf._fallback_vfs) > 0:
3318
3442
            # If we have fallbacks, go to the generic path
3319
3443
            for v in annotate.Annotator._get_needed_texts(self, key, pb=pb):
3320
3444
                yield v
3325
3449
                for idx, (sub_key, text, num_lines) in enumerate(
3326
3450
                                                self._extract_texts(records)):
3327
3451
                    if pb is not None:
3328
 
                        pb.update(gettext('annotating'), idx, len(records))
 
3452
                        pb.update('annotating', idx, len(records))
3329
3453
                    yield sub_key, text, num_lines
3330
3454
                for sub_key in ann_keys:
3331
3455
                    text = self._text_cache[sub_key]
3498
3622
 
3499
3623
try:
3500
3624
    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
3501
 
except ImportError, e:
3502
 
    osutils.failed_to_load_extension(e)
 
3625
except ImportError:
3503
3626
    from bzrlib._knit_load_data_py import _load_data_py as _load_data