/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: Andrew Bennetts
  • Date: 2010-04-13 04:33:55 UTC
  • mfrom: (5147 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5149.
  • Revision ID: andrew.bennetts@canonical.com-20100413043355-lg3id0uwtju0k3zs
MergeĀ lp:bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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,
 
37
    index,
 
38
    knit,
33
39
    osutils,
34
40
    multiparent,
35
41
    tsort,
36
42
    revision,
37
43
    ui,
38
44
    )
39
 
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
 
45
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
40
46
from bzrlib.transport.memory import MemoryTransport
41
47
""")
42
48
from bzrlib.inter import InterObject
43
49
from bzrlib.registry import Registry
44
50
from bzrlib.symbol_versioning import *
45
51
from bzrlib.textmerge import TextMerge
 
52
from bzrlib import bencode
46
53
 
47
54
 
48
55
adapter_registry = Registry()
58
65
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
59
66
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
60
67
    'bzrlib.knit', 'FTAnnotatedToFullText')
 
68
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
 
69
#     'bzrlib.knit', 'FTAnnotatedToChunked')
61
70
 
62
71
 
63
72
class ContentFactory(object):
64
73
    """Abstract interface for insertion and retrieval from a VersionedFile.
65
 
    
 
74
 
66
75
    :ivar sha1: None, or the sha1 of the content fulltext.
67
76
    :ivar storage_kind: The native storage kind of this factory. One of
68
77
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
83
92
        self.parents = None
84
93
 
85
94
 
 
95
class ChunkedContentFactory(ContentFactory):
 
96
    """Static data content factory.
 
97
 
 
98
    This takes a 'chunked' list of strings. The only requirement on 'chunked' is
 
99
    that ''.join(lines) becomes a valid fulltext. A tuple of a single string
 
100
    satisfies this, as does a list of lines.
 
101
 
 
102
    :ivar sha1: None, or the sha1 of the content fulltext.
 
103
    :ivar storage_kind: The native storage kind of this factory. Always
 
104
        'chunked'
 
105
    :ivar key: The key of this content. Each key is a tuple with a single
 
106
        string in it.
 
107
    :ivar parents: A tuple of parent keys for self.key. If the object has
 
108
        no parent information, None (as opposed to () for an empty list of
 
109
        parents).
 
110
     """
 
111
 
 
112
    def __init__(self, key, parents, sha1, chunks):
 
113
        """Create a ContentFactory."""
 
114
        self.sha1 = sha1
 
115
        self.storage_kind = 'chunked'
 
116
        self.key = key
 
117
        self.parents = parents
 
118
        self._chunks = chunks
 
119
 
 
120
    def get_bytes_as(self, storage_kind):
 
121
        if storage_kind == 'chunked':
 
122
            return self._chunks
 
123
        elif storage_kind == 'fulltext':
 
124
            return ''.join(self._chunks)
 
125
        raise errors.UnavailableRepresentation(self.key, storage_kind,
 
126
            self.storage_kind)
 
127
 
 
128
 
86
129
class FulltextContentFactory(ContentFactory):
87
130
    """Static data content factory.
88
131
 
89
132
    This takes a fulltext when created and just returns that during
90
133
    get_bytes_as('fulltext').
91
 
    
 
134
 
92
135
    :ivar sha1: None, or the sha1 of the content fulltext.
93
136
    :ivar storage_kind: The native storage kind of this factory. Always
94
137
        'fulltext'.
110
153
    def get_bytes_as(self, storage_kind):
111
154
        if storage_kind == self.storage_kind:
112
155
            return self._text
 
156
        elif storage_kind == 'chunked':
 
157
            return [self._text]
113
158
        raise errors.UnavailableRepresentation(self.key, storage_kind,
114
159
            self.storage_kind)
115
160
 
116
161
 
117
162
class AbsentContentFactory(ContentFactory):
118
163
    """A placeholder content factory for unavailable texts.
119
 
    
 
164
 
120
165
    :ivar sha1: None.
121
166
    :ivar storage_kind: 'absent'.
122
167
    :ivar key: The key of this content. Each key is a tuple with a single
131
176
        self.key = key
132
177
        self.parents = None
133
178
 
 
179
    def get_bytes_as(self, storage_kind):
 
180
        raise ValueError('A request was made for key: %s, but that'
 
181
                         ' content is not available, and the calling'
 
182
                         ' code does not handle if it is missing.'
 
183
                         % (self.key,))
 
184
 
134
185
 
135
186
class AdapterFactory(ContentFactory):
136
187
    """A content factory to adapt between key prefix's."""
158
209
 
159
210
class VersionedFile(object):
160
211
    """Versioned text file storage.
161
 
    
 
212
 
162
213
    A versioned file manages versions of line-based text files,
163
214
    keeping track of the originating version for each line.
164
215
 
202
253
    def insert_record_stream(self, stream):
203
254
        """Insert a record stream into this versioned file.
204
255
 
205
 
        :param stream: A stream of records to insert. 
 
256
        :param stream: A stream of records to insert.
206
257
        :return: None
207
258
        :seealso VersionedFile.get_record_stream:
208
259
        """
227
278
            the data back accurately. (Checking the lines have been split
228
279
            correctly is expensive and extremely unlikely to catch bugs so it
229
280
            is not done at runtime unless check_content is True.)
230
 
        :param parent_texts: An optional dictionary containing the opaque 
 
281
        :param parent_texts: An optional dictionary containing the opaque
231
282
            representations of some or all of the parents of version_id to
232
283
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
233
284
            returned by add_lines or data corruption can be caused.
261
312
        parent_texts=None, nostore_sha=None, random_id=False,
262
313
        check_content=True, left_matching_blocks=None):
263
314
        """Add lines to the versioned file, allowing ghosts to be present.
264
 
        
 
315
 
265
316
        This takes the same parameters as add_lines and returns the same.
266
317
        """
267
318
        self._check_write_ok()
291
342
 
292
343
    def get_format_signature(self):
293
344
        """Get a text description of the data encoding in this file.
294
 
        
 
345
 
295
346
        :since: 0.90
296
347
        """
297
348
        raise NotImplementedError(self.get_format_signature)
418
469
        if isinstance(version_ids, basestring):
419
470
            version_ids = [version_ids]
420
471
        raise NotImplementedError(self.get_ancestry)
421
 
        
 
472
 
422
473
    def get_ancestry_with_ghosts(self, version_ids):
423
474
        """Return a list of all ancestors of given version(s). This
424
475
        will not include the null revision.
425
476
 
426
477
        Must raise RevisionNotPresent if any of the given versions are
427
478
        not present in file history.
428
 
        
 
479
 
429
480
        Ghosts that are known about will be included in ancestry list,
430
481
        but are not explicitly marked.
431
482
        """
432
483
        raise NotImplementedError(self.get_ancestry_with_ghosts)
433
 
    
 
484
 
434
485
    def get_parent_map(self, version_ids):
435
486
        """Get a map of the parents of version_ids.
436
487
 
499
550
        unchanged   Alive in both a and b (possibly created in both)
500
551
        new-a       Created in a
501
552
        new-b       Created in b
502
 
        ghost-a     Killed in a, unborn in b    
 
553
        ghost-a     Killed in a, unborn in b
503
554
        ghost-b     Killed in b, unborn in a
504
555
        irrelevant  Not in either revision
505
556
        """
506
557
        raise NotImplementedError(VersionedFile.plan_merge)
507
 
        
 
558
 
508
559
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
509
560
                    b_marker=TextMerge.B_MARKER):
510
561
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
512
563
 
513
564
class RecordingVersionedFilesDecorator(object):
514
565
    """A minimal versioned files that records calls made on it.
515
 
    
 
566
 
516
567
    Only enough methods have been added to support tests using it to date.
517
568
 
518
569
    :ivar calls: A list of the calls made; can be reset at any time by
520
571
    """
521
572
 
522
573
    def __init__(self, backing_vf):
523
 
        """Create a RecordingVersionedFileDsecorator decorating backing_vf.
524
 
        
 
574
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
 
575
 
525
576
        :param backing_vf: The versioned file to answer all methods.
526
577
        """
527
578
        self._backing_vf = backing_vf
561
612
        return self._backing_vf.keys()
562
613
 
563
614
 
 
615
class OrderingVersionedFilesDecorator(RecordingVersionedFilesDecorator):
 
616
    """A VF that records calls, and returns keys in specific order.
 
617
 
 
618
    :ivar calls: A list of the calls made; can be reset at any time by
 
619
        assigning [] to it.
 
620
    """
 
621
 
 
622
    def __init__(self, backing_vf, key_priority):
 
623
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
 
624
 
 
625
        :param backing_vf: The versioned file to answer all methods.
 
626
        :param key_priority: A dictionary defining what order keys should be
 
627
            returned from an 'unordered' get_record_stream request.
 
628
            Keys with lower priority are returned first, keys not present in
 
629
            the map get an implicit priority of 0, and are returned in
 
630
            lexicographical order.
 
631
        """
 
632
        RecordingVersionedFilesDecorator.__init__(self, backing_vf)
 
633
        self._key_priority = key_priority
 
634
 
 
635
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
636
        self.calls.append(("get_record_stream", list(keys), sort_order,
 
637
            include_delta_closure))
 
638
        if sort_order == 'unordered':
 
639
            def sort_key(key):
 
640
                return (self._key_priority.get(key, 0), key)
 
641
            # Use a defined order by asking for the keys one-by-one from the
 
642
            # backing_vf
 
643
            for key in sorted(keys, key=sort_key):
 
644
                for record in self._backing_vf.get_record_stream([key],
 
645
                                'unordered', include_delta_closure):
 
646
                    yield record
 
647
        else:
 
648
            for record in self._backing_vf.get_record_stream(keys, sort_order,
 
649
                            include_delta_closure):
 
650
                yield record
 
651
 
 
652
 
564
653
class KeyMapper(object):
565
654
    """KeyMappers map between keys and underlying partitioned storage."""
566
655
 
575
664
 
576
665
    def unmap(self, partition_id):
577
666
        """Map a partitioned storage id back to a key prefix.
578
 
        
 
667
 
579
668
        :param partition_id: The underlying partition id.
580
669
        :return: As much of a key (or prefix) as is derivable from the partition
581
670
            id.
613
702
 
614
703
class PrefixMapper(URLEscapeMapper):
615
704
    """A key mapper that extracts the first component of a key.
616
 
    
 
705
 
617
706
    This mapper is for use with a transport based backend.
618
707
    """
619
708
 
652
741
 
653
742
class HashEscapedPrefixMapper(HashPrefixMapper):
654
743
    """Combines the escaped first component of a key with a hash.
655
 
    
 
744
 
656
745
    This mapper is for use with a transport based backend.
657
746
    """
658
747
 
714
803
        check_content=True):
715
804
        """Add a text to the store.
716
805
 
717
 
        :param key: The key tuple of the text to add.
 
806
        :param key: The key tuple of the text to add. If the last element is
 
807
            None, a CHK string will be generated during the addition.
718
808
        :param parents: The parents key tuples of the text to add.
719
809
        :param lines: A list of lines. Each line must be a bytestring. And all
720
810
            of them except the last must be terminated with \n and contain no
724
814
            the data back accurately. (Checking the lines have been split
725
815
            correctly is expensive and extremely unlikely to catch bugs so it
726
816
            is not done at runtime unless check_content is True.)
727
 
        :param parent_texts: An optional dictionary containing the opaque 
 
817
        :param parent_texts: An optional dictionary containing the opaque
728
818
            representations of some or all of the parents of version_id to
729
819
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
730
820
            returned by add_lines or data corruption can be caused.
747
837
        """
748
838
        raise NotImplementedError(self.add_lines)
749
839
 
 
840
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
841
        """Add a text to the store.
 
842
 
 
843
        This is a private function for use by CommitBuilder.
 
844
 
 
845
        :param key: The key tuple of the text to add. If the last element is
 
846
            None, a CHK string will be generated during the addition.
 
847
        :param parents: The parents key tuples of the text to add.
 
848
        :param text: A string containing the text to be committed.
 
849
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
850
            the versioned file if the digest of the lines matches this.
 
851
        :param random_id: If True a random id has been selected rather than
 
852
            an id determined by some deterministic process such as a converter
 
853
            from a foreign VCS. When True the backend may choose not to check
 
854
            for uniqueness of the resulting key within the versioned file, so
 
855
            this should only be done when the result is expected to be unique
 
856
            anyway.
 
857
        :param check_content: If True, the lines supplied are verified to be
 
858
            bytestrings that are correctly formed lines.
 
859
        :return: The text sha1, the number of bytes in the text, and an opaque
 
860
                 representation of the inserted version which can be provided
 
861
                 back to future _add_text calls in the parent_texts dictionary.
 
862
        """
 
863
        # The default implementation just thunks over to .add_lines(),
 
864
        # inefficient, but it works.
 
865
        return self.add_lines(key, parents, osutils.split_lines(text),
 
866
                              nostore_sha=nostore_sha,
 
867
                              random_id=random_id,
 
868
                              check_content=True)
 
869
 
750
870
    def add_mpdiffs(self, records):
751
871
        """Add mpdiffs to this VersionedFile.
752
872
 
765
885
                                  if not mpvf.has_version(p))
766
886
        # It seems likely that adding all the present parents as fulltexts can
767
887
        # easily exhaust memory.
768
 
        split_lines = osutils.split_lines
 
888
        chunks_to_lines = osutils.chunks_to_lines
769
889
        for record in self.get_record_stream(needed_parents, 'unordered',
770
890
            True):
771
891
            if record.storage_kind == 'absent':
772
892
                continue
773
 
            mpvf.add_version(split_lines(record.get_bytes_as('fulltext')),
 
893
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
774
894
                record.key, [])
775
895
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
776
896
            zip(records, mpvf.get_line_list(versions)):
794
914
        raise NotImplementedError(self.annotate)
795
915
 
796
916
    def check(self, progress_bar=None):
797
 
        """Check this object for integrity."""
 
917
        """Check this object for integrity.
 
918
        
 
919
        :param progress_bar: A progress bar to output as the check progresses.
 
920
        :param keys: Specific keys within the VersionedFiles to check. When
 
921
            this parameter is not None, check() becomes a generator as per
 
922
            get_record_stream. The difference to get_record_stream is that
 
923
            more or deeper checks will be performed.
 
924
        :return: None, or if keys was supplied a generator as per
 
925
            get_record_stream.
 
926
        """
798
927
        raise NotImplementedError(self.check)
799
928
 
800
929
    @staticmethod
801
930
    def check_not_reserved_id(version_id):
802
931
        revision.check_not_reserved_id(version_id)
803
932
 
 
933
    def clear_cache(self):
 
934
        """Clear whatever caches this VersionedFile holds.
 
935
 
 
936
        This is generally called after an operation has been performed, when we
 
937
        don't expect to be using this versioned file again soon.
 
938
        """
 
939
 
804
940
    def _check_lines_not_unicode(self, lines):
805
941
        """Check that lines being added to a versioned file are not unicode."""
806
942
        for line in lines:
813
949
            if '\n' in line[:-1]:
814
950
                raise errors.BzrBadParameterContainsNewline("lines")
815
951
 
 
952
    def get_known_graph_ancestry(self, keys):
 
953
        """Get a KnownGraph instance with the ancestry of keys."""
 
954
        # most basic implementation is a loop around get_parent_map
 
955
        pending = set(keys)
 
956
        parent_map = {}
 
957
        while pending:
 
958
            this_parent_map = self.get_parent_map(pending)
 
959
            parent_map.update(this_parent_map)
 
960
            pending = set()
 
961
            map(pending.update, this_parent_map.itervalues())
 
962
            pending = pending.difference(parent_map)
 
963
        kg = _mod_graph.KnownGraph(parent_map)
 
964
        return kg
 
965
 
816
966
    def get_parent_map(self, keys):
817
967
        """Get a map of the parents of keys.
818
968
 
846
996
        """
847
997
        raise NotImplementedError(self.get_sha1s)
848
998
 
 
999
    has_key = index._has_key_from_parent_map
 
1000
 
 
1001
    def get_missing_compression_parent_keys(self):
 
1002
        """Return an iterable of keys of missing compression parents.
 
1003
 
 
1004
        Check this after calling insert_record_stream to find out if there are
 
1005
        any missing compression parents.  If there are, the records that
 
1006
        depend on them are not able to be inserted safely. The precise
 
1007
        behaviour depends on the concrete VersionedFiles class in use.
 
1008
 
 
1009
        Classes that do not support this will raise NotImplementedError.
 
1010
        """
 
1011
        raise NotImplementedError(self.get_missing_compression_parent_keys)
 
1012
 
849
1013
    def insert_record_stream(self, stream):
850
1014
        """Insert a record stream into this container.
851
1015
 
852
 
        :param stream: A stream of records to insert. 
 
1016
        :param stream: A stream of records to insert.
853
1017
        :return: None
854
1018
        :seealso VersionedFile.get_record_stream:
855
1019
        """
899
1063
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
900
1064
        knit_keys.difference_update(ghosts)
901
1065
        lines = {}
902
 
        split_lines = osutils.split_lines
 
1066
        chunks_to_lines = osutils.chunks_to_lines
903
1067
        for record in self.get_record_stream(knit_keys, 'topological', True):
904
 
            lines[record.key] = split_lines(record.get_bytes_as('fulltext'))
 
1068
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
905
1069
            # line_block_dict = {}
906
1070
            # for parent, blocks in record.extract_line_blocks():
907
1071
            #   line_blocks[parent] = blocks
922
1086
                parent_lines, left_parent_blocks))
923
1087
        return diffs
924
1088
 
 
1089
    missing_keys = index._missing_keys_from_parent_map
 
1090
 
925
1091
    def _extract_blocks(self, version_id, source, target):
926
1092
        return None
927
1093
 
994
1160
            result.append((prefix + (origin,), line))
995
1161
        return result
996
1162
 
997
 
    def check(self, progress_bar=None):
 
1163
    def get_annotator(self):
 
1164
        return annotate.Annotator(self)
 
1165
 
 
1166
    def check(self, progress_bar=None, keys=None):
998
1167
        """See VersionedFiles.check()."""
 
1168
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
 
1169
        # this is tolerable. Ideally we'd pass keys down to check() and 
 
1170
        # have the older VersiondFile interface updated too.
999
1171
        for prefix, vf in self._iter_all_components():
1000
1172
            vf.check()
 
1173
        if keys is not None:
 
1174
            return self.get_record_stream(keys, 'unordered', True)
1001
1175
 
1002
1176
    def get_parent_map(self, keys):
1003
1177
        """Get a map of the parents of keys.
1081
1255
    def insert_record_stream(self, stream):
1082
1256
        """Insert a record stream into this container.
1083
1257
 
1084
 
        :param stream: A stream of records to insert. 
 
1258
        :param stream: A stream of records to insert.
1085
1259
        :return: None
1086
1260
        :seealso VersionedFile.get_record_stream:
1087
1261
        """
1208
1382
                lines = self._lines[key]
1209
1383
                parents = self._parents[key]
1210
1384
                pending.remove(key)
1211
 
                yield FulltextContentFactory(key, parents, None,
1212
 
                    ''.join(lines))
 
1385
                yield ChunkedContentFactory(key, parents, None, lines)
1213
1386
        for versionedfile in self.fallback_versionedfiles:
1214
1387
            for record in versionedfile.get_record_stream(
1215
1388
                pending, 'unordered', True):
1236
1409
            result[revision.NULL_REVISION] = ()
1237
1410
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1238
1411
        result.update(
1239
 
            _StackedParentsProvider(self._providers).get_parent_map(keys))
 
1412
            StackedParentsProvider(self._providers).get_parent_map(keys))
1240
1413
        for key, parents in result.iteritems():
1241
1414
            if parents == ():
1242
1415
                result[key] = (revision.NULL_REVISION,)
1245
1418
 
1246
1419
class PlanWeaveMerge(TextMerge):
1247
1420
    """Weave merge that takes a plan as its input.
1248
 
    
 
1421
 
1249
1422
    This exists so that VersionedFile.plan_merge is implementable.
1250
1423
    Most callers will want to use WeaveMerge instead.
1251
1424
    """
1253
1426
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1254
1427
                 b_marker=TextMerge.B_MARKER):
1255
1428
        TextMerge.__init__(self, a_marker, b_marker)
1256
 
        self.plan = plan
 
1429
        self.plan = list(plan)
1257
1430
 
1258
1431
    def _merge_struct(self):
1259
1432
        lines_a = []
1272
1445
                yield(lines_a,)
1273
1446
            else:
1274
1447
                yield (lines_a, lines_b)
1275
 
       
 
1448
 
1276
1449
        # We previously considered either 'unchanged' or 'killed-both' lines
1277
1450
        # to be possible places to resynchronize.  However, assuming agreement
1278
1451
        # on killed-both lines may be too aggressive. -- mbp 20060324
1284
1457
                lines_a = []
1285
1458
                lines_b = []
1286
1459
                ch_a = ch_b = False
1287
 
                
 
1460
 
1288
1461
            if state == 'unchanged':
1289
1462
                if line:
1290
1463
                    yield ([line],)
1306
1479
            elif state == 'conflicted-b':
1307
1480
                ch_b = ch_a = True
1308
1481
                lines_b.append(line)
 
1482
            elif state == 'killed-both':
 
1483
                # This counts as a change, even though there is no associated
 
1484
                # line
 
1485
                ch_b = ch_a = True
1309
1486
            else:
1310
1487
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1311
 
                        'killed-base', 'killed-both'):
 
1488
                        'killed-base'):
1312
1489
                    raise AssertionError(state)
1313
1490
        for struct in outstanding_struct():
1314
1491
            yield struct
1315
1492
 
 
1493
    def base_from_plan(self):
 
1494
        """Construct a BASE file from the plan text."""
 
1495
        base_lines = []
 
1496
        for state, line in self.plan:
 
1497
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
 
1498
                # If unchanged, then this line is straight from base. If a or b
 
1499
                # or both killed the line, then it *used* to be in base.
 
1500
                base_lines.append(line)
 
1501
            else:
 
1502
                if state not in ('killed-base', 'irrelevant',
 
1503
                                 'ghost-a', 'ghost-b',
 
1504
                                 'new-a', 'new-b',
 
1505
                                 'conflicted-a', 'conflicted-b'):
 
1506
                    # killed-base, irrelevant means it doesn't apply
 
1507
                    # ghost-a/ghost-b are harder to say for sure, but they
 
1508
                    # aren't in the 'inc_c' which means they aren't in the
 
1509
                    # shared base of a & b. So we don't include them.  And
 
1510
                    # obviously if the line is newly inserted, it isn't in base
 
1511
 
 
1512
                    # If 'conflicted-a' or b, then it is new vs one base, but
 
1513
                    # old versus another base. However, if we make it present
 
1514
                    # in the base, it will be deleted from the target, and it
 
1515
                    # seems better to get a line doubled in the merge result,
 
1516
                    # rather than have it deleted entirely.
 
1517
                    # Example, each node is the 'text' at that point:
 
1518
                    #           MN
 
1519
                    #          /   \
 
1520
                    #        MaN   MbN
 
1521
                    #         |  X  |
 
1522
                    #        MabN MbaN
 
1523
                    #          \   /
 
1524
                    #           ???
 
1525
                    # There was a criss-cross conflict merge. Both sides
 
1526
                    # include the other, but put themselves first.
 
1527
                    # Weave marks this as a 'clean' merge, picking OTHER over
 
1528
                    # THIS. (Though the details depend on order inserted into
 
1529
                    # weave, etc.)
 
1530
                    # LCA generates a plan:
 
1531
                    # [('unchanged', M),
 
1532
                    #  ('conflicted-b', b),
 
1533
                    #  ('unchanged', a),
 
1534
                    #  ('conflicted-a', b),
 
1535
                    #  ('unchanged', N)]
 
1536
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
 
1537
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
 
1538
                    # removes one 'b', and BASE vs OTHER removes the other)
 
1539
                    # If you include neither, 3-way creates a clean "MbabN" as
 
1540
                    # THIS adds one 'b', and OTHER does too.
 
1541
                    # It seems that having the line 2 times is better than
 
1542
                    # having it omitted. (Easier to manually delete than notice
 
1543
                    # it needs to be added.)
 
1544
                    raise AssertionError('Unknown state: %s' % (state,))
 
1545
        return base_lines
 
1546
 
1316
1547
 
1317
1548
class WeaveMerge(PlanWeaveMerge):
1318
1549
    """Weave merge that takes a VersionedFile and two versions as its input."""
1319
1550
 
1320
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1551
    def __init__(self, versionedfile, ver_a, ver_b,
1321
1552
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1322
1553
        plan = versionedfile.plan_merge(ver_a, ver_b)
1323
1554
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1324
1555
 
1325
1556
 
1326
1557
class VirtualVersionedFiles(VersionedFiles):
1327
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1558
    """Dummy implementation for VersionedFiles that uses other functions for
1328
1559
    obtaining fulltexts and parent maps.
1329
1560
 
1330
 
    This is always on the bottom of the stack and uses string keys 
 
1561
    This is always on the bottom of the stack and uses string keys
1331
1562
    (rather than tuples) internally.
1332
1563
    """
1333
1564
 
1335
1566
        """Create a VirtualVersionedFiles.
1336
1567
 
1337
1568
        :param get_parent_map: Same signature as Repository.get_parent_map.
1338
 
        :param get_lines: Should return lines for specified key or None if 
 
1569
        :param get_lines: Should return lines for specified key or None if
1339
1570
                          not available.
1340
1571
        """
1341
1572
        super(VirtualVersionedFiles, self).__init__()
1342
1573
        self._get_parent_map = get_parent_map
1343
1574
        self._get_lines = get_lines
1344
 
        
 
1575
 
1345
1576
    def check(self, progressbar=None):
1346
1577
        """See VersionedFiles.check.
1347
1578
 
1379
1610
            if lines is not None:
1380
1611
                if not isinstance(lines, list):
1381
1612
                    raise AssertionError
1382
 
                yield FulltextContentFactory((k,), None, 
 
1613
                yield ChunkedContentFactory((k,), None,
1383
1614
                        sha1=osutils.sha_strings(lines),
1384
 
                        text=''.join(lines))
 
1615
                        chunks=lines)
1385
1616
            else:
1386
1617
                yield AbsentContentFactory((k,))
1387
1618
 
1388
 
 
1389
 
 
 
1619
    def iter_lines_added_or_present_in_keys(self, keys, pb=None):
 
1620
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
 
1621
        for i, (key,) in enumerate(keys):
 
1622
            if pb is not None:
 
1623
                pb.update("Finding changed lines", i, len(keys))
 
1624
            for l in self._get_lines(key):
 
1625
                yield (l, key)
 
1626
 
 
1627
 
 
1628
def network_bytes_to_kind_and_offset(network_bytes):
 
1629
    """Strip of a record kind from the front of network_bytes.
 
1630
 
 
1631
    :param network_bytes: The bytes of a record.
 
1632
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
 
1633
    """
 
1634
    line_end = network_bytes.find('\n')
 
1635
    storage_kind = network_bytes[:line_end]
 
1636
    return storage_kind, line_end + 1
 
1637
 
 
1638
 
 
1639
class NetworkRecordStream(object):
 
1640
    """A record_stream which reconstitures a serialised stream."""
 
1641
 
 
1642
    def __init__(self, bytes_iterator):
 
1643
        """Create a NetworkRecordStream.
 
1644
 
 
1645
        :param bytes_iterator: An iterator of bytes. Each item in this
 
1646
            iterator should have been obtained from a record_streams'
 
1647
            record.get_bytes_as(record.storage_kind) call.
 
1648
        """
 
1649
        self._bytes_iterator = bytes_iterator
 
1650
        self._kind_factory = {
 
1651
            'fulltext': fulltext_network_to_record,
 
1652
            'groupcompress-block': groupcompress.network_block_to_records,
 
1653
            'knit-ft-gz': knit.knit_network_to_record,
 
1654
            'knit-delta-gz': knit.knit_network_to_record,
 
1655
            'knit-annotated-ft-gz': knit.knit_network_to_record,
 
1656
            'knit-annotated-delta-gz': knit.knit_network_to_record,
 
1657
            'knit-delta-closure': knit.knit_delta_closure_to_records,
 
1658
            }
 
1659
 
 
1660
    def read(self):
 
1661
        """Read the stream.
 
1662
 
 
1663
        :return: An iterator as per VersionedFiles.get_record_stream().
 
1664
        """
 
1665
        for bytes in self._bytes_iterator:
 
1666
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
1667
            for record in self._kind_factory[storage_kind](
 
1668
                storage_kind, bytes, line_end):
 
1669
                yield record
 
1670
 
 
1671
 
 
1672
def fulltext_network_to_record(kind, bytes, line_end):
 
1673
    """Convert a network fulltext record to record."""
 
1674
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1675
    record_meta = bytes[line_end+4:line_end+4+meta_len]
 
1676
    key, parents = bencode.bdecode_as_tuple(record_meta)
 
1677
    if parents == 'nil':
 
1678
        parents = None
 
1679
    fulltext = bytes[line_end+4+meta_len:]
 
1680
    return [FulltextContentFactory(key, parents, None, fulltext)]
 
1681
 
 
1682
 
 
1683
def _length_prefix(bytes):
 
1684
    return struct.pack('!L', len(bytes))
 
1685
 
 
1686
 
 
1687
def record_to_fulltext_bytes(record):
 
1688
    if record.parents is None:
 
1689
        parents = 'nil'
 
1690
    else:
 
1691
        parents = record.parents
 
1692
    record_meta = bencode.bencode((record.key, parents))
 
1693
    record_content = record.get_bytes_as('fulltext')
 
1694
    return "fulltext\n%s%s%s" % (
 
1695
        _length_prefix(record_meta), record_meta, record_content)
 
1696
 
 
1697
 
 
1698
def sort_groupcompress(parent_map):
 
1699
    """Sort and group the keys in parent_map into groupcompress order.
 
1700
 
 
1701
    groupcompress is defined (currently) as reverse-topological order, grouped
 
1702
    by the key prefix.
 
1703
 
 
1704
    :return: A sorted-list of keys
 
1705
    """
 
1706
    # gc-optimal ordering is approximately reverse topological,
 
1707
    # properly grouped by file-id.
 
1708
    per_prefix_map = {}
 
1709
    for item in parent_map.iteritems():
 
1710
        key = item[0]
 
1711
        if isinstance(key, str) or len(key) == 1:
 
1712
            prefix = ''
 
1713
        else:
 
1714
            prefix = key[0]
 
1715
        try:
 
1716
            per_prefix_map[prefix].append(item)
 
1717
        except KeyError:
 
1718
            per_prefix_map[prefix] = [item]
 
1719
 
 
1720
    present_keys = []
 
1721
    for prefix in sorted(per_prefix_map):
 
1722
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
1723
    return present_keys