~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
 
3
# Authors:
 
4
#   Johan Rydberg <jrydberg@gnu.org>
2
5
#
3
6
# This program is free software; you can redistribute it and/or modify
4
7
# it under the terms of the GNU General Public License as published by
12
15
#
13
16
# You should have received a copy of the GNU General Public License
14
17
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
19
 
17
20
"""Versioned text file storage api."""
18
21
 
19
 
from __future__ import absolute_import
20
 
 
21
22
from copy import copy
22
23
from cStringIO import StringIO
23
24
import os
24
 
import struct
 
25
import urllib
25
26
from zlib import adler32
26
27
 
27
28
from bzrlib.lazy_import import lazy_import
28
29
lazy_import(globals(), """
 
30
 
29
31
from bzrlib import (
30
 
    annotate,
31
 
    bencode,
32
32
    errors,
33
 
    graph as _mod_graph,
34
 
    groupcompress,
35
 
    index,
36
 
    knit,
37
33
    osutils,
38
34
    multiparent,
39
35
    tsort,
40
36
    revision,
41
 
    urlutils,
 
37
    ui,
42
38
    )
 
39
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
 
40
from bzrlib.transport.memory import MemoryTransport
43
41
""")
 
42
from bzrlib.inter import InterObject
44
43
from bzrlib.registry import Registry
 
44
from bzrlib.symbol_versioning import *
45
45
from bzrlib.textmerge import TextMerge
46
46
 
47
47
 
58
58
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
59
59
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
60
60
    'bzrlib.knit', 'FTAnnotatedToFullText')
61
 
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
62
 
#     'bzrlib.knit', 'FTAnnotatedToChunked')
63
61
 
64
62
 
65
63
class ContentFactory(object):
66
64
    """Abstract interface for insertion and retrieval from a VersionedFile.
67
 
 
 
65
    
68
66
    :ivar sha1: None, or the sha1 of the content fulltext.
69
67
    :ivar storage_kind: The native storage kind of this factory. One of
70
68
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
85
83
        self.parents = None
86
84
 
87
85
 
88
 
class ChunkedContentFactory(ContentFactory):
89
 
    """Static data content factory.
90
 
 
91
 
    This takes a 'chunked' list of strings. The only requirement on 'chunked' is
92
 
    that ''.join(lines) becomes a valid fulltext. A tuple of a single string
93
 
    satisfies this, as does a list of lines.
94
 
 
95
 
    :ivar sha1: None, or the sha1 of the content fulltext.
96
 
    :ivar storage_kind: The native storage kind of this factory. Always
97
 
        'chunked'
98
 
    :ivar key: The key of this content. Each key is a tuple with a single
99
 
        string in it.
100
 
    :ivar parents: A tuple of parent keys for self.key. If the object has
101
 
        no parent information, None (as opposed to () for an empty list of
102
 
        parents).
103
 
     """
104
 
 
105
 
    def __init__(self, key, parents, sha1, chunks):
106
 
        """Create a ContentFactory."""
107
 
        self.sha1 = sha1
108
 
        self.storage_kind = 'chunked'
109
 
        self.key = key
110
 
        self.parents = parents
111
 
        self._chunks = chunks
112
 
 
113
 
    def get_bytes_as(self, storage_kind):
114
 
        if storage_kind == 'chunked':
115
 
            return self._chunks
116
 
        elif storage_kind == 'fulltext':
117
 
            return ''.join(self._chunks)
118
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
119
 
            self.storage_kind)
120
 
 
121
 
 
122
86
class FulltextContentFactory(ContentFactory):
123
87
    """Static data content factory.
124
88
 
125
89
    This takes a fulltext when created and just returns that during
126
90
    get_bytes_as('fulltext').
127
 
 
 
91
    
128
92
    :ivar sha1: None, or the sha1 of the content fulltext.
129
93
    :ivar storage_kind: The native storage kind of this factory. Always
130
94
        'fulltext'.
146
110
    def get_bytes_as(self, storage_kind):
147
111
        if storage_kind == self.storage_kind:
148
112
            return self._text
149
 
        elif storage_kind == 'chunked':
150
 
            return [self._text]
151
113
        raise errors.UnavailableRepresentation(self.key, storage_kind,
152
114
            self.storage_kind)
153
115
 
154
116
 
155
117
class AbsentContentFactory(ContentFactory):
156
118
    """A placeholder content factory for unavailable texts.
157
 
 
 
119
    
158
120
    :ivar sha1: None.
159
121
    :ivar storage_kind: 'absent'.
160
122
    :ivar key: The key of this content. Each key is a tuple with a single
169
131
        self.key = key
170
132
        self.parents = None
171
133
 
172
 
    def get_bytes_as(self, storage_kind):
173
 
        raise ValueError('A request was made for key: %s, but that'
174
 
                         ' content is not available, and the calling'
175
 
                         ' code does not handle if it is missing.'
176
 
                         % (self.key,))
177
 
 
178
134
 
179
135
class AdapterFactory(ContentFactory):
180
136
    """A content factory to adapt between key prefix's."""
200
156
            yield record
201
157
 
202
158
 
203
 
class _MPDiffGenerator(object):
204
 
    """Pull out the functionality for generating mp_diffs."""
205
 
 
206
 
    def __init__(self, vf, keys):
207
 
        self.vf = vf
208
 
        # This is the order the keys were requested in
209
 
        self.ordered_keys = tuple(keys)
210
 
        # keys + their parents, what we need to compute the diffs
211
 
        self.needed_keys = ()
212
 
        # Map from key: mp_diff
213
 
        self.diffs = {}
214
 
        # Map from key: parents_needed (may have ghosts)
215
 
        self.parent_map = {}
216
 
        # Parents that aren't present
217
 
        self.ghost_parents = ()
218
 
        # Map from parent_key => number of children for this text
219
 
        self.refcounts = {}
220
 
        # Content chunks that are cached while we still need them
221
 
        self.chunks = {}
222
 
 
223
 
    def _find_needed_keys(self):
224
 
        """Find the set of keys we need to request.
225
 
 
226
 
        This includes all the original keys passed in, and the non-ghost
227
 
        parents of those keys.
228
 
 
229
 
        :return: (needed_keys, refcounts)
230
 
            needed_keys is the set of all texts we need to extract
231
 
            refcounts is a dict of {key: num_children} letting us know when we
232
 
                no longer need to cache a given parent text
233
 
        """
234
 
        # All the keys and their parents
235
 
        needed_keys = set(self.ordered_keys)
236
 
        parent_map = self.vf.get_parent_map(needed_keys)
237
 
        self.parent_map = parent_map
238
 
        # TODO: Should we be using a different construct here? I think this
239
 
        #       uses difference_update internally, and we expect the result to
240
 
        #       be tiny
241
 
        missing_keys = needed_keys.difference(parent_map)
242
 
        if missing_keys:
243
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
244
 
        # Parents that might be missing. They are allowed to be ghosts, but we
245
 
        # should check for them
246
 
        refcounts = {}
247
 
        setdefault = refcounts.setdefault
248
 
        just_parents = set()
249
 
        for child_key, parent_keys in parent_map.iteritems():
250
 
            if not parent_keys:
251
 
                # parent_keys may be None if a given VersionedFile claims to
252
 
                # not support graph operations.
253
 
                continue
254
 
            just_parents.update(parent_keys)
255
 
            needed_keys.update(parent_keys)
256
 
            for p in parent_keys:
257
 
                refcounts[p] = setdefault(p, 0) + 1
258
 
        just_parents.difference_update(parent_map)
259
 
        # Remove any parents that are actually ghosts from the needed set
260
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
261
 
        self.ghost_parents = just_parents.difference(self.present_parents)
262
 
        needed_keys.difference_update(self.ghost_parents)
263
 
        self.needed_keys = needed_keys
264
 
        self.refcounts = refcounts
265
 
        return needed_keys, refcounts
266
 
 
267
 
    def _compute_diff(self, key, parent_lines, lines):
268
 
        """Compute a single mp_diff, and store it in self._diffs"""
269
 
        if len(parent_lines) > 0:
270
 
            # XXX: _extract_blocks is not usefully defined anywhere...
271
 
            #      It was meant to extract the left-parent diff without
272
 
            #      having to recompute it for Knit content (pack-0.92,
273
 
            #      etc). That seems to have regressed somewhere
274
 
            left_parent_blocks = self.vf._extract_blocks(key,
275
 
                parent_lines[0], lines)
276
 
        else:
277
 
            left_parent_blocks = None
278
 
        diff = multiparent.MultiParent.from_lines(lines,
279
 
                    parent_lines, left_parent_blocks)
280
 
        self.diffs[key] = diff
281
 
 
282
 
    def _process_one_record(self, key, this_chunks):
283
 
        parent_keys = None
284
 
        if key in self.parent_map:
285
 
            # This record should be ready to diff, since we requested
286
 
            # content in 'topological' order
287
 
            parent_keys = self.parent_map.pop(key)
288
 
            # If a VersionedFile claims 'no-graph' support, then it may return
289
 
            # None for any parent request, so we replace it with an empty tuple
290
 
            if parent_keys is None:
291
 
                parent_keys = ()
292
 
            parent_lines = []
293
 
            for p in parent_keys:
294
 
                # Alternatively we could check p not in self.needed_keys, but
295
 
                # ghost_parents should be tiny versus huge
296
 
                if p in self.ghost_parents:
297
 
                    continue
298
 
                refcount = self.refcounts[p]
299
 
                if refcount == 1: # Last child reference
300
 
                    self.refcounts.pop(p)
301
 
                    parent_chunks = self.chunks.pop(p)
302
 
                else:
303
 
                    self.refcounts[p] = refcount - 1
304
 
                    parent_chunks = self.chunks[p]
305
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
306
 
                # TODO: Should we cache the line form? We did the
307
 
                #       computation to get it, but storing it this way will
308
 
                #       be less memory efficient...
309
 
                parent_lines.append(p_lines)
310
 
                del p_lines
311
 
            lines = osutils.chunks_to_lines(this_chunks)
312
 
            # Since we needed the lines, we'll go ahead and cache them this way
313
 
            this_chunks = lines
314
 
            self._compute_diff(key, parent_lines, lines)
315
 
            del lines
316
 
        # Is this content required for any more children?
317
 
        if key in self.refcounts:
318
 
            self.chunks[key] = this_chunks
319
 
 
320
 
    def _extract_diffs(self):
321
 
        needed_keys, refcounts = self._find_needed_keys()
322
 
        for record in self.vf.get_record_stream(needed_keys,
323
 
                                                'topological', True):
324
 
            if record.storage_kind == 'absent':
325
 
                raise errors.RevisionNotPresent(record.key, self.vf)
326
 
            self._process_one_record(record.key,
327
 
                                     record.get_bytes_as('chunked'))
328
 
        
329
 
    def compute_diffs(self):
330
 
        self._extract_diffs()
331
 
        dpop = self.diffs.pop
332
 
        return [dpop(k) for k in self.ordered_keys]
333
 
 
334
 
 
335
159
class VersionedFile(object):
336
160
    """Versioned text file storage.
337
 
 
 
161
    
338
162
    A versioned file manages versions of line-based text files,
339
163
    keeping track of the originating version for each line.
340
164
 
378
202
    def insert_record_stream(self, stream):
379
203
        """Insert a record stream into this versioned file.
380
204
 
381
 
        :param stream: A stream of records to insert.
 
205
        :param stream: A stream of records to insert. 
382
206
        :return: None
383
207
        :seealso VersionedFile.get_record_stream:
384
208
        """
403
227
            the data back accurately. (Checking the lines have been split
404
228
            correctly is expensive and extremely unlikely to catch bugs so it
405
229
            is not done at runtime unless check_content is True.)
406
 
        :param parent_texts: An optional dictionary containing the opaque
 
230
        :param parent_texts: An optional dictionary containing the opaque 
407
231
            representations of some or all of the parents of version_id to
408
232
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
409
233
            returned by add_lines or data corruption can be caused.
437
261
        parent_texts=None, nostore_sha=None, random_id=False,
438
262
        check_content=True, left_matching_blocks=None):
439
263
        """Add lines to the versioned file, allowing ghosts to be present.
440
 
 
 
264
        
441
265
        This takes the same parameters as add_lines and returns the same.
442
266
        """
443
267
        self._check_write_ok()
467
291
 
468
292
    def get_format_signature(self):
469
293
        """Get a text description of the data encoding in this file.
470
 
 
 
294
        
471
295
        :since: 0.90
472
296
        """
473
297
        raise NotImplementedError(self.get_format_signature)
474
298
 
475
299
    def make_mpdiffs(self, version_ids):
476
300
        """Create multiparent diffs for specified versions."""
477
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
478
 
        #      is a list of strings, not keys. And while self.get_record_stream
479
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
480
 
        #      strings... *sigh*
481
301
        knit_versions = set()
482
302
        knit_versions.update(version_ids)
483
303
        parent_map = self.get_parent_map(version_ids)
598
418
        if isinstance(version_ids, basestring):
599
419
            version_ids = [version_ids]
600
420
        raise NotImplementedError(self.get_ancestry)
601
 
 
 
421
        
602
422
    def get_ancestry_with_ghosts(self, version_ids):
603
423
        """Return a list of all ancestors of given version(s). This
604
424
        will not include the null revision.
605
425
 
606
426
        Must raise RevisionNotPresent if any of the given versions are
607
427
        not present in file history.
608
 
 
 
428
        
609
429
        Ghosts that are known about will be included in ancestry list,
610
430
        but are not explicitly marked.
611
431
        """
612
432
        raise NotImplementedError(self.get_ancestry_with_ghosts)
613
 
 
 
433
    
614
434
    def get_parent_map(self, version_ids):
615
435
        """Get a map of the parents of version_ids.
616
436
 
679
499
        unchanged   Alive in both a and b (possibly created in both)
680
500
        new-a       Created in a
681
501
        new-b       Created in b
682
 
        ghost-a     Killed in a, unborn in b
 
502
        ghost-a     Killed in a, unborn in b    
683
503
        ghost-b     Killed in b, unborn in a
684
504
        irrelevant  Not in either revision
685
505
        """
686
506
        raise NotImplementedError(VersionedFile.plan_merge)
687
 
 
 
507
        
688
508
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
689
509
                    b_marker=TextMerge.B_MARKER):
690
510
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
692
512
 
693
513
class RecordingVersionedFilesDecorator(object):
694
514
    """A minimal versioned files that records calls made on it.
695
 
 
 
515
    
696
516
    Only enough methods have been added to support tests using it to date.
697
517
 
698
518
    :ivar calls: A list of the calls made; can be reset at any time by
700
520
    """
701
521
 
702
522
    def __init__(self, backing_vf):
703
 
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
704
 
 
 
523
        """Create a RecordingVersionedFileDsecorator decorating backing_vf.
 
524
        
705
525
        :param backing_vf: The versioned file to answer all methods.
706
526
        """
707
527
        self._backing_vf = backing_vf
741
561
        return self._backing_vf.keys()
742
562
 
743
563
 
744
 
class OrderingVersionedFilesDecorator(RecordingVersionedFilesDecorator):
745
 
    """A VF that records calls, and returns keys in specific order.
746
 
 
747
 
    :ivar calls: A list of the calls made; can be reset at any time by
748
 
        assigning [] to it.
749
 
    """
750
 
 
751
 
    def __init__(self, backing_vf, key_priority):
752
 
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
753
 
 
754
 
        :param backing_vf: The versioned file to answer all methods.
755
 
        :param key_priority: A dictionary defining what order keys should be
756
 
            returned from an 'unordered' get_record_stream request.
757
 
            Keys with lower priority are returned first, keys not present in
758
 
            the map get an implicit priority of 0, and are returned in
759
 
            lexicographical order.
760
 
        """
761
 
        RecordingVersionedFilesDecorator.__init__(self, backing_vf)
762
 
        self._key_priority = key_priority
763
 
 
764
 
    def get_record_stream(self, keys, sort_order, include_delta_closure):
765
 
        self.calls.append(("get_record_stream", list(keys), sort_order,
766
 
            include_delta_closure))
767
 
        if sort_order == 'unordered':
768
 
            def sort_key(key):
769
 
                return (self._key_priority.get(key, 0), key)
770
 
            # Use a defined order by asking for the keys one-by-one from the
771
 
            # backing_vf
772
 
            for key in sorted(keys, key=sort_key):
773
 
                for record in self._backing_vf.get_record_stream([key],
774
 
                                'unordered', include_delta_closure):
775
 
                    yield record
776
 
        else:
777
 
            for record in self._backing_vf.get_record_stream(keys, sort_order,
778
 
                            include_delta_closure):
779
 
                yield record
780
 
 
781
 
 
782
564
class KeyMapper(object):
783
565
    """KeyMappers map between keys and underlying partitioned storage."""
784
566
 
793
575
 
794
576
    def unmap(self, partition_id):
795
577
        """Map a partitioned storage id back to a key prefix.
796
 
 
 
578
        
797
579
        :param partition_id: The underlying partition id.
798
580
        :return: As much of a key (or prefix) as is derivable from the partition
799
581
            id.
822
604
 
823
605
    def map(self, key):
824
606
        """See KeyMapper.map()."""
825
 
        return urlutils.quote(self._map(key))
 
607
        return urllib.quote(self._map(key))
826
608
 
827
609
    def unmap(self, partition_id):
828
610
        """See KeyMapper.unmap()."""
829
 
        return self._unmap(urlutils.unquote(partition_id))
 
611
        return self._unmap(urllib.unquote(partition_id))
830
612
 
831
613
 
832
614
class PrefixMapper(URLEscapeMapper):
833
615
    """A key mapper that extracts the first component of a key.
834
 
 
 
616
    
835
617
    This mapper is for use with a transport based backend.
836
618
    """
837
619
 
870
652
 
871
653
class HashEscapedPrefixMapper(HashPrefixMapper):
872
654
    """Combines the escaped first component of a key with a hash.
873
 
 
 
655
    
874
656
    This mapper is for use with a transport based backend.
875
657
    """
876
658
 
879
661
    def _escape(self, prefix):
880
662
        """Turn a key element into a filesystem safe string.
881
663
 
882
 
        This is similar to a plain urlutils.quote, except
 
664
        This is similar to a plain urllib.quote, except
883
665
        it uses specific safe characters, so that it doesn't
884
666
        have to translate a lot of valid file ids.
885
667
        """
892
674
 
893
675
    def _unescape(self, basename):
894
676
        """Escaped names are easily unescaped by urlutils."""
895
 
        return urlutils.unquote(basename)
 
677
        return urllib.unquote(basename)
896
678
 
897
679
 
898
680
def make_versioned_files_factory(versioned_file_factory, mapper):
925
707
 
926
708
    The use of tuples allows a single code base to support several different
927
709
    uses with only the mapping logic changing from instance to instance.
928
 
 
929
 
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
930
 
        this is a list of other VersionedFiles immediately underneath this
931
 
        one.  They may in turn each have further fallbacks.
932
710
    """
933
711
 
934
712
    def add_lines(self, key, parents, lines, parent_texts=None,
936
714
        check_content=True):
937
715
        """Add a text to the store.
938
716
 
939
 
        :param key: The key tuple of the text to add. If the last element is
940
 
            None, a CHK string will be generated during the addition.
 
717
        :param key: The key tuple of the text to add.
941
718
        :param parents: The parents key tuples of the text to add.
942
719
        :param lines: A list of lines. Each line must be a bytestring. And all
943
720
            of them except the last must be terminated with \n and contain no
947
724
            the data back accurately. (Checking the lines have been split
948
725
            correctly is expensive and extremely unlikely to catch bugs so it
949
726
            is not done at runtime unless check_content is True.)
950
 
        :param parent_texts: An optional dictionary containing the opaque
 
727
        :param parent_texts: An optional dictionary containing the opaque 
951
728
            representations of some or all of the parents of version_id to
952
729
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
953
730
            returned by add_lines or data corruption can be caused.
970
747
        """
971
748
        raise NotImplementedError(self.add_lines)
972
749
 
973
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
974
 
        """Add a text to the store.
975
 
 
976
 
        This is a private function for use by VersionedFileCommitBuilder.
977
 
 
978
 
        :param key: The key tuple of the text to add. If the last element is
979
 
            None, a CHK string will be generated during the addition.
980
 
        :param parents: The parents key tuples of the text to add.
981
 
        :param text: A string containing the text to be committed.
982
 
        :param nostore_sha: Raise ExistingContent and do not add the lines to
983
 
            the versioned file if the digest of the lines matches this.
984
 
        :param random_id: If True a random id has been selected rather than
985
 
            an id determined by some deterministic process such as a converter
986
 
            from a foreign VCS. When True the backend may choose not to check
987
 
            for uniqueness of the resulting key within the versioned file, so
988
 
            this should only be done when the result is expected to be unique
989
 
            anyway.
990
 
        :param check_content: If True, the lines supplied are verified to be
991
 
            bytestrings that are correctly formed lines.
992
 
        :return: The text sha1, the number of bytes in the text, and an opaque
993
 
                 representation of the inserted version which can be provided
994
 
                 back to future _add_text calls in the parent_texts dictionary.
995
 
        """
996
 
        # The default implementation just thunks over to .add_lines(),
997
 
        # inefficient, but it works.
998
 
        return self.add_lines(key, parents, osutils.split_lines(text),
999
 
                              nostore_sha=nostore_sha,
1000
 
                              random_id=random_id,
1001
 
                              check_content=True)
1002
 
 
1003
750
    def add_mpdiffs(self, records):
1004
751
        """Add mpdiffs to this VersionedFile.
1005
752
 
1018
765
                                  if not mpvf.has_version(p))
1019
766
        # It seems likely that adding all the present parents as fulltexts can
1020
767
        # easily exhaust memory.
1021
 
        chunks_to_lines = osutils.chunks_to_lines
 
768
        split_lines = osutils.split_lines
1022
769
        for record in self.get_record_stream(needed_parents, 'unordered',
1023
770
            True):
1024
771
            if record.storage_kind == 'absent':
1025
772
                continue
1026
 
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
 
773
            mpvf.add_version(split_lines(record.get_bytes_as('fulltext')),
1027
774
                record.key, [])
1028
775
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
1029
776
            zip(records, mpvf.get_line_list(versions)):
1047
794
        raise NotImplementedError(self.annotate)
1048
795
 
1049
796
    def check(self, progress_bar=None):
1050
 
        """Check this object for integrity.
1051
 
        
1052
 
        :param progress_bar: A progress bar to output as the check progresses.
1053
 
        :param keys: Specific keys within the VersionedFiles to check. When
1054
 
            this parameter is not None, check() becomes a generator as per
1055
 
            get_record_stream. The difference to get_record_stream is that
1056
 
            more or deeper checks will be performed.
1057
 
        :return: None, or if keys was supplied a generator as per
1058
 
            get_record_stream.
1059
 
        """
 
797
        """Check this object for integrity."""
1060
798
        raise NotImplementedError(self.check)
1061
799
 
1062
800
    @staticmethod
1063
801
    def check_not_reserved_id(version_id):
1064
802
        revision.check_not_reserved_id(version_id)
1065
803
 
1066
 
    def clear_cache(self):
1067
 
        """Clear whatever caches this VersionedFile holds.
1068
 
 
1069
 
        This is generally called after an operation has been performed, when we
1070
 
        don't expect to be using this versioned file again soon.
1071
 
        """
1072
 
 
1073
804
    def _check_lines_not_unicode(self, lines):
1074
805
        """Check that lines being added to a versioned file are not unicode."""
1075
806
        for line in lines:
1082
813
            if '\n' in line[:-1]:
1083
814
                raise errors.BzrBadParameterContainsNewline("lines")
1084
815
 
1085
 
    def get_known_graph_ancestry(self, keys):
1086
 
        """Get a KnownGraph instance with the ancestry of keys."""
1087
 
        # most basic implementation is a loop around get_parent_map
1088
 
        pending = set(keys)
1089
 
        parent_map = {}
1090
 
        while pending:
1091
 
            this_parent_map = self.get_parent_map(pending)
1092
 
            parent_map.update(this_parent_map)
1093
 
            pending = set()
1094
 
            map(pending.update, this_parent_map.itervalues())
1095
 
            pending = pending.difference(parent_map)
1096
 
        kg = _mod_graph.KnownGraph(parent_map)
1097
 
        return kg
1098
 
 
1099
816
    def get_parent_map(self, keys):
1100
817
        """Get a map of the parents of keys.
1101
818
 
1129
846
        """
1130
847
        raise NotImplementedError(self.get_sha1s)
1131
848
 
1132
 
    has_key = index._has_key_from_parent_map
1133
 
 
1134
 
    def get_missing_compression_parent_keys(self):
1135
 
        """Return an iterable of keys of missing compression parents.
1136
 
 
1137
 
        Check this after calling insert_record_stream to find out if there are
1138
 
        any missing compression parents.  If there are, the records that
1139
 
        depend on them are not able to be inserted safely. The precise
1140
 
        behaviour depends on the concrete VersionedFiles class in use.
1141
 
 
1142
 
        Classes that do not support this will raise NotImplementedError.
1143
 
        """
1144
 
        raise NotImplementedError(self.get_missing_compression_parent_keys)
1145
 
 
1146
849
    def insert_record_stream(self, stream):
1147
850
        """Insert a record stream into this container.
1148
851
 
1149
 
        :param stream: A stream of records to insert.
 
852
        :param stream: A stream of records to insert. 
1150
853
        :return: None
1151
854
        :seealso VersionedFile.get_record_stream:
1152
855
        """
1181
884
 
1182
885
    def make_mpdiffs(self, keys):
1183
886
        """Create multiparent diffs for specified keys."""
1184
 
        generator = _MPDiffGenerator(self, keys)
1185
 
        return generator.compute_diffs()
1186
 
 
1187
 
    def get_annotator(self):
1188
 
        return annotate.Annotator(self)
1189
 
 
1190
 
    missing_keys = index._missing_keys_from_parent_map
 
887
        keys_order = tuple(keys)
 
888
        keys = frozenset(keys)
 
889
        knit_keys = set(keys)
 
890
        parent_map = self.get_parent_map(keys)
 
891
        for parent_keys in parent_map.itervalues():
 
892
            if parent_keys:
 
893
                knit_keys.update(parent_keys)
 
894
        missing_keys = keys - set(parent_map)
 
895
        if missing_keys:
 
896
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
897
        # We need to filter out ghosts, because we can't diff against them.
 
898
        maybe_ghosts = knit_keys - keys
 
899
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
900
        knit_keys.difference_update(ghosts)
 
901
        lines = {}
 
902
        split_lines = osutils.split_lines
 
903
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
904
            lines[record.key] = split_lines(record.get_bytes_as('fulltext'))
 
905
            # line_block_dict = {}
 
906
            # for parent, blocks in record.extract_line_blocks():
 
907
            #   line_blocks[parent] = blocks
 
908
            # line_blocks[record.key] = line_block_dict
 
909
        diffs = []
 
910
        for key in keys_order:
 
911
            target = lines[key]
 
912
            parents = parent_map[key] or []
 
913
            # Note that filtering knit_keys can lead to a parent difference
 
914
            # between the creation and the application of the mpdiff.
 
915
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
916
            if len(parent_lines) > 0:
 
917
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
918
                    target)
 
919
            else:
 
920
                left_parent_blocks = None
 
921
            diffs.append(multiparent.MultiParent.from_lines(target,
 
922
                parent_lines, left_parent_blocks))
 
923
        return diffs
1191
924
 
1192
925
    def _extract_blocks(self, version_id, source, target):
1193
926
        return None
1194
927
 
1195
 
    def _transitive_fallbacks(self):
1196
 
        """Return the whole stack of fallback versionedfiles.
1197
 
 
1198
 
        This VersionedFiles may have a list of fallbacks, but it doesn't
1199
 
        necessarily know about the whole stack going down, and it can't know
1200
 
        at open time because they may change after the objects are opened.
1201
 
        """
1202
 
        all_fallbacks = []
1203
 
        for a_vfs in self._immediate_fallback_vfs:
1204
 
            all_fallbacks.append(a_vfs)
1205
 
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
1206
 
        return all_fallbacks
1207
 
 
1208
928
 
1209
929
class ThunkedVersionedFiles(VersionedFiles):
1210
930
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1274
994
            result.append((prefix + (origin,), line))
1275
995
        return result
1276
996
 
1277
 
    def check(self, progress_bar=None, keys=None):
 
997
    def check(self, progress_bar=None):
1278
998
        """See VersionedFiles.check()."""
1279
 
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1280
 
        # this is tolerable. Ideally we'd pass keys down to check() and 
1281
 
        # have the older VersiondFile interface updated too.
1282
999
        for prefix, vf in self._iter_all_components():
1283
1000
            vf.check()
1284
 
        if keys is not None:
1285
 
            return self.get_record_stream(keys, 'unordered', True)
1286
1001
 
1287
1002
    def get_parent_map(self, keys):
1288
1003
        """Get a map of the parents of keys.
1366
1081
    def insert_record_stream(self, stream):
1367
1082
        """Insert a record stream into this container.
1368
1083
 
1369
 
        :param stream: A stream of records to insert.
 
1084
        :param stream: A stream of records to insert. 
1370
1085
        :return: None
1371
1086
        :seealso VersionedFile.get_record_stream:
1372
1087
        """
1423
1138
        return result
1424
1139
 
1425
1140
 
1426
 
class VersionedFilesWithFallbacks(VersionedFiles):
1427
 
 
1428
 
    def without_fallbacks(self):
1429
 
        """Return a clone of this object without any fallbacks configured."""
1430
 
        raise NotImplementedError(self.without_fallbacks)
1431
 
 
1432
 
    def add_fallback_versioned_files(self, a_versioned_files):
1433
 
        """Add a source of texts for texts not present in this knit.
1434
 
 
1435
 
        :param a_versioned_files: A VersionedFiles object.
1436
 
        """
1437
 
        raise NotImplementedError(self.add_fallback_versioned_files)
1438
 
 
1439
 
    def get_known_graph_ancestry(self, keys):
1440
 
        """Get a KnownGraph instance with the ancestry of keys."""
1441
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1442
 
        for fallback in self._transitive_fallbacks():
1443
 
            if not missing_keys:
1444
 
                break
1445
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1446
 
                                                missing_keys)
1447
 
            parent_map.update(f_parent_map)
1448
 
            missing_keys = f_missing_keys
1449
 
        kg = _mod_graph.KnownGraph(parent_map)
1450
 
        return kg
1451
 
 
1452
 
 
1453
1141
class _PlanMergeVersionedFile(VersionedFiles):
1454
1142
    """A VersionedFile for uncommitted and committed texts.
1455
1143
 
1476
1164
        # line data for locally held keys.
1477
1165
        self._lines = {}
1478
1166
        # key lookup providers
1479
 
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
 
1167
        self._providers = [DictParentsProvider(self._parents)]
1480
1168
 
1481
1169
    def plan_merge(self, ver_a, ver_b, base=None):
1482
1170
        """See VersionedFile.plan_merge"""
1489
1177
 
1490
1178
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1491
1179
        from bzrlib.merge import _PlanLCAMerge
1492
 
        graph = _mod_graph.Graph(self)
 
1180
        graph = Graph(self)
1493
1181
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1494
1182
        if base is None:
1495
1183
            return new_plan
1520
1208
                lines = self._lines[key]
1521
1209
                parents = self._parents[key]
1522
1210
                pending.remove(key)
1523
 
                yield ChunkedContentFactory(key, parents, None, lines)
 
1211
                yield FulltextContentFactory(key, parents, None,
 
1212
                    ''.join(lines))
1524
1213
        for versionedfile in self.fallback_versionedfiles:
1525
1214
            for record in versionedfile.get_record_stream(
1526
1215
                pending, 'unordered', True):
1547
1236
            result[revision.NULL_REVISION] = ()
1548
1237
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1549
1238
        result.update(
1550
 
            _mod_graph.StackedParentsProvider(
1551
 
                self._providers).get_parent_map(keys))
 
1239
            _StackedParentsProvider(self._providers).get_parent_map(keys))
1552
1240
        for key, parents in result.iteritems():
1553
1241
            if parents == ():
1554
1242
                result[key] = (revision.NULL_REVISION,)
1557
1245
 
1558
1246
class PlanWeaveMerge(TextMerge):
1559
1247
    """Weave merge that takes a plan as its input.
1560
 
 
 
1248
    
1561
1249
    This exists so that VersionedFile.plan_merge is implementable.
1562
1250
    Most callers will want to use WeaveMerge instead.
1563
1251
    """
1565
1253
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1566
1254
                 b_marker=TextMerge.B_MARKER):
1567
1255
        TextMerge.__init__(self, a_marker, b_marker)
1568
 
        self.plan = list(plan)
 
1256
        self.plan = plan
1569
1257
 
1570
1258
    def _merge_struct(self):
1571
1259
        lines_a = []
1584
1272
                yield(lines_a,)
1585
1273
            else:
1586
1274
                yield (lines_a, lines_b)
1587
 
 
 
1275
       
1588
1276
        # We previously considered either 'unchanged' or 'killed-both' lines
1589
1277
        # to be possible places to resynchronize.  However, assuming agreement
1590
1278
        # on killed-both lines may be too aggressive. -- mbp 20060324
1596
1284
                lines_a = []
1597
1285
                lines_b = []
1598
1286
                ch_a = ch_b = False
1599
 
 
 
1287
                
1600
1288
            if state == 'unchanged':
1601
1289
                if line:
1602
1290
                    yield ([line],)
1618
1306
            elif state == 'conflicted-b':
1619
1307
                ch_b = ch_a = True
1620
1308
                lines_b.append(line)
1621
 
            elif state == 'killed-both':
1622
 
                # This counts as a change, even though there is no associated
1623
 
                # line
1624
 
                ch_b = ch_a = True
1625
1309
            else:
1626
1310
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1627
 
                        'killed-base'):
 
1311
                        'killed-base', 'killed-both'):
1628
1312
                    raise AssertionError(state)
1629
1313
        for struct in outstanding_struct():
1630
1314
            yield struct
1631
1315
 
1632
 
    def base_from_plan(self):
1633
 
        """Construct a BASE file from the plan text."""
1634
 
        base_lines = []
1635
 
        for state, line in self.plan:
1636
 
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
1637
 
                # If unchanged, then this line is straight from base. If a or b
1638
 
                # or both killed the line, then it *used* to be in base.
1639
 
                base_lines.append(line)
1640
 
            else:
1641
 
                if state not in ('killed-base', 'irrelevant',
1642
 
                                 'ghost-a', 'ghost-b',
1643
 
                                 'new-a', 'new-b',
1644
 
                                 'conflicted-a', 'conflicted-b'):
1645
 
                    # killed-base, irrelevant means it doesn't apply
1646
 
                    # ghost-a/ghost-b are harder to say for sure, but they
1647
 
                    # aren't in the 'inc_c' which means they aren't in the
1648
 
                    # shared base of a & b. So we don't include them.  And
1649
 
                    # obviously if the line is newly inserted, it isn't in base
1650
 
 
1651
 
                    # If 'conflicted-a' or b, then it is new vs one base, but
1652
 
                    # old versus another base. However, if we make it present
1653
 
                    # in the base, it will be deleted from the target, and it
1654
 
                    # seems better to get a line doubled in the merge result,
1655
 
                    # rather than have it deleted entirely.
1656
 
                    # Example, each node is the 'text' at that point:
1657
 
                    #           MN
1658
 
                    #          /   \
1659
 
                    #        MaN   MbN
1660
 
                    #         |  X  |
1661
 
                    #        MabN MbaN
1662
 
                    #          \   /
1663
 
                    #           ???
1664
 
                    # There was a criss-cross conflict merge. Both sides
1665
 
                    # include the other, but put themselves first.
1666
 
                    # Weave marks this as a 'clean' merge, picking OTHER over
1667
 
                    # THIS. (Though the details depend on order inserted into
1668
 
                    # weave, etc.)
1669
 
                    # LCA generates a plan:
1670
 
                    # [('unchanged', M),
1671
 
                    #  ('conflicted-b', b),
1672
 
                    #  ('unchanged', a),
1673
 
                    #  ('conflicted-a', b),
1674
 
                    #  ('unchanged', N)]
1675
 
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
1676
 
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
1677
 
                    # removes one 'b', and BASE vs OTHER removes the other)
1678
 
                    # If you include neither, 3-way creates a clean "MbabN" as
1679
 
                    # THIS adds one 'b', and OTHER does too.
1680
 
                    # It seems that having the line 2 times is better than
1681
 
                    # having it omitted. (Easier to manually delete than notice
1682
 
                    # it needs to be added.)
1683
 
                    raise AssertionError('Unknown state: %s' % (state,))
1684
 
        return base_lines
1685
 
 
1686
1316
 
1687
1317
class WeaveMerge(PlanWeaveMerge):
1688
1318
    """Weave merge that takes a VersionedFile and two versions as its input."""
1689
1319
 
1690
 
    def __init__(self, versionedfile, ver_a, ver_b,
 
1320
    def __init__(self, versionedfile, ver_a, ver_b, 
1691
1321
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1692
1322
        plan = versionedfile.plan_merge(ver_a, ver_b)
1693
1323
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1694
1324
 
1695
1325
 
1696
1326
class VirtualVersionedFiles(VersionedFiles):
1697
 
    """Dummy implementation for VersionedFiles that uses other functions for
 
1327
    """Dummy implementation for VersionedFiles that uses other functions for 
1698
1328
    obtaining fulltexts and parent maps.
1699
1329
 
1700
 
    This is always on the bottom of the stack and uses string keys
 
1330
    This is always on the bottom of the stack and uses string keys 
1701
1331
    (rather than tuples) internally.
1702
1332
    """
1703
1333
 
1705
1335
        """Create a VirtualVersionedFiles.
1706
1336
 
1707
1337
        :param get_parent_map: Same signature as Repository.get_parent_map.
1708
 
        :param get_lines: Should return lines for specified key or None if
 
1338
        :param get_lines: Should return lines for specified key or None if 
1709
1339
                          not available.
1710
1340
        """
1711
1341
        super(VirtualVersionedFiles, self).__init__()
1712
1342
        self._get_parent_map = get_parent_map
1713
1343
        self._get_lines = get_lines
1714
 
 
 
1344
        
1715
1345
    def check(self, progressbar=None):
1716
1346
        """See VersionedFiles.check.
1717
1347
 
1749
1379
            if lines is not None:
1750
1380
                if not isinstance(lines, list):
1751
1381
                    raise AssertionError
1752
 
                yield ChunkedContentFactory((k,), None,
 
1382
                yield FulltextContentFactory((k,), None, 
1753
1383
                        sha1=osutils.sha_strings(lines),
1754
 
                        chunks=lines)
 
1384
                        text=''.join(lines))
1755
1385
            else:
1756
1386
                yield AbsentContentFactory((k,))
1757
1387
 
1758
 
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
1759
 
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1760
 
        for i, (key,) in enumerate(keys):
1761
 
            if pb is not None:
1762
 
                pb.update("Finding changed lines", i, len(keys))
1763
 
            for l in self._get_lines(key):
1764
 
                yield (l, key)
1765
 
 
1766
 
 
1767
 
class NoDupeAddLinesDecorator(object):
1768
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1769
 
    is already present.
1770
 
    """
1771
 
 
1772
 
    def __init__(self, store):
1773
 
        self._store = store
1774
 
 
1775
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1776
 
            left_matching_blocks=None, nostore_sha=None, random_id=False,
1777
 
            check_content=True):
1778
 
        """See VersionedFiles.add_lines.
1779
 
        
1780
 
        This implementation may return None as the third element of the return
1781
 
        value when the original store wouldn't.
1782
 
        """
1783
 
        if nostore_sha:
1784
 
            raise NotImplementedError(
1785
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1786
 
                "nostore_sha behaviour.")
1787
 
        if key[-1] is None:
1788
 
            sha1 = osutils.sha_strings(lines)
1789
 
            key = ("sha1:" + sha1,)
1790
 
        else:
1791
 
            sha1 = None
1792
 
        if key in self._store.get_parent_map([key]):
1793
 
            # This key has already been inserted, so don't do it again.
1794
 
            if sha1 is None:
1795
 
                sha1 = osutils.sha_strings(lines)
1796
 
            return sha1, sum(map(len, lines)), None
1797
 
        return self._store.add_lines(key, parents, lines,
1798
 
                parent_texts=parent_texts,
1799
 
                left_matching_blocks=left_matching_blocks,
1800
 
                nostore_sha=nostore_sha, random_id=random_id,
1801
 
                check_content=check_content)
1802
 
 
1803
 
    def __getattr__(self, name):
1804
 
        return getattr(self._store, name)
1805
 
 
1806
 
 
1807
 
def network_bytes_to_kind_and_offset(network_bytes):
1808
 
    """Strip of a record kind from the front of network_bytes.
1809
 
 
1810
 
    :param network_bytes: The bytes of a record.
1811
 
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1812
 
    """
1813
 
    line_end = network_bytes.find('\n')
1814
 
    storage_kind = network_bytes[:line_end]
1815
 
    return storage_kind, line_end + 1
1816
 
 
1817
 
 
1818
 
class NetworkRecordStream(object):
1819
 
    """A record_stream which reconstitures a serialised stream."""
1820
 
 
1821
 
    def __init__(self, bytes_iterator):
1822
 
        """Create a NetworkRecordStream.
1823
 
 
1824
 
        :param bytes_iterator: An iterator of bytes. Each item in this
1825
 
            iterator should have been obtained from a record_streams'
1826
 
            record.get_bytes_as(record.storage_kind) call.
1827
 
        """
1828
 
        self._bytes_iterator = bytes_iterator
1829
 
        self._kind_factory = {
1830
 
            'fulltext': fulltext_network_to_record,
1831
 
            'groupcompress-block': groupcompress.network_block_to_records,
1832
 
            'knit-ft-gz': knit.knit_network_to_record,
1833
 
            'knit-delta-gz': knit.knit_network_to_record,
1834
 
            'knit-annotated-ft-gz': knit.knit_network_to_record,
1835
 
            'knit-annotated-delta-gz': knit.knit_network_to_record,
1836
 
            'knit-delta-closure': knit.knit_delta_closure_to_records,
1837
 
            }
1838
 
 
1839
 
    def read(self):
1840
 
        """Read the stream.
1841
 
 
1842
 
        :return: An iterator as per VersionedFiles.get_record_stream().
1843
 
        """
1844
 
        for bytes in self._bytes_iterator:
1845
 
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1846
 
            for record in self._kind_factory[storage_kind](
1847
 
                storage_kind, bytes, line_end):
1848
 
                yield record
1849
 
 
1850
 
 
1851
 
def fulltext_network_to_record(kind, bytes, line_end):
1852
 
    """Convert a network fulltext record to record."""
1853
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
1854
 
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1855
 
    key, parents = bencode.bdecode_as_tuple(record_meta)
1856
 
    if parents == 'nil':
1857
 
        parents = None
1858
 
    fulltext = bytes[line_end+4+meta_len:]
1859
 
    return [FulltextContentFactory(key, parents, None, fulltext)]
1860
 
 
1861
 
 
1862
 
def _length_prefix(bytes):
1863
 
    return struct.pack('!L', len(bytes))
1864
 
 
1865
 
 
1866
 
def record_to_fulltext_bytes(record):
1867
 
    if record.parents is None:
1868
 
        parents = 'nil'
1869
 
    else:
1870
 
        parents = record.parents
1871
 
    record_meta = bencode.bencode((record.key, parents))
1872
 
    record_content = record.get_bytes_as('fulltext')
1873
 
    return "fulltext\n%s%s%s" % (
1874
 
        _length_prefix(record_meta), record_meta, record_content)
1875
 
 
1876
 
 
1877
 
def sort_groupcompress(parent_map):
1878
 
    """Sort and group the keys in parent_map into groupcompress order.
1879
 
 
1880
 
    groupcompress is defined (currently) as reverse-topological order, grouped
1881
 
    by the key prefix.
1882
 
 
1883
 
    :return: A sorted-list of keys
1884
 
    """
1885
 
    # gc-optimal ordering is approximately reverse topological,
1886
 
    # properly grouped by file-id.
1887
 
    per_prefix_map = {}
1888
 
    for item in parent_map.iteritems():
1889
 
        key = item[0]
1890
 
        if isinstance(key, str) or len(key) == 1:
1891
 
            prefix = ''
1892
 
        else:
1893
 
            prefix = key[0]
1894
 
        try:
1895
 
            per_prefix_map[prefix].append(item)
1896
 
        except KeyError:
1897
 
            per_prefix_map[prefix] = [item]
1898
 
 
1899
 
    present_keys = []
1900
 
    for prefix in sorted(per_prefix_map):
1901
 
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1902
 
    return present_keys
1903
 
 
1904
 
 
1905
 
class _KeyRefs(object):
1906
 
 
1907
 
    def __init__(self, track_new_keys=False):
1908
 
        # dict mapping 'key' to 'set of keys referring to that key'
1909
 
        self.refs = {}
1910
 
        if track_new_keys:
1911
 
            # set remembering all new keys
1912
 
            self.new_keys = set()
1913
 
        else:
1914
 
            self.new_keys = None
1915
 
 
1916
 
    def clear(self):
1917
 
        if self.refs:
1918
 
            self.refs.clear()
1919
 
        if self.new_keys:
1920
 
            self.new_keys.clear()
1921
 
 
1922
 
    def add_references(self, key, refs):
1923
 
        # Record the new references
1924
 
        for referenced in refs:
1925
 
            try:
1926
 
                needed_by = self.refs[referenced]
1927
 
            except KeyError:
1928
 
                needed_by = self.refs[referenced] = set()
1929
 
            needed_by.add(key)
1930
 
        # Discard references satisfied by the new key
1931
 
        self.add_key(key)
1932
 
 
1933
 
    def get_new_keys(self):
1934
 
        return self.new_keys
1935
 
    
1936
 
    def get_unsatisfied_refs(self):
1937
 
        return self.refs.iterkeys()
1938
 
 
1939
 
    def _satisfy_refs_for_key(self, key):
1940
 
        try:
1941
 
            del self.refs[key]
1942
 
        except KeyError:
1943
 
            # No keys depended on this key.  That's ok.
1944
 
            pass
1945
 
 
1946
 
    def add_key(self, key):
1947
 
        # satisfy refs for key, and remember that we've seen this key.
1948
 
        self._satisfy_refs_for_key(key)
1949
 
        if self.new_keys is not None:
1950
 
            self.new_keys.add(key)
1951
 
 
1952
 
    def satisfy_refs_for_keys(self, keys):
1953
 
        for key in keys:
1954
 
            self._satisfy_refs_for_key(key)
1955
 
 
1956
 
    def get_referrers(self):
1957
 
        result = set()
1958
 
        for referrers in self.refs.itervalues():
1959
 
            result.update(referrers)
1960
 
        return result
1961
 
 
1962
1388
 
1963
1389