/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 breezy/bzr/chk_map.py

  • Committer: Jelmer Vernooij
  • Date: 2018-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
 
38
38
"""
39
39
 
 
40
from __future__ import absolute_import
 
41
 
40
42
import heapq
41
43
import threading
42
44
 
48
50
    static_tuple,
49
51
    trace,
50
52
    )
 
53
from ..sixish import (
 
54
    viewitems,
 
55
    viewvalues,
 
56
    )
51
57
from ..static_tuple import StaticTuple
52
58
 
53
59
# approx 4MB
54
60
# If each line is 50 bytes, and you have 255 internal pages, with 255-way fan
55
61
# out, it takes 3.1MB to cache the layer.
56
 
_PAGE_CACHE_SIZE = 4 * 1024 * 1024
 
62
_PAGE_CACHE_SIZE = 4*1024*1024
57
63
# Per thread caches for 2 reasons:
58
64
# - in the server we may be serving very different content, so we get less
59
65
#   cache thrashing.
62
68
# The page cache.
63
69
_thread_caches.page_cache = None
64
70
 
65
 
 
66
71
def _get_cache():
67
72
    """Get the per-thread page cache.
68
73
 
93
98
 
94
99
 
95
100
search_key_registry = registry.Registry()
96
 
search_key_registry.register(b'plain', _search_key_plain)
 
101
search_key_registry.register('plain', _search_key_plain)
97
102
 
98
103
 
99
104
class CHKMap(object):
132
137
        # Check preconditions first.
133
138
        as_st = StaticTuple.from_sequence
134
139
        new_items = {as_st(key) for (old, key, value) in delta
135
 
                     if key is not None and old is None}
 
140
                         if key is not None and old is None}
136
141
        existing_new = list(self.iteritems(key_filter=new_items))
137
142
        if existing_new:
138
143
            raise errors.InconsistentDeltaDelta(delta,
139
 
                                                "New items are already in the map %r." % existing_new)
 
144
                "New items are already in the map %r." % existing_new)
140
145
        # Now apply changes.
141
146
        for old, new, value in delta:
142
147
            if old is not None and old != new:
168
173
        if isinstance(node, StaticTuple):
169
174
            bytes = self._read_bytes(node)
170
175
            return _deserialise(bytes, node,
171
 
                                search_key_func=self._search_key_func)
 
176
                search_key_func=self._search_key_func)
172
177
        else:
173
178
            return node
174
179
 
177
182
            return _get_cache()[key]
178
183
        except KeyError:
179
184
            stream = self._store.get_record_stream([key], 'unordered', True)
180
 
            bytes = next(stream).get_bytes_as('fulltext')
 
185
            bytes = stream.next().get_bytes_as('fulltext')
181
186
            _get_cache()[key] = bytes
182
187
            return bytes
183
188
 
184
 
    def _dump_tree(self, include_keys=False, encoding='utf-8'):
 
189
    def _dump_tree(self, include_keys=False):
185
190
        """Return the tree in a string representation."""
186
191
        self._ensure_root()
187
 
        def decode(x): return x.decode(encoding)
188
 
        res = self._dump_tree_node(self._root_node, prefix=b'', indent='',
189
 
                                   decode=decode, include_keys=include_keys)
190
 
        res.append('')  # Give a trailing '\n'
191
 
        return '\n'.join(res)
 
192
        res = self._dump_tree_node(self._root_node, prefix='', indent='',
 
193
                                   include_keys=include_keys)
 
194
        res.append(b'') # Give a trailing '\n'
 
195
        return b'\n'.join(res)
192
196
 
193
 
    def _dump_tree_node(self, node, prefix, indent, decode, include_keys=True):
 
197
    def _dump_tree_node(self, node, prefix, indent, include_keys=True):
194
198
        """For this node and all children, generate a string representation."""
195
199
        result = []
196
200
        if not include_keys:
198
202
        else:
199
203
            node_key = node.key()
200
204
            if node_key is not None:
201
 
                key_str = ' %s' % (decode(node_key[0]),)
 
205
                key_str = b' %s' % (node_key[0],)
202
206
            else:
203
 
                key_str = ' None'
204
 
        result.append('%s%r %s%s' % (indent, decode(prefix), node.__class__.__name__,
205
 
                                     key_str))
 
207
                key_str = b' None'
 
208
        result.append(b'%s%r %s%s' % (indent, prefix, node.__class__.__name__,
 
209
                                      key_str))
206
210
        if isinstance(node, InternalNode):
207
211
            # Trigger all child nodes to get loaded
208
212
            list(node._iter_nodes(self._store))
209
 
            for prefix, sub in sorted(node._items.items()):
 
213
            for prefix, sub in sorted(viewitems(node._items)):
210
214
                result.extend(self._dump_tree_node(sub, prefix, indent + '  ',
211
 
                                                   decode=decode, include_keys=include_keys))
 
215
                                                   include_keys=include_keys))
212
216
        else:
213
 
            for key, value in sorted(node._items.items()):
 
217
            for key, value in sorted(viewitems(node._items)):
214
218
                # Don't use prefix nor indent here to line up when used in
215
219
                # tests in conjunction with assertEqualDiff
216
 
                result.append('      %r %r' % (
217
 
                    tuple([decode(ke) for ke in key]), decode(value)))
 
220
                result.append(b'      %r %r' % (tuple(key), value))
218
221
        return result
219
222
 
220
223
    @classmethod
221
224
    def from_dict(klass, store, initial_value, maximum_size=0, key_width=1,
222
 
                  search_key_func=None):
 
225
        search_key_func=None):
223
226
        """Create a CHKMap in store with initial_value as the content.
224
227
 
225
228
        :param store: The store to record initial_value in, a VersionedFiles
236
239
        :return: The root chk of the resulting CHKMap.
237
240
        """
238
241
        root_key = klass._create_directly(store, initial_value,
239
 
                                          maximum_size=maximum_size, key_width=key_width,
240
 
                                          search_key_func=search_key_func)
 
242
            maximum_size=maximum_size, key_width=key_width,
 
243
            search_key_func=search_key_func)
241
244
        if not isinstance(root_key, StaticTuple):
242
245
            raise AssertionError('we got a %s instead of a StaticTuple'
243
246
                                 % (type(root_key),))
250
253
        result._root_node.set_maximum_size(maximum_size)
251
254
        result._root_node._key_width = key_width
252
255
        delta = []
253
 
        for key, value in initial_value.items():
 
256
        for key, value in viewitems(initial_value):
254
257
            delta.append((None, key, value))
255
258
        root_key = result.apply_delta(delta)
256
259
        return root_key
263
266
        node._key_width = key_width
264
267
        as_st = StaticTuple.from_sequence
265
268
        node._items = dict((as_st(key), val)
266
 
                           for key, val in initial_value.items())
 
269
            for key, val in viewitems(initial_value))
267
270
        node._raw_size = sum(node._key_value_len(key, value)
268
 
                             for key, value in node._items.items())
 
271
            for key, value in viewitems(node._items))
269
272
        node._len = len(node._items)
270
273
        node._compute_search_prefix()
271
274
        node._compute_serialised_prefix()
272
 
        if (node._len > 1 and
273
 
            maximum_size and
274
 
                node._current_size() > maximum_size):
 
275
        if (node._len > 1
 
276
            and maximum_size
 
277
            and node._current_size() > maximum_size):
275
278
            prefix, node_details = node._split(store)
276
279
            if len(node_details) == 1:
277
280
                raise AssertionError('Failed to split using node._split')
323
326
        # key_path (a list of tuples, tail-sharing down the tree.)
324
327
        self_pending = []
325
328
        basis_pending = []
326
 
 
327
329
        def process_node(node, path, a_map, pending):
328
330
            # take a node and expand it
329
331
            node = a_map._get_node(node)
330
332
            if isinstance(node, LeafNode):
331
333
                path = (node._key, path)
332
 
                for key, value in node._items.items():
 
334
                for key, value in viewitems(node._items):
333
335
                    # For a LeafNode, the key is a serialized_key, rather than
334
336
                    # a search_key, but the heap is using search_keys
335
337
                    search_key = node._search_key_func(key)
337
339
            else:
338
340
                # type(node) == InternalNode
339
341
                path = (node._key, path)
340
 
                for prefix, child in node._items.items():
 
342
                for prefix, child in viewitems(node._items):
341
343
                    heapq.heappush(pending, (prefix, None, child, path))
342
 
 
343
344
        def process_common_internal_nodes(self_node, basis_node):
344
 
            self_items = set(self_node._items.items())
345
 
            basis_items = set(basis_node._items.items())
 
345
            self_items = set(viewitems(self_node._items))
 
346
            basis_items = set(viewitems(basis_node._items))
346
347
            path = (self_node._key, None)
347
348
            for prefix, child in self_items - basis_items:
348
349
                heapq.heappush(self_pending, (prefix, None, child, path))
349
350
            path = (basis_node._key, None)
350
351
            for prefix, child in basis_items - self_items:
351
352
                heapq.heappush(basis_pending, (prefix, None, child, path))
352
 
 
353
353
        def process_common_leaf_nodes(self_node, basis_node):
354
 
            self_items = set(self_node._items.items())
355
 
            basis_items = set(basis_node._items.items())
 
354
            self_items = set(viewitems(self_node._items))
 
355
            basis_items = set(viewitems(basis_node._items))
356
356
            path = (self_node._key, None)
357
357
            for key, value in self_items - basis_items:
358
358
                prefix = self._search_key_func(key)
361
361
            for key, value in basis_items - self_items:
362
362
                prefix = basis._search_key_func(key)
363
363
                heapq.heappush(basis_pending, (prefix, key, value, path))
364
 
 
365
364
        def process_common_prefix_nodes(self_node, self_path,
366
365
                                        basis_node, basis_path):
367
366
            # Would it be more efficient if we could request both at the same
368
367
            # time?
369
368
            self_node = self._get_node(self_node)
370
369
            basis_node = basis._get_node(basis_node)
371
 
            if (isinstance(self_node, InternalNode) and
372
 
                    isinstance(basis_node, InternalNode)):
 
370
            if (isinstance(self_node, InternalNode)
 
371
                and isinstance(basis_node, InternalNode)):
373
372
                # Matching internal nodes
374
373
                process_common_internal_nodes(self_node, basis_node)
375
 
            elif (isinstance(self_node, LeafNode) and
376
 
                  isinstance(basis_node, LeafNode)):
 
374
            elif (isinstance(self_node, LeafNode)
 
375
                  and isinstance(basis_node, LeafNode)):
377
376
                process_common_leaf_nodes(self_node, basis_node)
378
377
            else:
379
378
                process_node(self_node, self_path, self, self_pending)
382
381
        self_seen = set()
383
382
        basis_seen = set()
384
383
        excluded_keys = set()
385
 
 
386
384
        def check_excluded(key_path):
387
385
            # Note that this is N^2, it depends on us trimming trees
388
386
            # aggressively to not become slow.
472
470
                        basis_details = heapq.heappop(basis_pending)
473
471
                        if self_details[2] != basis_details[2]:
474
472
                            yield (self_details[1],
475
 
                                   basis_details[2], self_details[2])
 
473
                                basis_details[2], self_details[2])
476
474
                        continue
477
475
                    # At least one side wasn't a simple value
478
 
                    if (self._node_key(self_pending[0][2])
479
 
                            == self._node_key(basis_pending[0][2])):
 
476
                    if (self._node_key(self_pending[0][2]) ==
 
477
                        self._node_key(basis_pending[0][2])):
480
478
                        # Identical pointers, skip (and don't bother adding to
481
479
                        # excluded, it won't turn up again.
482
480
                        heapq.heappop(self_pending)
530
528
 
531
529
    def map(self, key, value):
532
530
        """Map a key tuple to value.
533
 
 
 
531
        
534
532
        :param key: A key to map.
535
533
        :param value: The value to assign to key.
536
534
        """
542
540
            self._root_node = node_details[0][1]
543
541
        else:
544
542
            self._root_node = InternalNode(prefix,
545
 
                                           search_key_func=self._search_key_func)
 
543
                                search_key_func=self._search_key_func)
546
544
            self._root_node.set_maximum_size(node_details[0][1].maximum_size)
547
545
            self._root_node._key_width = node_details[0][1]._key_width
548
546
            for split, node in node_details:
563
561
        self._ensure_root()
564
562
        if isinstance(self._root_node, InternalNode):
565
563
            unmapped = self._root_node.unmap(self._store, key,
566
 
                                             check_remap=check_remap)
 
564
                check_remap=check_remap)
567
565
        else:
568
566
            unmapped = self._root_node.unmap(self._store, key)
569
567
        self._root_node = unmapped
595
593
 
596
594
    __slots__ = ('_key', '_len', '_maximum_size', '_key_width',
597
595
                 '_raw_size', '_items', '_search_prefix', '_search_key_func'
598
 
                 )
 
596
                )
599
597
 
600
598
    def __init__(self, key_width=1):
601
599
        """Create a node.
657
655
            if left != right:
658
656
                pos -= 1
659
657
                break
660
 
        common = prefix[:pos + 1]
 
658
        common = prefix[:pos+1]
661
659
        return common
662
660
 
663
661
    @classmethod
683
681
# Singleton indicating we have not computed _search_prefix yet
684
682
_unknown = object()
685
683
 
686
 
 
687
684
class LeafNode(Node):
688
685
    """A node containing actual key:value pairs.
689
686
 
710
707
        return \
711
708
            '%s(key:%s len:%s size:%s max:%s prefix:%s keywidth:%s items:%s)' \
712
709
            % (self.__class__.__name__, self._key, self._len, self._raw_size,
713
 
               self._maximum_size, self._search_prefix, self._key_width, items_str)
 
710
            self._maximum_size, self._search_prefix, self._key_width, items_str)
714
711
 
715
712
    def _current_size(self):
716
713
        """Answer the current serialised size of this node.
727
724
            # entry lines
728
725
            prefix_len = len(self._common_serialised_prefix)
729
726
            bytes_for_items = (self._raw_size - (prefix_len * self._len))
730
 
        return (9 +  # 'chkleaf:\n' +
731
 
                len(str(self._maximum_size)) + 1 +
732
 
                len(str(self._key_width)) + 1 +
733
 
                len(str(self._len)) + 1 +
734
 
                prefix_len + 1 +
735
 
                bytes_for_items)
 
727
        return (9 # 'chkleaf:\n'
 
728
            + len(str(self._maximum_size)) + 1
 
729
            + len(str(self._key_width)) + 1
 
730
            + len(str(self._len)) + 1
 
731
            + prefix_len + 1
 
732
            + bytes_for_items)
736
733
 
737
734
    @classmethod
738
735
    def deserialise(klass, bytes, key, search_key_func=None):
769
766
                    # Short items, we need to match based on a prefix
770
767
                    filters.setdefault(len(key), set()).add(key)
771
768
            if filters:
772
 
                filters_itemview = filters.items()
773
 
                for item in self._items.items():
 
769
                filters_itemview = viewitems(filters)
 
770
                for item in viewitems(self._items):
774
771
                    for length, length_filter in filters_itemview:
775
772
                        if item[0][:length] in length_filter:
776
773
                            yield item
777
774
                            break
778
775
        else:
779
 
            yield from self._items.items()
 
776
            for item in viewitems(self._items):
 
777
                yield item
780
778
 
781
779
    def _key_value_len(self, key, value):
782
780
        # TODO: Should probably be done without actually joining the key, but
783
781
        #       then that can be done via the C extension
784
 
        return (len(self._serialise_key(key)) + 1 +
785
 
                len(b'%d' % value.count(b'\n')) + 1 +
786
 
                len(value) + 1)
 
782
        return (len(self._serialise_key(key)) + 1
 
783
                + len(str(value.count(b'\n'))) + 1
 
784
                + len(value) + 1)
787
785
 
788
786
    def _search_key(self, key):
789
787
        return self._search_key_func(key)
813
811
        else:
814
812
            self._search_prefix = self.common_prefix(
815
813
                self._search_prefix, search_key)
816
 
        if (self._len > 1 and
817
 
            self._maximum_size and
818
 
                self._current_size() > self._maximum_size):
 
814
        if (self._len > 1
 
815
            and self._maximum_size
 
816
            and self._current_size() > self._maximum_size):
819
817
            # Check to see if all of the search_keys for this node are
820
818
            # identical. We allow the node to grow under that circumstance
821
819
            # (we could track this as common state, but it is infrequent)
822
 
            if (search_key != self._search_prefix or
823
 
                    not self._are_search_keys_identical()):
 
820
            if (search_key != self._search_prefix
 
821
                or not self._are_search_keys_identical()):
824
822
                return True
825
823
        return False
826
824
 
837
835
        common_prefix = self._search_prefix
838
836
        split_at = len(common_prefix) + 1
839
837
        result = {}
840
 
        for key, value in self._items.items():
 
838
        for key, value in viewitems(self._items):
841
839
            search_key = self._search_key(key)
842
840
            prefix = search_key[:split_at]
843
841
            # TODO: Generally only 1 key can be exactly the right length,
849
847
            #       may get a '\00' node anywhere, but won't have keys of
850
848
            #       different lengths.
851
849
            if len(prefix) < split_at:
852
 
                prefix += b'\x00' * (split_at - len(prefix))
 
850
                prefix += b'\x00'*(split_at - len(prefix))
853
851
            if prefix not in result:
854
852
                node = LeafNode(search_key_func=self._search_key_func)
855
853
                node.set_maximum_size(self._maximum_size)
864
862
                    # path
865
863
                    result.pop(prefix)
866
864
                new_node = InternalNode(sub_prefix,
867
 
                                        search_key_func=self._search_key_func)
 
865
                    search_key_func=self._search_key_func)
868
866
                new_node.set_maximum_size(self._maximum_size)
869
867
                new_node._key_width = self._key_width
870
868
                for split, node in node_details:
871
869
                    new_node.add_node(split, node)
872
870
                result[prefix] = new_node
873
 
        return common_prefix, list(result.items())
 
871
        return common_prefix, list(viewitems(result))
874
872
 
875
873
    def map(self, store, key, value):
876
874
        """Map key to value."""
883
881
        else:
884
882
            if self._search_prefix is _unknown:
885
883
                raise AssertionError('%r must be known' % self._search_prefix)
886
 
            return self._search_prefix, [(b"", self)]
 
884
            return self._search_prefix, [("", self)]
887
885
 
888
886
    _serialise_key = b'\x00'.join
889
887
 
901
899
            lines.append(b'\n')
902
900
            if len(self._items) != 0:
903
901
                raise AssertionError('If _common_serialised_prefix is None'
904
 
                                     ' we should have no items')
 
902
                    ' we should have no items')
905
903
        else:
906
904
            lines.append(b'%s\n' % (self._common_serialised_prefix,))
907
905
            prefix_len = len(self._common_serialised_prefix)
908
 
        for key, value in sorted(self._items.items()):
 
906
        for key, value in sorted(viewitems(self._items)):
909
907
            # Always add a final newline
910
908
            value_lines = osutils.chunks_to_lines([value + b'\n'])
911
909
            serialized = b"%s\x00%d\n" % (self._serialise_key(key),
912
 
                                          len(value_lines))
 
910
                                         len(value_lines))
913
911
            if not serialized.startswith(self._common_serialised_prefix):
914
912
                raise AssertionError('We thought the common prefix was %r'
915
 
                                     ' but entry %r does not have it in common'
916
 
                                     % (self._common_serialised_prefix, serialized))
 
913
                    ' but entry %r does not have it in common'
 
914
                    % (self._common_serialised_prefix, serialized))
917
915
            lines.append(serialized[prefix_len:])
918
916
            lines.extend(value_lines)
919
917
        sha1, _, _ = store.add_lines((None,), (), lines)
993
991
 
994
992
    __slots__ = ('_node_width',)
995
993
 
996
 
    def __init__(self, prefix=b'', search_key_func=None):
 
994
    def __init__(self, prefix='', search_key_func=None):
997
995
        Node.__init__(self)
998
996
        # The size of an internalnode with default values and no children.
999
997
        # How many octets key prefixes within this node are.
1014
1012
            raise AssertionError("_search_prefix should not be None")
1015
1013
        if not prefix.startswith(self._search_prefix):
1016
1014
            raise AssertionError("prefixes mismatch: %s must start with %s"
1017
 
                                 % (prefix, self._search_prefix))
 
1015
                % (prefix, self._search_prefix))
1018
1016
        if len(prefix) != len(self._search_prefix) + 1:
1019
1017
            raise AssertionError("prefix wrong length: len(%s) is not %d" %
1020
 
                                 (prefix, len(self._search_prefix) + 1))
 
1018
                (prefix, len(self._search_prefix) + 1))
1021
1019
        self._len += len(node)
1022
1020
        if not len(self._items):
1023
1021
            self._node_width = len(prefix)
1024
1022
        if self._node_width != len(self._search_prefix) + 1:
1025
1023
            raise AssertionError("node width mismatch: %d is not %d" %
1026
 
                                 (self._node_width, len(self._search_prefix) + 1))
 
1024
                (self._node_width, len(self._search_prefix) + 1))
1027
1025
        self._items[prefix] = node
1028
1026
        self._key = None
1029
1027
 
1030
1028
    def _current_size(self):
1031
1029
        """Answer the current serialised size of this node."""
1032
 
        return (self._raw_size + len(str(self._len)) + len(str(self._key_width))
1033
 
                + len(str(self._maximum_size)))
 
1030
        return (self._raw_size + len(str(self._len)) + len(str(self._key_width)) +
 
1031
            len(str(self._maximum_size)))
1034
1032
 
1035
1033
    @classmethod
1036
1034
    def deserialise(klass, bytes, key, search_key_func=None):
1070
1068
            # yielding all nodes, yield whatever we have, and queue up a read
1071
1069
            # for whatever we are missing
1072
1070
            shortcut = True
1073
 
            for prefix, node in self._items.items():
 
1071
            for prefix, node in viewitems(self._items):
1074
1072
                if node.__class__ is StaticTuple:
1075
1073
                    keys[node] = (prefix, None)
1076
1074
                else:
1121
1119
            for key in key_filter:
1122
1120
                search_prefix = self._search_prefix_filter(key)
1123
1121
                length_filter = length_filters.setdefault(
1124
 
                    len(search_prefix), set())
 
1122
                                    len(search_prefix), set())
1125
1123
                length_filter.add(search_prefix)
1126
1124
                prefix_to_keys.setdefault(search_prefix, []).append(key)
1127
1125
 
1128
 
            if (self._node_width in length_filters and
1129
 
                    len(length_filters) == 1):
 
1126
            if (self._node_width in length_filters
 
1127
                and len(length_filters) == 1):
1130
1128
                # all of the search prefixes match exactly _node_width. This
1131
1129
                # means that everything is an exact match, and we can do a
1132
1130
                # lookup into self._items, rather than iterating over the items
1146
1144
            else:
1147
1145
                # The slow way. We walk every item in self._items, and check to
1148
1146
                # see if there are any matches
1149
 
                length_filters_itemview = length_filters.items()
1150
 
                for prefix, node in self._items.items():
 
1147
                length_filters_itemview = viewitems(length_filters)
 
1148
                for prefix, node in viewitems(self._items):
1151
1149
                    node_key_filter = []
1152
1150
                    for length, length_filter in length_filters_itemview:
1153
1151
                        sub_prefix = prefix[:length]
1154
1152
                        if sub_prefix in length_filter:
1155
1153
                            node_key_filter.extend(prefix_to_keys[sub_prefix])
1156
 
                    if node_key_filter:  # this key matched something, yield it
 
1154
                    if node_key_filter: # this key matched something, yield it
1157
1155
                        if node.__class__ is StaticTuple:
1158
1156
                            keys[node] = (prefix, node_key_filter)
1159
1157
                        else:
1168
1166
                    continue
1169
1167
                else:
1170
1168
                    node = _deserialise(bytes, key,
1171
 
                                        search_key_func=self._search_key_func)
 
1169
                        search_key_func=self._search_key_func)
1172
1170
                    prefix, node_key_filter = keys[key]
1173
1171
                    self._items[prefix] = node
1174
1172
                    found_keys.add(key)
1190
1188
                for record in stream:
1191
1189
                    bytes = record.get_bytes_as('fulltext')
1192
1190
                    node = _deserialise(bytes, record.key,
1193
 
                                        search_key_func=self._search_key_func)
 
1191
                        search_key_func=self._search_key_func)
1194
1192
                    prefix, node_key_filter = keys[record.key]
1195
1193
                    node_and_filters.append((node, node_key_filter))
1196
1194
                    self._items[prefix] = node
1205
1203
        search_key = self._search_key(key)
1206
1204
        if self._node_width != len(self._search_prefix) + 1:
1207
1205
            raise AssertionError("node width mismatch: %d is not %d" %
1208
 
                                 (self._node_width, len(self._search_prefix) + 1))
 
1206
                (self._node_width, len(self._search_prefix) + 1))
1209
1207
        if not search_key.startswith(self._search_prefix):
1210
1208
            # This key doesn't fit in this index, so we need to split at the
1211
1209
            # point where it would fit, insert self into that internal node,
1213
1211
            new_prefix = self.common_prefix(self._search_prefix,
1214
1212
                                            search_key)
1215
1213
            new_parent = InternalNode(new_prefix,
1216
 
                                      search_key_func=self._search_key_func)
 
1214
                search_key_func=self._search_key_func)
1217
1215
            new_parent.set_maximum_size(self._maximum_size)
1218
1216
            new_parent._key_width = self._key_width
1219
 
            new_parent.add_node(self._search_prefix[:len(new_prefix) + 1],
 
1217
            new_parent.add_node(self._search_prefix[:len(new_prefix)+1],
1220
1218
                                self)
1221
1219
            return new_parent.map(store, key, value)
1222
 
        children = [node for node, _ in self._iter_nodes(
1223
 
            store, key_filter=[key])]
 
1220
        children = [node for node, _
 
1221
                          in self._iter_nodes(store, key_filter=[key])]
1224
1222
        if children:
1225
1223
            child = children[0]
1226
1224
        else:
1255
1253
                    # amount is over a configurable limit.
1256
1254
                    new_size = child._current_size()
1257
1255
                    shrinkage = old_size - new_size
1258
 
                    if (shrinkage > 0 and new_size < _INTERESTING_NEW_SIZE or
1259
 
                            shrinkage > _INTERESTING_SHRINKAGE_LIMIT):
 
1256
                    if (shrinkage > 0 and new_size < _INTERESTING_NEW_SIZE
 
1257
                        or shrinkage > _INTERESTING_SHRINKAGE_LIMIT):
1260
1258
                        trace.mutter(
1261
1259
                            "checking remap as size shrunk by %d to be %d",
1262
1260
                            shrinkage, new_size)
1263
1261
                        new_node = self._check_remap(store)
1264
1262
            if new_node._search_prefix is None:
1265
1263
                raise AssertionError("_search_prefix should not be None")
1266
 
            return new_node._search_prefix, [(b'', new_node)]
 
1264
            return new_node._search_prefix, [('', new_node)]
1267
1265
        # child has overflown - create a new intermediate node.
1268
1266
        # XXX: This is where we might want to try and expand our depth
1269
1267
        # to refer to more bytes of every child (which would give us
1274
1272
            child.add_node(split, node)
1275
1273
        self._len = self._len - old_len + len(child)
1276
1274
        self._key = None
1277
 
        return self._search_prefix, [(b"", self)]
 
1275
        return self._search_prefix, [("", self)]
1278
1276
 
1279
1277
    def _new_child(self, search_key, klass):
1280
1278
        """Create a new child node of type klass."""
1291
1289
        :param store: A VersionedFiles honouring the CHK extensions.
1292
1290
        :return: An iterable of the keys inserted by this operation.
1293
1291
        """
1294
 
        for node in self._items.values():
 
1292
        for node in viewvalues(self._items):
1295
1293
            if isinstance(node, StaticTuple):
1296
1294
                # Never deserialised.
1297
1295
                continue
1308
1306
            raise AssertionError("_search_prefix should not be None")
1309
1307
        lines.append(b'%s\n' % (self._search_prefix,))
1310
1308
        prefix_len = len(self._search_prefix)
1311
 
        for prefix, node in sorted(self._items.items()):
 
1309
        for prefix, node in sorted(viewitems(self._items)):
1312
1310
            if isinstance(node, StaticTuple):
1313
1311
                key = node[0]
1314
1312
            else:
1316
1314
            serialised = b"%s\x00%s\n" % (prefix, key)
1317
1315
            if not serialised.startswith(self._search_prefix):
1318
1316
                raise AssertionError("prefixes mismatch: %s must start with %s"
1319
 
                                     % (serialised, self._search_prefix))
 
1317
                    % (serialised, self._search_prefix))
1320
1318
            lines.append(serialised[prefix_len:])
1321
1319
        sha1, _, _ = store.add_lines((None,), (), lines)
1322
1320
        self._key = StaticTuple(b"sha1:" + sha1,).intern()
1327
1325
        """Return the serialised key for key in this node."""
1328
1326
        # search keys are fixed width. All will be self._node_width wide, so we
1329
1327
        # pad as necessary.
1330
 
        return (self._search_key_func(key) + b'\x00' * self._node_width)[:self._node_width]
 
1328
        return (self._search_key_func(key) + b'\x00'*self._node_width)[:self._node_width]
1331
1329
 
1332
1330
    def _search_prefix_filter(self, key):
1333
1331
        """Serialise key for use as a prefix filter in iteritems."""
1350
1348
        if self._key is None:
1351
1349
            raise AssertionError("unserialised nodes have no refs.")
1352
1350
        refs = []
1353
 
        for value in self._items.values():
 
1351
        for value in viewvalues(self._items):
1354
1352
            if isinstance(value, StaticTuple):
1355
1353
                refs.append(value)
1356
1354
            else:
1371
1369
        if not len(self._items):
1372
1370
            raise AssertionError("can't unmap in an empty InternalNode.")
1373
1371
        children = [node for node, _
1374
 
                    in self._iter_nodes(store, key_filter=[key])]
 
1372
                          in self._iter_nodes(store, key_filter=[key])]
1375
1373
        if children:
1376
1374
            child = children[0]
1377
1375
        else:
1389
1387
            self._items[search_key] = unmapped
1390
1388
        if len(self._items) == 1:
1391
1389
            # this node is no longer needed:
1392
 
            return list(self._items.values())[0]
 
1390
            return list(viewvalues(self._items))[0]
1393
1391
        if isinstance(unmapped, InternalNode):
1394
1392
            return self
1395
1393
        if check_remap:
1439
1437
            if isinstance(node, InternalNode):
1440
1438
                # Without looking at any leaf nodes, we are sure
1441
1439
                return self
1442
 
            for key, value in node._items.items():
 
1440
            for key, value in viewitems(node._items):
1443
1441
                if new_leaf._map_no_split(key, value):
1444
1442
                    return self
1445
1443
        trace.mutter("remap generated a new LeafNode")
1452
1450
        node = LeafNode.deserialise(data, key, search_key_func=search_key_func)
1453
1451
    elif data.startswith(b"chknode:\n"):
1454
1452
        node = InternalNode.deserialise(data, key,
1455
 
                                        search_key_func=search_key_func)
 
1453
            search_key_func=search_key_func)
1456
1454
    else:
1457
1455
        raise AssertionError("Unknown node type.")
1458
1456
    return node
1528
1526
                #       indicate that we keep 100k prefix_refs around while
1529
1527
                #       processing. They *should* be shorter lived than that...
1530
1528
                #       It does cost us ~10s of processing time
1531
 
                prefix_refs = list(node._items.items())
 
1529
                prefix_refs = list(viewitems(node._items))
1532
1530
                items = []
1533
1531
            else:
1534
1532
                prefix_refs = []
1535
1533
                # Note: We don't use a StaticTuple here. Profiling showed a
1536
1534
                #       minor memory improvement (0.8MB out of 335MB peak 0.2%)
1537
1535
                #       But a significant slowdown (15s / 145s, or 10%)
1538
 
                items = list(node._items.items())
 
1536
                items = list(viewitems(node._items))
1539
1537
            yield record, node, prefix_refs, items
1540
1538
 
1541
1539
    def _read_old_roots(self):
1545
1543
                self._read_nodes_from_store(self._old_root_keys):
1546
1544
            # Uninteresting node
1547
1545
            prefix_refs = [p_r for p_r in prefix_refs
1548
 
                           if p_r[1] not in all_old_chks]
 
1546
                                if p_r[1] not in all_old_chks]
1549
1547
            new_refs = [p_r[1] for p_r in prefix_refs]
1550
1548
            all_old_chks.update(new_refs)
1551
1549
            # TODO: This might be a good time to turn items into StaticTuple
1601
1599
            # At this level, we now know all the uninteresting references
1602
1600
            # So we filter and queue up whatever is remaining
1603
1601
            prefix_refs = [p_r for p_r in prefix_refs
1604
 
                           if p_r[1] not in self._all_old_chks and
1605
 
                           p_r[1] not in processed_new_refs]
 
1602
                           if p_r[1] not in self._all_old_chks
 
1603
                              and p_r[1] not in processed_new_refs]
1606
1604
            refs = [p_r[1] for p_r in prefix_refs]
1607
1605
            new_prefixes.update([p_r[0] for p_r in prefix_refs])
1608
1606
            self._new_queue.extend(refs)
1614
1612
            #       self._new_item_queue will hold the contents of multiple
1615
1613
            #       records for an extended lifetime
1616
1614
            new_items = [item for item in items
1617
 
                         if item not in self._all_old_items]
 
1615
                               if item not in self._all_old_items]
1618
1616
            self._new_item_queue.extend(new_items)
1619
1617
            new_prefixes.update([self._search_key_func(item[0])
1620
1618
                                 for item in new_items])
1638
1636
        processed_new_refs = self._processed_new_refs
1639
1637
        all_old_items = self._all_old_items
1640
1638
        new_items = [item for item in self._new_item_queue
1641
 
                     if item not in all_old_items]
 
1639
                           if item not in all_old_items]
1642
1640
        self._new_item_queue = []
1643
1641
        if new_items:
1644
1642
            yield None, new_items
1735
1733
        _search_key_255,
1736
1734
        _deserialise_leaf_node,
1737
1735
        _deserialise_internal_node,
1738
 
        )  # noqa: F401
1739
 
search_key_registry.register(b'hash-16-way', _search_key_16)
1740
 
search_key_registry.register(b'hash-255-way', _search_key_255)
 
1736
        )
 
1737
search_key_registry.register('hash-16-way', _search_key_16)
 
1738
search_key_registry.register('hash-255-way', _search_key_255)
1741
1739
 
1742
1740
 
1743
1741
def _check_key(key):
1749
1747
    if not isinstance(key, StaticTuple):
1750
1748
        raise TypeError('key %r is not StaticTuple but %s' % (key, type(key)))
1751
1749
    if len(key) != 1:
1752
 
        raise ValueError('key %r should have length 1, not %d' %
1753
 
                         (key, len(key),))
 
1750
        raise ValueError('key %r should have length 1, not %d' % (key, len(key),))
1754
1751
    if not isinstance(key[0], str):
1755
1752
        raise TypeError('key %r should hold a str, not %r'
1756
1753
                        % (key, type(key[0])))
1757
1754
    if not key[0].startswith('sha1:'):
1758
1755
        raise ValueError('key %r should point to a sha1:' % (key,))
 
1756
 
 
1757