/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

  • Committer: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
22
22
from copy import copy
23
23
from cStringIO import StringIO
24
24
import os
 
25
import struct
25
26
from zlib import adler32
26
27
 
27
28
from bzrlib.lazy_import import lazy_import
29
30
import urllib
30
31
 
31
32
from bzrlib import (
 
33
    annotate,
32
34
    errors,
 
35
    graph as _mod_graph,
 
36
    groupcompress,
33
37
    index,
 
38
    knit,
34
39
    osutils,
35
40
    multiparent,
36
41
    tsort,
37
42
    revision,
38
43
    ui,
39
44
    )
40
 
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
 
45
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
41
46
from bzrlib.transport.memory import MemoryTransport
42
47
""")
43
 
from bzrlib.inter import InterObject
44
48
from bzrlib.registry import Registry
45
49
from bzrlib.symbol_versioning import *
46
50
from bzrlib.textmerge import TextMerge
 
51
from bzrlib import bencode
47
52
 
48
53
 
49
54
adapter_registry = Registry()
59
64
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
60
65
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
61
66
    'bzrlib.knit', 'FTAnnotatedToFullText')
 
67
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
 
68
#     'bzrlib.knit', 'FTAnnotatedToChunked')
62
69
 
63
70
 
64
71
class ContentFactory(object):
65
72
    """Abstract interface for insertion and retrieval from a VersionedFile.
66
 
    
 
73
 
67
74
    :ivar sha1: None, or the sha1 of the content fulltext.
68
75
    :ivar storage_kind: The native storage kind of this factory. One of
69
76
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
84
91
        self.parents = None
85
92
 
86
93
 
 
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
 
87
128
class FulltextContentFactory(ContentFactory):
88
129
    """Static data content factory.
89
130
 
90
131
    This takes a fulltext when created and just returns that during
91
132
    get_bytes_as('fulltext').
92
 
    
 
133
 
93
134
    :ivar sha1: None, or the sha1 of the content fulltext.
94
135
    :ivar storage_kind: The native storage kind of this factory. Always
95
136
        'fulltext'.
111
152
    def get_bytes_as(self, storage_kind):
112
153
        if storage_kind == self.storage_kind:
113
154
            return self._text
 
155
        elif storage_kind == 'chunked':
 
156
            return [self._text]
114
157
        raise errors.UnavailableRepresentation(self.key, storage_kind,
115
158
            self.storage_kind)
116
159
 
117
160
 
118
161
class AbsentContentFactory(ContentFactory):
119
162
    """A placeholder content factory for unavailable texts.
120
 
    
 
163
 
121
164
    :ivar sha1: None.
122
165
    :ivar storage_kind: 'absent'.
123
166
    :ivar key: The key of this content. Each key is a tuple with a single
132
175
        self.key = key
133
176
        self.parents = None
134
177
 
 
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
 
135
184
 
136
185
class AdapterFactory(ContentFactory):
137
186
    """A content factory to adapt between key prefix's."""
159
208
 
160
209
class VersionedFile(object):
161
210
    """Versioned text file storage.
162
 
    
 
211
 
163
212
    A versioned file manages versions of line-based text files,
164
213
    keeping track of the originating version for each line.
165
214
 
203
252
    def insert_record_stream(self, stream):
204
253
        """Insert a record stream into this versioned file.
205
254
 
206
 
        :param stream: A stream of records to insert. 
 
255
        :param stream: A stream of records to insert.
207
256
        :return: None
208
257
        :seealso VersionedFile.get_record_stream:
209
258
        """
228
277
            the data back accurately. (Checking the lines have been split
229
278
            correctly is expensive and extremely unlikely to catch bugs so it
230
279
            is not done at runtime unless check_content is True.)
231
 
        :param parent_texts: An optional dictionary containing the opaque 
 
280
        :param parent_texts: An optional dictionary containing the opaque
232
281
            representations of some or all of the parents of version_id to
233
282
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
234
283
            returned by add_lines or data corruption can be caused.
262
311
        parent_texts=None, nostore_sha=None, random_id=False,
263
312
        check_content=True, left_matching_blocks=None):
264
313
        """Add lines to the versioned file, allowing ghosts to be present.
265
 
        
 
314
 
266
315
        This takes the same parameters as add_lines and returns the same.
267
316
        """
268
317
        self._check_write_ok()
292
341
 
293
342
    def get_format_signature(self):
294
343
        """Get a text description of the data encoding in this file.
295
 
        
 
344
 
296
345
        :since: 0.90
297
346
        """
298
347
        raise NotImplementedError(self.get_format_signature)
419
468
        if isinstance(version_ids, basestring):
420
469
            version_ids = [version_ids]
421
470
        raise NotImplementedError(self.get_ancestry)
422
 
        
 
471
 
423
472
    def get_ancestry_with_ghosts(self, version_ids):
424
473
        """Return a list of all ancestors of given version(s). This
425
474
        will not include the null revision.
426
475
 
427
476
        Must raise RevisionNotPresent if any of the given versions are
428
477
        not present in file history.
429
 
        
 
478
 
430
479
        Ghosts that are known about will be included in ancestry list,
431
480
        but are not explicitly marked.
432
481
        """
433
482
        raise NotImplementedError(self.get_ancestry_with_ghosts)
434
 
    
 
483
 
435
484
    def get_parent_map(self, version_ids):
436
485
        """Get a map of the parents of version_ids.
437
486
 
500
549
        unchanged   Alive in both a and b (possibly created in both)
501
550
        new-a       Created in a
502
551
        new-b       Created in b
503
 
        ghost-a     Killed in a, unborn in b    
 
552
        ghost-a     Killed in a, unborn in b
504
553
        ghost-b     Killed in b, unborn in a
505
554
        irrelevant  Not in either revision
506
555
        """
507
556
        raise NotImplementedError(VersionedFile.plan_merge)
508
 
        
 
557
 
509
558
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
510
559
                    b_marker=TextMerge.B_MARKER):
511
560
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
513
562
 
514
563
class RecordingVersionedFilesDecorator(object):
515
564
    """A minimal versioned files that records calls made on it.
516
 
    
 
565
 
517
566
    Only enough methods have been added to support tests using it to date.
518
567
 
519
568
    :ivar calls: A list of the calls made; can be reset at any time by
521
570
    """
522
571
 
523
572
    def __init__(self, backing_vf):
524
 
        """Create a RecordingVersionedFileDsecorator decorating backing_vf.
525
 
        
 
573
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
 
574
 
526
575
        :param backing_vf: The versioned file to answer all methods.
527
576
        """
528
577
        self._backing_vf = backing_vf
562
611
        return self._backing_vf.keys()
563
612
 
564
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
 
565
652
class KeyMapper(object):
566
653
    """KeyMappers map between keys and underlying partitioned storage."""
567
654
 
576
663
 
577
664
    def unmap(self, partition_id):
578
665
        """Map a partitioned storage id back to a key prefix.
579
 
        
 
666
 
580
667
        :param partition_id: The underlying partition id.
581
668
        :return: As much of a key (or prefix) as is derivable from the partition
582
669
            id.
614
701
 
615
702
class PrefixMapper(URLEscapeMapper):
616
703
    """A key mapper that extracts the first component of a key.
617
 
    
 
704
 
618
705
    This mapper is for use with a transport based backend.
619
706
    """
620
707
 
653
740
 
654
741
class HashEscapedPrefixMapper(HashPrefixMapper):
655
742
    """Combines the escaped first component of a key with a hash.
656
 
    
 
743
 
657
744
    This mapper is for use with a transport based backend.
658
745
    """
659
746
 
715
802
        check_content=True):
716
803
        """Add a text to the store.
717
804
 
718
 
        :param key: The key tuple of the text to add.
 
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.
719
807
        :param parents: The parents key tuples of the text to add.
720
808
        :param lines: A list of lines. Each line must be a bytestring. And all
721
809
            of them except the last must be terminated with \n and contain no
725
813
            the data back accurately. (Checking the lines have been split
726
814
            correctly is expensive and extremely unlikely to catch bugs so it
727
815
            is not done at runtime unless check_content is True.)
728
 
        :param parent_texts: An optional dictionary containing the opaque 
 
816
        :param parent_texts: An optional dictionary containing the opaque
729
817
            representations of some or all of the parents of version_id to
730
818
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
731
819
            returned by add_lines or data corruption can be caused.
748
836
        """
749
837
        raise NotImplementedError(self.add_lines)
750
838
 
 
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
 
751
869
    def add_mpdiffs(self, records):
752
870
        """Add mpdiffs to this VersionedFile.
753
871
 
766
884
                                  if not mpvf.has_version(p))
767
885
        # It seems likely that adding all the present parents as fulltexts can
768
886
        # easily exhaust memory.
769
 
        split_lines = osutils.split_lines
 
887
        chunks_to_lines = osutils.chunks_to_lines
770
888
        for record in self.get_record_stream(needed_parents, 'unordered',
771
889
            True):
772
890
            if record.storage_kind == 'absent':
773
891
                continue
774
 
            mpvf.add_version(split_lines(record.get_bytes_as('fulltext')),
 
892
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
775
893
                record.key, [])
776
894
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
777
895
            zip(records, mpvf.get_line_list(versions)):
795
913
        raise NotImplementedError(self.annotate)
796
914
 
797
915
    def check(self, progress_bar=None):
798
 
        """Check this object for integrity."""
 
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
        """
799
926
        raise NotImplementedError(self.check)
800
927
 
801
928
    @staticmethod
802
929
    def check_not_reserved_id(version_id):
803
930
        revision.check_not_reserved_id(version_id)
804
931
 
 
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
 
805
939
    def _check_lines_not_unicode(self, lines):
806
940
        """Check that lines being added to a versioned file are not unicode."""
807
941
        for line in lines:
814
948
            if '\n' in line[:-1]:
815
949
                raise errors.BzrBadParameterContainsNewline("lines")
816
950
 
 
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
 
817
965
    def get_parent_map(self, keys):
818
966
        """Get a map of the parents of keys.
819
967
 
849
997
 
850
998
    has_key = index._has_key_from_parent_map
851
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
 
852
1012
    def insert_record_stream(self, stream):
853
1013
        """Insert a record stream into this container.
854
1014
 
855
 
        :param stream: A stream of records to insert. 
 
1015
        :param stream: A stream of records to insert.
856
1016
        :return: None
857
1017
        :seealso VersionedFile.get_record_stream:
858
1018
        """
902
1062
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
903
1063
        knit_keys.difference_update(ghosts)
904
1064
        lines = {}
905
 
        split_lines = osutils.split_lines
 
1065
        chunks_to_lines = osutils.chunks_to_lines
906
1066
        for record in self.get_record_stream(knit_keys, 'topological', True):
907
 
            lines[record.key] = split_lines(record.get_bytes_as('fulltext'))
 
1067
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
908
1068
            # line_block_dict = {}
909
1069
            # for parent, blocks in record.extract_line_blocks():
910
1070
            #   line_blocks[parent] = blocks
999
1159
            result.append((prefix + (origin,), line))
1000
1160
        return result
1001
1161
 
1002
 
    def check(self, progress_bar=None):
 
1162
    def get_annotator(self):
 
1163
        return annotate.Annotator(self)
 
1164
 
 
1165
    def check(self, progress_bar=None, keys=None):
1003
1166
        """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.
1004
1170
        for prefix, vf in self._iter_all_components():
1005
1171
            vf.check()
 
1172
        if keys is not None:
 
1173
            return self.get_record_stream(keys, 'unordered', True)
1006
1174
 
1007
1175
    def get_parent_map(self, keys):
1008
1176
        """Get a map of the parents of keys.
1086
1254
    def insert_record_stream(self, stream):
1087
1255
        """Insert a record stream into this container.
1088
1256
 
1089
 
        :param stream: A stream of records to insert. 
 
1257
        :param stream: A stream of records to insert.
1090
1258
        :return: None
1091
1259
        :seealso VersionedFile.get_record_stream:
1092
1260
        """
1213
1381
                lines = self._lines[key]
1214
1382
                parents = self._parents[key]
1215
1383
                pending.remove(key)
1216
 
                yield FulltextContentFactory(key, parents, None,
1217
 
                    ''.join(lines))
 
1384
                yield ChunkedContentFactory(key, parents, None, lines)
1218
1385
        for versionedfile in self.fallback_versionedfiles:
1219
1386
            for record in versionedfile.get_record_stream(
1220
1387
                pending, 'unordered', True):
1241
1408
            result[revision.NULL_REVISION] = ()
1242
1409
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1243
1410
        result.update(
1244
 
            _StackedParentsProvider(self._providers).get_parent_map(keys))
 
1411
            StackedParentsProvider(self._providers).get_parent_map(keys))
1245
1412
        for key, parents in result.iteritems():
1246
1413
            if parents == ():
1247
1414
                result[key] = (revision.NULL_REVISION,)
1250
1417
 
1251
1418
class PlanWeaveMerge(TextMerge):
1252
1419
    """Weave merge that takes a plan as its input.
1253
 
    
 
1420
 
1254
1421
    This exists so that VersionedFile.plan_merge is implementable.
1255
1422
    Most callers will want to use WeaveMerge instead.
1256
1423
    """
1258
1425
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1259
1426
                 b_marker=TextMerge.B_MARKER):
1260
1427
        TextMerge.__init__(self, a_marker, b_marker)
1261
 
        self.plan = plan
 
1428
        self.plan = list(plan)
1262
1429
 
1263
1430
    def _merge_struct(self):
1264
1431
        lines_a = []
1277
1444
                yield(lines_a,)
1278
1445
            else:
1279
1446
                yield (lines_a, lines_b)
1280
 
       
 
1447
 
1281
1448
        # We previously considered either 'unchanged' or 'killed-both' lines
1282
1449
        # to be possible places to resynchronize.  However, assuming agreement
1283
1450
        # on killed-both lines may be too aggressive. -- mbp 20060324
1289
1456
                lines_a = []
1290
1457
                lines_b = []
1291
1458
                ch_a = ch_b = False
1292
 
                
 
1459
 
1293
1460
            if state == 'unchanged':
1294
1461
                if line:
1295
1462
                    yield ([line],)
1311
1478
            elif state == 'conflicted-b':
1312
1479
                ch_b = ch_a = True
1313
1480
                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
1314
1485
            else:
1315
1486
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1316
 
                        'killed-base', 'killed-both'):
 
1487
                        'killed-base'):
1317
1488
                    raise AssertionError(state)
1318
1489
        for struct in outstanding_struct():
1319
1490
            yield struct
1320
1491
 
 
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
 
1321
1546
 
1322
1547
class WeaveMerge(PlanWeaveMerge):
1323
1548
    """Weave merge that takes a VersionedFile and two versions as its input."""
1324
1549
 
1325
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1550
    def __init__(self, versionedfile, ver_a, ver_b,
1326
1551
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1327
1552
        plan = versionedfile.plan_merge(ver_a, ver_b)
1328
1553
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1329
1554
 
1330
1555
 
1331
1556
class VirtualVersionedFiles(VersionedFiles):
1332
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1557
    """Dummy implementation for VersionedFiles that uses other functions for
1333
1558
    obtaining fulltexts and parent maps.
1334
1559
 
1335
 
    This is always on the bottom of the stack and uses string keys 
 
1560
    This is always on the bottom of the stack and uses string keys
1336
1561
    (rather than tuples) internally.
1337
1562
    """
1338
1563
 
1340
1565
        """Create a VirtualVersionedFiles.
1341
1566
 
1342
1567
        :param get_parent_map: Same signature as Repository.get_parent_map.
1343
 
        :param get_lines: Should return lines for specified key or None if 
 
1568
        :param get_lines: Should return lines for specified key or None if
1344
1569
                          not available.
1345
1570
        """
1346
1571
        super(VirtualVersionedFiles, self).__init__()
1347
1572
        self._get_parent_map = get_parent_map
1348
1573
        self._get_lines = get_lines
1349
 
        
 
1574
 
1350
1575
    def check(self, progressbar=None):
1351
1576
        """See VersionedFiles.check.
1352
1577
 
1384
1609
            if lines is not None:
1385
1610
                if not isinstance(lines, list):
1386
1611
                    raise AssertionError
1387
 
                yield FulltextContentFactory((k,), None, 
 
1612
                yield ChunkedContentFactory((k,), None,
1388
1613
                        sha1=osutils.sha_strings(lines),
1389
 
                        text=''.join(lines))
 
1614
                        chunks=lines)
1390
1615
            else:
1391
1616
                yield AbsentContentFactory((k,))
1392
1617
 
1393
 
 
1394
 
 
 
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