/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/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-30 19:16:23 UTC
  • mto: This revision was merged to the branch mainline in revision 6639.
  • Revision ID: jelmer@jelmer.uk-20170530191623-t9ls96vjy9wslpoo
Remove deprecated functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
2
 
#
3
 
# Authors:
4
 
#   Johan Rydberg <jrydberg@gnu.org>
 
1
# Copyright (C) 2006-2011 Canonical Ltd
5
2
#
6
3
# This program is free software; you can redistribute it and/or modify
7
4
# it under the terms of the GNU General Public License as published by
19
16
 
20
17
"""Versioned text file storage api."""
21
18
 
 
19
from __future__ import absolute_import
 
20
 
22
21
from copy import copy
23
 
from cStringIO import StringIO
24
22
import os
25
23
import struct
26
24
from zlib import adler32
27
25
 
28
 
from bzrlib.lazy_import import lazy_import
 
26
from .lazy_import import lazy_import
29
27
lazy_import(globals(), """
30
 
import urllib
31
 
 
32
 
from bzrlib import (
 
28
from breezy import (
33
29
    annotate,
 
30
    bencode,
34
31
    errors,
35
32
    graph as _mod_graph,
36
33
    groupcompress,
40
37
    multiparent,
41
38
    tsort,
42
39
    revision,
43
 
    ui,
 
40
    urlutils,
44
41
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
 
from bzrlib.transport.memory import MemoryTransport
47
42
""")
48
 
from bzrlib.registry import Registry
49
 
from bzrlib.symbol_versioning import *
50
 
from bzrlib.textmerge import TextMerge
51
 
from bzrlib import bencode
 
43
from .registry import Registry
 
44
from .sixish import (
 
45
    BytesIO,
 
46
    )
 
47
from .textmerge import TextMerge
52
48
 
53
49
 
54
50
adapter_registry = Registry()
55
 
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'bzrlib.knit',
 
51
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'breezy.knit',
56
52
    'DeltaPlainToFullText')
57
 
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'bzrlib.knit',
 
53
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'breezy.knit',
58
54
    'FTPlainToFullText')
59
55
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
60
 
    'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
 
56
    'breezy.knit', 'DeltaAnnotatedToUnannotated')
61
57
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
62
 
    'bzrlib.knit', 'DeltaAnnotatedToFullText')
 
58
    'breezy.knit', 'DeltaAnnotatedToFullText')
63
59
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
64
 
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
 
60
    'breezy.knit', 'FTAnnotatedToUnannotated')
65
61
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
66
 
    'bzrlib.knit', 'FTAnnotatedToFullText')
 
62
    'breezy.knit', 'FTAnnotatedToFullText')
67
63
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
68
 
#     'bzrlib.knit', 'FTAnnotatedToChunked')
 
64
#     'breezy.knit', 'FTAnnotatedToChunked')
69
65
 
70
66
 
71
67
class ContentFactory(object):
206
202
            yield record
207
203
 
208
204
 
 
205
class _MPDiffGenerator(object):
 
206
    """Pull out the functionality for generating mp_diffs."""
 
207
 
 
208
    def __init__(self, vf, keys):
 
209
        self.vf = vf
 
210
        # This is the order the keys were requested in
 
211
        self.ordered_keys = tuple(keys)
 
212
        # keys + their parents, what we need to compute the diffs
 
213
        self.needed_keys = ()
 
214
        # Map from key: mp_diff
 
215
        self.diffs = {}
 
216
        # Map from key: parents_needed (may have ghosts)
 
217
        self.parent_map = {}
 
218
        # Parents that aren't present
 
219
        self.ghost_parents = ()
 
220
        # Map from parent_key => number of children for this text
 
221
        self.refcounts = {}
 
222
        # Content chunks that are cached while we still need them
 
223
        self.chunks = {}
 
224
 
 
225
    def _find_needed_keys(self):
 
226
        """Find the set of keys we need to request.
 
227
 
 
228
        This includes all the original keys passed in, and the non-ghost
 
229
        parents of those keys.
 
230
 
 
231
        :return: (needed_keys, refcounts)
 
232
            needed_keys is the set of all texts we need to extract
 
233
            refcounts is a dict of {key: num_children} letting us know when we
 
234
                no longer need to cache a given parent text
 
235
        """
 
236
        # All the keys and their parents
 
237
        needed_keys = set(self.ordered_keys)
 
238
        parent_map = self.vf.get_parent_map(needed_keys)
 
239
        self.parent_map = parent_map
 
240
        # TODO: Should we be using a different construct here? I think this
 
241
        #       uses difference_update internally, and we expect the result to
 
242
        #       be tiny
 
243
        missing_keys = needed_keys.difference(parent_map)
 
244
        if missing_keys:
 
245
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
246
        # Parents that might be missing. They are allowed to be ghosts, but we
 
247
        # should check for them
 
248
        refcounts = {}
 
249
        setdefault = refcounts.setdefault
 
250
        just_parents = set()
 
251
        for child_key, parent_keys in parent_map.iteritems():
 
252
            if not parent_keys:
 
253
                # parent_keys may be None if a given VersionedFile claims to
 
254
                # not support graph operations.
 
255
                continue
 
256
            just_parents.update(parent_keys)
 
257
            needed_keys.update(parent_keys)
 
258
            for p in parent_keys:
 
259
                refcounts[p] = setdefault(p, 0) + 1
 
260
        just_parents.difference_update(parent_map)
 
261
        # Remove any parents that are actually ghosts from the needed set
 
262
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
263
        self.ghost_parents = just_parents.difference(self.present_parents)
 
264
        needed_keys.difference_update(self.ghost_parents)
 
265
        self.needed_keys = needed_keys
 
266
        self.refcounts = refcounts
 
267
        return needed_keys, refcounts
 
268
 
 
269
    def _compute_diff(self, key, parent_lines, lines):
 
270
        """Compute a single mp_diff, and store it in self._diffs"""
 
271
        if len(parent_lines) > 0:
 
272
            # XXX: _extract_blocks is not usefully defined anywhere...
 
273
            #      It was meant to extract the left-parent diff without
 
274
            #      having to recompute it for Knit content (pack-0.92,
 
275
            #      etc). That seems to have regressed somewhere
 
276
            left_parent_blocks = self.vf._extract_blocks(key,
 
277
                parent_lines[0], lines)
 
278
        else:
 
279
            left_parent_blocks = None
 
280
        diff = multiparent.MultiParent.from_lines(lines,
 
281
                    parent_lines, left_parent_blocks)
 
282
        self.diffs[key] = diff
 
283
 
 
284
    def _process_one_record(self, key, this_chunks):
 
285
        parent_keys = None
 
286
        if key in self.parent_map:
 
287
            # This record should be ready to diff, since we requested
 
288
            # content in 'topological' order
 
289
            parent_keys = self.parent_map.pop(key)
 
290
            # If a VersionedFile claims 'no-graph' support, then it may return
 
291
            # None for any parent request, so we replace it with an empty tuple
 
292
            if parent_keys is None:
 
293
                parent_keys = ()
 
294
            parent_lines = []
 
295
            for p in parent_keys:
 
296
                # Alternatively we could check p not in self.needed_keys, but
 
297
                # ghost_parents should be tiny versus huge
 
298
                if p in self.ghost_parents:
 
299
                    continue
 
300
                refcount = self.refcounts[p]
 
301
                if refcount == 1: # Last child reference
 
302
                    self.refcounts.pop(p)
 
303
                    parent_chunks = self.chunks.pop(p)
 
304
                else:
 
305
                    self.refcounts[p] = refcount - 1
 
306
                    parent_chunks = self.chunks[p]
 
307
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
308
                # TODO: Should we cache the line form? We did the
 
309
                #       computation to get it, but storing it this way will
 
310
                #       be less memory efficient...
 
311
                parent_lines.append(p_lines)
 
312
                del p_lines
 
313
            lines = osutils.chunks_to_lines(this_chunks)
 
314
            # Since we needed the lines, we'll go ahead and cache them this way
 
315
            this_chunks = lines
 
316
            self._compute_diff(key, parent_lines, lines)
 
317
            del lines
 
318
        # Is this content required for any more children?
 
319
        if key in self.refcounts:
 
320
            self.chunks[key] = this_chunks
 
321
 
 
322
    def _extract_diffs(self):
 
323
        needed_keys, refcounts = self._find_needed_keys()
 
324
        for record in self.vf.get_record_stream(needed_keys,
 
325
                                                'topological', True):
 
326
            if record.storage_kind == 'absent':
 
327
                raise errors.RevisionNotPresent(record.key, self.vf)
 
328
            self._process_one_record(record.key,
 
329
                                     record.get_bytes_as('chunked'))
 
330
        
 
331
    def compute_diffs(self):
 
332
        self._extract_diffs()
 
333
        dpop = self.diffs.pop
 
334
        return [dpop(k) for k in self.ordered_keys]
 
335
 
 
336
 
209
337
class VersionedFile(object):
210
338
    """Versioned text file storage.
211
339
 
348
476
 
349
477
    def make_mpdiffs(self, version_ids):
350
478
        """Create multiparent diffs for specified versions."""
 
479
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
480
        #      is a list of strings, not keys. And while self.get_record_stream
 
481
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
482
        #      strings... *sigh*
351
483
        knit_versions = set()
352
484
        knit_versions.update(version_ids)
353
485
        parent_map = self.get_parent_map(version_ids)
454
586
        raise NotImplementedError(self.get_lines)
455
587
 
456
588
    def _get_lf_split_line_list(self, version_ids):
457
 
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
 
589
        return [BytesIO(t).readlines() for t in self.get_texts(version_ids)]
458
590
 
459
591
    def get_ancestry(self, version_ids, topo_sorted=True):
460
592
        """Return a list of all ancestors of given version(s). This
692
824
 
693
825
    def map(self, key):
694
826
        """See KeyMapper.map()."""
695
 
        return urllib.quote(self._map(key))
 
827
        return urlutils.quote(self._map(key))
696
828
 
697
829
    def unmap(self, partition_id):
698
830
        """See KeyMapper.unmap()."""
699
 
        return self._unmap(urllib.unquote(partition_id))
 
831
        return self._unmap(urlutils.unquote(partition_id))
700
832
 
701
833
 
702
834
class PrefixMapper(URLEscapeMapper):
749
881
    def _escape(self, prefix):
750
882
        """Turn a key element into a filesystem safe string.
751
883
 
752
 
        This is similar to a plain urllib.quote, except
 
884
        This is similar to a plain urlutils.quote, except
753
885
        it uses specific safe characters, so that it doesn't
754
886
        have to translate a lot of valid file ids.
755
887
        """
762
894
 
763
895
    def _unescape(self, basename):
764
896
        """Escaped names are easily unescaped by urlutils."""
765
 
        return urllib.unquote(basename)
 
897
        return urlutils.unquote(basename)
766
898
 
767
899
 
768
900
def make_versioned_files_factory(versioned_file_factory, mapper):
789
921
 
790
922
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
791
923
    may have a different length key-size, but that size will be constant for
792
 
    all texts added to or retrieved from it. For instance, bzrlib uses
 
924
    all texts added to or retrieved from it. For instance, breezy uses
793
925
    instances with a key-size of 2 for storing user files in a repository, with
794
926
    the first element the fileid, and the second the version of that file.
795
927
 
796
928
    The use of tuples allows a single code base to support several different
797
929
    uses with only the mapping logic changing from instance to instance.
 
930
 
 
931
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
932
        this is a list of other VersionedFiles immediately underneath this
 
933
        one.  They may in turn each have further fallbacks.
798
934
    """
799
935
 
800
936
    def add_lines(self, key, parents, lines, parent_texts=None,
839
975
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
840
976
        """Add a text to the store.
841
977
 
842
 
        This is a private function for use by CommitBuilder.
 
978
        This is a private function for use by VersionedFileCommitBuilder.
843
979
 
844
980
        :param key: The key tuple of the text to add. If the last element is
845
981
            None, a CHK string will be generated during the addition.
995
1131
        """
996
1132
        raise NotImplementedError(self.get_sha1s)
997
1133
 
998
 
    has_key = index._has_key_from_parent_map
 
1134
    __contains__ = index._has_key_from_parent_map
999
1135
 
1000
1136
    def get_missing_compression_parent_keys(self):
1001
1137
        """Return an iterable of keys of missing compression parents.
1047
1183
 
1048
1184
    def make_mpdiffs(self, keys):
1049
1185
        """Create multiparent diffs for specified keys."""
1050
 
        keys_order = tuple(keys)
1051
 
        keys = frozenset(keys)
1052
 
        knit_keys = set(keys)
1053
 
        parent_map = self.get_parent_map(keys)
1054
 
        for parent_keys in parent_map.itervalues():
1055
 
            if parent_keys:
1056
 
                knit_keys.update(parent_keys)
1057
 
        missing_keys = keys - set(parent_map)
1058
 
        if missing_keys:
1059
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
1060
 
        # We need to filter out ghosts, because we can't diff against them.
1061
 
        maybe_ghosts = knit_keys - keys
1062
 
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
1063
 
        knit_keys.difference_update(ghosts)
1064
 
        lines = {}
1065
 
        chunks_to_lines = osutils.chunks_to_lines
1066
 
        for record in self.get_record_stream(knit_keys, 'topological', True):
1067
 
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
1068
 
            # line_block_dict = {}
1069
 
            # for parent, blocks in record.extract_line_blocks():
1070
 
            #   line_blocks[parent] = blocks
1071
 
            # line_blocks[record.key] = line_block_dict
1072
 
        diffs = []
1073
 
        for key in keys_order:
1074
 
            target = lines[key]
1075
 
            parents = parent_map[key] or []
1076
 
            # Note that filtering knit_keys can lead to a parent difference
1077
 
            # between the creation and the application of the mpdiff.
1078
 
            parent_lines = [lines[p] for p in parents if p in knit_keys]
1079
 
            if len(parent_lines) > 0:
1080
 
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
1081
 
                    target)
1082
 
            else:
1083
 
                left_parent_blocks = None
1084
 
            diffs.append(multiparent.MultiParent.from_lines(target,
1085
 
                parent_lines, left_parent_blocks))
1086
 
        return diffs
 
1186
        generator = _MPDiffGenerator(self, keys)
 
1187
        return generator.compute_diffs()
 
1188
 
 
1189
    def get_annotator(self):
 
1190
        return annotate.Annotator(self)
1087
1191
 
1088
1192
    missing_keys = index._missing_keys_from_parent_map
1089
1193
 
1090
1194
    def _extract_blocks(self, version_id, source, target):
1091
1195
        return None
1092
1196
 
 
1197
    def _transitive_fallbacks(self):
 
1198
        """Return the whole stack of fallback versionedfiles.
 
1199
 
 
1200
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1201
        necessarily know about the whole stack going down, and it can't know
 
1202
        at open time because they may change after the objects are opened.
 
1203
        """
 
1204
        all_fallbacks = []
 
1205
        for a_vfs in self._immediate_fallback_vfs:
 
1206
            all_fallbacks.append(a_vfs)
 
1207
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1208
        return all_fallbacks
 
1209
 
1093
1210
 
1094
1211
class ThunkedVersionedFiles(VersionedFiles):
1095
1212
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1159
1276
            result.append((prefix + (origin,), line))
1160
1277
        return result
1161
1278
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1279
    def check(self, progress_bar=None, keys=None):
1166
1280
        """See VersionedFiles.check()."""
1167
1281
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1207
1321
    def _get_all_prefixes(self):
1208
1322
        # Identify all key prefixes.
1209
1323
        # XXX: A bit hacky, needs polish.
1210
 
        if type(self._mapper) == ConstantMapper:
 
1324
        if isinstance(self._mapper, ConstantMapper):
1211
1325
            paths = [self._mapper.map(())]
1212
1326
            prefixes = [()]
1213
1327
        else:
1311
1425
        return result
1312
1426
 
1313
1427
 
 
1428
class VersionedFilesWithFallbacks(VersionedFiles):
 
1429
 
 
1430
    def without_fallbacks(self):
 
1431
        """Return a clone of this object without any fallbacks configured."""
 
1432
        raise NotImplementedError(self.without_fallbacks)
 
1433
 
 
1434
    def add_fallback_versioned_files(self, a_versioned_files):
 
1435
        """Add a source of texts for texts not present in this knit.
 
1436
 
 
1437
        :param a_versioned_files: A VersionedFiles object.
 
1438
        """
 
1439
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1440
 
 
1441
    def get_known_graph_ancestry(self, keys):
 
1442
        """Get a KnownGraph instance with the ancestry of keys."""
 
1443
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1444
        for fallback in self._transitive_fallbacks():
 
1445
            if not missing_keys:
 
1446
                break
 
1447
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1448
                                                missing_keys)
 
1449
            parent_map.update(f_parent_map)
 
1450
            missing_keys = f_missing_keys
 
1451
        kg = _mod_graph.KnownGraph(parent_map)
 
1452
        return kg
 
1453
 
 
1454
 
1314
1455
class _PlanMergeVersionedFile(VersionedFiles):
1315
1456
    """A VersionedFile for uncommitted and committed texts.
1316
1457
 
1337
1478
        # line data for locally held keys.
1338
1479
        self._lines = {}
1339
1480
        # key lookup providers
1340
 
        self._providers = [DictParentsProvider(self._parents)]
 
1481
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1341
1482
 
1342
1483
    def plan_merge(self, ver_a, ver_b, base=None):
1343
1484
        """See VersionedFile.plan_merge"""
1344
 
        from bzrlib.merge import _PlanMerge
 
1485
        from .merge import _PlanMerge
1345
1486
        if base is None:
1346
1487
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
1347
1488
        old_plan = list(_PlanMerge(ver_a, base, self, (self._file_id,)).plan_merge())
1349
1490
        return _PlanMerge._subtract_plans(old_plan, new_plan)
1350
1491
 
1351
1492
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1352
 
        from bzrlib.merge import _PlanLCAMerge
1353
 
        graph = Graph(self)
 
1493
        from .merge import _PlanLCAMerge
 
1494
        graph = _mod_graph.Graph(self)
1354
1495
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1355
1496
        if base is None:
1356
1497
            return new_plan
1363
1504
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1364
1505
        are permitted.  Only reserved ids are permitted.
1365
1506
        """
1366
 
        if type(key) is not tuple:
 
1507
        if not isinstance(key, tuple):
1367
1508
            raise TypeError(key)
1368
1509
        if not revision.is_reserved_id(key[-1]):
1369
1510
            raise ValueError('Only reserved ids may be used')
1408
1549
            result[revision.NULL_REVISION] = ()
1409
1550
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1410
1551
        result.update(
1411
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1552
            _mod_graph.StackedParentsProvider(
 
1553
                self._providers).get_parent_map(keys))
1412
1554
        for key, parents in result.iteritems():
1413
1555
            if parents == ():
1414
1556
                result[key] = (revision.NULL_REVISION,)
1624
1766
                yield (l, key)
1625
1767
 
1626
1768
 
 
1769
class NoDupeAddLinesDecorator(object):
 
1770
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1771
    is already present.
 
1772
    """
 
1773
 
 
1774
    def __init__(self, store):
 
1775
        self._store = store
 
1776
 
 
1777
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1778
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1779
            check_content=True):
 
1780
        """See VersionedFiles.add_lines.
 
1781
        
 
1782
        This implementation may return None as the third element of the return
 
1783
        value when the original store wouldn't.
 
1784
        """
 
1785
        if nostore_sha:
 
1786
            raise NotImplementedError(
 
1787
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1788
                "nostore_sha behaviour.")
 
1789
        if key[-1] is None:
 
1790
            sha1 = osutils.sha_strings(lines)
 
1791
            key = ("sha1:" + sha1,)
 
1792
        else:
 
1793
            sha1 = None
 
1794
        if key in self._store.get_parent_map([key]):
 
1795
            # This key has already been inserted, so don't do it again.
 
1796
            if sha1 is None:
 
1797
                sha1 = osutils.sha_strings(lines)
 
1798
            return sha1, sum(map(len, lines)), None
 
1799
        return self._store.add_lines(key, parents, lines,
 
1800
                parent_texts=parent_texts,
 
1801
                left_matching_blocks=left_matching_blocks,
 
1802
                nostore_sha=nostore_sha, random_id=random_id,
 
1803
                check_content=check_content)
 
1804
 
 
1805
    def __getattr__(self, name):
 
1806
        return getattr(self._store, name)
 
1807
 
 
1808
 
1627
1809
def network_bytes_to_kind_and_offset(network_bytes):
1628
1810
    """Strip of a record kind from the front of network_bytes.
1629
1811
 
1720
1902
    for prefix in sorted(per_prefix_map):
1721
1903
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1722
1904
    return present_keys
 
1905
 
 
1906
 
 
1907
class _KeyRefs(object):
 
1908
 
 
1909
    def __init__(self, track_new_keys=False):
 
1910
        # dict mapping 'key' to 'set of keys referring to that key'
 
1911
        self.refs = {}
 
1912
        if track_new_keys:
 
1913
            # set remembering all new keys
 
1914
            self.new_keys = set()
 
1915
        else:
 
1916
            self.new_keys = None
 
1917
 
 
1918
    def clear(self):
 
1919
        if self.refs:
 
1920
            self.refs.clear()
 
1921
        if self.new_keys:
 
1922
            self.new_keys.clear()
 
1923
 
 
1924
    def add_references(self, key, refs):
 
1925
        # Record the new references
 
1926
        for referenced in refs:
 
1927
            try:
 
1928
                needed_by = self.refs[referenced]
 
1929
            except KeyError:
 
1930
                needed_by = self.refs[referenced] = set()
 
1931
            needed_by.add(key)
 
1932
        # Discard references satisfied by the new key
 
1933
        self.add_key(key)
 
1934
 
 
1935
    def get_new_keys(self):
 
1936
        return self.new_keys
 
1937
    
 
1938
    def get_unsatisfied_refs(self):
 
1939
        return self.refs.iterkeys()
 
1940
 
 
1941
    def _satisfy_refs_for_key(self, key):
 
1942
        try:
 
1943
            del self.refs[key]
 
1944
        except KeyError:
 
1945
            # No keys depended on this key.  That's ok.
 
1946
            pass
 
1947
 
 
1948
    def add_key(self, key):
 
1949
        # satisfy refs for key, and remember that we've seen this key.
 
1950
        self._satisfy_refs_for_key(key)
 
1951
        if self.new_keys is not None:
 
1952
            self.new_keys.add(key)
 
1953
 
 
1954
    def satisfy_refs_for_keys(self, keys):
 
1955
        for key in keys:
 
1956
            self._satisfy_refs_for_key(key)
 
1957
 
 
1958
    def get_referrers(self):
 
1959
        result = set()
 
1960
        for referrers in self.refs.itervalues():
 
1961
            result.update(referrers)
 
1962
        return result
 
1963
 
 
1964
 
 
1965