/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

Update to bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
15
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
 
# 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
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
22
 
from copy import copy
23
22
from cStringIO import StringIO
24
23
import os
25
 
import struct
 
24
import urllib
26
25
from zlib import adler32
27
26
 
28
27
from bzrlib.lazy_import import lazy_import
29
28
lazy_import(globals(), """
30
 
import urllib
31
29
 
32
30
from bzrlib import (
33
 
    annotate,
34
31
    errors,
35
 
    graph as _mod_graph,
36
 
    groupcompress,
37
 
    index,
38
 
    knit,
39
32
    osutils,
40
33
    multiparent,
41
34
    tsort,
42
35
    revision,
43
36
    ui,
44
37
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
 
38
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
46
39
from bzrlib.transport.memory import MemoryTransport
47
40
""")
 
41
from bzrlib.inter import InterObject
48
42
from bzrlib.registry import Registry
49
43
from bzrlib.symbol_versioning import *
50
44
from bzrlib.textmerge import TextMerge
51
 
from bzrlib import bencode
52
45
 
53
46
 
54
47
adapter_registry = Registry()
64
57
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
65
58
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
66
59
    'bzrlib.knit', 'FTAnnotatedToFullText')
67
 
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
68
 
#     'bzrlib.knit', 'FTAnnotatedToChunked')
69
60
 
70
61
 
71
62
class ContentFactory(object):
72
63
    """Abstract interface for insertion and retrieval from a VersionedFile.
73
 
 
 
64
    
74
65
    :ivar sha1: None, or the sha1 of the content fulltext.
75
66
    :ivar storage_kind: The native storage kind of this factory. One of
76
67
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
91
82
        self.parents = None
92
83
 
93
84
 
94
 
class ChunkedContentFactory(ContentFactory):
95
 
    """Static data content factory.
96
 
 
97
 
    This takes a 'chunked' list of strings. The only requirement on 'chunked' is
98
 
    that ''.join(lines) becomes a valid fulltext. A tuple of a single string
99
 
    satisfies this, as does a list of lines.
100
 
 
101
 
    :ivar sha1: None, or the sha1 of the content fulltext.
102
 
    :ivar storage_kind: The native storage kind of this factory. Always
103
 
        'chunked'
104
 
    :ivar key: The key of this content. Each key is a tuple with a single
105
 
        string in it.
106
 
    :ivar parents: A tuple of parent keys for self.key. If the object has
107
 
        no parent information, None (as opposed to () for an empty list of
108
 
        parents).
109
 
     """
110
 
 
111
 
    def __init__(self, key, parents, sha1, chunks):
112
 
        """Create a ContentFactory."""
113
 
        self.sha1 = sha1
114
 
        self.storage_kind = 'chunked'
115
 
        self.key = key
116
 
        self.parents = parents
117
 
        self._chunks = chunks
118
 
 
119
 
    def get_bytes_as(self, storage_kind):
120
 
        if storage_kind == 'chunked':
121
 
            return self._chunks
122
 
        elif storage_kind == 'fulltext':
123
 
            return ''.join(self._chunks)
124
 
        raise errors.UnavailableRepresentation(self.key, storage_kind,
125
 
            self.storage_kind)
126
 
 
127
 
 
128
85
class FulltextContentFactory(ContentFactory):
129
86
    """Static data content factory.
130
87
 
131
88
    This takes a fulltext when created and just returns that during
132
89
    get_bytes_as('fulltext').
133
 
 
 
90
    
134
91
    :ivar sha1: None, or the sha1 of the content fulltext.
135
92
    :ivar storage_kind: The native storage kind of this factory. Always
136
93
        'fulltext'.
152
109
    def get_bytes_as(self, storage_kind):
153
110
        if storage_kind == self.storage_kind:
154
111
            return self._text
155
 
        elif storage_kind == 'chunked':
156
 
            return [self._text]
157
112
        raise errors.UnavailableRepresentation(self.key, storage_kind,
158
113
            self.storage_kind)
159
114
 
160
115
 
161
116
class AbsentContentFactory(ContentFactory):
162
117
    """A placeholder content factory for unavailable texts.
163
 
 
 
118
    
164
119
    :ivar sha1: None.
165
120
    :ivar storage_kind: 'absent'.
166
121
    :ivar key: The key of this content. Each key is a tuple with a single
175
130
        self.key = key
176
131
        self.parents = None
177
132
 
178
 
    def get_bytes_as(self, storage_kind):
179
 
        raise ValueError('A request was made for key: %s, but that'
180
 
                         ' content is not available, and the calling'
181
 
                         ' code does not handle if it is missing.'
182
 
                         % (self.key,))
183
 
 
184
133
 
185
134
class AdapterFactory(ContentFactory):
186
135
    """A content factory to adapt between key prefix's."""
208
157
 
209
158
class VersionedFile(object):
210
159
    """Versioned text file storage.
211
 
 
 
160
    
212
161
    A versioned file manages versions of line-based text files,
213
162
    keeping track of the originating version for each line.
214
163
 
252
201
    def insert_record_stream(self, stream):
253
202
        """Insert a record stream into this versioned file.
254
203
 
255
 
        :param stream: A stream of records to insert.
 
204
        :param stream: A stream of records to insert. 
256
205
        :return: None
257
206
        :seealso VersionedFile.get_record_stream:
258
207
        """
277
226
            the data back accurately. (Checking the lines have been split
278
227
            correctly is expensive and extremely unlikely to catch bugs so it
279
228
            is not done at runtime unless check_content is True.)
280
 
        :param parent_texts: An optional dictionary containing the opaque
 
229
        :param parent_texts: An optional dictionary containing the opaque 
281
230
            representations of some or all of the parents of version_id to
282
231
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
283
232
            returned by add_lines or data corruption can be caused.
311
260
        parent_texts=None, nostore_sha=None, random_id=False,
312
261
        check_content=True, left_matching_blocks=None):
313
262
        """Add lines to the versioned file, allowing ghosts to be present.
314
 
 
 
263
        
315
264
        This takes the same parameters as add_lines and returns the same.
316
265
        """
317
266
        self._check_write_ok()
341
290
 
342
291
    def get_format_signature(self):
343
292
        """Get a text description of the data encoding in this file.
344
 
 
 
293
        
345
294
        :since: 0.90
346
295
        """
347
296
        raise NotImplementedError(self.get_format_signature)
423
372
                    parent_ids, lines, vf_parents,
424
373
                    left_matching_blocks=left_matching_blocks)
425
374
            vf_parents[version] = version_text
426
 
        sha1s = self.get_sha1s(versions)
427
 
        for version, parent_ids, expected_sha1, mpdiff in records:
428
 
            if expected_sha1 != sha1s[version]:
 
375
        for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
 
376
             zip(records, self.get_sha1s(versions)):
 
377
            if expected_sha1 != sha1:
429
378
                raise errors.VersionedFileInvalidChecksum(version)
430
379
 
 
380
    def get_sha1s(self, version_ids):
 
381
        """Get the stored sha1 sums for the given revisions.
 
382
 
 
383
        :param version_ids: The names of the versions to lookup
 
384
        :return: a list of sha1s in order according to the version_ids
 
385
        """
 
386
        raise NotImplementedError(self.get_sha1s)
 
387
 
431
388
    def get_text(self, version_id):
432
389
        """Return version contents as a text string.
433
390
 
468
425
        if isinstance(version_ids, basestring):
469
426
            version_ids = [version_ids]
470
427
        raise NotImplementedError(self.get_ancestry)
471
 
 
 
428
        
472
429
    def get_ancestry_with_ghosts(self, version_ids):
473
430
        """Return a list of all ancestors of given version(s). This
474
431
        will not include the null revision.
475
432
 
476
433
        Must raise RevisionNotPresent if any of the given versions are
477
434
        not present in file history.
478
 
 
 
435
        
479
436
        Ghosts that are known about will be included in ancestry list,
480
437
        but are not explicitly marked.
481
438
        """
482
439
        raise NotImplementedError(self.get_ancestry_with_ghosts)
483
 
 
 
440
    
484
441
    def get_parent_map(self, version_ids):
485
442
        """Get a map of the parents of version_ids.
486
443
 
549
506
        unchanged   Alive in both a and b (possibly created in both)
550
507
        new-a       Created in a
551
508
        new-b       Created in b
552
 
        ghost-a     Killed in a, unborn in b
 
509
        ghost-a     Killed in a, unborn in b    
553
510
        ghost-b     Killed in b, unborn in a
554
511
        irrelevant  Not in either revision
555
512
        """
556
513
        raise NotImplementedError(VersionedFile.plan_merge)
557
 
 
 
514
        
558
515
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
559
516
                    b_marker=TextMerge.B_MARKER):
560
517
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
562
519
 
563
520
class RecordingVersionedFilesDecorator(object):
564
521
    """A minimal versioned files that records calls made on it.
565
 
 
 
522
    
566
523
    Only enough methods have been added to support tests using it to date.
567
524
 
568
525
    :ivar calls: A list of the calls made; can be reset at any time by
570
527
    """
571
528
 
572
529
    def __init__(self, backing_vf):
573
 
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
574
 
 
 
530
        """Create a RecordingVersionedFileDsecorator decorating backing_vf.
 
531
        
575
532
        :param backing_vf: The versioned file to answer all methods.
576
533
        """
577
534
        self._backing_vf = backing_vf
578
535
        self.calls = []
579
536
 
580
 
    def add_lines(self, key, parents, lines, parent_texts=None,
581
 
        left_matching_blocks=None, nostore_sha=None, random_id=False,
582
 
        check_content=True):
583
 
        self.calls.append(("add_lines", key, parents, lines, parent_texts,
584
 
            left_matching_blocks, nostore_sha, random_id, check_content))
585
 
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
586
 
            left_matching_blocks, nostore_sha, random_id, check_content)
587
 
 
588
 
    def check(self):
589
 
        self._backing_vf.check()
590
 
 
591
 
    def get_parent_map(self, keys):
592
 
        self.calls.append(("get_parent_map", copy(keys)))
593
 
        return self._backing_vf.get_parent_map(keys)
594
 
 
595
537
    def get_record_stream(self, keys, sort_order, include_delta_closure):
596
 
        self.calls.append(("get_record_stream", list(keys), sort_order,
 
538
        self.calls.append(("get_record_stream", keys, sort_order,
597
539
            include_delta_closure))
598
540
        return self._backing_vf.get_record_stream(keys, sort_order,
599
541
            include_delta_closure)
600
542
 
601
 
    def get_sha1s(self, keys):
602
 
        self.calls.append(("get_sha1s", copy(keys)))
603
 
        return self._backing_vf.get_sha1s(keys)
604
 
 
605
 
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
606
 
        self.calls.append(("iter_lines_added_or_present_in_keys", copy(keys)))
607
 
        return self._backing_vf.iter_lines_added_or_present_in_keys(keys, pb=pb)
608
 
 
609
 
    def keys(self):
610
 
        self.calls.append(("keys",))
611
 
        return self._backing_vf.keys()
612
 
 
613
 
 
614
 
class OrderingVersionedFilesDecorator(RecordingVersionedFilesDecorator):
615
 
    """A VF that records calls, and returns keys in specific order.
616
 
 
617
 
    :ivar calls: A list of the calls made; can be reset at any time by
618
 
        assigning [] to it.
619
 
    """
620
 
 
621
 
    def __init__(self, backing_vf, key_priority):
622
 
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
623
 
 
624
 
        :param backing_vf: The versioned file to answer all methods.
625
 
        :param key_priority: A dictionary defining what order keys should be
626
 
            returned from an 'unordered' get_record_stream request.
627
 
            Keys with lower priority are returned first, keys not present in
628
 
            the map get an implicit priority of 0, and are returned in
629
 
            lexicographical order.
630
 
        """
631
 
        RecordingVersionedFilesDecorator.__init__(self, backing_vf)
632
 
        self._key_priority = key_priority
633
 
 
634
 
    def get_record_stream(self, keys, sort_order, include_delta_closure):
635
 
        self.calls.append(("get_record_stream", list(keys), sort_order,
636
 
            include_delta_closure))
637
 
        if sort_order == 'unordered':
638
 
            def sort_key(key):
639
 
                return (self._key_priority.get(key, 0), key)
640
 
            # Use a defined order by asking for the keys one-by-one from the
641
 
            # backing_vf
642
 
            for key in sorted(keys, key=sort_key):
643
 
                for record in self._backing_vf.get_record_stream([key],
644
 
                                'unordered', include_delta_closure):
645
 
                    yield record
646
 
        else:
647
 
            for record in self._backing_vf.get_record_stream(keys, sort_order,
648
 
                            include_delta_closure):
649
 
                yield record
650
 
 
651
543
 
652
544
class KeyMapper(object):
653
 
    """KeyMappers map between keys and underlying partitioned storage."""
 
545
    """KeyMappers map between keys and underlying paritioned storage."""
654
546
 
655
547
    def map(self, key):
656
548
        """Map key to an underlying storage identifier.
663
555
 
664
556
    def unmap(self, partition_id):
665
557
        """Map a partitioned storage id back to a key prefix.
666
 
 
 
558
        
667
559
        :param partition_id: The underlying partition id.
668
 
        :return: As much of a key (or prefix) as is derivable from the partition
 
560
        :return: As much of a key (or prefix) as is derivable from the parition
669
561
            id.
670
562
        """
671
563
        raise NotImplementedError(self.unmap)
701
593
 
702
594
class PrefixMapper(URLEscapeMapper):
703
595
    """A key mapper that extracts the first component of a key.
704
 
 
 
596
    
705
597
    This mapper is for use with a transport based backend.
706
598
    """
707
599
 
740
632
 
741
633
class HashEscapedPrefixMapper(HashPrefixMapper):
742
634
    """Combines the escaped first component of a key with a hash.
743
 
 
 
635
    
744
636
    This mapper is for use with a transport based backend.
745
637
    """
746
638
 
786
678
 
787
679
    Currently no implementation allows the graph of different key prefixes to
788
680
    intersect, but the API does allow such implementations in the future.
789
 
 
790
 
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
791
 
    may have a different length key-size, but that size will be constant for
792
 
    all texts added to or retrieved from it. For instance, bzrlib uses
793
 
    instances with a key-size of 2 for storing user files in a repository, with
794
 
    the first element the fileid, and the second the version of that file.
795
 
 
796
 
    The use of tuples allows a single code base to support several different
797
 
    uses with only the mapping logic changing from instance to instance.
798
681
    """
799
682
 
800
683
    def add_lines(self, key, parents, lines, parent_texts=None,
802
685
        check_content=True):
803
686
        """Add a text to the store.
804
687
 
805
 
        :param key: The key tuple of the text to add. If the last element is
806
 
            None, a CHK string will be generated during the addition.
 
688
        :param key: The key tuple of the text to add.
807
689
        :param parents: The parents key tuples of the text to add.
808
690
        :param lines: A list of lines. Each line must be a bytestring. And all
809
691
            of them except the last must be terminated with \n and contain no
813
695
            the data back accurately. (Checking the lines have been split
814
696
            correctly is expensive and extremely unlikely to catch bugs so it
815
697
            is not done at runtime unless check_content is True.)
816
 
        :param parent_texts: An optional dictionary containing the opaque
 
698
        :param parent_texts: An optional dictionary containing the opaque 
817
699
            representations of some or all of the parents of version_id to
818
700
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
819
701
            returned by add_lines or data corruption can be caused.
836
718
        """
837
719
        raise NotImplementedError(self.add_lines)
838
720
 
839
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
840
 
        """Add a text to the store.
841
 
 
842
 
        This is a private function for use by CommitBuilder.
843
 
 
844
 
        :param key: The key tuple of the text to add. If the last element is
845
 
            None, a CHK string will be generated during the addition.
846
 
        :param parents: The parents key tuples of the text to add.
847
 
        :param text: A string containing the text to be committed.
848
 
        :param nostore_sha: Raise ExistingContent and do not add the lines to
849
 
            the versioned file if the digest of the lines matches this.
850
 
        :param random_id: If True a random id has been selected rather than
851
 
            an id determined by some deterministic process such as a converter
852
 
            from a foreign VCS. When True the backend may choose not to check
853
 
            for uniqueness of the resulting key within the versioned file, so
854
 
            this should only be done when the result is expected to be unique
855
 
            anyway.
856
 
        :param check_content: If True, the lines supplied are verified to be
857
 
            bytestrings that are correctly formed lines.
858
 
        :return: The text sha1, the number of bytes in the text, and an opaque
859
 
                 representation of the inserted version which can be provided
860
 
                 back to future _add_text calls in the parent_texts dictionary.
861
 
        """
862
 
        # The default implementation just thunks over to .add_lines(),
863
 
        # inefficient, but it works.
864
 
        return self.add_lines(key, parents, osutils.split_lines(text),
865
 
                              nostore_sha=nostore_sha,
866
 
                              random_id=random_id,
867
 
                              check_content=True)
868
 
 
869
721
    def add_mpdiffs(self, records):
870
722
        """Add mpdiffs to this VersionedFile.
871
723
 
884
736
                                  if not mpvf.has_version(p))
885
737
        # It seems likely that adding all the present parents as fulltexts can
886
738
        # easily exhaust memory.
887
 
        chunks_to_lines = osutils.chunks_to_lines
888
 
        for record in self.get_record_stream(needed_parents, 'unordered',
 
739
        present_parents = set(self.get_parent_map(needed_parents).keys())
 
740
        split_lines = osutils.split_lines
 
741
        for record in self.get_record_stream(present_parents, 'unordered',
889
742
            True):
890
 
            if record.storage_kind == 'absent':
891
 
                continue
892
 
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
 
743
            mpvf.add_version(split_lines(record.get_bytes_as('fulltext')),
893
744
                record.key, [])
894
745
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
895
746
            zip(records, mpvf.get_line_list(versions)):
913
764
        raise NotImplementedError(self.annotate)
914
765
 
915
766
    def check(self, progress_bar=None):
916
 
        """Check this object for integrity.
917
 
        
918
 
        :param progress_bar: A progress bar to output as the check progresses.
919
 
        :param keys: Specific keys within the VersionedFiles to check. When
920
 
            this parameter is not None, check() becomes a generator as per
921
 
            get_record_stream. The difference to get_record_stream is that
922
 
            more or deeper checks will be performed.
923
 
        :return: None, or if keys was supplied a generator as per
924
 
            get_record_stream.
925
 
        """
 
767
        """Check this object for integrity."""
926
768
        raise NotImplementedError(self.check)
927
769
 
928
770
    @staticmethod
929
771
    def check_not_reserved_id(version_id):
930
772
        revision.check_not_reserved_id(version_id)
931
773
 
932
 
    def clear_cache(self):
933
 
        """Clear whatever caches this VersionedFile holds.
934
 
 
935
 
        This is generally called after an operation has been performed, when we
936
 
        don't expect to be using this versioned file again soon.
937
 
        """
938
 
 
939
774
    def _check_lines_not_unicode(self, lines):
940
775
        """Check that lines being added to a versioned file are not unicode."""
941
776
        for line in lines:
948
783
            if '\n' in line[:-1]:
949
784
                raise errors.BzrBadParameterContainsNewline("lines")
950
785
 
951
 
    def get_known_graph_ancestry(self, keys):
952
 
        """Get a KnownGraph instance with the ancestry of keys."""
953
 
        # most basic implementation is a loop around get_parent_map
954
 
        pending = set(keys)
955
 
        parent_map = {}
956
 
        while pending:
957
 
            this_parent_map = self.get_parent_map(pending)
958
 
            parent_map.update(this_parent_map)
959
 
            pending = set()
960
 
            map(pending.update, this_parent_map.itervalues())
961
 
            pending = pending.difference(parent_map)
962
 
        kg = _mod_graph.KnownGraph(parent_map)
963
 
        return kg
964
 
 
965
786
    def get_parent_map(self, keys):
966
787
        """Get a map of the parents of keys.
967
788
 
989
810
        """Get the sha1's of the texts for the given keys.
990
811
 
991
812
        :param keys: The names of the keys to lookup
992
 
        :return: a dict from key to sha1 digest. Keys of texts which are not
993
 
            present in the store are not present in the returned
994
 
            dictionary.
 
813
        :return: a list of sha1s matching keys.
995
814
        """
996
815
        raise NotImplementedError(self.get_sha1s)
997
816
 
998
 
    has_key = index._has_key_from_parent_map
999
 
 
1000
 
    def get_missing_compression_parent_keys(self):
1001
 
        """Return an iterable of keys of missing compression parents.
1002
 
 
1003
 
        Check this after calling insert_record_stream to find out if there are
1004
 
        any missing compression parents.  If there are, the records that
1005
 
        depend on them are not able to be inserted safely. The precise
1006
 
        behaviour depends on the concrete VersionedFiles class in use.
1007
 
 
1008
 
        Classes that do not support this will raise NotImplementedError.
1009
 
        """
1010
 
        raise NotImplementedError(self.get_missing_compression_parent_keys)
1011
 
 
1012
817
    def insert_record_stream(self, stream):
1013
818
        """Insert a record stream into this container.
1014
819
 
1015
 
        :param stream: A stream of records to insert.
 
820
        :param stream: A stream of records to insert. 
1016
821
        :return: None
1017
822
        :seealso VersionedFile.get_record_stream:
1018
823
        """
1056
861
                knit_keys.update(parent_keys)
1057
862
        missing_keys = keys - set(parent_map)
1058
863
        if missing_keys:
1059
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
864
            raise errors.RevisionNotPresent(missing_keys.pop(), self)
1060
865
        # We need to filter out ghosts, because we can't diff against them.
1061
866
        maybe_ghosts = knit_keys - keys
1062
867
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
1063
868
        knit_keys.difference_update(ghosts)
1064
869
        lines = {}
1065
 
        chunks_to_lines = osutils.chunks_to_lines
 
870
        split_lines = osutils.split_lines
1066
871
        for record in self.get_record_stream(knit_keys, 'topological', True):
1067
 
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
872
            lines[record.key] = split_lines(record.get_bytes_as('fulltext'))
1068
873
            # line_block_dict = {}
1069
874
            # for parent, blocks in record.extract_line_blocks():
1070
875
            #   line_blocks[parent] = blocks
1085
890
                parent_lines, left_parent_blocks))
1086
891
        return diffs
1087
892
 
1088
 
    missing_keys = index._missing_keys_from_parent_map
1089
 
 
1090
893
    def _extract_blocks(self, version_id, source, target):
1091
894
        return None
1092
895
 
1159
962
            result.append((prefix + (origin,), line))
1160
963
        return result
1161
964
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
 
    def check(self, progress_bar=None, keys=None):
 
965
    def check(self, progress_bar=None):
1166
966
        """See VersionedFiles.check()."""
1167
 
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1168
 
        # this is tolerable. Ideally we'd pass keys down to check() and 
1169
 
        # have the older VersiondFile interface updated too.
1170
967
        for prefix, vf in self._iter_all_components():
1171
968
            vf.check()
1172
 
        if keys is not None:
1173
 
            return self.get_record_stream(keys, 'unordered', True)
1174
969
 
1175
970
    def get_parent_map(self, keys):
1176
971
        """Get a map of the parents of keys.
1247
1042
        sha1s = {}
1248
1043
        for prefix,suffixes, vf in self._iter_keys_vf(keys):
1249
1044
            vf_sha1s = vf.get_sha1s(suffixes)
1250
 
            for suffix, sha1 in vf_sha1s.iteritems():
 
1045
            for suffix, sha1 in zip(suffixes, vf.get_sha1s(suffixes)):
1251
1046
                sha1s[prefix + (suffix,)] = sha1
1252
 
        return sha1s
 
1047
        return [sha1s[key] for key in keys]
1253
1048
 
1254
1049
    def insert_record_stream(self, stream):
1255
1050
        """Insert a record stream into this container.
1256
1051
 
1257
 
        :param stream: A stream of records to insert.
 
1052
        :param stream: A stream of records to insert. 
1258
1053
        :return: None
1259
1054
        :seealso VersionedFile.get_record_stream:
1260
1055
        """
1363
1158
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1364
1159
        are permitted.  Only reserved ids are permitted.
1365
1160
        """
1366
 
        if type(key) is not tuple:
1367
 
            raise TypeError(key)
 
1161
        if type(key) != tuple:
 
1162
            import pdb;pdb.set_trace()
1368
1163
        if not revision.is_reserved_id(key[-1]):
1369
1164
            raise ValueError('Only reserved ids may be used')
1370
1165
        if parents is None:
1381
1176
                lines = self._lines[key]
1382
1177
                parents = self._parents[key]
1383
1178
                pending.remove(key)
1384
 
                yield ChunkedContentFactory(key, parents, None, lines)
 
1179
                yield FulltextContentFactory(key, parents, None,
 
1180
                    ''.join(lines))
1385
1181
        for versionedfile in self.fallback_versionedfiles:
1386
1182
            for record in versionedfile.get_record_stream(
1387
1183
                pending, 'unordered', True):
1401
1197
        # We create a new provider because a fallback may have been added.
1402
1198
        # If we make fallbacks private we can update a stack list and avoid
1403
1199
        # object creation thrashing.
1404
 
        keys = set(keys)
1405
 
        result = {}
1406
 
        if revision.NULL_REVISION in keys:
1407
 
            keys.remove(revision.NULL_REVISION)
1408
 
            result[revision.NULL_REVISION] = ()
1409
1200
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1410
 
        result.update(
1411
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1201
        result = _StackedParentsProvider(self._providers).get_parent_map(keys)
1412
1202
        for key, parents in result.iteritems():
1413
1203
            if parents == ():
1414
1204
                result[key] = (revision.NULL_REVISION,)
1417
1207
 
1418
1208
class PlanWeaveMerge(TextMerge):
1419
1209
    """Weave merge that takes a plan as its input.
1420
 
 
 
1210
    
1421
1211
    This exists so that VersionedFile.plan_merge is implementable.
1422
1212
    Most callers will want to use WeaveMerge instead.
1423
1213
    """
1425
1215
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1426
1216
                 b_marker=TextMerge.B_MARKER):
1427
1217
        TextMerge.__init__(self, a_marker, b_marker)
1428
 
        self.plan = list(plan)
 
1218
        self.plan = plan
1429
1219
 
1430
1220
    def _merge_struct(self):
1431
1221
        lines_a = []
1444
1234
                yield(lines_a,)
1445
1235
            else:
1446
1236
                yield (lines_a, lines_b)
1447
 
 
 
1237
       
1448
1238
        # We previously considered either 'unchanged' or 'killed-both' lines
1449
1239
        # to be possible places to resynchronize.  However, assuming agreement
1450
1240
        # on killed-both lines may be too aggressive. -- mbp 20060324
1456
1246
                lines_a = []
1457
1247
                lines_b = []
1458
1248
                ch_a = ch_b = False
1459
 
 
 
1249
                
1460
1250
            if state == 'unchanged':
1461
1251
                if line:
1462
1252
                    yield ([line],)
1478
1268
            elif state == 'conflicted-b':
1479
1269
                ch_b = ch_a = True
1480
1270
                lines_b.append(line)
1481
 
            elif state == 'killed-both':
1482
 
                # This counts as a change, even though there is no associated
1483
 
                # line
1484
 
                ch_b = ch_a = True
1485
1271
            else:
1486
1272
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1487
 
                        'killed-base'):
 
1273
                        'killed-base', 'killed-both'):
1488
1274
                    raise AssertionError(state)
1489
1275
        for struct in outstanding_struct():
1490
1276
            yield struct
1491
1277
 
1492
 
    def base_from_plan(self):
1493
 
        """Construct a BASE file from the plan text."""
1494
 
        base_lines = []
1495
 
        for state, line in self.plan:
1496
 
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
1497
 
                # If unchanged, then this line is straight from base. If a or b
1498
 
                # or both killed the line, then it *used* to be in base.
1499
 
                base_lines.append(line)
1500
 
            else:
1501
 
                if state not in ('killed-base', 'irrelevant',
1502
 
                                 'ghost-a', 'ghost-b',
1503
 
                                 'new-a', 'new-b',
1504
 
                                 'conflicted-a', 'conflicted-b'):
1505
 
                    # killed-base, irrelevant means it doesn't apply
1506
 
                    # ghost-a/ghost-b are harder to say for sure, but they
1507
 
                    # aren't in the 'inc_c' which means they aren't in the
1508
 
                    # shared base of a & b. So we don't include them.  And
1509
 
                    # obviously if the line is newly inserted, it isn't in base
1510
 
 
1511
 
                    # If 'conflicted-a' or b, then it is new vs one base, but
1512
 
                    # old versus another base. However, if we make it present
1513
 
                    # in the base, it will be deleted from the target, and it
1514
 
                    # seems better to get a line doubled in the merge result,
1515
 
                    # rather than have it deleted entirely.
1516
 
                    # Example, each node is the 'text' at that point:
1517
 
                    #           MN
1518
 
                    #          /   \
1519
 
                    #        MaN   MbN
1520
 
                    #         |  X  |
1521
 
                    #        MabN MbaN
1522
 
                    #          \   /
1523
 
                    #           ???
1524
 
                    # There was a criss-cross conflict merge. Both sides
1525
 
                    # include the other, but put themselves first.
1526
 
                    # Weave marks this as a 'clean' merge, picking OTHER over
1527
 
                    # THIS. (Though the details depend on order inserted into
1528
 
                    # weave, etc.)
1529
 
                    # LCA generates a plan:
1530
 
                    # [('unchanged', M),
1531
 
                    #  ('conflicted-b', b),
1532
 
                    #  ('unchanged', a),
1533
 
                    #  ('conflicted-a', b),
1534
 
                    #  ('unchanged', N)]
1535
 
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
1536
 
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
1537
 
                    # removes one 'b', and BASE vs OTHER removes the other)
1538
 
                    # If you include neither, 3-way creates a clean "MbabN" as
1539
 
                    # THIS adds one 'b', and OTHER does too.
1540
 
                    # It seems that having the line 2 times is better than
1541
 
                    # having it omitted. (Easier to manually delete than notice
1542
 
                    # it needs to be added.)
1543
 
                    raise AssertionError('Unknown state: %s' % (state,))
1544
 
        return base_lines
1545
 
 
1546
1278
 
1547
1279
class WeaveMerge(PlanWeaveMerge):
1548
1280
    """Weave merge that takes a VersionedFile and two versions as its input."""
1549
1281
 
1550
 
    def __init__(self, versionedfile, ver_a, ver_b,
 
1282
    def __init__(self, versionedfile, ver_a, ver_b, 
1551
1283
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1552
1284
        plan = versionedfile.plan_merge(ver_a, ver_b)
1553
1285
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1554
1286
 
1555
1287
 
1556
 
class VirtualVersionedFiles(VersionedFiles):
1557
 
    """Dummy implementation for VersionedFiles that uses other functions for
1558
 
    obtaining fulltexts and parent maps.
1559
 
 
1560
 
    This is always on the bottom of the stack and uses string keys
1561
 
    (rather than tuples) internally.
1562
 
    """
1563
 
 
1564
 
    def __init__(self, get_parent_map, get_lines):
1565
 
        """Create a VirtualVersionedFiles.
1566
 
 
1567
 
        :param get_parent_map: Same signature as Repository.get_parent_map.
1568
 
        :param get_lines: Should return lines for specified key or None if
1569
 
                          not available.
1570
 
        """
1571
 
        super(VirtualVersionedFiles, self).__init__()
1572
 
        self._get_parent_map = get_parent_map
1573
 
        self._get_lines = get_lines
1574
 
 
1575
 
    def check(self, progressbar=None):
1576
 
        """See VersionedFiles.check.
1577
 
 
1578
 
        :note: Always returns True for VirtualVersionedFiles.
1579
 
        """
1580
 
        return True
1581
 
 
1582
 
    def add_mpdiffs(self, records):
1583
 
        """See VersionedFiles.mpdiffs.
1584
 
 
1585
 
        :note: Not implemented for VirtualVersionedFiles.
1586
 
        """
1587
 
        raise NotImplementedError(self.add_mpdiffs)
1588
 
 
1589
 
    def get_parent_map(self, keys):
1590
 
        """See VersionedFiles.get_parent_map."""
1591
 
        return dict([((k,), tuple([(p,) for p in v]))
1592
 
            for k,v in self._get_parent_map([k for (k,) in keys]).iteritems()])
1593
 
 
1594
 
    def get_sha1s(self, keys):
1595
 
        """See VersionedFiles.get_sha1s."""
1596
 
        ret = {}
1597
 
        for (k,) in keys:
1598
 
            lines = self._get_lines(k)
1599
 
            if lines is not None:
1600
 
                if not isinstance(lines, list):
1601
 
                    raise AssertionError
1602
 
                ret[(k,)] = osutils.sha_strings(lines)
1603
 
        return ret
1604
 
 
1605
 
    def get_record_stream(self, keys, ordering, include_delta_closure):
1606
 
        """See VersionedFiles.get_record_stream."""
1607
 
        for (k,) in list(keys):
1608
 
            lines = self._get_lines(k)
1609
 
            if lines is not None:
1610
 
                if not isinstance(lines, list):
1611
 
                    raise AssertionError
1612
 
                yield ChunkedContentFactory((k,), None,
1613
 
                        sha1=osutils.sha_strings(lines),
1614
 
                        chunks=lines)
1615
 
            else:
1616
 
                yield AbsentContentFactory((k,))
1617
 
 
1618
 
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
1619
 
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1620
 
        for i, (key,) in enumerate(keys):
1621
 
            if pb is not None:
1622
 
                pb.update("Finding changed lines", i, len(keys))
1623
 
            for l in self._get_lines(key):
1624
 
                yield (l, key)
1625
 
 
1626
 
 
1627
 
def network_bytes_to_kind_and_offset(network_bytes):
1628
 
    """Strip of a record kind from the front of network_bytes.
1629
 
 
1630
 
    :param network_bytes: The bytes of a record.
1631
 
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1632
 
    """
1633
 
    line_end = network_bytes.find('\n')
1634
 
    storage_kind = network_bytes[:line_end]
1635
 
    return storage_kind, line_end + 1
1636
 
 
1637
 
 
1638
 
class NetworkRecordStream(object):
1639
 
    """A record_stream which reconstitures a serialised stream."""
1640
 
 
1641
 
    def __init__(self, bytes_iterator):
1642
 
        """Create a NetworkRecordStream.
1643
 
 
1644
 
        :param bytes_iterator: An iterator of bytes. Each item in this
1645
 
            iterator should have been obtained from a record_streams'
1646
 
            record.get_bytes_as(record.storage_kind) call.
1647
 
        """
1648
 
        self._bytes_iterator = bytes_iterator
1649
 
        self._kind_factory = {
1650
 
            'fulltext': fulltext_network_to_record,
1651
 
            'groupcompress-block': groupcompress.network_block_to_records,
1652
 
            'knit-ft-gz': knit.knit_network_to_record,
1653
 
            'knit-delta-gz': knit.knit_network_to_record,
1654
 
            'knit-annotated-ft-gz': knit.knit_network_to_record,
1655
 
            'knit-annotated-delta-gz': knit.knit_network_to_record,
1656
 
            'knit-delta-closure': knit.knit_delta_closure_to_records,
1657
 
            }
1658
 
 
1659
 
    def read(self):
1660
 
        """Read the stream.
1661
 
 
1662
 
        :return: An iterator as per VersionedFiles.get_record_stream().
1663
 
        """
1664
 
        for bytes in self._bytes_iterator:
1665
 
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1666
 
            for record in self._kind_factory[storage_kind](
1667
 
                storage_kind, bytes, line_end):
1668
 
                yield record
1669
 
 
1670
 
 
1671
 
def fulltext_network_to_record(kind, bytes, line_end):
1672
 
    """Convert a network fulltext record to record."""
1673
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
1674
 
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1675
 
    key, parents = bencode.bdecode_as_tuple(record_meta)
1676
 
    if parents == 'nil':
1677
 
        parents = None
1678
 
    fulltext = bytes[line_end+4+meta_len:]
1679
 
    return [FulltextContentFactory(key, parents, None, fulltext)]
1680
 
 
1681
 
 
1682
 
def _length_prefix(bytes):
1683
 
    return struct.pack('!L', len(bytes))
1684
 
 
1685
 
 
1686
 
def record_to_fulltext_bytes(record):
1687
 
    if record.parents is None:
1688
 
        parents = 'nil'
1689
 
    else:
1690
 
        parents = record.parents
1691
 
    record_meta = bencode.bencode((record.key, parents))
1692
 
    record_content = record.get_bytes_as('fulltext')
1693
 
    return "fulltext\n%s%s%s" % (
1694
 
        _length_prefix(record_meta), record_meta, record_content)
1695
 
 
1696
 
 
1697
 
def sort_groupcompress(parent_map):
1698
 
    """Sort and group the keys in parent_map into groupcompress order.
1699
 
 
1700
 
    groupcompress is defined (currently) as reverse-topological order, grouped
1701
 
    by the key prefix.
1702
 
 
1703
 
    :return: A sorted-list of keys
1704
 
    """
1705
 
    # gc-optimal ordering is approximately reverse topological,
1706
 
    # properly grouped by file-id.
1707
 
    per_prefix_map = {}
1708
 
    for item in parent_map.iteritems():
1709
 
        key = item[0]
1710
 
        if isinstance(key, str) or len(key) == 1:
1711
 
            prefix = ''
1712
 
        else:
1713
 
            prefix = key[0]
1714
 
        try:
1715
 
            per_prefix_map[prefix].append(item)
1716
 
        except KeyError:
1717
 
            per_prefix_map[prefix] = [item]
1718
 
 
1719
 
    present_keys = []
1720
 
    for prefix in sorted(per_prefix_map):
1721
 
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1722
 
    return present_keys