~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Aaron Bentley
  • Date: 2007-12-09 23:53:50 UTC
  • mto: This revision was merged to the branch mainline in revision 3133.
  • Revision ID: aaron.bentley@utoronto.ca-20071209235350-qp39yk0xzx7a4f6p
Don't use the base if not cherrypicking

Show diffs side-by-side

added added

removed removed

Lines of Context:
71
71
from copy import copy
72
72
from cStringIO import StringIO
73
73
import os
 
74
import sha
74
75
import time
75
76
import warnings
76
77
 
77
 
from bzrlib.lazy_import import lazy_import
78
 
lazy_import(globals(), """
79
 
from bzrlib import tsort
80
 
""")
81
78
from bzrlib import (
82
 
    errors,
83
 
    osutils,
84
79
    progress,
85
80
    )
 
81
from bzrlib.trace import mutter
86
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
87
83
        RevisionAlreadyPresent,
88
84
        RevisionNotPresent,
89
 
        UnavailableRepresentation,
90
85
        WeaveRevisionAlreadyPresent,
91
86
        WeaveRevisionNotPresent,
92
87
        )
93
 
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
 
88
import bzrlib.errors as errors
 
89
from bzrlib.osutils import sha_strings
94
90
import bzrlib.patiencediff
95
 
from bzrlib.revision import NULL_REVISION
96
 
from bzrlib.symbol_versioning import *
97
 
from bzrlib.trace import mutter
98
 
from bzrlib.versionedfile import (
99
 
    AbsentContentFactory,
100
 
    adapter_registry,
101
 
    ContentFactory,
102
 
    VersionedFile,
103
 
    )
 
91
from bzrlib.tsort import topo_sort
 
92
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
104
93
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
105
94
 
106
95
 
107
 
class WeaveContentFactory(ContentFactory):
108
 
    """Content factory for streaming from weaves.
109
 
 
110
 
    :seealso ContentFactory:
111
 
    """
112
 
 
113
 
    def __init__(self, version, weave):
114
 
        """Create a WeaveContentFactory for version from weave."""
115
 
        ContentFactory.__init__(self)
116
 
        self.sha1 = weave.get_sha1s([version])[version]
117
 
        self.key = (version,)
118
 
        parents = weave.get_parent_map([version])[version]
119
 
        self.parents = tuple((parent,) for parent in parents)
120
 
        self.storage_kind = 'fulltext'
121
 
        self._weave = weave
122
 
 
123
 
    def get_bytes_as(self, storage_kind):
124
 
        if storage_kind == 'fulltext':
125
 
            return self._weave.get_text(self.key[-1])
126
 
        elif storage_kind == 'chunked':
127
 
            return self._weave.get_lines(self.key[-1])
128
 
        else:
129
 
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
130
 
 
131
 
 
132
96
class Weave(VersionedFile):
133
97
    """weave - versioned text file storage.
134
98
    
219
183
    """
220
184
 
221
185
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
222
 
                 '_weave_name', '_matcher', '_allow_reserved']
223
 
 
224
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
225
 
                 get_scope=None, allow_reserved=False):
226
 
        """Create a weave.
227
 
 
228
 
        :param get_scope: A callable that returns an opaque object to be used
229
 
            for detecting when this weave goes out of scope (should stop
230
 
            answering requests or allowing mutation).
231
 
        """
232
 
        super(Weave, self).__init__()
 
186
                 '_weave_name', '_matcher']
 
187
    
 
188
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
189
        super(Weave, self).__init__(access_mode)
233
190
        self._weave = []
234
191
        self._parents = []
235
192
        self._sha1s = []
240
197
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
241
198
        else:
242
199
            self._matcher = matcher
243
 
        if get_scope is None:
244
 
            get_scope = lambda:None
245
 
        self._get_scope = get_scope
246
 
        self._scope = get_scope()
247
 
        self._access_mode = access_mode
248
 
        self._allow_reserved = allow_reserved
249
200
 
250
201
    def __repr__(self):
251
202
        return "Weave(%r)" % self._weave_name
252
203
 
253
 
    def _check_write_ok(self):
254
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
255
 
        if self._get_scope() != self._scope:
256
 
            raise errors.OutSideTransaction()
257
 
        if self._access_mode != 'w':
258
 
            raise errors.ReadOnlyObjectDirtiedError(self)
259
 
 
260
204
    def copy(self):
261
205
        """Return a deep copy of self.
262
206
        
285
229
 
286
230
    def _lookup(self, name):
287
231
        """Convert symbolic version name to index."""
288
 
        if not self._allow_reserved:
289
 
            self.check_not_reserved_id(name)
 
232
        self.check_not_reserved_id(name)
290
233
        try:
291
234
            return self._name_map[name]
292
235
        except KeyError:
302
245
 
303
246
    __contains__ = has_version
304
247
 
305
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
306
 
        """Get a stream of records for versions.
307
 
 
308
 
        :param versions: The versions to include. Each version is a tuple
309
 
            (version,).
310
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
311
 
            sorted stream has compression parents strictly before their
312
 
            children.
313
 
        :param include_delta_closure: If True then the closure across any
314
 
            compression parents will be included (in the opaque data).
315
 
        :return: An iterator of ContentFactory objects, each of which is only
316
 
            valid until the iterator is advanced.
317
 
        """
318
 
        versions = [version[-1] for version in versions]
319
 
        if ordering == 'topological':
320
 
            parents = self.get_parent_map(versions)
321
 
            new_versions = tsort.topo_sort(parents)
322
 
            new_versions.extend(set(versions).difference(set(parents)))
323
 
            versions = new_versions
324
 
        for version in versions:
325
 
            if version in self:
326
 
                yield WeaveContentFactory(version, self)
327
 
            else:
328
 
                yield AbsentContentFactory((version,))
329
 
 
330
 
    def get_parent_map(self, version_ids):
331
 
        """See VersionedFile.get_parent_map."""
332
 
        result = {}
333
 
        for version_id in version_ids:
334
 
            if version_id == NULL_REVISION:
335
 
                parents = ()
336
 
            else:
337
 
                try:
338
 
                    parents = tuple(
339
 
                        map(self._idx_to_name,
340
 
                            self._parents[self._lookup(version_id)]))
341
 
                except RevisionNotPresent:
342
 
                    continue
343
 
            result[version_id] = parents
344
 
        return result
345
 
 
346
 
    def get_parents_with_ghosts(self, version_id):
347
 
        raise NotImplementedError(self.get_parents_with_ghosts)
348
 
 
349
 
    def insert_record_stream(self, stream):
350
 
        """Insert a record stream into this versioned file.
351
 
 
352
 
        :param stream: A stream of records to insert. 
353
 
        :return: None
354
 
        :seealso VersionedFile.get_record_stream:
355
 
        """
356
 
        adapters = {}
357
 
        for record in stream:
358
 
            # Raise an error when a record is missing.
359
 
            if record.storage_kind == 'absent':
360
 
                raise RevisionNotPresent([record.key[0]], self)
361
 
            # adapt to non-tuple interface
362
 
            parents = [parent[0] for parent in record.parents]
363
 
            if (record.storage_kind == 'fulltext'
364
 
                or record.storage_kind == 'chunked'):
365
 
                self.add_lines(record.key[0], parents,
366
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
367
 
            else:
368
 
                adapter_key = record.storage_kind, 'fulltext'
369
 
                try:
370
 
                    adapter = adapters[adapter_key]
371
 
                except KeyError:
372
 
                    adapter_factory = adapter_registry.get(adapter_key)
373
 
                    adapter = adapter_factory(self)
374
 
                    adapters[adapter_key] = adapter
375
 
                lines = split_lines(adapter.get_bytes(
376
 
                    record, record.get_bytes_as(record.storage_kind)))
377
 
                try:
378
 
                    self.add_lines(record.key[0], parents, lines)
379
 
                except RevisionAlreadyPresent:
380
 
                    pass
 
248
    def get_parents(self, version_id):
 
249
        """See VersionedFile.get_parent."""
 
250
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
381
251
 
382
252
    def _check_repeated_add(self, name, parents, text, sha1):
383
253
        """Check that a duplicated add is OK.
414
284
 
415
285
        :param nostore_sha: See VersionedFile.add_lines.
416
286
        """
 
287
        assert isinstance(version_id, basestring)
417
288
        self._check_lines_not_unicode(lines)
418
289
        self._check_lines_are_lines(lines)
419
290
        if not sha1:
494
365
            #print 'raw match', tag, i1, i2, j1, j2
495
366
            if tag == 'equal':
496
367
                continue
 
368
 
497
369
            i1 = basis_lineno[i1]
498
370
            i2 = basis_lineno[i2]
 
371
 
 
372
            assert 0 <= j1 <= j2 <= len(lines)
 
373
 
 
374
            #print tag, i1, i2, j1, j2
 
375
 
499
376
            # the deletion and insertion are handled separately.
500
377
            # first delete the region.
501
378
            if i1 != i2:
514
391
                offset += 2 + (j2 - j1)
515
392
        return new_version
516
393
 
 
394
    def _clone_text(self, new_version_id, old_version_id, parents):
 
395
        """See VersionedFile.clone_text."""
 
396
        old_lines = self.get_text(old_version_id)
 
397
        self.add_lines(new_version_id, parents, old_lines)
 
398
 
517
399
    def _inclusions(self, versions):
518
400
        """Return set of all ancestors of given version(s)."""
519
401
        if not len(versions):
561
443
        """
562
444
        return len(other_parents.difference(my_parents)) == 0
563
445
 
564
 
    def annotate(self, version_id):
565
 
        """Return a list of (version-id, line) tuples for version_id.
 
446
    def annotate_iter(self, version_id):
 
447
        """Yield list of (version-id, line) pairs for the specified version.
566
448
 
567
449
        The index indicates when the line originated in the weave."""
568
450
        incls = [self._lookup(version_id)]
569
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
570
 
            self._extract(incls)]
 
451
        for origin, lineno, text in self._extract(incls):
 
452
            yield self._idx_to_name(origin), text
571
453
 
572
454
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
573
455
                                                pb=None):
602
484
                elif c == '}':
603
485
                    istack.pop()
604
486
                elif c == '[':
 
487
                    assert self._names[v] not in dset
605
488
                    dset.add(self._names[v])
606
489
                elif c == ']':
607
490
                    dset.remove(self._names[v])
608
491
                else:
609
492
                    raise WeaveFormatError('unexpected instruction %r' % v)
610
493
            else:
 
494
                assert l.__class__ in (str, unicode)
 
495
                assert istack
611
496
                yield lineno, istack[-1], frozenset(dset), l
612
497
            lineno += 1
613
498
 
662
547
                # not in either revision
663
548
                yield 'irrelevant', line
664
549
 
 
550
        yield 'unchanged', ''           # terminator
 
551
 
665
552
    def _extract(self, versions):
666
553
        """Yield annotation of lines in included set.
667
554
 
721
608
                c, v = l
722
609
                isactive = None
723
610
                if c == '{':
 
611
                    assert v not in iset
724
612
                    istack.append(v)
725
613
                    iset.add(v)
726
614
                elif c == '}':
727
615
                    iset.remove(istack.pop())
728
616
                elif c == '[':
729
617
                    if v in included:
 
618
                        assert v not in dset
730
619
                        dset.add(v)
731
 
                elif c == ']':
 
620
                else:
 
621
                    assert c == ']'
732
622
                    if v in included:
 
623
                        assert v in dset
733
624
                        dset.remove(v)
734
 
                else:
735
 
                    raise AssertionError()
736
625
            else:
 
626
                assert l.__class__ in (str, unicode)
737
627
                if isactive is None:
738
628
                    isactive = (not dset) and istack and (istack[-1] in included)
739
629
                if isactive:
770
660
                       expected_sha1, measured_sha1))
771
661
        return result
772
662
 
 
663
    def get_sha1(self, version_id):
 
664
        """See VersionedFile.get_sha1()."""
 
665
        return self._sha1s[self._lookup(version_id)]
 
666
 
773
667
    def get_sha1s(self, version_ids):
774
668
        """See VersionedFile.get_sha1s()."""
775
 
        result = {}
776
 
        for v in version_ids:
777
 
            result[v] = self._sha1s[self._lookup(v)]
778
 
        return result
 
669
        return [self._sha1s[self._lookup(v)] for v in version_ids]
779
670
 
780
671
    def num_versions(self):
781
672
        """How many versions are in this weave?"""
782
673
        l = len(self._parents)
 
674
        assert l == len(self._sha1s)
783
675
        return l
784
676
 
785
677
    __len__ = num_versions
805
697
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
806
698
            # The problem is that set membership is much more expensive
807
699
            name = self._idx_to_name(i)
808
 
            sha1s[name] = sha()
 
700
            sha1s[name] = sha.new()
809
701
            texts[name] = []
810
702
            new_inc = set([name])
811
703
            for p in self._parents[i]:
812
704
                new_inc.update(inclusions[self._idx_to_name(p)])
813
705
 
814
 
            if set(new_inc) != set(self.get_ancestry(name)):
815
 
                raise AssertionError(
816
 
                    'failed %s != %s' 
817
 
                    % (set(new_inc), set(self.get_ancestry(name))))
 
706
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
707
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
818
708
            inclusions[name] = new_inc
819
709
 
820
710
        nlines = len(self._weave)
850
740
        # no lines outside of insertion blocks, that deletions are
851
741
        # properly paired, etc.
852
742
 
 
743
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
744
        """Worker routine for join()."""
 
745
        if not other.versions():
 
746
            return          # nothing to update, easy
 
747
 
 
748
        if not version_ids:
 
749
            # versions is never none, InterWeave checks this.
 
750
            return 0
 
751
 
 
752
        # two loops so that we do not change ourselves before verifying it
 
753
        # will be ok
 
754
        # work through in index order to make sure we get all dependencies
 
755
        names_to_join = []
 
756
        processed = 0
 
757
        # get the selected versions only that are in other.versions.
 
758
        version_ids = set(other.versions()).intersection(set(version_ids))
 
759
        # pull in the referenced graph.
 
760
        version_ids = other.get_ancestry(version_ids)
 
761
        pending_graph = [(version, other.get_parents(version)) for
 
762
                         version in version_ids]
 
763
        for name in topo_sort(pending_graph):
 
764
            other_idx = other._name_map[name]
 
765
            # returns True if we have it, False if we need it.
 
766
            if not self._check_version_consistent(other, other_idx, name):
 
767
                names_to_join.append((other_idx, name))
 
768
            processed += 1
 
769
 
 
770
 
 
771
        if pb and not msg:
 
772
            msg = 'weave join'
 
773
 
 
774
        merged = 0
 
775
        time0 = time.time()
 
776
        for other_idx, name in names_to_join:
 
777
            # TODO: If all the parents of the other version are already
 
778
            # present then we can avoid some work by just taking the delta
 
779
            # and adjusting the offsets.
 
780
            new_parents = self._imported_parents(other, other_idx)
 
781
            sha1 = other._sha1s[other_idx]
 
782
 
 
783
            merged += 1
 
784
 
 
785
            if pb:
 
786
                pb.update(msg, merged, len(names_to_join))
 
787
           
 
788
            lines = other.get_lines(other_idx)
 
789
            self._add(name, lines, new_parents, sha1)
 
790
 
 
791
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
792
                merged, processed, self._weave_name, time.time()-time0))
 
793
 
853
794
    def _imported_parents(self, other, other_idx):
854
795
        """Return list of parents in self corresponding to indexes in other."""
855
796
        new_parents = []
912
853
 
913
854
    WEAVE_SUFFIX = '.weave'
914
855
    
915
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
856
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
916
857
        """Create a WeaveFile.
917
858
        
918
859
        :param create: If not True, only open an existing knit.
919
860
        """
920
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
921
 
            allow_reserved=False)
 
861
        super(WeaveFile, self).__init__(name, access_mode)
922
862
        self._transport = transport
923
863
        self._filemode = filemode
924
864
        try:
939
879
        self._save()
940
880
        return result
941
881
 
 
882
    def _clone_text(self, new_version_id, old_version_id, parents):
 
883
        """See VersionedFile.clone_text."""
 
884
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
885
        self._save
 
886
 
942
887
    def copy_to(self, name, transport):
943
888
        """See VersionedFile.copy_to()."""
944
889
        # as we are all in memory always, just serialise to the new place.
947
892
        sio.seek(0)
948
893
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
949
894
 
 
895
    def create_empty(self, name, transport, filemode=None):
 
896
        return WeaveFile(name, transport, filemode, create=True)
 
897
 
950
898
    def _save(self):
951
899
        """Save the weave."""
952
900
        self._check_write_ok()
953
901
        sio = StringIO()
954
902
        write_weave_v5(self, sio)
955
903
        sio.seek(0)
956
 
        bytes = sio.getvalue()
957
 
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
958
 
        try:
959
 
            self._transport.put_bytes(path, bytes, self._filemode)
960
 
        except errors.NoSuchFile:
961
 
            self._transport.mkdir(dirname(path))
962
 
            self._transport.put_bytes(path, bytes, self._filemode)
 
904
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
905
                                 sio,
 
906
                                 self._filemode)
963
907
 
964
908
    @staticmethod
965
909
    def get_suffixes():
966
910
        """See VersionedFile.get_suffixes()."""
967
911
        return [WeaveFile.WEAVE_SUFFIX]
968
912
 
969
 
    def insert_record_stream(self, stream):
970
 
        super(WeaveFile, self).insert_record_stream(stream)
971
 
        self._save()
972
 
 
973
 
    @deprecated_method(one_five)
974
913
    def join(self, other, pb=None, msg=None, version_ids=None,
975
914
             ignore_missing=False):
976
915
        """Join other into self and save."""
1001
940
    # map from version name -> all parent names
1002
941
    combined_parents = _reweave_parent_graphs(wa, wb)
1003
942
    mutter("combined parents: %r", combined_parents)
1004
 
    order = tsort.topo_sort(combined_parents.iteritems())
 
943
    order = topo_sort(combined_parents.iteritems())
1005
944
    mutter("order to reweave: %r", order)
1006
945
 
1007
946
    if pb and not msg:
1240
1179
if __name__ == '__main__':
1241
1180
    import sys
1242
1181
    sys.exit(main(sys.argv))
 
1182
 
 
1183
 
 
1184
class InterWeave(InterVersionedFile):
 
1185
    """Optimised code paths for weave to weave operations."""
 
1186
    
 
1187
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1188
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1189
    
 
1190
    @staticmethod
 
1191
    def is_compatible(source, target):
 
1192
        """Be compatible with weaves."""
 
1193
        try:
 
1194
            return (isinstance(source, Weave) and
 
1195
                    isinstance(target, Weave))
 
1196
        except AttributeError:
 
1197
            return False
 
1198
 
 
1199
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1200
        """See InterVersionedFile.join."""
 
1201
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1202
        if self.target.versions() == [] and version_ids is None:
 
1203
            self.target._copy_weave_content(self.source)
 
1204
            return
 
1205
        self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1206
 
 
1207
 
 
1208
InterVersionedFile.register_optimiser(InterWeave)