~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Robert Collins
  • Date: 2006-01-05 22:30:59 UTC
  • mto: (1534.1.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1536.
  • Revision ID: robertc@robertcollins.net-20060105223059-a8b64f7b47cf12fb
 * bzrlib.osutils.safe_unicode now exists to provide parameter coercion
   for functions that need unicode strings. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
# have slight specializations for different ways its used: annotate,
51
51
# basis for add, get, etc.
52
52
 
53
 
# TODO: Perhaps the API should work only in names to hide the integer
54
 
# indexes from the user?
 
53
# TODO: Probably the API should work only in names to hide the integer
 
54
# indexes from the user.
55
55
 
56
56
# TODO: Is there any potential performance win by having an add()
57
57
# variant that is passed a pre-cooked version of the single basis
58
58
# version?
59
59
 
60
 
# TODO: Have a way to go back and insert a revision V1 that is a parent 
61
 
# of an already-stored revision V2.  This means some lines previously 
62
 
# counted as new in V2 will be discovered to have actually come from V1.  
63
 
# It is probably necessary to insert V1, then compute a whole new diff 
64
 
# from the mashed ancestors to V2.  This must be repeated for every 
65
 
# direct child of V1.  The deltas from V2 to its descendents won't change,
66
 
# although their location within the weave may change.  It may be possible
67
 
# to just adjust the location of those instructions rather than 
68
 
# re-weaving the whole thing.  This is expected to be a fairly rare
69
 
# operation, only used when inserting data that was previously a ghost.
70
 
 
71
 
 
72
 
 
73
 
 
 
60
# TODO: Reweave can possibly be made faster by remembering diffs
 
61
# where the basis and destination are unchanged.
 
62
 
 
63
# FIXME: Sometimes we will be given a parents list for a revision
 
64
# that includes some redundant parents (i.e. already a parent of 
 
65
# something in the list.)  We should eliminate them.  This can 
 
66
# be done fairly efficiently because the sequence numbers constrain
 
67
# the possible relationships.
 
68
 
 
69
 
 
70
import os
74
71
import sha
75
72
from difflib import SequenceMatcher
76
73
 
77
 
 
78
 
 
79
 
 
80
 
class WeaveError(Exception):
81
 
    """Exception in processing weave"""
82
 
 
83
 
 
84
 
class WeaveFormatError(WeaveError):
85
 
    """Weave invariant violated"""
86
 
    
 
74
from bzrlib.trace import mutter
 
75
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
 
76
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
 
77
import bzrlib.errors as errors
 
78
from bzrlib.tsort import topo_sort
 
79
 
87
80
 
88
81
class Weave(object):
89
82
    """weave - versioned text file storage.
185
178
        self._name_map = {}
186
179
        self._weave_name = weave_name
187
180
 
 
181
    def __repr__(self):
 
182
        return "Weave(%r)" % self._weave_name
 
183
 
188
184
 
189
185
    def copy(self):
190
186
        """Return a deep copy of self.
210
206
    def __ne__(self, other):
211
207
        return not self.__eq__(other)
212
208
 
 
209
    def __contains__(self, name):
 
210
        return self._name_map.has_key(name)
213
211
 
214
212
    def maybe_lookup(self, name_or_index):
215
213
        """Convert possible symbolic name to index, or pass through indexes."""
224
222
        try:
225
223
            return self._name_map[name]
226
224
        except KeyError:
227
 
            raise WeaveError("name %r not present in weave %r" %
228
 
                             (name, self._weave_name))
 
225
            raise WeaveRevisionNotPresent(name, self)
229
226
 
 
227
    def names(self):
 
228
        return self._names[:]
230
229
 
231
230
    def iter_names(self):
232
231
        """Yield a list of all names in this weave."""
235
234
    def idx_to_name(self, version):
236
235
        return self._names[version]
237
236
 
238
 
 
239
237
    def _check_repeated_add(self, name, parents, text, sha1):
240
238
        """Check that a duplicated add is OK.
241
239
 
242
240
        If it is, return the (old) index; otherwise raise an exception.
243
241
        """
244
242
        idx = self.lookup(name)
245
 
        if sorted(self._parents[idx]) != sorted(parents):
246
 
            raise WeaveError("name \"%s\" already present in weave "
247
 
                             "with different parents" % name)
248
 
        if sha1 != self._sha1s[idx]:
249
 
            raise WeaveError("name \"%s\" already present in weave "
250
 
                             "with different text" % name)            
 
243
        if sorted(self._parents[idx]) != sorted(parents) \
 
244
            or sha1 != self._sha1s[idx]:
 
245
            raise WeaveRevisionAlreadyPresent(name, self)
251
246
        return idx
252
247
        
253
 
 
254
 
        
255
248
    def add(self, name, parents, text, sha1=None):
256
249
        """Add a single text on top of the weave.
257
250
  
398
391
        return self._parents[version]
399
392
 
400
393
 
 
394
    def parent_names(self, version):
 
395
        """Return version names for parents of a version."""
 
396
        return map(self.idx_to_name, self._parents[self.lookup(version)])
 
397
 
 
398
 
401
399
    def minimal_parents(self, version):
402
400
        """Find the minimal set of parents for the version."""
403
401
        included = self._parents[version]
453
451
        for origin, lineno, text in self._extract(incls):
454
452
            yield origin, text
455
453
 
456
 
 
457
454
    def _walk(self):
458
455
        """Walk the weave.
459
456
 
481
478
                elif c == ']':
482
479
                    dset.remove(v)
483
480
                else:
484
 
                    raise WeaveFormatError('unexpected instruction %r'
485
 
                                           % v)
 
481
                    raise WeaveFormatError('unexpected instruction %r' % v)
486
482
            else:
487
483
                assert isinstance(l, basestring)
488
484
                assert istack
489
485
                yield lineno, istack[-1], dset, l
490
486
            lineno += 1
491
487
 
492
 
 
 
488
        if istack:
 
489
            raise WeaveFormatError("unclosed insertion blocks "
 
490
                    "at end of weave: %s" % istack)
 
491
        if dset:
 
492
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
493
                                   % dset)
493
494
 
494
495
    def _extract(self, versions):
495
496
        """Yield annotation of lines in included set.
542
543
                if isactive:
543
544
                    result.append((istack[-1], lineno, l))
544
545
            lineno += 1
545
 
 
546
546
        if istack:
547
 
            raise WFE("unclosed insertion blocks at end of weave",
548
 
                                   istack)
 
547
            raise WeaveFormatError("unclosed insertion blocks "
 
548
                    "at end of weave: %s" % istack)
549
549
        if dset:
550
 
            raise WFE("unclosed deletion blocks at end of weave",
551
 
                                   dset)
552
 
 
 
550
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
551
                                   % dset)
553
552
        return result
554
 
    
555
553
 
556
554
 
557
555
    def get_iter(self, name_or_index):
558
556
        """Yield lines for the specified version."""
559
557
        incls = [self.maybe_lookup(name_or_index)]
 
558
        if len(incls) == 1:
 
559
            index = incls[0]
 
560
            cur_sha = sha.new()
 
561
        else:
 
562
            # We don't have sha1 sums for multiple entries
 
563
            cur_sha = None
560
564
        for origin, lineno, line in self._extract(incls):
 
565
            if cur_sha:
 
566
                cur_sha.update(line)
561
567
            yield line
 
568
        if cur_sha:
 
569
            expected_sha1 = self._sha1s[index]
 
570
            measured_sha1 = cur_sha.hexdigest() 
 
571
            if measured_sha1 != expected_sha1:
 
572
                raise errors.WeaveInvalidChecksum(
 
573
                        'file %s, revision %s, expected: %s, measured %s' 
 
574
                        % (self._weave_name, self._names[index],
 
575
                           expected_sha1, measured_sha1))
562
576
 
563
577
 
564
578
    def get_text(self, name_or_index):
573
587
    get = get_lines
574
588
 
575
589
 
 
590
    def get_sha1(self, name):
 
591
        """Get the stored sha1 sum for the given revision.
 
592
        
 
593
        :param name: The name of the version to lookup
 
594
        """
 
595
        return self._sha1s[self.lookup(name)]
 
596
 
576
597
    def mash_iter(self, included):
577
598
        """Return composed version of multiple included versions."""
578
599
        included = map(self.maybe_lookup, included)
598
619
    def __len__(self):
599
620
        return self.numversions()
600
621
 
601
 
 
602
622
    def check(self, progress_bar=None):
603
623
        # check no circular inclusions
604
624
        for version in range(self.numversions()):
609
629
                    raise WeaveFormatError("invalid included version %d for index %d"
610
630
                                           % (inclusions[-1], version))
611
631
 
612
 
        # try extracting all versions; this is a bit slow and parallel
613
 
        # extraction could be used
 
632
        # try extracting all versions; parallel extraction is used
614
633
        nv = self.numversions()
 
634
        sha1s = [sha.new() for i in range(nv)]
 
635
        texts = [[] for i in range(nv)]
 
636
        inclusions = []
 
637
        for i in range(nv):
 
638
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
 
639
            # The problem is that set membership is much more expensive
 
640
            new_inc = set([i])
 
641
            for p in self._parents[i]:
 
642
                new_inc.update(inclusions[p])
 
643
 
 
644
            #assert set(new_inc) == self.inclusions([i]), 'failed %s != %s' % (new_inc, self.inclusions([i]))
 
645
            inclusions.append(new_inc)
 
646
 
 
647
        nlines = len(self._weave)
 
648
 
 
649
        update_text = 'checking weave'
 
650
        if self._weave_name:
 
651
            short_name = os.path.basename(self._weave_name)
 
652
            update_text = 'checking %s' % (short_name,)
 
653
            update_text = update_text[:25]
 
654
 
 
655
        for lineno, insert, deleteset, line in self._walk():
 
656
            if progress_bar:
 
657
                progress_bar.update(update_text, lineno, nlines)
 
658
 
 
659
            for j, j_inc in enumerate(inclusions):
 
660
                # The active inclusion must be an ancestor,
 
661
                # and no ancestors must have deleted this line,
 
662
                # because we don't support resurrection.
 
663
                if (insert in j_inc) and not (deleteset & j_inc):
 
664
                    sha1s[j].update(line)
 
665
 
615
666
        for version in range(nv):
616
 
            if progress_bar:
617
 
                progress_bar.update('checking text', version, nv)
618
 
            s = sha.new()
619
 
            for l in self.get_iter(version):
620
 
                s.update(l)
621
 
            hd = s.hexdigest()
 
667
            hd = sha1s[version].hexdigest()
622
668
            expected = self._sha1s[version]
623
669
            if hd != expected:
624
 
                raise WeaveError("mismatched sha1 for version %d; "
625
 
                                 "got %s, expected %s"
626
 
                                 % (version, hd, expected))
 
670
                raise errors.WeaveInvalidChecksum(
 
671
                        "mismatched sha1 for version %s: "
 
672
                        "got %s, expected %s"
 
673
                        % (self._names[version], hd, expected))
627
674
 
628
675
        # TODO: check insertions are properly nested, that there are
629
676
        # no lines outside of insertion blocks, that deletions are
630
677
        # properly paired, etc.
631
678
 
632
 
 
633
 
 
634
 
    def merge(self, merge_versions):
635
 
        """Automerge and mark conflicts between versions.
636
 
 
637
 
        This returns a sequence, each entry describing alternatives
638
 
        for a chunk of the file.  Each of the alternatives is given as
639
 
        a list of lines.
640
 
 
641
 
        If there is a chunk of the file where there's no diagreement,
642
 
        only one alternative is given.
643
 
        """
644
 
        # approach: find the included versions common to all the
645
 
        # merged versions
646
 
        raise NotImplementedError()
647
 
 
648
 
 
649
 
 
650
679
    def _delta(self, included, lines):
651
680
        """Return changes from basis to new revision.
652
681
 
721
750
        lines_a = []
722
751
        lines_b = []
723
752
        ch_a = ch_b = False
724
 
 
 
753
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
 
754
        # conflicted regions), rather than just inserting the markers.
 
755
        # 
 
756
        # TODO: Show some version information (e.g. author, date) on 
 
757
        # conflicted regions.
725
758
        for state, line in plan:
726
759
            if state == 'unchanged' or state == 'killed-both':
727
760
                # resync and flush queued conflicts changes if any
735
768
                elif lines_a == lines_b:
736
769
                    for l in lines_a: yield l
737
770
                else:
738
 
                    yield '<<<<\n'
 
771
                    yield '<<<<<<<\n'
739
772
                    for l in lines_a: yield l
740
 
                    yield '====\n'
 
773
                    yield '=======\n'
741
774
                    for l in lines_b: yield l
742
 
                    yield '>>>>\n'
 
775
                    yield '>>>>>>>\n'
743
776
 
744
777
                del lines_a[:]
745
778
                del lines_b[:]
765
798
                                 'killed-both'), \
766
799
                       state
767
800
 
768
 
                
 
801
 
769
802
    def join(self, other):
 
803
        import sys, time
770
804
        """Integrate versions from other into this weave.
771
805
 
772
806
        The resulting weave contains all the history of both weaves; 
774
808
        retrieved from self after this call.
775
809
 
776
810
        It is illegal for the two weaves to contain different values 
777
 
        for any version.
 
811
        or different parents for any version.  See also reweave().
778
812
        """
779
813
        if other.numversions() == 0:
780
814
            return          # nothing to update, easy
 
815
        # two loops so that we do not change ourselves before verifying it
 
816
        # will be ok
781
817
        # work through in index order to make sure we get all dependencies
782
818
        for other_idx, name in enumerate(other._names):
783
 
            # TODO: If all the parents of the other version are already 
 
819
            self._check_version_consistent(other, other_idx, name)
 
820
 
 
821
        merged = 0
 
822
        processed = 0
 
823
        time0 = time.time( )
 
824
        for other_idx, name in enumerate(other._names):
 
825
            # TODO: If all the parents of the other version are already
784
826
            # present then we can avoid some work by just taking the delta
785
827
            # and adjusting the offsets.
786
 
            if self._check_version_consistent(other, other_idx, name):
787
 
                continue
788
828
            new_parents = self._imported_parents(other, other_idx)
 
829
            sha1 = other._sha1s[other_idx]
 
830
 
 
831
            processed += 1
 
832
           
 
833
            if name in self._names:
 
834
                idx = self.lookup(name)
 
835
                n1 = map(other.idx_to_name, other._parents[other_idx] )
 
836
                n2 = map(self.idx_to_name, self._parents[other_idx] )
 
837
                if sha1 ==  self._sha1s[idx] and n1 == n2:
 
838
                        continue
 
839
 
 
840
            merged += 1
789
841
            lines = other.get_lines(other_idx)
790
 
            sha1 = other._sha1s[other_idx]
791
842
            self.add(name, new_parents, lines, sha1)
792
843
 
793
 
 
 
844
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
845
                merged,processed,self._weave_name, time.time( )-time0))
 
846
 
 
847
 
 
848
 
 
849
 
794
850
    def _imported_parents(self, other, other_idx):
795
851
        """Return list of parents in self corresponding to indexes in other."""
796
852
        new_parents = []
828
884
            n1.sort()
829
885
            n2.sort()
830
886
            if n1 != n2:
831
 
                raise WeaveError("inconsistent parents for version {%s}: "
832
 
                                 "%s vs %s"
833
 
                                 % (name, n1, n2))
 
887
                raise WeaveParentMismatch("inconsistent parents "
 
888
                    "for version {%s}: %s vs %s" % (name, n1, n2))
834
889
            else:
835
890
                return True         # ok!
836
891
        else:
837
892
            return False
838
893
 
 
894
    def reweave(self, other):
 
895
        """Reweave self with other."""
 
896
        new_weave = reweave(self, other)
 
897
        for attr in self.__slots__:
 
898
            setattr(self, attr, getattr(new_weave, attr))
 
899
 
 
900
 
 
901
def reweave(wa, wb):
 
902
    """Combine two weaves and return the result.
 
903
 
 
904
    This works even if a revision R has different parents in 
 
905
    wa and wb.  In the resulting weave all the parents are given.
 
906
 
 
907
    This is done by just building up a new weave, maintaining ordering 
 
908
    of the versions in the two inputs.  More efficient approaches
 
909
    might be possible but it should only be necessary to do 
 
910
    this operation rarely, when a new previously ghost version is 
 
911
    inserted.
 
912
    """
 
913
    wr = Weave()
 
914
    ia = ib = 0
 
915
    queue_a = range(wa.numversions())
 
916
    queue_b = range(wb.numversions())
 
917
    # first determine combined parents of all versions
 
918
    # map from version name -> all parent names
 
919
    combined_parents = _reweave_parent_graphs(wa, wb)
 
920
    mutter("combined parents: %r", combined_parents)
 
921
    order = topo_sort(combined_parents.iteritems())
 
922
    mutter("order to reweave: %r", order)
 
923
    for name in order:
 
924
        if name in wa._name_map:
 
925
            lines = wa.get_lines(name)
 
926
            if name in wb._name_map:
 
927
                assert lines == wb.get_lines(name)
 
928
        else:
 
929
            lines = wb.get_lines(name)
 
930
        wr.add(name, combined_parents[name], lines)
 
931
    return wr
 
932
 
 
933
 
 
934
def _reweave_parent_graphs(wa, wb):
 
935
    """Return combined parent ancestry for two weaves.
 
936
    
 
937
    Returned as a list of (version_name, set(parent_names))"""
 
938
    combined = {}
 
939
    for weave in [wa, wb]:
 
940
        for idx, name in enumerate(weave._names):
 
941
            p = combined.setdefault(name, set())
 
942
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
943
    return combined
 
944
 
839
945
 
840
946
def weave_toc(w):
841
947
    """Show the weave's table-of-contents"""
1083
1189
    return ret
1084
1190
 
1085
1191
 
 
1192
def lsprofile_main(argv): 
 
1193
    from bzrlib.lsprof import profile
 
1194
    ret,stats = profile(main, argv)
 
1195
    stats.sort()
 
1196
    stats.pprint()
 
1197
    return ret
 
1198
 
 
1199
 
1086
1200
if __name__ == '__main__':
1087
1201
    import sys
1088
1202
    if '--profile' in sys.argv:
1089
1203
        args = sys.argv[:]
1090
1204
        args.remove('--profile')
1091
1205
        sys.exit(profile_main(args))
 
1206
    elif '--lsprof' in sys.argv:
 
1207
        args = sys.argv[:]
 
1208
        args.remove('--lsprof')
 
1209
        sys.exit(lsprofile_main(args))
1092
1210
    else:
1093
1211
        sys.exit(main(sys.argv))
1094
1212