~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2006-01-13 08:12:22 UTC
  • mfrom: (1185.63.5 bzr.patches)
  • Revision ID: mbp@sourcefrog.net-20060113081222-6b572004a2ade0cc
[merge] test_hashcache_raise from Denys

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
 
                
 
801
 
774
802
    def join(self, other):
 
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; 
787
816
        # will be ok
788
817
        # work through in index order to make sure we get all dependencies
789
818
        for other_idx, name in enumerate(other._names):
790
 
            if self._check_version_consistent(other, other_idx, name):
791
 
                continue
 
819
            self._check_version_consistent(other, other_idx, name)
 
820
 
 
821
        merged = 0
 
822
        processed = 0
 
823
        time0 = time.time( )
792
824
        for other_idx, name in enumerate(other._names):
793
 
            # TODO: If all the parents of the other version are already 
 
825
            # TODO: If all the parents of the other version are already
794
826
            # present then we can avoid some work by just taking the delta
795
827
            # and adjusting the offsets.
796
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
797
841
            lines = other.get_lines(other_idx)
798
 
            sha1 = other._sha1s[other_idx]
799
842
            self.add(name, new_parents, lines, sha1)
800
843
 
801
 
 
 
844
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
845
                merged,processed,self._weave_name, time.time( )-time0))
 
846
 
 
847
 
 
848
 
 
849
 
802
850
    def _imported_parents(self, other, other_idx):
803
851
        """Return list of parents in self corresponding to indexes in other."""
804
852
        new_parents = []
870
918
    # map from version name -> all parent names
871
919
    combined_parents = _reweave_parent_graphs(wa, wb)
872
920
    mutter("combined parents: %r", combined_parents)
873
 
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
 
921
    order = topo_sort(combined_parents.iteritems())
874
922
    mutter("order to reweave: %r", order)
875
923
    for name in order:
876
924
        if name in wa._name_map:
895
943
    return combined
896
944
 
897
945
 
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
946
def weave_toc(w):
933
947
    """Show the weave's table-of-contents"""
934
948
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
1175
1189
    return ret
1176
1190
 
1177
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
 
1178
1200
if __name__ == '__main__':
1179
1201
    import sys
1180
1202
    if '--profile' in sys.argv:
1181
1203
        args = sys.argv[:]
1182
1204
        args.remove('--profile')
1183
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))
1184
1210
    else:
1185
1211
        sys.exit(main(sys.argv))
1186
1212