~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Frank Aspell
  • Date: 2009-02-17 11:40:05 UTC
  • mto: (4054.1.1 doc)
  • mto: This revision was merged to the branch mainline in revision 4056.
  • Revision ID: frankaspell@googlemail.com-20090217114005-ojufrp6rqht664um
Fixed typos.

Fixed some typos in bzr doc's using "aspell -l en -c FILENAME".

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
22
from copy import copy
23
23
from cStringIO import StringIO
24
24
import os
25
 
import struct
26
25
from zlib import adler32
27
26
 
28
27
from bzrlib.lazy_import import lazy_import
30
29
import urllib
31
30
 
32
31
from bzrlib import (
33
 
    annotate,
34
32
    errors,
35
 
    graph as _mod_graph,
36
 
    groupcompress,
37
33
    index,
38
 
    knit,
39
34
    osutils,
40
35
    multiparent,
41
36
    tsort,
42
37
    revision,
43
38
    ui,
44
39
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
 
40
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
46
41
from bzrlib.transport.memory import MemoryTransport
47
42
""")
 
43
from bzrlib.inter import InterObject
48
44
from bzrlib.registry import Registry
 
45
from bzrlib.symbol_versioning import *
49
46
from bzrlib.textmerge import TextMerge
50
 
from bzrlib import bencode
51
47
 
52
48
 
53
49
adapter_registry = Registry()
69
65
 
70
66
class ContentFactory(object):
71
67
    """Abstract interface for insertion and retrieval from a VersionedFile.
72
 
 
 
68
    
73
69
    :ivar sha1: None, or the sha1 of the content fulltext.
74
70
    :ivar storage_kind: The native storage kind of this factory. One of
75
71
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
159
155
 
160
156
class AbsentContentFactory(ContentFactory):
161
157
    """A placeholder content factory for unavailable texts.
162
 
 
 
158
    
163
159
    :ivar sha1: None.
164
160
    :ivar storage_kind: 'absent'.
165
161
    :ivar key: The key of this content. Each key is a tuple with a single
174
170
        self.key = key
175
171
        self.parents = None
176
172
 
177
 
    def get_bytes_as(self, storage_kind):
178
 
        raise ValueError('A request was made for key: %s, but that'
179
 
                         ' content is not available, and the calling'
180
 
                         ' code does not handle if it is missing.'
181
 
                         % (self.key,))
182
 
 
183
173
 
184
174
class AdapterFactory(ContentFactory):
185
175
    """A content factory to adapt between key prefix's."""
205
195
            yield record
206
196
 
207
197
 
208
 
class _MPDiffGenerator(object):
209
 
    """Pull out the functionality for generating mp_diffs."""
210
 
 
211
 
    def __init__(self, vf, keys):
212
 
        self.vf = vf
213
 
        # This is the order the keys were requested in
214
 
        self.ordered_keys = tuple(keys)
215
 
        # keys + their parents, what we need to compute the diffs
216
 
        self.needed_keys = ()
217
 
        # Map from key: mp_diff
218
 
        self.diffs = {}
219
 
        # Map from key: parents_needed (may have ghosts)
220
 
        self.parent_map = {}
221
 
        # Parents that aren't present
222
 
        self.ghost_parents = ()
223
 
        # Map from parent_key => number of children for this text
224
 
        self.refcounts = {}
225
 
        # Content chunks that are cached while we still need them
226
 
        self.chunks = {}
227
 
 
228
 
    def _find_needed_keys(self):
229
 
        """Find the set of keys we need to request.
230
 
 
231
 
        This includes all the original keys passed in, and the non-ghost
232
 
        parents of those keys.
233
 
 
234
 
        :return: (needed_keys, refcounts)
235
 
            needed_keys is the set of all texts we need to extract
236
 
            refcounts is a dict of {key: num_children} letting us know when we
237
 
                no longer need to cache a given parent text
238
 
        """
239
 
        # All the keys and their parents
240
 
        needed_keys = set(self.ordered_keys)
241
 
        parent_map = self.vf.get_parent_map(needed_keys)
242
 
        self.parent_map = parent_map
243
 
        # TODO: Should we be using a different construct here? I think this
244
 
        #       uses difference_update internally, and we expect the result to
245
 
        #       be tiny
246
 
        missing_keys = needed_keys.difference(parent_map)
247
 
        if missing_keys:
248
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
249
 
        # Parents that might be missing. They are allowed to be ghosts, but we
250
 
        # should check for them
251
 
        refcounts = {}
252
 
        setdefault = refcounts.setdefault
253
 
        just_parents = set()
254
 
        for child_key, parent_keys in parent_map.iteritems():
255
 
            if not parent_keys:
256
 
                # parent_keys may be None if a given VersionedFile claims to
257
 
                # not support graph operations.
258
 
                continue
259
 
            just_parents.update(parent_keys)
260
 
            needed_keys.update(parent_keys)
261
 
            for p in parent_keys:
262
 
                refcounts[p] = setdefault(p, 0) + 1
263
 
        just_parents.difference_update(parent_map)
264
 
        # Remove any parents that are actually ghosts from the needed set
265
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
266
 
        self.ghost_parents = just_parents.difference(self.present_parents)
267
 
        needed_keys.difference_update(self.ghost_parents)
268
 
        self.needed_keys = needed_keys
269
 
        self.refcounts = refcounts
270
 
        return needed_keys, refcounts
271
 
 
272
 
    def _compute_diff(self, key, parent_lines, lines):
273
 
        """Compute a single mp_diff, and store it in self._diffs"""
274
 
        if len(parent_lines) > 0:
275
 
            # XXX: _extract_blocks is not usefully defined anywhere...
276
 
            #      It was meant to extract the left-parent diff without
277
 
            #      having to recompute it for Knit content (pack-0.92,
278
 
            #      etc). That seems to have regressed somewhere
279
 
            left_parent_blocks = self.vf._extract_blocks(key,
280
 
                parent_lines[0], lines)
281
 
        else:
282
 
            left_parent_blocks = None
283
 
        diff = multiparent.MultiParent.from_lines(lines,
284
 
                    parent_lines, left_parent_blocks)
285
 
        self.diffs[key] = diff
286
 
 
287
 
    def _process_one_record(self, key, this_chunks):
288
 
        parent_keys = None
289
 
        if key in self.parent_map:
290
 
            # This record should be ready to diff, since we requested
291
 
            # content in 'topological' order
292
 
            parent_keys = self.parent_map.pop(key)
293
 
            # If a VersionedFile claims 'no-graph' support, then it may return
294
 
            # None for any parent request, so we replace it with an empty tuple
295
 
            if parent_keys is None:
296
 
                parent_keys = ()
297
 
            parent_lines = []
298
 
            for p in parent_keys:
299
 
                # Alternatively we could check p not in self.needed_keys, but
300
 
                # ghost_parents should be tiny versus huge
301
 
                if p in self.ghost_parents:
302
 
                    continue
303
 
                refcount = self.refcounts[p]
304
 
                if refcount == 1: # Last child reference
305
 
                    self.refcounts.pop(p)
306
 
                    parent_chunks = self.chunks.pop(p)
307
 
                else:
308
 
                    self.refcounts[p] = refcount - 1
309
 
                    parent_chunks = self.chunks[p]
310
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
311
 
                # TODO: Should we cache the line form? We did the
312
 
                #       computation to get it, but storing it this way will
313
 
                #       be less memory efficient...
314
 
                parent_lines.append(p_lines)
315
 
                del p_lines
316
 
            lines = osutils.chunks_to_lines(this_chunks)
317
 
            # Since we needed the lines, we'll go ahead and cache them this way
318
 
            this_chunks = lines
319
 
            self._compute_diff(key, parent_lines, lines)
320
 
            del lines
321
 
        # Is this content required for any more children?
322
 
        if key in self.refcounts:
323
 
            self.chunks[key] = this_chunks
324
 
 
325
 
    def _extract_diffs(self):
326
 
        needed_keys, refcounts = self._find_needed_keys()
327
 
        for record in self.vf.get_record_stream(needed_keys,
328
 
                                                'topological', True):
329
 
            if record.storage_kind == 'absent':
330
 
                raise errors.RevisionNotPresent(record.key, self.vf)
331
 
            self._process_one_record(record.key,
332
 
                                     record.get_bytes_as('chunked'))
333
 
        
334
 
    def compute_diffs(self):
335
 
        self._extract_diffs()
336
 
        dpop = self.diffs.pop
337
 
        return [dpop(k) for k in self.ordered_keys]
338
 
 
339
 
 
340
198
class VersionedFile(object):
341
199
    """Versioned text file storage.
342
 
 
 
200
    
343
201
    A versioned file manages versions of line-based text files,
344
202
    keeping track of the originating version for each line.
345
203
 
383
241
    def insert_record_stream(self, stream):
384
242
        """Insert a record stream into this versioned file.
385
243
 
386
 
        :param stream: A stream of records to insert.
 
244
        :param stream: A stream of records to insert. 
387
245
        :return: None
388
246
        :seealso VersionedFile.get_record_stream:
389
247
        """
408
266
            the data back accurately. (Checking the lines have been split
409
267
            correctly is expensive and extremely unlikely to catch bugs so it
410
268
            is not done at runtime unless check_content is True.)
411
 
        :param parent_texts: An optional dictionary containing the opaque
 
269
        :param parent_texts: An optional dictionary containing the opaque 
412
270
            representations of some or all of the parents of version_id to
413
271
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
414
272
            returned by add_lines or data corruption can be caused.
442
300
        parent_texts=None, nostore_sha=None, random_id=False,
443
301
        check_content=True, left_matching_blocks=None):
444
302
        """Add lines to the versioned file, allowing ghosts to be present.
445
 
 
 
303
        
446
304
        This takes the same parameters as add_lines and returns the same.
447
305
        """
448
306
        self._check_write_ok()
472
330
 
473
331
    def get_format_signature(self):
474
332
        """Get a text description of the data encoding in this file.
475
 
 
 
333
        
476
334
        :since: 0.90
477
335
        """
478
336
        raise NotImplementedError(self.get_format_signature)
479
337
 
480
338
    def make_mpdiffs(self, version_ids):
481
339
        """Create multiparent diffs for specified versions."""
482
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
483
 
        #      is a list of strings, not keys. And while self.get_record_stream
484
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
485
 
        #      strings... *sigh*
486
340
        knit_versions = set()
487
341
        knit_versions.update(version_ids)
488
342
        parent_map = self.get_parent_map(version_ids)
603
457
        if isinstance(version_ids, basestring):
604
458
            version_ids = [version_ids]
605
459
        raise NotImplementedError(self.get_ancestry)
606
 
 
 
460
        
607
461
    def get_ancestry_with_ghosts(self, version_ids):
608
462
        """Return a list of all ancestors of given version(s). This
609
463
        will not include the null revision.
610
464
 
611
465
        Must raise RevisionNotPresent if any of the given versions are
612
466
        not present in file history.
613
 
 
 
467
        
614
468
        Ghosts that are known about will be included in ancestry list,
615
469
        but are not explicitly marked.
616
470
        """
617
471
        raise NotImplementedError(self.get_ancestry_with_ghosts)
618
 
 
 
472
    
619
473
    def get_parent_map(self, version_ids):
620
474
        """Get a map of the parents of version_ids.
621
475
 
684
538
        unchanged   Alive in both a and b (possibly created in both)
685
539
        new-a       Created in a
686
540
        new-b       Created in b
687
 
        ghost-a     Killed in a, unborn in b
 
541
        ghost-a     Killed in a, unborn in b    
688
542
        ghost-b     Killed in b, unborn in a
689
543
        irrelevant  Not in either revision
690
544
        """
691
545
        raise NotImplementedError(VersionedFile.plan_merge)
692
 
 
 
546
        
693
547
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
694
548
                    b_marker=TextMerge.B_MARKER):
695
549
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
697
551
 
698
552
class RecordingVersionedFilesDecorator(object):
699
553
    """A minimal versioned files that records calls made on it.
700
 
 
 
554
    
701
555
    Only enough methods have been added to support tests using it to date.
702
556
 
703
557
    :ivar calls: A list of the calls made; can be reset at any time by
706
560
 
707
561
    def __init__(self, backing_vf):
708
562
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
709
 
 
 
563
        
710
564
        :param backing_vf: The versioned file to answer all methods.
711
565
        """
712
566
        self._backing_vf = backing_vf
798
652
 
799
653
    def unmap(self, partition_id):
800
654
        """Map a partitioned storage id back to a key prefix.
801
 
 
 
655
        
802
656
        :param partition_id: The underlying partition id.
803
657
        :return: As much of a key (or prefix) as is derivable from the partition
804
658
            id.
836
690
 
837
691
class PrefixMapper(URLEscapeMapper):
838
692
    """A key mapper that extracts the first component of a key.
839
 
 
 
693
    
840
694
    This mapper is for use with a transport based backend.
841
695
    """
842
696
 
875
729
 
876
730
class HashEscapedPrefixMapper(HashPrefixMapper):
877
731
    """Combines the escaped first component of a key with a hash.
878
 
 
 
732
    
879
733
    This mapper is for use with a transport based backend.
880
734
    """
881
735
 
937
791
        check_content=True):
938
792
        """Add a text to the store.
939
793
 
940
 
        :param key: The key tuple of the text to add. If the last element is
941
 
            None, a CHK string will be generated during the addition.
 
794
        :param key: The key tuple of the text to add.
942
795
        :param parents: The parents key tuples of the text to add.
943
796
        :param lines: A list of lines. Each line must be a bytestring. And all
944
797
            of them except the last must be terminated with \n and contain no
948
801
            the data back accurately. (Checking the lines have been split
949
802
            correctly is expensive and extremely unlikely to catch bugs so it
950
803
            is not done at runtime unless check_content is True.)
951
 
        :param parent_texts: An optional dictionary containing the opaque
 
804
        :param parent_texts: An optional dictionary containing the opaque 
952
805
            representations of some or all of the parents of version_id to
953
806
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
954
807
            returned by add_lines or data corruption can be caused.
971
824
        """
972
825
        raise NotImplementedError(self.add_lines)
973
826
 
974
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
975
 
        """Add a text to the store.
976
 
 
977
 
        This is a private function for use by CommitBuilder.
978
 
 
979
 
        :param key: The key tuple of the text to add. If the last element is
980
 
            None, a CHK string will be generated during the addition.
981
 
        :param parents: The parents key tuples of the text to add.
982
 
        :param text: A string containing the text to be committed.
983
 
        :param nostore_sha: Raise ExistingContent and do not add the lines to
984
 
            the versioned file if the digest of the lines matches this.
985
 
        :param random_id: If True a random id has been selected rather than
986
 
            an id determined by some deterministic process such as a converter
987
 
            from a foreign VCS. When True the backend may choose not to check
988
 
            for uniqueness of the resulting key within the versioned file, so
989
 
            this should only be done when the result is expected to be unique
990
 
            anyway.
991
 
        :param check_content: If True, the lines supplied are verified to be
992
 
            bytestrings that are correctly formed lines.
993
 
        :return: The text sha1, the number of bytes in the text, and an opaque
994
 
                 representation of the inserted version which can be provided
995
 
                 back to future _add_text calls in the parent_texts dictionary.
996
 
        """
997
 
        # The default implementation just thunks over to .add_lines(),
998
 
        # inefficient, but it works.
999
 
        return self.add_lines(key, parents, osutils.split_lines(text),
1000
 
                              nostore_sha=nostore_sha,
1001
 
                              random_id=random_id,
1002
 
                              check_content=True)
1003
 
 
1004
827
    def add_mpdiffs(self, records):
1005
828
        """Add mpdiffs to this VersionedFile.
1006
829
 
1048
871
        raise NotImplementedError(self.annotate)
1049
872
 
1050
873
    def check(self, progress_bar=None):
1051
 
        """Check this object for integrity.
1052
 
        
1053
 
        :param progress_bar: A progress bar to output as the check progresses.
1054
 
        :param keys: Specific keys within the VersionedFiles to check. When
1055
 
            this parameter is not None, check() becomes a generator as per
1056
 
            get_record_stream. The difference to get_record_stream is that
1057
 
            more or deeper checks will be performed.
1058
 
        :return: None, or if keys was supplied a generator as per
1059
 
            get_record_stream.
1060
 
        """
 
874
        """Check this object for integrity."""
1061
875
        raise NotImplementedError(self.check)
1062
876
 
1063
877
    @staticmethod
1064
878
    def check_not_reserved_id(version_id):
1065
879
        revision.check_not_reserved_id(version_id)
1066
880
 
1067
 
    def clear_cache(self):
1068
 
        """Clear whatever caches this VersionedFile holds.
1069
 
 
1070
 
        This is generally called after an operation has been performed, when we
1071
 
        don't expect to be using this versioned file again soon.
1072
 
        """
1073
 
 
1074
881
    def _check_lines_not_unicode(self, lines):
1075
882
        """Check that lines being added to a versioned file are not unicode."""
1076
883
        for line in lines:
1083
890
            if '\n' in line[:-1]:
1084
891
                raise errors.BzrBadParameterContainsNewline("lines")
1085
892
 
1086
 
    def get_known_graph_ancestry(self, keys):
1087
 
        """Get a KnownGraph instance with the ancestry of keys."""
1088
 
        # most basic implementation is a loop around get_parent_map
1089
 
        pending = set(keys)
1090
 
        parent_map = {}
1091
 
        while pending:
1092
 
            this_parent_map = self.get_parent_map(pending)
1093
 
            parent_map.update(this_parent_map)
1094
 
            pending = set()
1095
 
            map(pending.update, this_parent_map.itervalues())
1096
 
            pending = pending.difference(parent_map)
1097
 
        kg = _mod_graph.KnownGraph(parent_map)
1098
 
        return kg
1099
 
 
1100
893
    def get_parent_map(self, keys):
1101
894
        """Get a map of the parents of keys.
1102
895
 
1132
925
 
1133
926
    has_key = index._has_key_from_parent_map
1134
927
 
1135
 
    def get_missing_compression_parent_keys(self):
1136
 
        """Return an iterable of keys of missing compression parents.
1137
 
 
1138
 
        Check this after calling insert_record_stream to find out if there are
1139
 
        any missing compression parents.  If there are, the records that
1140
 
        depend on them are not able to be inserted safely. The precise
1141
 
        behaviour depends on the concrete VersionedFiles class in use.
1142
 
 
1143
 
        Classes that do not support this will raise NotImplementedError.
1144
 
        """
1145
 
        raise NotImplementedError(self.get_missing_compression_parent_keys)
1146
 
 
1147
928
    def insert_record_stream(self, stream):
1148
929
        """Insert a record stream into this container.
1149
930
 
1150
 
        :param stream: A stream of records to insert.
 
931
        :param stream: A stream of records to insert. 
1151
932
        :return: None
1152
933
        :seealso VersionedFile.get_record_stream:
1153
934
        """
1182
963
 
1183
964
    def make_mpdiffs(self, keys):
1184
965
        """Create multiparent diffs for specified keys."""
1185
 
        generator = _MPDiffGenerator(self, keys)
1186
 
        return generator.compute_diffs()
1187
 
 
1188
 
    def get_annotator(self):
1189
 
        return annotate.Annotator(self)
 
966
        keys_order = tuple(keys)
 
967
        keys = frozenset(keys)
 
968
        knit_keys = set(keys)
 
969
        parent_map = self.get_parent_map(keys)
 
970
        for parent_keys in parent_map.itervalues():
 
971
            if parent_keys:
 
972
                knit_keys.update(parent_keys)
 
973
        missing_keys = keys - set(parent_map)
 
974
        if missing_keys:
 
975
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
976
        # We need to filter out ghosts, because we can't diff against them.
 
977
        maybe_ghosts = knit_keys - keys
 
978
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
979
        knit_keys.difference_update(ghosts)
 
980
        lines = {}
 
981
        chunks_to_lines = osutils.chunks_to_lines
 
982
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
983
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
984
            # line_block_dict = {}
 
985
            # for parent, blocks in record.extract_line_blocks():
 
986
            #   line_blocks[parent] = blocks
 
987
            # line_blocks[record.key] = line_block_dict
 
988
        diffs = []
 
989
        for key in keys_order:
 
990
            target = lines[key]
 
991
            parents = parent_map[key] or []
 
992
            # Note that filtering knit_keys can lead to a parent difference
 
993
            # between the creation and the application of the mpdiff.
 
994
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
995
            if len(parent_lines) > 0:
 
996
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
997
                    target)
 
998
            else:
 
999
                left_parent_blocks = None
 
1000
            diffs.append(multiparent.MultiParent.from_lines(target,
 
1001
                parent_lines, left_parent_blocks))
 
1002
        return diffs
1190
1003
 
1191
1004
    missing_keys = index._missing_keys_from_parent_map
1192
1005
 
1262
1075
            result.append((prefix + (origin,), line))
1263
1076
        return result
1264
1077
 
1265
 
    def check(self, progress_bar=None, keys=None):
 
1078
    def check(self, progress_bar=None):
1266
1079
        """See VersionedFiles.check()."""
1267
 
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1268
 
        # this is tolerable. Ideally we'd pass keys down to check() and 
1269
 
        # have the older VersiondFile interface updated too.
1270
1080
        for prefix, vf in self._iter_all_components():
1271
1081
            vf.check()
1272
 
        if keys is not None:
1273
 
            return self.get_record_stream(keys, 'unordered', True)
1274
1082
 
1275
1083
    def get_parent_map(self, keys):
1276
1084
        """Get a map of the parents of keys.
1354
1162
    def insert_record_stream(self, stream):
1355
1163
        """Insert a record stream into this container.
1356
1164
 
1357
 
        :param stream: A stream of records to insert.
 
1165
        :param stream: A stream of records to insert. 
1358
1166
        :return: None
1359
1167
        :seealso VersionedFile.get_record_stream:
1360
1168
        """
1508
1316
            result[revision.NULL_REVISION] = ()
1509
1317
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1510
1318
        result.update(
1511
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1319
            _StackedParentsProvider(self._providers).get_parent_map(keys))
1512
1320
        for key, parents in result.iteritems():
1513
1321
            if parents == ():
1514
1322
                result[key] = (revision.NULL_REVISION,)
1517
1325
 
1518
1326
class PlanWeaveMerge(TextMerge):
1519
1327
    """Weave merge that takes a plan as its input.
1520
 
 
 
1328
    
1521
1329
    This exists so that VersionedFile.plan_merge is implementable.
1522
1330
    Most callers will want to use WeaveMerge instead.
1523
1331
    """
1525
1333
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1526
1334
                 b_marker=TextMerge.B_MARKER):
1527
1335
        TextMerge.__init__(self, a_marker, b_marker)
1528
 
        self.plan = list(plan)
 
1336
        self.plan = plan
1529
1337
 
1530
1338
    def _merge_struct(self):
1531
1339
        lines_a = []
1544
1352
                yield(lines_a,)
1545
1353
            else:
1546
1354
                yield (lines_a, lines_b)
1547
 
 
 
1355
       
1548
1356
        # We previously considered either 'unchanged' or 'killed-both' lines
1549
1357
        # to be possible places to resynchronize.  However, assuming agreement
1550
1358
        # on killed-both lines may be too aggressive. -- mbp 20060324
1556
1364
                lines_a = []
1557
1365
                lines_b = []
1558
1366
                ch_a = ch_b = False
1559
 
 
 
1367
                
1560
1368
            if state == 'unchanged':
1561
1369
                if line:
1562
1370
                    yield ([line],)
1578
1386
            elif state == 'conflicted-b':
1579
1387
                ch_b = ch_a = True
1580
1388
                lines_b.append(line)
1581
 
            elif state == 'killed-both':
1582
 
                # This counts as a change, even though there is no associated
1583
 
                # line
1584
 
                ch_b = ch_a = True
1585
1389
            else:
1586
1390
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1587
 
                        'killed-base'):
 
1391
                        'killed-base', 'killed-both'):
1588
1392
                    raise AssertionError(state)
1589
1393
        for struct in outstanding_struct():
1590
1394
            yield struct
1591
1395
 
1592
 
    def base_from_plan(self):
1593
 
        """Construct a BASE file from the plan text."""
1594
 
        base_lines = []
1595
 
        for state, line in self.plan:
1596
 
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
1597
 
                # If unchanged, then this line is straight from base. If a or b
1598
 
                # or both killed the line, then it *used* to be in base.
1599
 
                base_lines.append(line)
1600
 
            else:
1601
 
                if state not in ('killed-base', 'irrelevant',
1602
 
                                 'ghost-a', 'ghost-b',
1603
 
                                 'new-a', 'new-b',
1604
 
                                 'conflicted-a', 'conflicted-b'):
1605
 
                    # killed-base, irrelevant means it doesn't apply
1606
 
                    # ghost-a/ghost-b are harder to say for sure, but they
1607
 
                    # aren't in the 'inc_c' which means they aren't in the
1608
 
                    # shared base of a & b. So we don't include them.  And
1609
 
                    # obviously if the line is newly inserted, it isn't in base
1610
 
 
1611
 
                    # If 'conflicted-a' or b, then it is new vs one base, but
1612
 
                    # old versus another base. However, if we make it present
1613
 
                    # in the base, it will be deleted from the target, and it
1614
 
                    # seems better to get a line doubled in the merge result,
1615
 
                    # rather than have it deleted entirely.
1616
 
                    # Example, each node is the 'text' at that point:
1617
 
                    #           MN
1618
 
                    #          /   \
1619
 
                    #        MaN   MbN
1620
 
                    #         |  X  |
1621
 
                    #        MabN MbaN
1622
 
                    #          \   /
1623
 
                    #           ???
1624
 
                    # There was a criss-cross conflict merge. Both sides
1625
 
                    # include the other, but put themselves first.
1626
 
                    # Weave marks this as a 'clean' merge, picking OTHER over
1627
 
                    # THIS. (Though the details depend on order inserted into
1628
 
                    # weave, etc.)
1629
 
                    # LCA generates a plan:
1630
 
                    # [('unchanged', M),
1631
 
                    #  ('conflicted-b', b),
1632
 
                    #  ('unchanged', a),
1633
 
                    #  ('conflicted-a', b),
1634
 
                    #  ('unchanged', N)]
1635
 
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
1636
 
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
1637
 
                    # removes one 'b', and BASE vs OTHER removes the other)
1638
 
                    # If you include neither, 3-way creates a clean "MbabN" as
1639
 
                    # THIS adds one 'b', and OTHER does too.
1640
 
                    # It seems that having the line 2 times is better than
1641
 
                    # having it omitted. (Easier to manually delete than notice
1642
 
                    # it needs to be added.)
1643
 
                    raise AssertionError('Unknown state: %s' % (state,))
1644
 
        return base_lines
1645
 
 
1646
1396
 
1647
1397
class WeaveMerge(PlanWeaveMerge):
1648
1398
    """Weave merge that takes a VersionedFile and two versions as its input."""
1649
1399
 
1650
 
    def __init__(self, versionedfile, ver_a, ver_b,
 
1400
    def __init__(self, versionedfile, ver_a, ver_b, 
1651
1401
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1652
1402
        plan = versionedfile.plan_merge(ver_a, ver_b)
1653
1403
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1654
1404
 
1655
1405
 
1656
1406
class VirtualVersionedFiles(VersionedFiles):
1657
 
    """Dummy implementation for VersionedFiles that uses other functions for
 
1407
    """Dummy implementation for VersionedFiles that uses other functions for 
1658
1408
    obtaining fulltexts and parent maps.
1659
1409
 
1660
 
    This is always on the bottom of the stack and uses string keys
 
1410
    This is always on the bottom of the stack and uses string keys 
1661
1411
    (rather than tuples) internally.
1662
1412
    """
1663
1413
 
1665
1415
        """Create a VirtualVersionedFiles.
1666
1416
 
1667
1417
        :param get_parent_map: Same signature as Repository.get_parent_map.
1668
 
        :param get_lines: Should return lines for specified key or None if
 
1418
        :param get_lines: Should return lines for specified key or None if 
1669
1419
                          not available.
1670
1420
        """
1671
1421
        super(VirtualVersionedFiles, self).__init__()
1672
1422
        self._get_parent_map = get_parent_map
1673
1423
        self._get_lines = get_lines
1674
 
 
 
1424
        
1675
1425
    def check(self, progressbar=None):
1676
1426
        """See VersionedFiles.check.
1677
1427
 
1719
1469
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1720
1470
        for i, (key,) in enumerate(keys):
1721
1471
            if pb is not None:
1722
 
                pb.update("Finding changed lines", i, len(keys))
 
1472
                pb.update("iterating texts", i, len(keys))
1723
1473
            for l in self._get_lines(key):
1724
1474
                yield (l, key)
1725
 
 
1726
 
 
1727
 
class NoDupeAddLinesDecorator(object):
1728
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1729
 
    is already present.
1730
 
    """
1731
 
 
1732
 
    def __init__(self, store):
1733
 
        self._store = store
1734
 
 
1735
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1736
 
            left_matching_blocks=None, nostore_sha=None, random_id=False,
1737
 
            check_content=True):
1738
 
        """See VersionedFiles.add_lines.
1739
 
        
1740
 
        This implementation may return None as the third element of the return
1741
 
        value when the original store wouldn't.
1742
 
        """
1743
 
        if nostore_sha:
1744
 
            raise NotImplementedError(
1745
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1746
 
                "nostore_sha behaviour.")
1747
 
        if key[-1] is None:
1748
 
            sha1 = osutils.sha_strings(lines)
1749
 
            key = ("sha1:" + sha1,)
1750
 
        else:
1751
 
            sha1 = None
1752
 
        if key in self._store.get_parent_map([key]):
1753
 
            # This key has already been inserted, so don't do it again.
1754
 
            if sha1 is None:
1755
 
                sha1 = osutils.sha_strings(lines)
1756
 
            return sha1, sum(map(len, lines)), None
1757
 
        return self._store.add_lines(key, parents, lines,
1758
 
                parent_texts=parent_texts,
1759
 
                left_matching_blocks=left_matching_blocks,
1760
 
                nostore_sha=nostore_sha, random_id=random_id,
1761
 
                check_content=check_content)
1762
 
 
1763
 
    def __getattr__(self, name):
1764
 
        return getattr(self._store, name)
1765
 
 
1766
 
 
1767
 
def network_bytes_to_kind_and_offset(network_bytes):
1768
 
    """Strip of a record kind from the front of network_bytes.
1769
 
 
1770
 
    :param network_bytes: The bytes of a record.
1771
 
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1772
 
    """
1773
 
    line_end = network_bytes.find('\n')
1774
 
    storage_kind = network_bytes[:line_end]
1775
 
    return storage_kind, line_end + 1
1776
 
 
1777
 
 
1778
 
class NetworkRecordStream(object):
1779
 
    """A record_stream which reconstitures a serialised stream."""
1780
 
 
1781
 
    def __init__(self, bytes_iterator):
1782
 
        """Create a NetworkRecordStream.
1783
 
 
1784
 
        :param bytes_iterator: An iterator of bytes. Each item in this
1785
 
            iterator should have been obtained from a record_streams'
1786
 
            record.get_bytes_as(record.storage_kind) call.
1787
 
        """
1788
 
        self._bytes_iterator = bytes_iterator
1789
 
        self._kind_factory = {
1790
 
            'fulltext': fulltext_network_to_record,
1791
 
            'groupcompress-block': groupcompress.network_block_to_records,
1792
 
            'knit-ft-gz': knit.knit_network_to_record,
1793
 
            'knit-delta-gz': knit.knit_network_to_record,
1794
 
            'knit-annotated-ft-gz': knit.knit_network_to_record,
1795
 
            'knit-annotated-delta-gz': knit.knit_network_to_record,
1796
 
            'knit-delta-closure': knit.knit_delta_closure_to_records,
1797
 
            }
1798
 
 
1799
 
    def read(self):
1800
 
        """Read the stream.
1801
 
 
1802
 
        :return: An iterator as per VersionedFiles.get_record_stream().
1803
 
        """
1804
 
        for bytes in self._bytes_iterator:
1805
 
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1806
 
            for record in self._kind_factory[storage_kind](
1807
 
                storage_kind, bytes, line_end):
1808
 
                yield record
1809
 
 
1810
 
 
1811
 
def fulltext_network_to_record(kind, bytes, line_end):
1812
 
    """Convert a network fulltext record to record."""
1813
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
1814
 
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1815
 
    key, parents = bencode.bdecode_as_tuple(record_meta)
1816
 
    if parents == 'nil':
1817
 
        parents = None
1818
 
    fulltext = bytes[line_end+4+meta_len:]
1819
 
    return [FulltextContentFactory(key, parents, None, fulltext)]
1820
 
 
1821
 
 
1822
 
def _length_prefix(bytes):
1823
 
    return struct.pack('!L', len(bytes))
1824
 
 
1825
 
 
1826
 
def record_to_fulltext_bytes(record):
1827
 
    if record.parents is None:
1828
 
        parents = 'nil'
1829
 
    else:
1830
 
        parents = record.parents
1831
 
    record_meta = bencode.bencode((record.key, parents))
1832
 
    record_content = record.get_bytes_as('fulltext')
1833
 
    return "fulltext\n%s%s%s" % (
1834
 
        _length_prefix(record_meta), record_meta, record_content)
1835
 
 
1836
 
 
1837
 
def sort_groupcompress(parent_map):
1838
 
    """Sort and group the keys in parent_map into groupcompress order.
1839
 
 
1840
 
    groupcompress is defined (currently) as reverse-topological order, grouped
1841
 
    by the key prefix.
1842
 
 
1843
 
    :return: A sorted-list of keys
1844
 
    """
1845
 
    # gc-optimal ordering is approximately reverse topological,
1846
 
    # properly grouped by file-id.
1847
 
    per_prefix_map = {}
1848
 
    for item in parent_map.iteritems():
1849
 
        key = item[0]
1850
 
        if isinstance(key, str) or len(key) == 1:
1851
 
            prefix = ''
1852
 
        else:
1853
 
            prefix = key[0]
1854
 
        try:
1855
 
            per_prefix_map[prefix].append(item)
1856
 
        except KeyError:
1857
 
            per_prefix_map[prefix] = [item]
1858
 
 
1859
 
    present_keys = []
1860
 
    for prefix in sorted(per_prefix_map):
1861
 
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1862
 
    return present_keys