~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
 
#
3
 
# Authors:
4
 
#   Johan Rydberg <jrydberg@gnu.org>
 
1
# Copyright (C) 2006-2011 Canonical Ltd
5
2
#
6
3
# This program is free software; you can redistribute it and/or modify
7
4
# it under the terms of the GNU General Public License as published by
15
12
#
16
13
# You should have received a copy of the GNU General Public License
17
14
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
16
 
20
17
"""Versioned text file storage api."""
21
18
 
 
19
from __future__ import absolute_import
 
20
 
22
21
from copy import copy
23
22
from cStringIO import StringIO
24
23
import os
 
24
import struct
25
25
from zlib import adler32
26
26
 
27
27
from bzrlib.lazy_import import lazy_import
28
28
lazy_import(globals(), """
29
 
import urllib
30
 
 
31
29
from bzrlib import (
 
30
    annotate,
 
31
    bencode,
32
32
    errors,
 
33
    graph as _mod_graph,
 
34
    groupcompress,
33
35
    index,
 
36
    knit,
34
37
    osutils,
35
38
    multiparent,
36
39
    tsort,
37
40
    revision,
38
 
    ui,
 
41
    urlutils,
39
42
    )
40
 
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
41
 
from bzrlib.transport.memory import MemoryTransport
42
43
""")
43
 
from bzrlib.inter import InterObject
44
44
from bzrlib.registry import Registry
45
 
from bzrlib.symbol_versioning import *
46
45
from bzrlib.textmerge import TextMerge
47
46
 
48
47
 
65
64
 
66
65
class ContentFactory(object):
67
66
    """Abstract interface for insertion and retrieval from a VersionedFile.
68
 
    
 
67
 
69
68
    :ivar sha1: None, or the sha1 of the content fulltext.
70
69
    :ivar storage_kind: The native storage kind of this factory. One of
71
70
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
155
154
 
156
155
class AbsentContentFactory(ContentFactory):
157
156
    """A placeholder content factory for unavailable texts.
158
 
    
 
157
 
159
158
    :ivar sha1: None.
160
159
    :ivar storage_kind: 'absent'.
161
160
    :ivar key: The key of this content. Each key is a tuple with a single
170
169
        self.key = key
171
170
        self.parents = None
172
171
 
 
172
    def get_bytes_as(self, storage_kind):
 
173
        raise ValueError('A request was made for key: %s, but that'
 
174
                         ' content is not available, and the calling'
 
175
                         ' code does not handle if it is missing.'
 
176
                         % (self.key,))
 
177
 
173
178
 
174
179
class AdapterFactory(ContentFactory):
175
180
    """A content factory to adapt between key prefix's."""
195
200
            yield record
196
201
 
197
202
 
 
203
class _MPDiffGenerator(object):
 
204
    """Pull out the functionality for generating mp_diffs."""
 
205
 
 
206
    def __init__(self, vf, keys):
 
207
        self.vf = vf
 
208
        # This is the order the keys were requested in
 
209
        self.ordered_keys = tuple(keys)
 
210
        # keys + their parents, what we need to compute the diffs
 
211
        self.needed_keys = ()
 
212
        # Map from key: mp_diff
 
213
        self.diffs = {}
 
214
        # Map from key: parents_needed (may have ghosts)
 
215
        self.parent_map = {}
 
216
        # Parents that aren't present
 
217
        self.ghost_parents = ()
 
218
        # Map from parent_key => number of children for this text
 
219
        self.refcounts = {}
 
220
        # Content chunks that are cached while we still need them
 
221
        self.chunks = {}
 
222
 
 
223
    def _find_needed_keys(self):
 
224
        """Find the set of keys we need to request.
 
225
 
 
226
        This includes all the original keys passed in, and the non-ghost
 
227
        parents of those keys.
 
228
 
 
229
        :return: (needed_keys, refcounts)
 
230
            needed_keys is the set of all texts we need to extract
 
231
            refcounts is a dict of {key: num_children} letting us know when we
 
232
                no longer need to cache a given parent text
 
233
        """
 
234
        # All the keys and their parents
 
235
        needed_keys = set(self.ordered_keys)
 
236
        parent_map = self.vf.get_parent_map(needed_keys)
 
237
        self.parent_map = parent_map
 
238
        # TODO: Should we be using a different construct here? I think this
 
239
        #       uses difference_update internally, and we expect the result to
 
240
        #       be tiny
 
241
        missing_keys = needed_keys.difference(parent_map)
 
242
        if missing_keys:
 
243
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
244
        # Parents that might be missing. They are allowed to be ghosts, but we
 
245
        # should check for them
 
246
        refcounts = {}
 
247
        setdefault = refcounts.setdefault
 
248
        just_parents = set()
 
249
        for child_key, parent_keys in parent_map.iteritems():
 
250
            if not parent_keys:
 
251
                # parent_keys may be None if a given VersionedFile claims to
 
252
                # not support graph operations.
 
253
                continue
 
254
            just_parents.update(parent_keys)
 
255
            needed_keys.update(parent_keys)
 
256
            for p in parent_keys:
 
257
                refcounts[p] = setdefault(p, 0) + 1
 
258
        just_parents.difference_update(parent_map)
 
259
        # Remove any parents that are actually ghosts from the needed set
 
260
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
261
        self.ghost_parents = just_parents.difference(self.present_parents)
 
262
        needed_keys.difference_update(self.ghost_parents)
 
263
        self.needed_keys = needed_keys
 
264
        self.refcounts = refcounts
 
265
        return needed_keys, refcounts
 
266
 
 
267
    def _compute_diff(self, key, parent_lines, lines):
 
268
        """Compute a single mp_diff, and store it in self._diffs"""
 
269
        if len(parent_lines) > 0:
 
270
            # XXX: _extract_blocks is not usefully defined anywhere...
 
271
            #      It was meant to extract the left-parent diff without
 
272
            #      having to recompute it for Knit content (pack-0.92,
 
273
            #      etc). That seems to have regressed somewhere
 
274
            left_parent_blocks = self.vf._extract_blocks(key,
 
275
                parent_lines[0], lines)
 
276
        else:
 
277
            left_parent_blocks = None
 
278
        diff = multiparent.MultiParent.from_lines(lines,
 
279
                    parent_lines, left_parent_blocks)
 
280
        self.diffs[key] = diff
 
281
 
 
282
    def _process_one_record(self, key, this_chunks):
 
283
        parent_keys = None
 
284
        if key in self.parent_map:
 
285
            # This record should be ready to diff, since we requested
 
286
            # content in 'topological' order
 
287
            parent_keys = self.parent_map.pop(key)
 
288
            # If a VersionedFile claims 'no-graph' support, then it may return
 
289
            # None for any parent request, so we replace it with an empty tuple
 
290
            if parent_keys is None:
 
291
                parent_keys = ()
 
292
            parent_lines = []
 
293
            for p in parent_keys:
 
294
                # Alternatively we could check p not in self.needed_keys, but
 
295
                # ghost_parents should be tiny versus huge
 
296
                if p in self.ghost_parents:
 
297
                    continue
 
298
                refcount = self.refcounts[p]
 
299
                if refcount == 1: # Last child reference
 
300
                    self.refcounts.pop(p)
 
301
                    parent_chunks = self.chunks.pop(p)
 
302
                else:
 
303
                    self.refcounts[p] = refcount - 1
 
304
                    parent_chunks = self.chunks[p]
 
305
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
306
                # TODO: Should we cache the line form? We did the
 
307
                #       computation to get it, but storing it this way will
 
308
                #       be less memory efficient...
 
309
                parent_lines.append(p_lines)
 
310
                del p_lines
 
311
            lines = osutils.chunks_to_lines(this_chunks)
 
312
            # Since we needed the lines, we'll go ahead and cache them this way
 
313
            this_chunks = lines
 
314
            self._compute_diff(key, parent_lines, lines)
 
315
            del lines
 
316
        # Is this content required for any more children?
 
317
        if key in self.refcounts:
 
318
            self.chunks[key] = this_chunks
 
319
 
 
320
    def _extract_diffs(self):
 
321
        needed_keys, refcounts = self._find_needed_keys()
 
322
        for record in self.vf.get_record_stream(needed_keys,
 
323
                                                'topological', True):
 
324
            if record.storage_kind == 'absent':
 
325
                raise errors.RevisionNotPresent(record.key, self.vf)
 
326
            self._process_one_record(record.key,
 
327
                                     record.get_bytes_as('chunked'))
 
328
        
 
329
    def compute_diffs(self):
 
330
        self._extract_diffs()
 
331
        dpop = self.diffs.pop
 
332
        return [dpop(k) for k in self.ordered_keys]
 
333
 
 
334
 
198
335
class VersionedFile(object):
199
336
    """Versioned text file storage.
200
 
    
 
337
 
201
338
    A versioned file manages versions of line-based text files,
202
339
    keeping track of the originating version for each line.
203
340
 
241
378
    def insert_record_stream(self, stream):
242
379
        """Insert a record stream into this versioned file.
243
380
 
244
 
        :param stream: A stream of records to insert. 
 
381
        :param stream: A stream of records to insert.
245
382
        :return: None
246
383
        :seealso VersionedFile.get_record_stream:
247
384
        """
266
403
            the data back accurately. (Checking the lines have been split
267
404
            correctly is expensive and extremely unlikely to catch bugs so it
268
405
            is not done at runtime unless check_content is True.)
269
 
        :param parent_texts: An optional dictionary containing the opaque 
 
406
        :param parent_texts: An optional dictionary containing the opaque
270
407
            representations of some or all of the parents of version_id to
271
408
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
272
409
            returned by add_lines or data corruption can be caused.
300
437
        parent_texts=None, nostore_sha=None, random_id=False,
301
438
        check_content=True, left_matching_blocks=None):
302
439
        """Add lines to the versioned file, allowing ghosts to be present.
303
 
        
 
440
 
304
441
        This takes the same parameters as add_lines and returns the same.
305
442
        """
306
443
        self._check_write_ok()
330
467
 
331
468
    def get_format_signature(self):
332
469
        """Get a text description of the data encoding in this file.
333
 
        
 
470
 
334
471
        :since: 0.90
335
472
        """
336
473
        raise NotImplementedError(self.get_format_signature)
337
474
 
338
475
    def make_mpdiffs(self, version_ids):
339
476
        """Create multiparent diffs for specified versions."""
 
477
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
478
        #      is a list of strings, not keys. And while self.get_record_stream
 
479
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
480
        #      strings... *sigh*
340
481
        knit_versions = set()
341
482
        knit_versions.update(version_ids)
342
483
        parent_map = self.get_parent_map(version_ids)
457
598
        if isinstance(version_ids, basestring):
458
599
            version_ids = [version_ids]
459
600
        raise NotImplementedError(self.get_ancestry)
460
 
        
 
601
 
461
602
    def get_ancestry_with_ghosts(self, version_ids):
462
603
        """Return a list of all ancestors of given version(s). This
463
604
        will not include the null revision.
464
605
 
465
606
        Must raise RevisionNotPresent if any of the given versions are
466
607
        not present in file history.
467
 
        
 
608
 
468
609
        Ghosts that are known about will be included in ancestry list,
469
610
        but are not explicitly marked.
470
611
        """
471
612
        raise NotImplementedError(self.get_ancestry_with_ghosts)
472
 
    
 
613
 
473
614
    def get_parent_map(self, version_ids):
474
615
        """Get a map of the parents of version_ids.
475
616
 
538
679
        unchanged   Alive in both a and b (possibly created in both)
539
680
        new-a       Created in a
540
681
        new-b       Created in b
541
 
        ghost-a     Killed in a, unborn in b    
 
682
        ghost-a     Killed in a, unborn in b
542
683
        ghost-b     Killed in b, unborn in a
543
684
        irrelevant  Not in either revision
544
685
        """
545
686
        raise NotImplementedError(VersionedFile.plan_merge)
546
 
        
 
687
 
547
688
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
548
689
                    b_marker=TextMerge.B_MARKER):
549
690
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
551
692
 
552
693
class RecordingVersionedFilesDecorator(object):
553
694
    """A minimal versioned files that records calls made on it.
554
 
    
 
695
 
555
696
    Only enough methods have been added to support tests using it to date.
556
697
 
557
698
    :ivar calls: A list of the calls made; can be reset at any time by
560
701
 
561
702
    def __init__(self, backing_vf):
562
703
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
563
 
        
 
704
 
564
705
        :param backing_vf: The versioned file to answer all methods.
565
706
        """
566
707
        self._backing_vf = backing_vf
652
793
 
653
794
    def unmap(self, partition_id):
654
795
        """Map a partitioned storage id back to a key prefix.
655
 
        
 
796
 
656
797
        :param partition_id: The underlying partition id.
657
798
        :return: As much of a key (or prefix) as is derivable from the partition
658
799
            id.
681
822
 
682
823
    def map(self, key):
683
824
        """See KeyMapper.map()."""
684
 
        return urllib.quote(self._map(key))
 
825
        return urlutils.quote(self._map(key))
685
826
 
686
827
    def unmap(self, partition_id):
687
828
        """See KeyMapper.unmap()."""
688
 
        return self._unmap(urllib.unquote(partition_id))
 
829
        return self._unmap(urlutils.unquote(partition_id))
689
830
 
690
831
 
691
832
class PrefixMapper(URLEscapeMapper):
692
833
    """A key mapper that extracts the first component of a key.
693
 
    
 
834
 
694
835
    This mapper is for use with a transport based backend.
695
836
    """
696
837
 
729
870
 
730
871
class HashEscapedPrefixMapper(HashPrefixMapper):
731
872
    """Combines the escaped first component of a key with a hash.
732
 
    
 
873
 
733
874
    This mapper is for use with a transport based backend.
734
875
    """
735
876
 
738
879
    def _escape(self, prefix):
739
880
        """Turn a key element into a filesystem safe string.
740
881
 
741
 
        This is similar to a plain urllib.quote, except
 
882
        This is similar to a plain urlutils.quote, except
742
883
        it uses specific safe characters, so that it doesn't
743
884
        have to translate a lot of valid file ids.
744
885
        """
751
892
 
752
893
    def _unescape(self, basename):
753
894
        """Escaped names are easily unescaped by urlutils."""
754
 
        return urllib.unquote(basename)
 
895
        return urlutils.unquote(basename)
755
896
 
756
897
 
757
898
def make_versioned_files_factory(versioned_file_factory, mapper):
784
925
 
785
926
    The use of tuples allows a single code base to support several different
786
927
    uses with only the mapping logic changing from instance to instance.
 
928
 
 
929
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
930
        this is a list of other VersionedFiles immediately underneath this
 
931
        one.  They may in turn each have further fallbacks.
787
932
    """
788
933
 
789
934
    def add_lines(self, key, parents, lines, parent_texts=None,
791
936
        check_content=True):
792
937
        """Add a text to the store.
793
938
 
794
 
        :param key: The key tuple of the text to add.
 
939
        :param key: The key tuple of the text to add. If the last element is
 
940
            None, a CHK string will be generated during the addition.
795
941
        :param parents: The parents key tuples of the text to add.
796
942
        :param lines: A list of lines. Each line must be a bytestring. And all
797
943
            of them except the last must be terminated with \n and contain no
801
947
            the data back accurately. (Checking the lines have been split
802
948
            correctly is expensive and extremely unlikely to catch bugs so it
803
949
            is not done at runtime unless check_content is True.)
804
 
        :param parent_texts: An optional dictionary containing the opaque 
 
950
        :param parent_texts: An optional dictionary containing the opaque
805
951
            representations of some or all of the parents of version_id to
806
952
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
807
953
            returned by add_lines or data corruption can be caused.
824
970
        """
825
971
        raise NotImplementedError(self.add_lines)
826
972
 
 
973
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
974
        """Add a text to the store.
 
975
 
 
976
        This is a private function for use by VersionedFileCommitBuilder.
 
977
 
 
978
        :param key: The key tuple of the text to add. If the last element is
 
979
            None, a CHK string will be generated during the addition.
 
980
        :param parents: The parents key tuples of the text to add.
 
981
        :param text: A string containing the text to be committed.
 
982
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
983
            the versioned file if the digest of the lines matches this.
 
984
        :param random_id: If True a random id has been selected rather than
 
985
            an id determined by some deterministic process such as a converter
 
986
            from a foreign VCS. When True the backend may choose not to check
 
987
            for uniqueness of the resulting key within the versioned file, so
 
988
            this should only be done when the result is expected to be unique
 
989
            anyway.
 
990
        :param check_content: If True, the lines supplied are verified to be
 
991
            bytestrings that are correctly formed lines.
 
992
        :return: The text sha1, the number of bytes in the text, and an opaque
 
993
                 representation of the inserted version which can be provided
 
994
                 back to future _add_text calls in the parent_texts dictionary.
 
995
        """
 
996
        # The default implementation just thunks over to .add_lines(),
 
997
        # inefficient, but it works.
 
998
        return self.add_lines(key, parents, osutils.split_lines(text),
 
999
                              nostore_sha=nostore_sha,
 
1000
                              random_id=random_id,
 
1001
                              check_content=True)
 
1002
 
827
1003
    def add_mpdiffs(self, records):
828
1004
        """Add mpdiffs to this VersionedFile.
829
1005
 
871
1047
        raise NotImplementedError(self.annotate)
872
1048
 
873
1049
    def check(self, progress_bar=None):
874
 
        """Check this object for integrity."""
 
1050
        """Check this object for integrity.
 
1051
        
 
1052
        :param progress_bar: A progress bar to output as the check progresses.
 
1053
        :param keys: Specific keys within the VersionedFiles to check. When
 
1054
            this parameter is not None, check() becomes a generator as per
 
1055
            get_record_stream. The difference to get_record_stream is that
 
1056
            more or deeper checks will be performed.
 
1057
        :return: None, or if keys was supplied a generator as per
 
1058
            get_record_stream.
 
1059
        """
875
1060
        raise NotImplementedError(self.check)
876
1061
 
877
1062
    @staticmethod
878
1063
    def check_not_reserved_id(version_id):
879
1064
        revision.check_not_reserved_id(version_id)
880
1065
 
 
1066
    def clear_cache(self):
 
1067
        """Clear whatever caches this VersionedFile holds.
 
1068
 
 
1069
        This is generally called after an operation has been performed, when we
 
1070
        don't expect to be using this versioned file again soon.
 
1071
        """
 
1072
 
881
1073
    def _check_lines_not_unicode(self, lines):
882
1074
        """Check that lines being added to a versioned file are not unicode."""
883
1075
        for line in lines:
890
1082
            if '\n' in line[:-1]:
891
1083
                raise errors.BzrBadParameterContainsNewline("lines")
892
1084
 
 
1085
    def get_known_graph_ancestry(self, keys):
 
1086
        """Get a KnownGraph instance with the ancestry of keys."""
 
1087
        # most basic implementation is a loop around get_parent_map
 
1088
        pending = set(keys)
 
1089
        parent_map = {}
 
1090
        while pending:
 
1091
            this_parent_map = self.get_parent_map(pending)
 
1092
            parent_map.update(this_parent_map)
 
1093
            pending = set()
 
1094
            map(pending.update, this_parent_map.itervalues())
 
1095
            pending = pending.difference(parent_map)
 
1096
        kg = _mod_graph.KnownGraph(parent_map)
 
1097
        return kg
 
1098
 
893
1099
    def get_parent_map(self, keys):
894
1100
        """Get a map of the parents of keys.
895
1101
 
925
1131
 
926
1132
    has_key = index._has_key_from_parent_map
927
1133
 
 
1134
    def get_missing_compression_parent_keys(self):
 
1135
        """Return an iterable of keys of missing compression parents.
 
1136
 
 
1137
        Check this after calling insert_record_stream to find out if there are
 
1138
        any missing compression parents.  If there are, the records that
 
1139
        depend on them are not able to be inserted safely. The precise
 
1140
        behaviour depends on the concrete VersionedFiles class in use.
 
1141
 
 
1142
        Classes that do not support this will raise NotImplementedError.
 
1143
        """
 
1144
        raise NotImplementedError(self.get_missing_compression_parent_keys)
 
1145
 
928
1146
    def insert_record_stream(self, stream):
929
1147
        """Insert a record stream into this container.
930
1148
 
931
 
        :param stream: A stream of records to insert. 
 
1149
        :param stream: A stream of records to insert.
932
1150
        :return: None
933
1151
        :seealso VersionedFile.get_record_stream:
934
1152
        """
963
1181
 
964
1182
    def make_mpdiffs(self, keys):
965
1183
        """Create multiparent diffs for specified keys."""
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
 
1184
        generator = _MPDiffGenerator(self, keys)
 
1185
        return generator.compute_diffs()
 
1186
 
 
1187
    def get_annotator(self):
 
1188
        return annotate.Annotator(self)
1003
1189
 
1004
1190
    missing_keys = index._missing_keys_from_parent_map
1005
1191
 
1006
1192
    def _extract_blocks(self, version_id, source, target):
1007
1193
        return None
1008
1194
 
 
1195
    def _transitive_fallbacks(self):
 
1196
        """Return the whole stack of fallback versionedfiles.
 
1197
 
 
1198
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1199
        necessarily know about the whole stack going down, and it can't know
 
1200
        at open time because they may change after the objects are opened.
 
1201
        """
 
1202
        all_fallbacks = []
 
1203
        for a_vfs in self._immediate_fallback_vfs:
 
1204
            all_fallbacks.append(a_vfs)
 
1205
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1206
        return all_fallbacks
 
1207
 
1009
1208
 
1010
1209
class ThunkedVersionedFiles(VersionedFiles):
1011
1210
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1075
1274
            result.append((prefix + (origin,), line))
1076
1275
        return result
1077
1276
 
1078
 
    def check(self, progress_bar=None):
 
1277
    def check(self, progress_bar=None, keys=None):
1079
1278
        """See VersionedFiles.check()."""
 
1279
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
 
1280
        # this is tolerable. Ideally we'd pass keys down to check() and 
 
1281
        # have the older VersiondFile interface updated too.
1080
1282
        for prefix, vf in self._iter_all_components():
1081
1283
            vf.check()
 
1284
        if keys is not None:
 
1285
            return self.get_record_stream(keys, 'unordered', True)
1082
1286
 
1083
1287
    def get_parent_map(self, keys):
1084
1288
        """Get a map of the parents of keys.
1162
1366
    def insert_record_stream(self, stream):
1163
1367
        """Insert a record stream into this container.
1164
1368
 
1165
 
        :param stream: A stream of records to insert. 
 
1369
        :param stream: A stream of records to insert.
1166
1370
        :return: None
1167
1371
        :seealso VersionedFile.get_record_stream:
1168
1372
        """
1219
1423
        return result
1220
1424
 
1221
1425
 
 
1426
class VersionedFilesWithFallbacks(VersionedFiles):
 
1427
 
 
1428
    def without_fallbacks(self):
 
1429
        """Return a clone of this object without any fallbacks configured."""
 
1430
        raise NotImplementedError(self.without_fallbacks)
 
1431
 
 
1432
    def add_fallback_versioned_files(self, a_versioned_files):
 
1433
        """Add a source of texts for texts not present in this knit.
 
1434
 
 
1435
        :param a_versioned_files: A VersionedFiles object.
 
1436
        """
 
1437
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1438
 
 
1439
    def get_known_graph_ancestry(self, keys):
 
1440
        """Get a KnownGraph instance with the ancestry of keys."""
 
1441
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1442
        for fallback in self._transitive_fallbacks():
 
1443
            if not missing_keys:
 
1444
                break
 
1445
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1446
                                                missing_keys)
 
1447
            parent_map.update(f_parent_map)
 
1448
            missing_keys = f_missing_keys
 
1449
        kg = _mod_graph.KnownGraph(parent_map)
 
1450
        return kg
 
1451
 
 
1452
 
1222
1453
class _PlanMergeVersionedFile(VersionedFiles):
1223
1454
    """A VersionedFile for uncommitted and committed texts.
1224
1455
 
1245
1476
        # line data for locally held keys.
1246
1477
        self._lines = {}
1247
1478
        # key lookup providers
1248
 
        self._providers = [DictParentsProvider(self._parents)]
 
1479
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1249
1480
 
1250
1481
    def plan_merge(self, ver_a, ver_b, base=None):
1251
1482
        """See VersionedFile.plan_merge"""
1258
1489
 
1259
1490
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1260
1491
        from bzrlib.merge import _PlanLCAMerge
1261
 
        graph = Graph(self)
 
1492
        graph = _mod_graph.Graph(self)
1262
1493
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1263
1494
        if base is None:
1264
1495
            return new_plan
1316
1547
            result[revision.NULL_REVISION] = ()
1317
1548
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1318
1549
        result.update(
1319
 
            _StackedParentsProvider(self._providers).get_parent_map(keys))
 
1550
            _mod_graph.StackedParentsProvider(
 
1551
                self._providers).get_parent_map(keys))
1320
1552
        for key, parents in result.iteritems():
1321
1553
            if parents == ():
1322
1554
                result[key] = (revision.NULL_REVISION,)
1325
1557
 
1326
1558
class PlanWeaveMerge(TextMerge):
1327
1559
    """Weave merge that takes a plan as its input.
1328
 
    
 
1560
 
1329
1561
    This exists so that VersionedFile.plan_merge is implementable.
1330
1562
    Most callers will want to use WeaveMerge instead.
1331
1563
    """
1333
1565
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1334
1566
                 b_marker=TextMerge.B_MARKER):
1335
1567
        TextMerge.__init__(self, a_marker, b_marker)
1336
 
        self.plan = plan
 
1568
        self.plan = list(plan)
1337
1569
 
1338
1570
    def _merge_struct(self):
1339
1571
        lines_a = []
1352
1584
                yield(lines_a,)
1353
1585
            else:
1354
1586
                yield (lines_a, lines_b)
1355
 
       
 
1587
 
1356
1588
        # We previously considered either 'unchanged' or 'killed-both' lines
1357
1589
        # to be possible places to resynchronize.  However, assuming agreement
1358
1590
        # on killed-both lines may be too aggressive. -- mbp 20060324
1364
1596
                lines_a = []
1365
1597
                lines_b = []
1366
1598
                ch_a = ch_b = False
1367
 
                
 
1599
 
1368
1600
            if state == 'unchanged':
1369
1601
                if line:
1370
1602
                    yield ([line],)
1386
1618
            elif state == 'conflicted-b':
1387
1619
                ch_b = ch_a = True
1388
1620
                lines_b.append(line)
 
1621
            elif state == 'killed-both':
 
1622
                # This counts as a change, even though there is no associated
 
1623
                # line
 
1624
                ch_b = ch_a = True
1389
1625
            else:
1390
1626
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1391
 
                        'killed-base', 'killed-both'):
 
1627
                        'killed-base'):
1392
1628
                    raise AssertionError(state)
1393
1629
        for struct in outstanding_struct():
1394
1630
            yield struct
1395
1631
 
 
1632
    def base_from_plan(self):
 
1633
        """Construct a BASE file from the plan text."""
 
1634
        base_lines = []
 
1635
        for state, line in self.plan:
 
1636
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
 
1637
                # If unchanged, then this line is straight from base. If a or b
 
1638
                # or both killed the line, then it *used* to be in base.
 
1639
                base_lines.append(line)
 
1640
            else:
 
1641
                if state not in ('killed-base', 'irrelevant',
 
1642
                                 'ghost-a', 'ghost-b',
 
1643
                                 'new-a', 'new-b',
 
1644
                                 'conflicted-a', 'conflicted-b'):
 
1645
                    # killed-base, irrelevant means it doesn't apply
 
1646
                    # ghost-a/ghost-b are harder to say for sure, but they
 
1647
                    # aren't in the 'inc_c' which means they aren't in the
 
1648
                    # shared base of a & b. So we don't include them.  And
 
1649
                    # obviously if the line is newly inserted, it isn't in base
 
1650
 
 
1651
                    # If 'conflicted-a' or b, then it is new vs one base, but
 
1652
                    # old versus another base. However, if we make it present
 
1653
                    # in the base, it will be deleted from the target, and it
 
1654
                    # seems better to get a line doubled in the merge result,
 
1655
                    # rather than have it deleted entirely.
 
1656
                    # Example, each node is the 'text' at that point:
 
1657
                    #           MN
 
1658
                    #          /   \
 
1659
                    #        MaN   MbN
 
1660
                    #         |  X  |
 
1661
                    #        MabN MbaN
 
1662
                    #          \   /
 
1663
                    #           ???
 
1664
                    # There was a criss-cross conflict merge. Both sides
 
1665
                    # include the other, but put themselves first.
 
1666
                    # Weave marks this as a 'clean' merge, picking OTHER over
 
1667
                    # THIS. (Though the details depend on order inserted into
 
1668
                    # weave, etc.)
 
1669
                    # LCA generates a plan:
 
1670
                    # [('unchanged', M),
 
1671
                    #  ('conflicted-b', b),
 
1672
                    #  ('unchanged', a),
 
1673
                    #  ('conflicted-a', b),
 
1674
                    #  ('unchanged', N)]
 
1675
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
 
1676
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
 
1677
                    # removes one 'b', and BASE vs OTHER removes the other)
 
1678
                    # If you include neither, 3-way creates a clean "MbabN" as
 
1679
                    # THIS adds one 'b', and OTHER does too.
 
1680
                    # It seems that having the line 2 times is better than
 
1681
                    # having it omitted. (Easier to manually delete than notice
 
1682
                    # it needs to be added.)
 
1683
                    raise AssertionError('Unknown state: %s' % (state,))
 
1684
        return base_lines
 
1685
 
1396
1686
 
1397
1687
class WeaveMerge(PlanWeaveMerge):
1398
1688
    """Weave merge that takes a VersionedFile and two versions as its input."""
1399
1689
 
1400
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1690
    def __init__(self, versionedfile, ver_a, ver_b,
1401
1691
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1402
1692
        plan = versionedfile.plan_merge(ver_a, ver_b)
1403
1693
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1404
1694
 
1405
1695
 
1406
1696
class VirtualVersionedFiles(VersionedFiles):
1407
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1697
    """Dummy implementation for VersionedFiles that uses other functions for
1408
1698
    obtaining fulltexts and parent maps.
1409
1699
 
1410
 
    This is always on the bottom of the stack and uses string keys 
 
1700
    This is always on the bottom of the stack and uses string keys
1411
1701
    (rather than tuples) internally.
1412
1702
    """
1413
1703
 
1415
1705
        """Create a VirtualVersionedFiles.
1416
1706
 
1417
1707
        :param get_parent_map: Same signature as Repository.get_parent_map.
1418
 
        :param get_lines: Should return lines for specified key or None if 
 
1708
        :param get_lines: Should return lines for specified key or None if
1419
1709
                          not available.
1420
1710
        """
1421
1711
        super(VirtualVersionedFiles, self).__init__()
1422
1712
        self._get_parent_map = get_parent_map
1423
1713
        self._get_lines = get_lines
1424
 
        
 
1714
 
1425
1715
    def check(self, progressbar=None):
1426
1716
        """See VersionedFiles.check.
1427
1717
 
1469
1759
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1470
1760
        for i, (key,) in enumerate(keys):
1471
1761
            if pb is not None:
1472
 
                pb.update("iterating texts", i, len(keys))
 
1762
                pb.update("Finding changed lines", i, len(keys))
1473
1763
            for l in self._get_lines(key):
1474
1764
                yield (l, key)
 
1765
 
 
1766
 
 
1767
class NoDupeAddLinesDecorator(object):
 
1768
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1769
    is already present.
 
1770
    """
 
1771
 
 
1772
    def __init__(self, store):
 
1773
        self._store = store
 
1774
 
 
1775
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1776
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1777
            check_content=True):
 
1778
        """See VersionedFiles.add_lines.
 
1779
        
 
1780
        This implementation may return None as the third element of the return
 
1781
        value when the original store wouldn't.
 
1782
        """
 
1783
        if nostore_sha:
 
1784
            raise NotImplementedError(
 
1785
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1786
                "nostore_sha behaviour.")
 
1787
        if key[-1] is None:
 
1788
            sha1 = osutils.sha_strings(lines)
 
1789
            key = ("sha1:" + sha1,)
 
1790
        else:
 
1791
            sha1 = None
 
1792
        if key in self._store.get_parent_map([key]):
 
1793
            # This key has already been inserted, so don't do it again.
 
1794
            if sha1 is None:
 
1795
                sha1 = osutils.sha_strings(lines)
 
1796
            return sha1, sum(map(len, lines)), None
 
1797
        return self._store.add_lines(key, parents, lines,
 
1798
                parent_texts=parent_texts,
 
1799
                left_matching_blocks=left_matching_blocks,
 
1800
                nostore_sha=nostore_sha, random_id=random_id,
 
1801
                check_content=check_content)
 
1802
 
 
1803
    def __getattr__(self, name):
 
1804
        return getattr(self._store, name)
 
1805
 
 
1806
 
 
1807
def network_bytes_to_kind_and_offset(network_bytes):
 
1808
    """Strip of a record kind from the front of network_bytes.
 
1809
 
 
1810
    :param network_bytes: The bytes of a record.
 
1811
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
 
1812
    """
 
1813
    line_end = network_bytes.find('\n')
 
1814
    storage_kind = network_bytes[:line_end]
 
1815
    return storage_kind, line_end + 1
 
1816
 
 
1817
 
 
1818
class NetworkRecordStream(object):
 
1819
    """A record_stream which reconstitures a serialised stream."""
 
1820
 
 
1821
    def __init__(self, bytes_iterator):
 
1822
        """Create a NetworkRecordStream.
 
1823
 
 
1824
        :param bytes_iterator: An iterator of bytes. Each item in this
 
1825
            iterator should have been obtained from a record_streams'
 
1826
            record.get_bytes_as(record.storage_kind) call.
 
1827
        """
 
1828
        self._bytes_iterator = bytes_iterator
 
1829
        self._kind_factory = {
 
1830
            'fulltext': fulltext_network_to_record,
 
1831
            'groupcompress-block': groupcompress.network_block_to_records,
 
1832
            'knit-ft-gz': knit.knit_network_to_record,
 
1833
            'knit-delta-gz': knit.knit_network_to_record,
 
1834
            'knit-annotated-ft-gz': knit.knit_network_to_record,
 
1835
            'knit-annotated-delta-gz': knit.knit_network_to_record,
 
1836
            'knit-delta-closure': knit.knit_delta_closure_to_records,
 
1837
            }
 
1838
 
 
1839
    def read(self):
 
1840
        """Read the stream.
 
1841
 
 
1842
        :return: An iterator as per VersionedFiles.get_record_stream().
 
1843
        """
 
1844
        for bytes in self._bytes_iterator:
 
1845
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
1846
            for record in self._kind_factory[storage_kind](
 
1847
                storage_kind, bytes, line_end):
 
1848
                yield record
 
1849
 
 
1850
 
 
1851
def fulltext_network_to_record(kind, bytes, line_end):
 
1852
    """Convert a network fulltext record to record."""
 
1853
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1854
    record_meta = bytes[line_end+4:line_end+4+meta_len]
 
1855
    key, parents = bencode.bdecode_as_tuple(record_meta)
 
1856
    if parents == 'nil':
 
1857
        parents = None
 
1858
    fulltext = bytes[line_end+4+meta_len:]
 
1859
    return [FulltextContentFactory(key, parents, None, fulltext)]
 
1860
 
 
1861
 
 
1862
def _length_prefix(bytes):
 
1863
    return struct.pack('!L', len(bytes))
 
1864
 
 
1865
 
 
1866
def record_to_fulltext_bytes(record):
 
1867
    if record.parents is None:
 
1868
        parents = 'nil'
 
1869
    else:
 
1870
        parents = record.parents
 
1871
    record_meta = bencode.bencode((record.key, parents))
 
1872
    record_content = record.get_bytes_as('fulltext')
 
1873
    return "fulltext\n%s%s%s" % (
 
1874
        _length_prefix(record_meta), record_meta, record_content)
 
1875
 
 
1876
 
 
1877
def sort_groupcompress(parent_map):
 
1878
    """Sort and group the keys in parent_map into groupcompress order.
 
1879
 
 
1880
    groupcompress is defined (currently) as reverse-topological order, grouped
 
1881
    by the key prefix.
 
1882
 
 
1883
    :return: A sorted-list of keys
 
1884
    """
 
1885
    # gc-optimal ordering is approximately reverse topological,
 
1886
    # properly grouped by file-id.
 
1887
    per_prefix_map = {}
 
1888
    for item in parent_map.iteritems():
 
1889
        key = item[0]
 
1890
        if isinstance(key, str) or len(key) == 1:
 
1891
            prefix = ''
 
1892
        else:
 
1893
            prefix = key[0]
 
1894
        try:
 
1895
            per_prefix_map[prefix].append(item)
 
1896
        except KeyError:
 
1897
            per_prefix_map[prefix] = [item]
 
1898
 
 
1899
    present_keys = []
 
1900
    for prefix in sorted(per_prefix_map):
 
1901
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
1902
    return present_keys
 
1903
 
 
1904
 
 
1905
class _KeyRefs(object):
 
1906
 
 
1907
    def __init__(self, track_new_keys=False):
 
1908
        # dict mapping 'key' to 'set of keys referring to that key'
 
1909
        self.refs = {}
 
1910
        if track_new_keys:
 
1911
            # set remembering all new keys
 
1912
            self.new_keys = set()
 
1913
        else:
 
1914
            self.new_keys = None
 
1915
 
 
1916
    def clear(self):
 
1917
        if self.refs:
 
1918
            self.refs.clear()
 
1919
        if self.new_keys:
 
1920
            self.new_keys.clear()
 
1921
 
 
1922
    def add_references(self, key, refs):
 
1923
        # Record the new references
 
1924
        for referenced in refs:
 
1925
            try:
 
1926
                needed_by = self.refs[referenced]
 
1927
            except KeyError:
 
1928
                needed_by = self.refs[referenced] = set()
 
1929
            needed_by.add(key)
 
1930
        # Discard references satisfied by the new key
 
1931
        self.add_key(key)
 
1932
 
 
1933
    def get_new_keys(self):
 
1934
        return self.new_keys
 
1935
    
 
1936
    def get_unsatisfied_refs(self):
 
1937
        return self.refs.iterkeys()
 
1938
 
 
1939
    def _satisfy_refs_for_key(self, key):
 
1940
        try:
 
1941
            del self.refs[key]
 
1942
        except KeyError:
 
1943
            # No keys depended on this key.  That's ok.
 
1944
            pass
 
1945
 
 
1946
    def add_key(self, key):
 
1947
        # satisfy refs for key, and remember that we've seen this key.
 
1948
        self._satisfy_refs_for_key(key)
 
1949
        if self.new_keys is not None:
 
1950
            self.new_keys.add(key)
 
1951
 
 
1952
    def satisfy_refs_for_keys(self, keys):
 
1953
        for key in keys:
 
1954
            self._satisfy_refs_for_key(key)
 
1955
 
 
1956
    def get_referrers(self):
 
1957
        result = set()
 
1958
        for referrers in self.refs.itervalues():
 
1959
            result.update(referrers)
 
1960
        return result
 
1961
 
 
1962
 
 
1963