~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Robert Collins
  • Date: 2005-10-11 03:19:29 UTC
  • Revision ID: robertc@robertcollins.net-20051011031929-2d523107133c43be
further tuning of pull, do not do a local merge or fetch at all, if the remote branch is no newer than we are

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: Probably the API should work only in names to hide the integer
54
 
# indexes from the user.
 
53
# TODO: Perhaps 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: 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
 
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
 
71
74
import sha
72
75
from difflib import SequenceMatcher
73
76
 
74
77
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
 
 
 
78
 
 
79
 
 
80
class WeaveError(Exception):
 
81
    """Exception in processing weave"""
 
82
 
 
83
 
 
84
class WeaveFormatError(WeaveError):
 
85
    """Weave invariant violated"""
 
86
 
 
87
 
 
88
class WeaveParentMismatch(WeaveError):
 
89
    """Parents are mismatched between two revisions."""
 
90
    
80
91
 
81
92
class Weave(object):
82
93
    """weave - versioned text file storage.
178
189
        self._name_map = {}
179
190
        self._weave_name = weave_name
180
191
 
181
 
    def __repr__(self):
182
 
        return "Weave(%r)" % self._weave_name
183
 
 
184
192
 
185
193
    def copy(self):
186
194
        """Return a deep copy of self.
206
214
    def __ne__(self, other):
207
215
        return not self.__eq__(other)
208
216
 
209
 
    def __contains__(self, name):
210
 
        return self._name_map.has_key(name)
211
217
 
212
218
    def maybe_lookup(self, name_or_index):
213
219
        """Convert possible symbolic name to index, or pass through indexes."""
222
228
        try:
223
229
            return self._name_map[name]
224
230
        except KeyError:
225
 
            raise WeaveRevisionNotPresent(name, self)
 
231
            raise WeaveError("name %r not present in weave %r" %
 
232
                             (name, self._weave_name))
226
233
 
227
 
    def names(self):
228
 
        return self._names[:]
229
234
 
230
235
    def iter_names(self):
231
236
        """Yield a list of all names in this weave."""
234
239
    def idx_to_name(self, version):
235
240
        return self._names[version]
236
241
 
 
242
 
237
243
    def _check_repeated_add(self, name, parents, text, sha1):
238
244
        """Check that a duplicated add is OK.
239
245
 
240
246
        If it is, return the (old) index; otherwise raise an exception.
241
247
        """
242
248
        idx = self.lookup(name)
243
 
        if sorted(self._parents[idx]) != sorted(parents) \
244
 
            or sha1 != self._sha1s[idx]:
245
 
            raise WeaveRevisionAlreadyPresent(name, self)
 
249
        if sorted(self._parents[idx]) != sorted(parents):
 
250
            raise WeaveError("name \"%s\" already present in weave "
 
251
                             "with different parents" % name)
 
252
        if sha1 != self._sha1s[idx]:
 
253
            raise WeaveError("name \"%s\" already present in weave "
 
254
                             "with different text" % name)            
246
255
        return idx
247
256
        
 
257
 
 
258
        
248
259
    def add(self, name, parents, text, sha1=None):
249
260
        """Add a single text on top of the weave.
250
261
  
451
462
        for origin, lineno, text in self._extract(incls):
452
463
            yield origin, text
453
464
 
 
465
 
454
466
    def _walk(self):
455
467
        """Walk the weave.
456
468
 
478
490
                elif c == ']':
479
491
                    dset.remove(v)
480
492
                else:
481
 
                    raise WeaveFormatError('unexpected instruction %r' % v)
 
493
                    raise WeaveFormatError('unexpected instruction %r'
 
494
                                           % v)
482
495
            else:
483
496
                assert isinstance(l, basestring)
484
497
                assert istack
485
498
                yield lineno, istack[-1], dset, l
486
499
            lineno += 1
487
500
 
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)
 
501
 
494
502
 
495
503
    def _extract(self, versions):
496
504
        """Yield annotation of lines in included set.
543
551
                if isactive:
544
552
                    result.append((istack[-1], lineno, l))
545
553
            lineno += 1
 
554
 
546
555
        if istack:
547
 
            raise WeaveFormatError("unclosed insertion blocks "
548
 
                    "at end of weave: %s" % istack)
 
556
            raise WFE("unclosed insertion blocks at end of weave",
 
557
                                   istack)
549
558
        if dset:
550
 
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
551
 
                                   % dset)
 
559
            raise WFE("unclosed deletion blocks at end of weave",
 
560
                                   dset)
 
561
 
552
562
        return result
 
563
    
553
564
 
554
565
 
555
566
    def get_iter(self, name_or_index):
556
567
        """Yield lines for the specified version."""
557
568
        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
564
569
        for origin, lineno, line in self._extract(incls):
565
 
            if cur_sha:
566
 
                cur_sha.update(line)
567
570
            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))
576
571
 
577
572
 
578
573
    def get_text(self, name_or_index):
587
582
    get = get_lines
588
583
 
589
584
 
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
 
 
597
585
    def mash_iter(self, included):
598
586
        """Return composed version of multiple included versions."""
599
587
        included = map(self.maybe_lookup, included)
619
607
    def __len__(self):
620
608
        return self.numversions()
621
609
 
 
610
 
622
611
    def check(self, progress_bar=None):
623
612
        # check no circular inclusions
624
613
        for version in range(self.numversions()):
629
618
                    raise WeaveFormatError("invalid included version %d for index %d"
630
619
                                           % (inclusions[-1], version))
631
620
 
632
 
        # try extracting all versions; parallel extraction is used
 
621
        # try extracting all versions; this is a bit slow and parallel
 
622
        # extraction could be used
633
623
        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():
 
624
        for version in range(nv):
656
625
            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
 
 
666
 
        for version in range(nv):
667
 
            hd = sha1s[version].hexdigest()
 
626
                progress_bar.update('checking text', version, nv)
 
627
            s = sha.new()
 
628
            for l in self.get_iter(version):
 
629
                s.update(l)
 
630
            hd = s.hexdigest()
668
631
            expected = self._sha1s[version]
669
632
            if hd != expected:
670
 
                raise errors.WeaveInvalidChecksum(
671
 
                        "mismatched sha1 for version %s: "
672
 
                        "got %s, expected %s"
673
 
                        % (self._names[version], hd, expected))
 
633
                raise WeaveError("mismatched sha1 for version %d; "
 
634
                                 "got %s, expected %s"
 
635
                                 % (version, hd, expected))
674
636
 
675
637
        # TODO: check insertions are properly nested, that there are
676
638
        # no lines outside of insertion blocks, that deletions are
677
639
        # properly paired, etc.
678
640
 
 
641
 
 
642
 
 
643
    def merge(self, merge_versions):
 
644
        """Automerge and mark conflicts between versions.
 
645
 
 
646
        This returns a sequence, each entry describing alternatives
 
647
        for a chunk of the file.  Each of the alternatives is given as
 
648
        a list of lines.
 
649
 
 
650
        If there is a chunk of the file where there's no diagreement,
 
651
        only one alternative is given.
 
652
        """
 
653
        # approach: find the included versions common to all the
 
654
        # merged versions
 
655
        raise NotImplementedError()
 
656
 
 
657
 
 
658
 
679
659
    def _delta(self, included, lines):
680
660
        """Return changes from basis to new revision.
681
661
 
750
730
        lines_a = []
751
731
        lines_b = []
752
732
        ch_a = ch_b = False
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.
 
733
 
758
734
        for state, line in plan:
759
735
            if state == 'unchanged' or state == 'killed-both':
760
736
                # resync and flush queued conflicts changes if any
768
744
                elif lines_a == lines_b:
769
745
                    for l in lines_a: yield l
770
746
                else:
771
 
                    yield '<<<<<<<\n'
 
747
                    yield '<<<<\n'
772
748
                    for l in lines_a: yield l
773
 
                    yield '=======\n'
 
749
                    yield '====\n'
774
750
                    for l in lines_b: yield l
775
 
                    yield '>>>>>>>\n'
 
751
                    yield '>>>>\n'
776
752
 
777
753
                del lines_a[:]
778
754
                del lines_b[:]
798
774
                                 'killed-both'), \
799
775
                       state
800
776
 
801
 
 
 
777
                
802
778
    def join(self, other):
803
 
        import sys, time
804
779
        """Integrate versions from other into this weave.
805
780
 
806
781
        The resulting weave contains all the history of both weaves; 
816
791
        # will be ok
817
792
        # work through in index order to make sure we get all dependencies
818
793
        for other_idx, name in enumerate(other._names):
819
 
            self._check_version_consistent(other, other_idx, name)
820
 
 
821
 
        merged = 0
822
 
        processed = 0
823
 
        time0 = time.time( )
 
794
            if self._check_version_consistent(other, other_idx, name):
 
795
                continue
824
796
        for other_idx, name in enumerate(other._names):
825
 
            # TODO: If all the parents of the other version are already
 
797
            # TODO: If all the parents of the other version are already 
826
798
            # present then we can avoid some work by just taking the delta
827
799
            # and adjusting the offsets.
828
800
            new_parents = self._imported_parents(other, other_idx)
 
801
            lines = other.get_lines(other_idx)
829
802
            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
841
 
            lines = other.get_lines(other_idx)
842
803
            self.add(name, new_parents, lines, sha1)
843
804
 
844
 
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
845
 
                merged,processed,self._weave_name, time.time( )-time0))
846
 
 
847
 
 
848
 
 
849
 
 
 
805
 
850
806
    def _imported_parents(self, other, other_idx):
851
807
        """Return list of parents in self corresponding to indexes in other."""
852
808
        new_parents = []
918
874
    # map from version name -> all parent names
919
875
    combined_parents = _reweave_parent_graphs(wa, wb)
920
876
    mutter("combined parents: %r", combined_parents)
921
 
    order = topo_sort(combined_parents.iteritems())
 
877
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
922
878
    mutter("order to reweave: %r", order)
923
879
    for name in order:
924
880
        if name in wa._name_map:
943
899
    return combined
944
900
 
945
901
 
 
902
def _make_reweave_order(wa_order, wb_order, combined_parents):
 
903
    """Return an order to reweave versions respecting parents."""
 
904
    done = set()
 
905
    result = []
 
906
    ia = ib = 0
 
907
    next_a = next_b = None
 
908
    len_a = len(wa_order)
 
909
    len_b = len(wb_order)
 
910
    while ia < len(wa_order) or ib < len(wb_order):
 
911
        if ia < len_a:
 
912
            next_a = wa_order[ia]
 
913
            if next_a in done:
 
914
                ia += 1
 
915
                continue
 
916
            if combined_parents[next_a].issubset(done):
 
917
                ia += 1
 
918
                result.append(next_a)
 
919
                done.add(next_a)
 
920
                continue
 
921
        if ib < len_b:
 
922
            next_b = wb_order[ib]
 
923
            if next_b in done:
 
924
                ib += 1
 
925
                continue
 
926
            elif combined_parents[next_b].issubset(done):
 
927
                ib += 1
 
928
                result.append(next_b)
 
929
                done.add(next_b)
 
930
                continue
 
931
        raise WeaveError("don't know how to reweave at {%s} and {%s}"
 
932
                         % (next_a, next_b))
 
933
    return result
 
934
 
 
935
 
946
936
def weave_toc(w):
947
937
    """Show the weave's table-of-contents"""
948
938
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
1189
1179
    return ret
1190
1180
 
1191
1181
 
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
 
 
1200
1182
if __name__ == '__main__':
1201
1183
    import sys
1202
1184
    if '--profile' in sys.argv:
1203
1185
        args = sys.argv[:]
1204
1186
        args.remove('--profile')
1205
1187
        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))
1210
1188
    else:
1211
1189
        sys.exit(main(sys.argv))
1212
1190