~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-18 02:33:47 UTC
  • mfrom: (1534.1.24 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060218023347-0952c65f668bfd68
Merge Robert Collins integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
67
67
# the possible relationships.
68
68
 
69
69
 
 
70
import os
70
71
import sha
71
72
from difflib import SequenceMatcher
72
73
 
73
74
from bzrlib.trace import mutter
74
 
 
75
 
 
76
 
class WeaveError(Exception):
77
 
    """Exception in processing weave"""
78
 
 
79
 
 
80
 
class WeaveFormatError(WeaveError):
81
 
    """Weave invariant violated"""
82
 
 
83
 
 
84
 
class WeaveParentMismatch(WeaveError):
85
 
    """Parents are mismatched between two revisions."""
86
 
    
 
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
  
458
451
        for origin, lineno, text in self._extract(incls):
459
452
            yield origin, text
460
453
 
461
 
 
462
454
    def _walk(self):
463
455
        """Walk the weave.
464
456
 
486
478
                elif c == ']':
487
479
                    dset.remove(v)
488
480
                else:
489
 
                    raise WeaveFormatError('unexpected instruction %r'
490
 
                                           % v)
 
481
                    raise WeaveFormatError('unexpected instruction %r' % v)
491
482
            else:
492
483
                assert isinstance(l, basestring)
493
484
                assert istack
494
485
                yield lineno, istack[-1], dset, l
495
486
            lineno += 1
496
487
 
497
 
 
 
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)
498
494
 
499
495
    def _extract(self, versions):
500
496
        """Yield annotation of lines in included set.
547
543
                if isactive:
548
544
                    result.append((istack[-1], lineno, l))
549
545
            lineno += 1
550
 
 
551
546
        if istack:
552
 
            raise WFE("unclosed insertion blocks at end of weave",
553
 
                                   istack)
 
547
            raise WeaveFormatError("unclosed insertion blocks "
 
548
                    "at end of weave: %s" % istack)
554
549
        if dset:
555
 
            raise WFE("unclosed deletion blocks at end of weave",
556
 
                                   dset)
557
 
 
 
550
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
551
                                   % dset)
558
552
        return result
559
 
    
560
553
 
561
554
 
562
555
    def get_iter(self, name_or_index):
563
556
        """Yield lines for the specified version."""
564
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
565
564
        for origin, lineno, line in self._extract(incls):
 
565
            if cur_sha:
 
566
                cur_sha.update(line)
566
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))
567
576
 
568
577
 
569
578
    def get_text(self, name_or_index):
578
587
    get = get_lines
579
588
 
580
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
 
581
597
    def mash_iter(self, included):
582
598
        """Return composed version of multiple included versions."""
583
599
        included = map(self.maybe_lookup, included)
603
619
    def __len__(self):
604
620
        return self.numversions()
605
621
 
606
 
 
607
622
    def check(self, progress_bar=None):
608
623
        # check no circular inclusions
609
624
        for version in range(self.numversions()):
614
629
                    raise WeaveFormatError("invalid included version %d for index %d"
615
630
                                           % (inclusions[-1], version))
616
631
 
617
 
        # try extracting all versions; this is a bit slow and parallel
618
 
        # extraction could be used
 
632
        # try extracting all versions; parallel extraction is used
619
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
 
620
666
        for version in range(nv):
621
 
            if progress_bar:
622
 
                progress_bar.update('checking text', version, nv)
623
 
            s = sha.new()
624
 
            for l in self.get_iter(version):
625
 
                s.update(l)
626
 
            hd = s.hexdigest()
 
667
            hd = sha1s[version].hexdigest()
627
668
            expected = self._sha1s[version]
628
669
            if hd != expected:
629
 
                raise WeaveError("mismatched sha1 for version %d; "
630
 
                                 "got %s, expected %s"
631
 
                                 % (version, hd, expected))
 
670
                raise errors.WeaveInvalidChecksum(
 
671
                        "mismatched sha1 for version %s: "
 
672
                        "got %s, expected %s"
 
673
                        % (self._names[version], hd, expected))
632
674
 
633
675
        # TODO: check insertions are properly nested, that there are
634
676
        # no lines outside of insertion blocks, that deletions are
635
677
        # properly paired, etc.
636
678
 
637
 
 
638
 
 
639
 
    def merge(self, merge_versions):
640
 
        """Automerge and mark conflicts between versions.
641
 
 
642
 
        This returns a sequence, each entry describing alternatives
643
 
        for a chunk of the file.  Each of the alternatives is given as
644
 
        a list of lines.
645
 
 
646
 
        If there is a chunk of the file where there's no diagreement,
647
 
        only one alternative is given.
648
 
        """
649
 
        # approach: find the included versions common to all the
650
 
        # merged versions
651
 
        raise NotImplementedError()
652
 
 
653
 
 
654
 
 
655
679
    def _delta(self, included, lines):
656
680
        """Return changes from basis to new revision.
657
681
 
726
750
        lines_a = []
727
751
        lines_b = []
728
752
        ch_a = ch_b = False
729
 
 
 
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.
730
758
        for state, line in plan:
731
759
            if state == 'unchanged' or state == 'killed-both':
732
760
                # resync and flush queued conflicts changes if any
740
768
                elif lines_a == lines_b:
741
769
                    for l in lines_a: yield l
742
770
                else:
743
 
                    yield '<<<<\n'
 
771
                    yield '<<<<<<<\n'
744
772
                    for l in lines_a: yield l
745
 
                    yield '====\n'
 
773
                    yield '=======\n'
746
774
                    for l in lines_b: yield l
747
 
                    yield '>>>>\n'
 
775
                    yield '>>>>>>>\n'
748
776
 
749
777
                del lines_a[:]
750
778
                del lines_b[:]
770
798
                                 'killed-both'), \
771
799
                       state
772
800
 
773
 
                
774
 
    def join(self, other):
 
801
 
 
802
    def join(self, other, pb=None, msg=None):
 
803
        import sys, time
775
804
        """Integrate versions from other into this weave.
776
805
 
777
806
        The resulting weave contains all the history of both weaves; 
780
809
 
781
810
        It is illegal for the two weaves to contain different values 
782
811
        or different parents for any version.  See also reweave().
 
812
 
 
813
        :param other: The other weave to pull into this one
 
814
        :param pb: An optional progress bar
 
815
        :param msg: An optional message to display for progress
783
816
        """
784
817
        if other.numversions() == 0:
785
818
            return          # nothing to update, easy
786
819
        # two loops so that we do not change ourselves before verifying it
787
820
        # will be ok
788
821
        # work through in index order to make sure we get all dependencies
789
 
        for other_idx, name in enumerate(other._names):
790
 
            if self._check_version_consistent(other, other_idx, name):
791
 
                continue
792
 
        for other_idx, name in enumerate(other._names):
793
 
            # TODO: If all the parents of the other version are already 
 
822
        names_to_join = []
 
823
        processed = 0
 
824
        for other_idx, name in enumerate(other._names):
 
825
            self._check_version_consistent(other, other_idx, name)
 
826
            sha1 = other._sha1s[other_idx]
 
827
 
 
828
            processed += 1
 
829
 
 
830
            if name in self._name_map:
 
831
                idx = self.lookup(name)
 
832
                n1 = set(map(other.idx_to_name, other._parents[other_idx]))
 
833
                n2 = set(map(self.idx_to_name, self._parents[idx]))
 
834
                if sha1 ==  self._sha1s[idx] and n1 == n2:
 
835
                        continue
 
836
 
 
837
            names_to_join.append((other_idx, name))
 
838
 
 
839
        if pb and not msg:
 
840
            msg = 'weave join'
 
841
 
 
842
        merged = 0
 
843
        time0 = time.time( )
 
844
        for other_idx, name in names_to_join:
 
845
            # TODO: If all the parents of the other version are already
794
846
            # present then we can avoid some work by just taking the delta
795
847
            # and adjusting the offsets.
796
848
            new_parents = self._imported_parents(other, other_idx)
 
849
            sha1 = other._sha1s[other_idx]
 
850
 
 
851
            merged += 1
 
852
 
 
853
            if pb:
 
854
                pb.update(msg, merged, len(names_to_join))
 
855
           
797
856
            lines = other.get_lines(other_idx)
798
 
            sha1 = other._sha1s[other_idx]
799
857
            self.add(name, new_parents, lines, sha1)
800
858
 
801
 
 
 
859
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
860
                merged, processed, self._weave_name, time.time( )-time0))
 
861
 
802
862
    def _imported_parents(self, other, other_idx):
803
863
        """Return list of parents in self corresponding to indexes in other."""
804
864
        new_parents = []
831
891
                                 % (name))
832
892
            self_parents = self._parents[this_idx]
833
893
            other_parents = other._parents[other_idx]
834
 
            n1 = [self._names[i] for i in self_parents]
835
 
            n2 = [other._names[i] for i in other_parents]
836
 
            n1.sort()
837
 
            n2.sort()
 
894
            n1 = set([self._names[i] for i in self_parents])
 
895
            n2 = set([other._names[i] for i in other_parents])
838
896
            if n1 != n2:
839
897
                raise WeaveParentMismatch("inconsistent parents "
840
898
                    "for version {%s}: %s vs %s" % (name, n1, n2))
843
901
        else:
844
902
            return False
845
903
 
846
 
    def reweave(self, other):
847
 
        """Reweave self with other."""
848
 
        new_weave = reweave(self, other)
 
904
    def reweave(self, other, pb=None, msg=None):
 
905
        """Reweave self with other.
 
906
 
 
907
        :param other: The other weave to merge
 
908
        :param pb: An optional progress bar, indicating how far done we are
 
909
        :param msg: An optional message for the progress
 
910
        """
 
911
        new_weave = reweave(self, other, pb=pb, msg=msg)
849
912
        for attr in self.__slots__:
850
913
            setattr(self, attr, getattr(new_weave, attr))
851
914
 
852
915
 
853
 
def reweave(wa, wb):
 
916
def reweave(wa, wb, pb=None, msg=None):
854
917
    """Combine two weaves and return the result.
855
918
 
856
919
    This works even if a revision R has different parents in 
861
924
    might be possible but it should only be necessary to do 
862
925
    this operation rarely, when a new previously ghost version is 
863
926
    inserted.
 
927
 
 
928
    :param pb: An optional progress bar, indicating how far done we are
 
929
    :param msg: An optional message for the progress
864
930
    """
865
931
    wr = Weave()
866
932
    ia = ib = 0
870
936
    # map from version name -> all parent names
871
937
    combined_parents = _reweave_parent_graphs(wa, wb)
872
938
    mutter("combined parents: %r", combined_parents)
873
 
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
 
939
    order = topo_sort(combined_parents.iteritems())
874
940
    mutter("order to reweave: %r", order)
875
 
    for name in order:
 
941
 
 
942
    if pb and not msg:
 
943
        msg = 'reweave'
 
944
 
 
945
    for idx, name in enumerate(order):
 
946
        if pb:
 
947
            pb.update(msg, idx, len(order))
876
948
        if name in wa._name_map:
877
949
            lines = wa.get_lines(name)
878
950
            if name in wb._name_map:
879
 
                assert lines == wb.get_lines(name)
 
951
                lines_b = wb.get_lines(name)
 
952
                if lines != lines_b:
 
953
                    mutter('Weaves differ on content. rev_id {%s}', name)
 
954
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
 
955
                    import difflib
 
956
                    lines = list(difflib.unified_diff(lines, lines_b,
 
957
                            wa._weave_name, wb._weave_name))
 
958
                    mutter('lines:\n%s', ''.join(lines))
 
959
                    raise errors.WeaveTextDiffers(name, wa, wb)
880
960
        else:
881
961
            lines = wb.get_lines(name)
882
962
        wr.add(name, combined_parents[name], lines)
895
975
    return combined
896
976
 
897
977
 
898
 
def _make_reweave_order(wa_order, wb_order, combined_parents):
899
 
    """Return an order to reweave versions respecting parents."""
900
 
    done = set()
901
 
    result = []
902
 
    ia = ib = 0
903
 
    next_a = next_b = None
904
 
    len_a = len(wa_order)
905
 
    len_b = len(wb_order)
906
 
    while ia < len(wa_order) or ib < len(wb_order):
907
 
        if ia < len_a:
908
 
            next_a = wa_order[ia]
909
 
            if next_a in done:
910
 
                ia += 1
911
 
                continue
912
 
            if combined_parents[next_a].issubset(done):
913
 
                ia += 1
914
 
                result.append(next_a)
915
 
                done.add(next_a)
916
 
                continue
917
 
        if ib < len_b:
918
 
            next_b = wb_order[ib]
919
 
            if next_b in done:
920
 
                ib += 1
921
 
                continue
922
 
            elif combined_parents[next_b].issubset(done):
923
 
                ib += 1
924
 
                result.append(next_b)
925
 
                done.add(next_b)
926
 
                continue
927
 
        raise WeaveError("don't know how to reweave at {%s} and {%s}"
928
 
                         % (next_a, next_b))
929
 
    return result
930
 
 
931
 
 
932
978
def weave_toc(w):
933
979
    """Show the weave's table-of-contents"""
934
980
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
1175
1221
    return ret
1176
1222
 
1177
1223
 
 
1224
def lsprofile_main(argv): 
 
1225
    from bzrlib.lsprof import profile
 
1226
    ret,stats = profile(main, argv)
 
1227
    stats.sort()
 
1228
    stats.pprint()
 
1229
    return ret
 
1230
 
 
1231
 
1178
1232
if __name__ == '__main__':
1179
1233
    import sys
1180
1234
    if '--profile' in sys.argv:
1181
1235
        args = sys.argv[:]
1182
1236
        args.remove('--profile')
1183
1237
        sys.exit(profile_main(args))
 
1238
    elif '--lsprof' in sys.argv:
 
1239
        args = sys.argv[:]
 
1240
        args.remove('--lsprof')
 
1241
        sys.exit(lsprofile_main(args))
1184
1242
    else:
1185
1243
        sys.exit(main(sys.argv))
1186
1244