~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2008-07-07 21:54:04 UTC
  • mto: This revision was merged to the branch mainline in revision 3533.
  • Revision ID: jelmer@samba.org-20080707215404-09t83ot6mv02jr6w
Move functionality to add ignores to the ignore file into a separate function.

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
715
535
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
716
536
            left_matching_blocks, nostore_sha, random_id, check_content)
717
537
 
718
 
    def check(self):
719
 
        self._backing_vf.check()
720
 
 
721
538
    def get_parent_map(self, keys):
722
539
        self.calls.append(("get_parent_map", copy(keys)))
723
540
        return self._backing_vf.get_parent_map(keys)
741
558
        return self._backing_vf.keys()
742
559
 
743
560
 
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
561
class KeyMapper(object):
783
562
    """KeyMappers map between keys and underlying partitioned storage."""
784
563
 
793
572
 
794
573
    def unmap(self, partition_id):
795
574
        """Map a partitioned storage id back to a key prefix.
796
 
 
 
575
        
797
576
        :param partition_id: The underlying partition id.
798
577
        :return: As much of a key (or prefix) as is derivable from the partition
799
578
            id.
822
601
 
823
602
    def map(self, key):
824
603
        """See KeyMapper.map()."""
825
 
        return urlutils.quote(self._map(key))
 
604
        return urllib.quote(self._map(key))
826
605
 
827
606
    def unmap(self, partition_id):
828
607
        """See KeyMapper.unmap()."""
829
 
        return self._unmap(urlutils.unquote(partition_id))
 
608
        return self._unmap(urllib.unquote(partition_id))
830
609
 
831
610
 
832
611
class PrefixMapper(URLEscapeMapper):
833
612
    """A key mapper that extracts the first component of a key.
834
 
 
 
613
    
835
614
    This mapper is for use with a transport based backend.
836
615
    """
837
616
 
870
649
 
871
650
class HashEscapedPrefixMapper(HashPrefixMapper):
872
651
    """Combines the escaped first component of a key with a hash.
873
 
 
 
652
    
874
653
    This mapper is for use with a transport based backend.
875
654
    """
876
655
 
879
658
    def _escape(self, prefix):
880
659
        """Turn a key element into a filesystem safe string.
881
660
 
882
 
        This is similar to a plain urlutils.quote, except
 
661
        This is similar to a plain urllib.quote, except
883
662
        it uses specific safe characters, so that it doesn't
884
663
        have to translate a lot of valid file ids.
885
664
        """
892
671
 
893
672
    def _unescape(self, basename):
894
673
        """Escaped names are easily unescaped by urlutils."""
895
 
        return urlutils.unquote(basename)
 
674
        return urllib.unquote(basename)
896
675
 
897
676
 
898
677
def make_versioned_files_factory(versioned_file_factory, mapper):
925
704
 
926
705
    The use of tuples allows a single code base to support several different
927
706
    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
707
    """
933
708
 
934
709
    def add_lines(self, key, parents, lines, parent_texts=None,
936
711
        check_content=True):
937
712
        """Add a text to the store.
938
713
 
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.
 
714
        :param key: The key tuple of the text to add.
941
715
        :param parents: The parents key tuples of the text to add.
942
716
        :param lines: A list of lines. Each line must be a bytestring. And all
943
717
            of them except the last must be terminated with \n and contain no
947
721
            the data back accurately. (Checking the lines have been split
948
722
            correctly is expensive and extremely unlikely to catch bugs so it
949
723
            is not done at runtime unless check_content is True.)
950
 
        :param parent_texts: An optional dictionary containing the opaque
 
724
        :param parent_texts: An optional dictionary containing the opaque 
951
725
            representations of some or all of the parents of version_id to
952
726
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
953
727
            returned by add_lines or data corruption can be caused.
970
744
        """
971
745
        raise NotImplementedError(self.add_lines)
972
746
 
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
747
    def add_mpdiffs(self, records):
1004
748
        """Add mpdiffs to this VersionedFile.
1005
749
 
1018
762
                                  if not mpvf.has_version(p))
1019
763
        # It seems likely that adding all the present parents as fulltexts can
1020
764
        # easily exhaust memory.
1021
 
        chunks_to_lines = osutils.chunks_to_lines
 
765
        split_lines = osutils.split_lines
1022
766
        for record in self.get_record_stream(needed_parents, 'unordered',
1023
767
            True):
1024
768
            if record.storage_kind == 'absent':
1025
769
                continue
1026
 
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
 
770
            mpvf.add_version(split_lines(record.get_bytes_as('fulltext')),
1027
771
                record.key, [])
1028
772
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
1029
773
            zip(records, mpvf.get_line_list(versions)):
1047
791
        raise NotImplementedError(self.annotate)
1048
792
 
1049
793
    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
 
        """
 
794
        """Check this object for integrity."""
1060
795
        raise NotImplementedError(self.check)
1061
796
 
1062
797
    @staticmethod
1063
798
    def check_not_reserved_id(version_id):
1064
799
        revision.check_not_reserved_id(version_id)
1065
800
 
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
801
    def _check_lines_not_unicode(self, lines):
1074
802
        """Check that lines being added to a versioned file are not unicode."""
1075
803
        for line in lines:
1082
810
            if '\n' in line[:-1]:
1083
811
                raise errors.BzrBadParameterContainsNewline("lines")
1084
812
 
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
813
    def get_parent_map(self, keys):
1100
814
        """Get a map of the parents of keys.
1101
815
 
1129
843
        """
1130
844
        raise NotImplementedError(self.get_sha1s)
1131
845
 
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
846
    def insert_record_stream(self, stream):
1147
847
        """Insert a record stream into this container.
1148
848
 
1149
 
        :param stream: A stream of records to insert.
 
849
        :param stream: A stream of records to insert. 
1150
850
        :return: None
1151
851
        :seealso VersionedFile.get_record_stream:
1152
852
        """
1181
881
 
1182
882
    def make_mpdiffs(self, keys):
1183
883
        """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
 
884
        keys_order = tuple(keys)
 
885
        keys = frozenset(keys)
 
886
        knit_keys = set(keys)
 
887
        parent_map = self.get_parent_map(keys)
 
888
        for parent_keys in parent_map.itervalues():
 
889
            if parent_keys:
 
890
                knit_keys.update(parent_keys)
 
891
        missing_keys = keys - set(parent_map)
 
892
        if missing_keys:
 
893
            raise errors.RevisionNotPresent(missing_keys.pop(), self)
 
894
        # We need to filter out ghosts, because we can't diff against them.
 
895
        maybe_ghosts = knit_keys - keys
 
896
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
897
        knit_keys.difference_update(ghosts)
 
898
        lines = {}
 
899
        split_lines = osutils.split_lines
 
900
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
901
            lines[record.key] = split_lines(record.get_bytes_as('fulltext'))
 
902
            # line_block_dict = {}
 
903
            # for parent, blocks in record.extract_line_blocks():
 
904
            #   line_blocks[parent] = blocks
 
905
            # line_blocks[record.key] = line_block_dict
 
906
        diffs = []
 
907
        for key in keys_order:
 
908
            target = lines[key]
 
909
            parents = parent_map[key] or []
 
910
            # Note that filtering knit_keys can lead to a parent difference
 
911
            # between the creation and the application of the mpdiff.
 
912
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
913
            if len(parent_lines) > 0:
 
914
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
915
                    target)
 
916
            else:
 
917
                left_parent_blocks = None
 
918
            diffs.append(multiparent.MultiParent.from_lines(target,
 
919
                parent_lines, left_parent_blocks))
 
920
        return diffs
1191
921
 
1192
922
    def _extract_blocks(self, version_id, source, target):
1193
923
        return None
1194
924
 
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
925
 
1209
926
class ThunkedVersionedFiles(VersionedFiles):
1210
927
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1274
991
            result.append((prefix + (origin,), line))
1275
992
        return result
1276
993
 
1277
 
    def check(self, progress_bar=None, keys=None):
 
994
    def check(self, progress_bar=None):
1278
995
        """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
996
        for prefix, vf in self._iter_all_components():
1283
997
            vf.check()
1284
 
        if keys is not None:
1285
 
            return self.get_record_stream(keys, 'unordered', True)
1286
998
 
1287
999
    def get_parent_map(self, keys):
1288
1000
        """Get a map of the parents of keys.
1366
1078
    def insert_record_stream(self, stream):
1367
1079
        """Insert a record stream into this container.
1368
1080
 
1369
 
        :param stream: A stream of records to insert.
 
1081
        :param stream: A stream of records to insert. 
1370
1082
        :return: None
1371
1083
        :seealso VersionedFile.get_record_stream:
1372
1084
        """
1423
1135
        return result
1424
1136
 
1425
1137
 
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
1138
class _PlanMergeVersionedFile(VersionedFiles):
1454
1139
    """A VersionedFile for uncommitted and committed texts.
1455
1140
 
1476
1161
        # line data for locally held keys.
1477
1162
        self._lines = {}
1478
1163
        # key lookup providers
1479
 
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
 
1164
        self._providers = [DictParentsProvider(self._parents)]
1480
1165
 
1481
1166
    def plan_merge(self, ver_a, ver_b, base=None):
1482
1167
        """See VersionedFile.plan_merge"""
1489
1174
 
1490
1175
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1491
1176
        from bzrlib.merge import _PlanLCAMerge
1492
 
        graph = _mod_graph.Graph(self)
 
1177
        graph = Graph(self)
1493
1178
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1494
1179
        if base is None:
1495
1180
            return new_plan
1520
1205
                lines = self._lines[key]
1521
1206
                parents = self._parents[key]
1522
1207
                pending.remove(key)
1523
 
                yield ChunkedContentFactory(key, parents, None, lines)
 
1208
                yield FulltextContentFactory(key, parents, None,
 
1209
                    ''.join(lines))
1524
1210
        for versionedfile in self.fallback_versionedfiles:
1525
1211
            for record in versionedfile.get_record_stream(
1526
1212
                pending, 'unordered', True):
1547
1233
            result[revision.NULL_REVISION] = ()
1548
1234
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1549
1235
        result.update(
1550
 
            _mod_graph.StackedParentsProvider(
1551
 
                self._providers).get_parent_map(keys))
 
1236
            _StackedParentsProvider(self._providers).get_parent_map(keys))
1552
1237
        for key, parents in result.iteritems():
1553
1238
            if parents == ():
1554
1239
                result[key] = (revision.NULL_REVISION,)
1557
1242
 
1558
1243
class PlanWeaveMerge(TextMerge):
1559
1244
    """Weave merge that takes a plan as its input.
1560
 
 
 
1245
    
1561
1246
    This exists so that VersionedFile.plan_merge is implementable.
1562
1247
    Most callers will want to use WeaveMerge instead.
1563
1248
    """
1565
1250
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1566
1251
                 b_marker=TextMerge.B_MARKER):
1567
1252
        TextMerge.__init__(self, a_marker, b_marker)
1568
 
        self.plan = list(plan)
 
1253
        self.plan = plan
1569
1254
 
1570
1255
    def _merge_struct(self):
1571
1256
        lines_a = []
1584
1269
                yield(lines_a,)
1585
1270
            else:
1586
1271
                yield (lines_a, lines_b)
1587
 
 
 
1272
       
1588
1273
        # We previously considered either 'unchanged' or 'killed-both' lines
1589
1274
        # to be possible places to resynchronize.  However, assuming agreement
1590
1275
        # on killed-both lines may be too aggressive. -- mbp 20060324
1596
1281
                lines_a = []
1597
1282
                lines_b = []
1598
1283
                ch_a = ch_b = False
1599
 
 
 
1284
                
1600
1285
            if state == 'unchanged':
1601
1286
                if line:
1602
1287
                    yield ([line],)
1618
1303
            elif state == 'conflicted-b':
1619
1304
                ch_b = ch_a = True
1620
1305
                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
1306
            else:
1626
1307
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1627
 
                        'killed-base'):
 
1308
                        'killed-base', 'killed-both'):
1628
1309
                    raise AssertionError(state)
1629
1310
        for struct in outstanding_struct():
1630
1311
            yield struct
1631
1312
 
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
1313
 
1687
1314
class WeaveMerge(PlanWeaveMerge):
1688
1315
    """Weave merge that takes a VersionedFile and two versions as its input."""
1689
1316
 
1690
 
    def __init__(self, versionedfile, ver_a, ver_b,
 
1317
    def __init__(self, versionedfile, ver_a, ver_b, 
1691
1318
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1692
1319
        plan = versionedfile.plan_merge(ver_a, ver_b)
1693
1320
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1694
1321
 
1695
1322
 
1696
 
class VirtualVersionedFiles(VersionedFiles):
1697
 
    """Dummy implementation for VersionedFiles that uses other functions for
1698
 
    obtaining fulltexts and parent maps.
1699
 
 
1700
 
    This is always on the bottom of the stack and uses string keys
1701
 
    (rather than tuples) internally.
1702
 
    """
1703
 
 
1704
 
    def __init__(self, get_parent_map, get_lines):
1705
 
        """Create a VirtualVersionedFiles.
1706
 
 
1707
 
        :param get_parent_map: Same signature as Repository.get_parent_map.
1708
 
        :param get_lines: Should return lines for specified key or None if
1709
 
                          not available.
1710
 
        """
1711
 
        super(VirtualVersionedFiles, self).__init__()
1712
 
        self._get_parent_map = get_parent_map
1713
 
        self._get_lines = get_lines
1714
 
 
1715
 
    def check(self, progressbar=None):
1716
 
        """See VersionedFiles.check.
1717
 
 
1718
 
        :note: Always returns True for VirtualVersionedFiles.
1719
 
        """
1720
 
        return True
1721
 
 
1722
 
    def add_mpdiffs(self, records):
1723
 
        """See VersionedFiles.mpdiffs.
1724
 
 
1725
 
        :note: Not implemented for VirtualVersionedFiles.
1726
 
        """
1727
 
        raise NotImplementedError(self.add_mpdiffs)
1728
 
 
1729
 
    def get_parent_map(self, keys):
1730
 
        """See VersionedFiles.get_parent_map."""
1731
 
        return dict([((k,), tuple([(p,) for p in v]))
1732
 
            for k,v in self._get_parent_map([k for (k,) in keys]).iteritems()])
1733
 
 
1734
 
    def get_sha1s(self, keys):
1735
 
        """See VersionedFiles.get_sha1s."""
1736
 
        ret = {}
1737
 
        for (k,) in keys:
1738
 
            lines = self._get_lines(k)
1739
 
            if lines is not None:
1740
 
                if not isinstance(lines, list):
1741
 
                    raise AssertionError
1742
 
                ret[(k,)] = osutils.sha_strings(lines)
1743
 
        return ret
1744
 
 
1745
 
    def get_record_stream(self, keys, ordering, include_delta_closure):
1746
 
        """See VersionedFiles.get_record_stream."""
1747
 
        for (k,) in list(keys):
1748
 
            lines = self._get_lines(k)
1749
 
            if lines is not None:
1750
 
                if not isinstance(lines, list):
1751
 
                    raise AssertionError
1752
 
                yield ChunkedContentFactory((k,), None,
1753
 
                        sha1=osutils.sha_strings(lines),
1754
 
                        chunks=lines)
1755
 
            else:
1756
 
                yield AbsentContentFactory((k,))
1757
 
 
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
 
 
1963