~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-01 11:53:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050901115302-2fcc6c750f0abe34
- make external commands work again

  code is now much simpler; no translation to objects and back again

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
"""Weave - storage of related text file versions"""
23
23
 
 
24
# before intset (r923) 2000 versions in 41.5s
 
25
# with intset (r926) 2000 versions in 93s !!!
 
26
# better to just use plain sets.
 
27
 
 
28
# making _extract build and return a list, rather than being a generator
 
29
# takes 37.94s
 
30
 
 
31
# with python -O, r923 does 2000 versions in 36.87s
 
32
 
 
33
# with optimizations to avoid mutating lists - 35.75!  I guess copying
 
34
# all the elements every time costs more than the small manipulations.
 
35
# a surprisingly small change.
 
36
 
 
37
# r931, which avoids using a generator for extract, does 36.98s
 
38
 
 
39
# with memoized inclusions, takes 41.49s; not very good
 
40
 
 
41
# with slots, takes 37.35s; without takes 39.16, a bit surprising
 
42
 
 
43
# with the delta calculation mixed in with the add method, rather than
 
44
# separated, takes 36.78s
 
45
 
 
46
# with delta folded in and mutation of the list, 36.13s
 
47
 
 
48
# with all this and simplification of add code, 33s
 
49
 
 
50
 
 
51
 
 
52
 
 
53
 
 
54
# TODO: Perhaps have copy method for Weave instances?
24
55
 
25
56
# XXX: If we do weaves this way, will a merge still behave the same
26
57
# way if it's done in a different order?  That's a pretty desirable
43
74
 
44
75
# TODO: Parallel-extract that passes back each line along with a
45
76
# description of which revisions include it.  Nice for checking all
46
 
# shas or calculating stats in parallel.
 
77
# shas in parallel.
47
78
 
48
79
# TODO: Using a single _extract routine and then processing the output
49
80
# is probably inefficient.  It's simple enough that we can afford to
50
81
# have slight specializations for different ways its used: annotate,
51
82
# basis for add, get, etc.
52
83
 
53
 
# TODO: Probably the API should work only in names to hide the integer
54
 
# indexes from the user.
55
 
 
56
 
# TODO: Is there any potential performance win by having an add()
57
 
# variant that is passed a pre-cooked version of the single basis
58
 
# version?
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.
 
84
# TODO: Perhaps the API should work only in names to hide the integer
 
85
# indexes from the user?
 
86
 
68
87
 
69
88
 
70
89
import sha
71
 
from difflib import SequenceMatcher
72
90
 
73
 
from bzrlib.trace import mutter
74
91
 
75
92
 
76
93
class WeaveError(Exception):
79
96
 
80
97
class WeaveFormatError(WeaveError):
81
98
    """Weave invariant violated"""
82
 
 
83
 
 
84
 
class WeaveParentMismatch(WeaveError):
85
 
    """Parents are mismatched between two revisions."""
86
99
    
87
100
 
88
101
class Weave(object):
100
113
 
101
114
    * a nonnegative index number.
102
115
 
103
 
    * a version-id string.
 
116
    * a version-id string. (not implemented yet)
104
117
 
105
118
    Typically the index number will be valid only inside this weave and
106
119
    the version-id is used to reference it in the larger world.
168
181
 
169
182
    _name_map
170
183
        For each name, the version number.
171
 
 
172
 
    _weave_name
173
 
        Descriptive name of this weave; typically the filename if known.
174
 
        Set by read_weave.
175
184
    """
176
185
 
177
 
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
178
 
                 '_weave_name']
 
186
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
179
187
    
180
 
    def __init__(self, weave_name=None):
 
188
    def __init__(self):
181
189
        self._weave = []
182
190
        self._parents = []
183
191
        self._sha1s = []
184
192
        self._names = []
185
193
        self._name_map = {}
186
 
        self._weave_name = weave_name
187
 
 
188
 
 
189
 
    def copy(self):
190
 
        """Return a deep copy of self.
191
 
        
192
 
        The copy can be modified without affecting the original weave."""
193
 
        other = Weave()
194
 
        other._weave = self._weave[:]
195
 
        other._parents = self._parents[:]
196
 
        other._sha1s = self._sha1s[:]
197
 
        other._names = self._names[:]
198
 
        other._name_map = self._name_map.copy()
199
 
        other._weave_name = self._weave_name
200
 
        return other
 
194
 
201
195
 
202
196
    def __eq__(self, other):
203
197
        if not isinstance(other, Weave):
211
205
        return not self.__eq__(other)
212
206
 
213
207
 
214
 
    def maybe_lookup(self, name_or_index):
215
 
        """Convert possible symbolic name to index, or pass through indexes."""
216
 
        if isinstance(name_or_index, (int, long)):
217
 
            return name_or_index
218
 
        else:
219
 
            return self.lookup(name_or_index)
220
 
 
221
 
        
222
208
    def lookup(self, name):
223
 
        """Convert symbolic version name to index."""
224
209
        try:
225
210
            return self._name_map[name]
226
211
        except KeyError:
227
 
            raise WeaveError("name %r not present in weave %r" %
228
 
                             (name, self._weave_name))
229
 
 
230
 
 
231
 
    def iter_names(self):
232
 
        """Yield a list of all names in this weave."""
233
 
        return iter(self._names)
234
 
 
235
 
    def idx_to_name(self, version):
236
 
        return self._names[version]
237
 
 
238
 
 
239
 
    def _check_repeated_add(self, name, parents, text, sha1):
240
 
        """Check that a duplicated add is OK.
241
 
 
242
 
        If it is, return the (old) index; otherwise raise an exception.
243
 
        """
244
 
        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)            
251
 
        return idx
252
 
        
253
 
 
254
 
        
255
 
    def add(self, name, parents, text, sha1=None):
 
212
            raise WeaveError("name %s not present in weave" % name)
 
213
 
 
214
        
 
215
    def add(self, name, parents, text):
256
216
        """Add a single text on top of the weave.
257
217
  
258
218
        Returns the index number of the newly added version.
265
225
            List or set of direct parent version numbers.
266
226
            
267
227
        text
268
 
            Sequence of lines to be added in the new version.
269
 
 
270
 
        sha -- SHA-1 of the file, if known.  This is trusted to be
271
 
            correct if supplied.
272
 
        """
273
 
        from bzrlib.osutils import sha_strings
 
228
            Sequence of lines to be added in the new version."""
274
229
 
275
230
        assert isinstance(name, basestring)
276
 
        if sha1 is None:
277
 
            sha1 = sha_strings(text)
278
231
        if name in self._name_map:
279
 
            return self._check_repeated_add(name, parents, text, sha1)
280
 
 
281
 
        parents = map(self.maybe_lookup, parents)
 
232
            raise WeaveError("name %r already present in weave" % name)
 
233
        
282
234
        self._check_versions(parents)
283
235
        ## self._check_lines(text)
284
236
        new_version = len(self._parents)
285
237
 
 
238
        s = sha.new()
 
239
        map(s.update, text)
 
240
        sha1 = s.hexdigest()
 
241
        del s
286
242
 
287
243
        # if we abort after here the (in-memory) weave will be corrupt because only
288
244
        # some fields are updated
337
293
        #print 'basis_lines:', basis_lines
338
294
        #print 'new_lines:  ', lines
339
295
 
 
296
        from difflib import SequenceMatcher
340
297
        s = SequenceMatcher(None, basis_lines, text)
341
298
 
342
299
        # offset gives the number of lines that have been inserted
377
334
 
378
335
        return new_version
379
336
 
380
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
381
 
        """Add an identical text to old_rev_id as new_rev_id."""
382
 
        old_lines = self.get(self.lookup(old_rev_id))
383
 
        self.add(new_rev_id, parents, old_lines)
384
337
 
385
338
    def inclusions(self, versions):
386
339
        """Return set of all ancestors of given version(s)."""
387
340
        i = set(versions)
388
 
        for v in xrange(max(versions), 0, -1):
389
 
            if v in i:
390
 
                # include all its parents
391
 
                i.update(self._parents[v])
392
 
        return i
393
 
        ## except IndexError:
394
 
        ##     raise ValueError("version %d not present in weave" % v)
395
 
 
396
 
 
397
 
    def parents(self, version):
398
 
        return self._parents[version]
399
 
 
400
 
 
401
 
    def parent_names(self, version):
402
 
        """Return version names for parents of a version."""
403
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
 
341
        v = max(versions)
 
342
        try:
 
343
            while v >= 0:
 
344
                if v in i:
 
345
                    # include all its parents
 
346
                    i.update(self._parents[v])
 
347
                v -= 1
 
348
            return i
 
349
        except IndexError:
 
350
            raise ValueError("version %d not present in weave" % v)
404
351
 
405
352
 
406
353
    def minimal_parents(self, version):
446
393
                raise IndexError("invalid version number %r" % i)
447
394
 
448
395
    
449
 
    def annotate(self, name_or_index):
450
 
        return list(self.annotate_iter(name_or_index))
451
 
 
452
 
 
453
 
    def annotate_iter(self, name_or_index):
 
396
    def annotate(self, index):
 
397
        return list(self.annotate_iter(index))
 
398
 
 
399
 
 
400
    def annotate_iter(self, version):
454
401
        """Yield list of (index-id, line) pairs for the specified version.
455
402
 
456
403
        The index indicates when the line originated in the weave."""
457
 
        incls = [self.maybe_lookup(name_or_index)]
458
 
        for origin, lineno, text in self._extract(incls):
 
404
        for origin, lineno, text in self._extract([version]):
459
405
            yield origin, text
460
406
 
461
407
 
505
451
 
506
452
        The set typically but not necessarily corresponds to a version.
507
453
        """
508
 
        for i in versions:
509
 
            if not isinstance(i, int):
510
 
                raise ValueError(i)
511
 
            
512
454
        included = self.inclusions(versions)
513
455
 
514
456
        istack = []
559
501
    
560
502
 
561
503
 
562
 
    def get_iter(self, name_or_index):
 
504
    def get_iter(self, version):
563
505
        """Yield lines for the specified version."""
564
 
        incls = [self.maybe_lookup(name_or_index)]
565
 
        for origin, lineno, line in self._extract(incls):
 
506
        for origin, lineno, line in self._extract([version]):
566
507
            yield line
567
508
 
568
509
 
569
 
    def get_text(self, name_or_index):
570
 
        return ''.join(self.get_iter(name_or_index))
571
 
        assert isinstance(version, int)
572
 
 
573
 
 
574
 
    def get_lines(self, name_or_index):
575
 
        return list(self.get_iter(name_or_index))
576
 
 
577
 
 
578
 
    get = get_lines
 
510
    def get(self, index):
 
511
        return list(self.get_iter(index))
579
512
 
580
513
 
581
514
    def mash_iter(self, included):
582
515
        """Return composed version of multiple included versions."""
583
 
        included = map(self.maybe_lookup, included)
584
516
        for origin, lineno, text in self._extract(included):
585
517
            yield text
586
518
 
646
578
        If there is a chunk of the file where there's no diagreement,
647
579
        only one alternative is given.
648
580
        """
 
581
 
649
582
        # approach: find the included versions common to all the
650
583
        # merged versions
651
584
        raise NotImplementedError()
671
604
        If line1=line2, this is a pure insert; if newlines=[] this is a
672
605
        pure delete.  (Similar to difflib.)
673
606
        """
674
 
        raise NotImplementedError()
 
607
 
675
608
 
676
609
            
677
610
    def plan_merge(self, ver_a, ver_b):
771
704
                       state
772
705
 
773
706
                
774
 
    def join(self, other):
775
 
        """Integrate versions from other into this weave.
776
 
 
777
 
        The resulting weave contains all the history of both weaves; 
778
 
        any version you could retrieve from either self or other can be 
779
 
        retrieved from self after this call.
780
 
 
781
 
        It is illegal for the two weaves to contain different values 
782
 
        or different parents for any version.  See also reweave().
783
 
        """
784
 
        if other.numversions() == 0:
785
 
            return          # nothing to update, easy
786
 
        # two loops so that we do not change ourselves before verifying it
787
 
        # will be ok
788
 
        # 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 
794
 
            # present then we can avoid some work by just taking the delta
795
 
            # and adjusting the offsets.
796
 
            new_parents = self._imported_parents(other, other_idx)
797
 
            lines = other.get_lines(other_idx)
798
 
            sha1 = other._sha1s[other_idx]
799
 
            self.add(name, new_parents, lines, sha1)
800
 
 
801
 
 
802
 
    def _imported_parents(self, other, other_idx):
803
 
        """Return list of parents in self corresponding to indexes in other."""
804
 
        new_parents = []
805
 
        for parent_idx in other._parents[other_idx]:
806
 
            parent_name = other._names[parent_idx]
807
 
            if parent_name not in self._names:
808
 
                # should not be possible
809
 
                raise WeaveError("missing parent {%s} of {%s} in %r" 
810
 
                                 % (parent_name, other._name_map[other_idx], self))
811
 
            new_parents.append(self._name_map[parent_name])
812
 
        return new_parents
813
 
 
814
 
    def _check_version_consistent(self, other, other_idx, name):
815
 
        """Check if a version in consistent in this and other.
816
 
 
817
 
        To be consistent it must have:
818
 
 
819
 
         * the same text
820
 
         * the same direct parents (by name, not index, and disregarding
821
 
           order)
822
 
        
823
 
        If present & correct return True;
824
 
        if not present in self return False; 
825
 
        if inconsistent raise error."""
826
 
        this_idx = self._name_map.get(name, -1)
827
 
        if this_idx != -1:
828
 
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
829
 
                raise WeaveError("inconsistent texts for version {%s} "
830
 
                                 "when joining weaves"
831
 
                                 % (name))
832
 
            self_parents = self._parents[this_idx]
833
 
            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()
838
 
            if n1 != n2:
839
 
                raise WeaveParentMismatch("inconsistent parents "
840
 
                    "for version {%s}: %s vs %s" % (name, n1, n2))
841
 
            else:
842
 
                return True         # ok!
843
 
        else:
844
 
            return False
845
 
 
846
 
    def reweave(self, other):
847
 
        """Reweave self with other."""
848
 
        new_weave = reweave(self, other)
849
 
        for attr in self.__slots__:
850
 
            setattr(self, attr, getattr(new_weave, attr))
851
 
 
852
 
 
853
 
def reweave(wa, wb):
854
 
    """Combine two weaves and return the result.
855
 
 
856
 
    This works even if a revision R has different parents in 
857
 
    wa and wb.  In the resulting weave all the parents are given.
858
 
 
859
 
    This is done by just building up a new weave, maintaining ordering 
860
 
    of the versions in the two inputs.  More efficient approaches
861
 
    might be possible but it should only be necessary to do 
862
 
    this operation rarely, when a new previously ghost version is 
863
 
    inserted.
864
 
    """
865
 
    wr = Weave()
866
 
    ia = ib = 0
867
 
    queue_a = range(wa.numversions())
868
 
    queue_b = range(wb.numversions())
869
 
    # first determine combined parents of all versions
870
 
    # map from version name -> all parent names
871
 
    combined_parents = _reweave_parent_graphs(wa, wb)
872
 
    mutter("combined parents: %r", combined_parents)
873
 
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
874
 
    mutter("order to reweave: %r", order)
875
 
    for name in order:
876
 
        if name in wa._name_map:
877
 
            lines = wa.get_lines(name)
878
 
            if name in wb._name_map:
879
 
                assert lines == wb.get_lines(name)
880
 
        else:
881
 
            lines = wb.get_lines(name)
882
 
        wr.add(name, combined_parents[name], lines)
883
 
    return wr
884
 
 
885
 
 
886
 
def _reweave_parent_graphs(wa, wb):
887
 
    """Return combined parent ancestry for two weaves.
888
 
    
889
 
    Returned as a list of (version_name, set(parent_names))"""
890
 
    combined = {}
891
 
    for weave in [wa, wb]:
892
 
        for idx, name in enumerate(weave._names):
893
 
            p = combined.setdefault(name, set())
894
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
895
 
    return combined
896
 
 
897
 
 
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
 
707
 
 
708
 
 
709
 
930
710
 
931
711
 
932
712
def weave_toc(w):
943
723
 
944
724
 
945
725
 
946
 
def weave_stats(weave_file, pb):
 
726
def weave_stats(weave_file):
 
727
    from bzrlib.progress import ProgressBar
947
728
    from bzrlib.weavefile import read_weave
948
729
 
 
730
    pb = ProgressBar()
 
731
 
949
732
    wf = file(weave_file, 'rb')
950
733
    w = read_weave(wf)
951
734
    # FIXME: doesn't work on pipes
955
738
    vers = len(w)
956
739
    for i in range(vers):
957
740
        pb.update('checking sizes', i, vers)
958
 
        for origin, lineno, line in w._extract([i]):
 
741
        for line in w.get_iter(i):
959
742
            total += len(line)
960
743
 
961
744
    pb.clear()
992
775
        Display composite of all selected versions.
993
776
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
994
777
        Auto-merge two versions and display conflicts.
995
 
    weave diff WEAVEFILE VERSION1 VERSION2 
996
 
        Show differences between two versions.
997
778
 
998
779
example:
999
780
 
1024
805
def main(argv):
1025
806
    import sys
1026
807
    import os
1027
 
    try:
1028
 
        import bzrlib
1029
 
    except ImportError:
1030
 
        # in case we're run directly from the subdirectory
1031
 
        sys.path.append('..')
1032
 
        import bzrlib
1033
 
    from bzrlib.weavefile import write_weave, read_weave
 
808
    from weavefile import write_weave, read_weave
1034
809
    from bzrlib.progress import ProgressBar
1035
810
 
1036
811
    try:
1073
848
        w = readit()
1074
849
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1075
850
 
1076
 
    elif cmd == 'diff':
1077
 
        from difflib import unified_diff
1078
 
        w = readit()
1079
 
        fn = argv[2]
1080
 
        v1, v2 = map(int, argv[3:5])
1081
 
        lines1 = w.get(v1)
1082
 
        lines2 = w.get(v2)
1083
 
        diff_gen = unified_diff(lines1, lines2,
1084
 
                                '%s version %d' % (fn, v1),
1085
 
                                '%s version %d' % (fn, v2))
1086
 
        sys.stdout.writelines(diff_gen)
1087
 
            
1088
851
    elif cmd == 'annotate':
1089
852
        w = readit()
1090
853
        # newline is added to all lines regardless; too hard to get
1102
865
        weave_toc(readit())
1103
866
 
1104
867
    elif cmd == 'stats':
1105
 
        weave_stats(argv[2], ProgressBar())
 
868
        weave_stats(argv[2])
1106
869
        
1107
870
    elif cmd == 'check':
1108
871
        w = readit()