~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
78
78
from bzrlib import (
79
79
    progress,
80
80
    )
81
 
from bzrlib.trace import mutter
82
81
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
83
82
        RevisionAlreadyPresent,
84
83
        RevisionNotPresent,
 
84
        UnavailableRepresentation,
85
85
        WeaveRevisionAlreadyPresent,
86
86
        WeaveRevisionNotPresent,
87
87
        )
88
88
import bzrlib.errors as errors
89
 
from bzrlib.osutils import sha_strings
 
89
from bzrlib.osutils import dirname, sha_strings, split_lines
90
90
import bzrlib.patiencediff
 
91
from bzrlib.revision import NULL_REVISION
 
92
from bzrlib.symbol_versioning import *
 
93
from bzrlib.trace import mutter
91
94
from bzrlib.tsort import topo_sort
92
 
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
 
95
from bzrlib.versionedfile import (
 
96
    AbsentContentFactory,
 
97
    adapter_registry,
 
98
    ContentFactory,
 
99
    VersionedFile,
 
100
    )
93
101
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
94
102
 
95
103
 
 
104
class WeaveContentFactory(ContentFactory):
 
105
    """Content factory for streaming from weaves.
 
106
 
 
107
    :seealso ContentFactory:
 
108
    """
 
109
 
 
110
    def __init__(self, version, weave):
 
111
        """Create a WeaveContentFactory for version from weave."""
 
112
        ContentFactory.__init__(self)
 
113
        self.sha1 = weave.get_sha1s([version])[version]
 
114
        self.key = (version,)
 
115
        parents = weave.get_parent_map([version])[version]
 
116
        self.parents = tuple((parent,) for parent in parents)
 
117
        self.storage_kind = 'fulltext'
 
118
        self._weave = weave
 
119
 
 
120
    def get_bytes_as(self, storage_kind):
 
121
        if storage_kind == 'fulltext':
 
122
            return self._weave.get_text(self.key[-1])
 
123
        else:
 
124
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
 
125
 
 
126
 
96
127
class Weave(VersionedFile):
97
128
    """weave - versioned text file storage.
98
129
    
183
214
    """
184
215
 
185
216
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
186
 
                 '_weave_name', '_matcher']
 
217
                 '_weave_name', '_matcher', '_allow_reserved']
187
218
    
188
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
219
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
 
220
                 get_scope=None, allow_reserved=False):
 
221
        """Create a weave.
 
222
 
 
223
        :param get_scope: A callable that returns an opaque object to be used
 
224
            for detecting when this weave goes out of scope (should stop
 
225
            answering requests or allowing mutation).
 
226
        """
189
227
        super(Weave, self).__init__(access_mode)
190
228
        self._weave = []
191
229
        self._parents = []
197
235
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
198
236
        else:
199
237
            self._matcher = matcher
 
238
        if get_scope is None:
 
239
            get_scope = lambda:None
 
240
        self._get_scope = get_scope
 
241
        self._scope = get_scope()
 
242
        self._access_mode = access_mode
 
243
        self._allow_reserved = allow_reserved
200
244
 
201
245
    def __repr__(self):
202
246
        return "Weave(%r)" % self._weave_name
203
247
 
 
248
    def _check_write_ok(self):
 
249
        """Is the versioned file marked as 'finished' ? Raise if it is."""
 
250
        if self._get_scope() != self._scope:
 
251
            raise errors.OutSideTransaction()
 
252
        if self._access_mode != 'w':
 
253
            raise errors.ReadOnlyObjectDirtiedError(self)
 
254
 
204
255
    def copy(self):
205
256
        """Return a deep copy of self.
206
257
        
229
280
 
230
281
    def _lookup(self, name):
231
282
        """Convert symbolic version name to index."""
232
 
        self.check_not_reserved_id(name)
 
283
        if not self._allow_reserved:
 
284
            self.check_not_reserved_id(name)
233
285
        try:
234
286
            return self._name_map[name]
235
287
        except KeyError:
245
297
 
246
298
    __contains__ = has_version
247
299
 
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)])
 
300
    def get_record_stream(self, versions, ordering, include_delta_closure):
 
301
        """Get a stream of records for versions.
 
302
 
 
303
        :param versions: The versions to include. Each version is a tuple
 
304
            (version,).
 
305
        :param ordering: Either 'unordered' or 'topological'. A topologically
 
306
            sorted stream has compression parents strictly before their
 
307
            children.
 
308
        :param include_delta_closure: If True then the closure across any
 
309
            compression parents will be included (in the opaque data).
 
310
        :return: An iterator of ContentFactory objects, each of which is only
 
311
            valid until the iterator is advanced.
 
312
        """
 
313
        versions = [version[-1] for version in versions]
 
314
        if ordering == 'topological':
 
315
            parents = self.get_parent_map(versions)
 
316
            new_versions = topo_sort(parents)
 
317
            new_versions.extend(set(versions).difference(set(parents)))
 
318
            versions = new_versions
 
319
        for version in versions:
 
320
            if version in self:
 
321
                yield WeaveContentFactory(version, self)
 
322
            else:
 
323
                yield AbsentContentFactory((version,))
 
324
 
 
325
    def get_parent_map(self, version_ids):
 
326
        """See VersionedFile.get_parent_map."""
 
327
        result = {}
 
328
        for version_id in version_ids:
 
329
            if version_id == NULL_REVISION:
 
330
                parents = ()
 
331
            else:
 
332
                try:
 
333
                    parents = tuple(
 
334
                        map(self._idx_to_name,
 
335
                            self._parents[self._lookup(version_id)]))
 
336
                except RevisionNotPresent:
 
337
                    continue
 
338
            result[version_id] = parents
 
339
        return result
 
340
 
 
341
    def get_parents_with_ghosts(self, version_id):
 
342
        raise NotImplementedError(self.get_parents_with_ghosts)
 
343
 
 
344
    def insert_record_stream(self, stream):
 
345
        """Insert a record stream into this versioned file.
 
346
 
 
347
        :param stream: A stream of records to insert. 
 
348
        :return: None
 
349
        :seealso VersionedFile.get_record_stream:
 
350
        """
 
351
        adapters = {}
 
352
        for record in stream:
 
353
            # Raise an error when a record is missing.
 
354
            if record.storage_kind == 'absent':
 
355
                raise RevisionNotPresent([record.key[0]], self)
 
356
            # adapt to non-tuple interface
 
357
            parents = [parent[0] for parent in record.parents]
 
358
            if record.storage_kind == 'fulltext':
 
359
                self.add_lines(record.key[0], parents,
 
360
                    split_lines(record.get_bytes_as('fulltext')))
 
361
            else:
 
362
                adapter_key = record.storage_kind, 'fulltext'
 
363
                try:
 
364
                    adapter = adapters[adapter_key]
 
365
                except KeyError:
 
366
                    adapter_factory = adapter_registry.get(adapter_key)
 
367
                    adapter = adapter_factory(self)
 
368
                    adapters[adapter_key] = adapter
 
369
                lines = split_lines(adapter.get_bytes(
 
370
                    record, record.get_bytes_as(record.storage_kind)))
 
371
                try:
 
372
                    self.add_lines(record.key[0], parents, lines)
 
373
                except RevisionAlreadyPresent:
 
374
                    pass
251
375
 
252
376
    def _check_repeated_add(self, name, parents, text, sha1):
253
377
        """Check that a duplicated add is OK.
284
408
 
285
409
        :param nostore_sha: See VersionedFile.add_lines.
286
410
        """
287
 
        assert isinstance(version_id, basestring)
288
411
        self._check_lines_not_unicode(lines)
289
412
        self._check_lines_are_lines(lines)
290
413
        if not sha1:
365
488
            #print 'raw match', tag, i1, i2, j1, j2
366
489
            if tag == 'equal':
367
490
                continue
368
 
 
369
491
            i1 = basis_lineno[i1]
370
492
            i2 = basis_lineno[i2]
371
 
 
372
 
            assert 0 <= j1 <= j2 <= len(lines)
373
 
 
374
 
            #print tag, i1, i2, j1, j2
375
 
 
376
493
            # the deletion and insertion are handled separately.
377
494
            # first delete the region.
378
495
            if i1 != i2:
391
508
                offset += 2 + (j2 - j1)
392
509
        return new_version
393
510
 
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
 
 
399
511
    def _inclusions(self, versions):
400
512
        """Return set of all ancestors of given version(s)."""
401
513
        if not len(versions):
443
555
        """
444
556
        return len(other_parents.difference(my_parents)) == 0
445
557
 
446
 
    def annotate_iter(self, version_id):
447
 
        """Yield list of (version-id, line) pairs for the specified version.
 
558
    def annotate(self, version_id):
 
559
        """Return a list of (version-id, line) tuples for version_id.
448
560
 
449
561
        The index indicates when the line originated in the weave."""
450
562
        incls = [self._lookup(version_id)]
451
 
        for origin, lineno, text in self._extract(incls):
452
 
            yield self._idx_to_name(origin), text
 
563
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
 
564
            self._extract(incls)]
453
565
 
454
566
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
455
567
                                                pb=None):
463
575
            # properly, we do not filter down to that
464
576
            # if inserted not in version_ids: continue
465
577
            if line[-1] != '\n':
466
 
                yield line + '\n'
 
578
                yield line + '\n', inserted
467
579
            else:
468
 
                yield line
 
580
                yield line, inserted
469
581
 
470
582
    def _walk_internal(self, version_ids=None):
471
583
        """Helper method for weave actions."""
484
596
                elif c == '}':
485
597
                    istack.pop()
486
598
                elif c == '[':
487
 
                    assert self._names[v] not in dset
488
599
                    dset.add(self._names[v])
489
600
                elif c == ']':
490
601
                    dset.remove(self._names[v])
491
602
                else:
492
603
                    raise WeaveFormatError('unexpected instruction %r' % v)
493
604
            else:
494
 
                assert l.__class__ in (str, unicode)
495
 
                assert istack
496
605
                yield lineno, istack[-1], frozenset(dset), l
497
606
            lineno += 1
498
607
 
547
656
                # not in either revision
548
657
                yield 'irrelevant', line
549
658
 
550
 
        yield 'unchanged', ''           # terminator
551
 
 
552
659
    def _extract(self, versions):
553
660
        """Yield annotation of lines in included set.
554
661
 
608
715
                c, v = l
609
716
                isactive = None
610
717
                if c == '{':
611
 
                    assert v not in iset
612
718
                    istack.append(v)
613
719
                    iset.add(v)
614
720
                elif c == '}':
615
721
                    iset.remove(istack.pop())
616
722
                elif c == '[':
617
723
                    if v in included:
618
 
                        assert v not in dset
619
724
                        dset.add(v)
620
 
                else:
621
 
                    assert c == ']'
 
725
                elif c == ']':
622
726
                    if v in included:
623
 
                        assert v in dset
624
727
                        dset.remove(v)
 
728
                else:
 
729
                    raise AssertionError()
625
730
            else:
626
 
                assert l.__class__ in (str, unicode)
627
731
                if isactive is None:
628
732
                    isactive = (not dset) and istack and (istack[-1] in included)
629
733
                if isactive:
660
764
                       expected_sha1, measured_sha1))
661
765
        return result
662
766
 
663
 
    def get_sha1(self, version_id):
664
 
        """See VersionedFile.get_sha1()."""
665
 
        return self._sha1s[self._lookup(version_id)]
666
 
 
667
767
    def get_sha1s(self, version_ids):
668
768
        """See VersionedFile.get_sha1s()."""
669
 
        return [self._sha1s[self._lookup(v)] for v in version_ids]
 
769
        result = {}
 
770
        for v in version_ids:
 
771
            result[v] = self._sha1s[self._lookup(v)]
 
772
        return result
670
773
 
671
774
    def num_versions(self):
672
775
        """How many versions are in this weave?"""
673
776
        l = len(self._parents)
674
 
        assert l == len(self._sha1s)
675
777
        return l
676
778
 
677
779
    __len__ = num_versions
703
805
            for p in self._parents[i]:
704
806
                new_inc.update(inclusions[self._idx_to_name(p)])
705
807
 
706
 
            assert set(new_inc) == set(self.get_ancestry(name)), \
707
 
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
808
            if set(new_inc) != set(self.get_ancestry(name)):
 
809
                raise AssertionError(
 
810
                    'failed %s != %s' 
 
811
                    % (set(new_inc), set(self.get_ancestry(name))))
708
812
            inclusions[name] = new_inc
709
813
 
710
814
        nlines = len(self._weave)
740
844
        # no lines outside of insertion blocks, that deletions are
741
845
        # properly paired, etc.
742
846
 
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
 
 
794
847
    def _imported_parents(self, other, other_idx):
795
848
        """Return list of parents in self corresponding to indexes in other."""
796
849
        new_parents = []
853
906
 
854
907
    WEAVE_SUFFIX = '.weave'
855
908
    
856
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
 
909
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
857
910
        """Create a WeaveFile.
858
911
        
859
912
        :param create: If not True, only open an existing knit.
860
913
        """
861
 
        super(WeaveFile, self).__init__(name, access_mode)
 
914
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
 
915
            allow_reserved=False)
862
916
        self._transport = transport
863
917
        self._filemode = filemode
864
918
        try:
879
933
        self._save()
880
934
        return result
881
935
 
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
 
 
887
936
    def copy_to(self, name, transport):
888
937
        """See VersionedFile.copy_to()."""
889
938
        # as we are all in memory always, just serialise to the new place.
892
941
        sio.seek(0)
893
942
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
894
943
 
895
 
    def create_empty(self, name, transport, filemode=None):
896
 
        return WeaveFile(name, transport, filemode, create=True)
897
 
 
898
944
    def _save(self):
899
945
        """Save the weave."""
900
946
        self._check_write_ok()
901
947
        sio = StringIO()
902
948
        write_weave_v5(self, sio)
903
949
        sio.seek(0)
904
 
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
905
 
                                 sio,
906
 
                                 self._filemode)
 
950
        bytes = sio.getvalue()
 
951
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
 
952
        try:
 
953
            self._transport.put_bytes(path, bytes, self._filemode)
 
954
        except errors.NoSuchFile:
 
955
            self._transport.mkdir(dirname(path))
 
956
            self._transport.put_bytes(path, bytes, self._filemode)
907
957
 
908
958
    @staticmethod
909
959
    def get_suffixes():
910
960
        """See VersionedFile.get_suffixes()."""
911
961
        return [WeaveFile.WEAVE_SUFFIX]
912
962
 
 
963
    def insert_record_stream(self, stream):
 
964
        super(WeaveFile, self).insert_record_stream(stream)
 
965
        self._save()
 
966
 
 
967
    @deprecated_method(one_five)
913
968
    def join(self, other, pb=None, msg=None, version_ids=None,
914
969
             ignore_missing=False):
915
970
        """Join other into self and save."""
1179
1234
if __name__ == '__main__':
1180
1235
    import sys
1181
1236
    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)