~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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