~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-07-18 14:28:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050718142813-253ec02b9636bd98
- add stubbed-out test for clashing replace and delete

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
 
46
46
# with delta folded in and mutation of the list, 36.13s
47
47
 
48
 
# with all this and simplification of add code, 33s
49
 
 
50
 
 
51
 
 
 
48
# with all this and simplification of add code, 33s 
52
49
 
53
50
 
54
51
# TODO: Perhaps have copy method for Weave instances?
62
59
# binaries, perhaps by naively splitting on \n or perhaps using
63
60
# something like a rolling checksum.
64
61
 
 
62
# TODO: Track version names as well as indexes. 
 
63
 
65
64
# TODO: End marker for each version so we can stop reading?
66
65
 
67
66
# TODO: Check that no insertion occurs inside a deletion that was
72
71
# properly nested, that there is no text outside of an insertion, that
73
72
# insertions or deletions are not repeated, etc.
74
73
 
 
74
# TODO: Make the info command just show info, not extract everything:
 
75
# it can be much faster.
 
76
 
 
77
# TODO: Perhaps use long integers as sets instead of set objects; may
 
78
# be faster.
 
79
 
75
80
# TODO: Parallel-extract that passes back each line along with a
76
81
# description of which revisions include it.  Nice for checking all
77
82
# shas in parallel.
78
83
 
79
 
# TODO: Using a single _extract routine and then processing the output
80
 
# is probably inefficient.  It's simple enough that we can afford to
81
 
# have slight specializations for different ways its used: annotate,
82
 
# basis for add, get, etc.
83
 
 
84
 
# TODO: Perhaps the API should work only in names to hide the integer
85
 
# indexes from the user?
86
 
 
87
 
 
88
 
 
89
 
import sha
90
84
 
91
85
 
92
86
 
113
107
 
114
108
    * a nonnegative index number.
115
109
 
116
 
    * a version-id string. (not implemented yet)
 
110
    * a version-id string.
117
111
 
118
112
    Typically the index number will be valid only inside this weave and
119
113
    the version-id is used to reference it in the larger world.
130
124
    The instruction can be '{' or '}' for an insertion block, and '['
131
125
    and ']' for a deletion block respectively.  The version is the
132
126
    integer version index.  There is no replace operator, only deletes
133
 
    and inserts.  For '}', the end of an insertion, there is no
134
 
    version parameter because it always closes the most recently
135
 
    opened insertion.
 
127
    and inserts.
136
128
 
137
129
    Constraints/notes:
138
130
 
174
166
        each version; the parent's parents are implied.
175
167
 
176
168
    _sha1s
177
 
        List of hex SHA-1 of each version.
178
 
 
179
 
    _names
180
 
        List of symbolic names for each version.  Each should be unique.
181
 
 
182
 
    _name_map
183
 
        For each name, the version number.
 
169
        List of hex SHA-1 of each version, or None if not recorded.
184
170
    """
185
171
 
186
 
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
 
172
    __slots__ = ['_weave', '_parents', '_sha1s']
187
173
    
188
174
    def __init__(self):
189
175
        self._weave = []
190
176
        self._parents = []
191
177
        self._sha1s = []
192
 
        self._names = []
193
 
        self._name_map = {}
194
178
 
195
179
 
196
180
    def __eq__(self, other):
197
181
        if not isinstance(other, Weave):
198
182
            return False
199
183
        return self._parents == other._parents \
200
 
               and self._weave == other._weave \
201
 
               and self._sha1s == other._sha1s 
 
184
               and self._weave == other._weave
 
185
    
202
186
 
203
 
    
204
187
    def __ne__(self, other):
205
188
        return not self.__eq__(other)
206
189
 
207
 
 
208
 
    def lookup(self, name):
209
 
        try:
210
 
            return self._name_map[name]
211
 
        except KeyError:
212
 
            raise WeaveError("name %s not present in weave" % name)
213
 
 
214
190
        
215
 
    def add(self, name, parents, text):
 
191
    def add(self, parents, text):
216
192
        """Add a single text on top of the weave.
217
193
  
218
194
        Returns the index number of the newly added version.
219
195
 
220
 
        name
221
 
            Symbolic name for this version.
222
 
            (Typically the revision-id of the revision that added it.)
223
 
 
224
196
        parents
225
197
            List or set of direct parent version numbers.
226
198
            
227
199
        text
228
200
            Sequence of lines to be added in the new version."""
229
201
 
230
 
        assert isinstance(name, basestring)
231
 
        if name in self._name_map:
232
 
            raise WeaveError("name %r already present in weave" % name)
233
 
        
234
202
        self._check_versions(parents)
235
203
        ## self._check_lines(text)
236
204
        new_version = len(self._parents)
237
205
 
 
206
        import sha
238
207
        s = sha.new()
239
208
        map(s.update, text)
240
209
        sha1 = s.hexdigest()
241
210
        del s
242
211
 
243
 
        # if we abort after here the (in-memory) weave will be corrupt because only
244
 
        # some fields are updated
245
 
        self._parents.append(parents[:])
 
212
        # if we abort after here the weave will be corrupt
 
213
        self._parents.append(frozenset(parents))
246
214
        self._sha1s.append(sha1)
247
 
        self._names.append(name)
248
 
        self._name_map[name] = new_version
249
215
 
250
216
            
251
217
        if not parents:
256
222
            if text:
257
223
                self._weave.append(('{', new_version))
258
224
                self._weave.extend(text)
259
 
                self._weave.append(('}', None))
 
225
                self._weave.append(('}', new_version))
260
226
        
261
227
            return new_version
262
228
 
278
244
            basis_lineno.append(lineno)
279
245
            basis_lines.append(line)
280
246
 
281
 
        # another small special case: a merge, producing the same text
282
 
        # as auto-merge
 
247
        # another small special case: a merge, producing the same text as auto-merge
283
248
        if text == basis_lines:
284
249
            return new_version            
285
250
 
328
293
                # we don't destroy ourselves
329
294
                i = i2 + offset
330
295
                self._weave[i:i] = ([('{', new_version)] 
331
 
                                    + text[j1:j2] 
332
 
                                    + [('}', None)])
 
296
                                + text[j1:j2] 
 
297
                                + [('}', new_version)])
333
298
                offset += 2 + (j2 - j1)
334
299
 
335
300
        return new_version
425
390
                if c == '{':
426
391
                    istack.append(v)
427
392
                elif c == '}':
428
 
                    istack.pop()
 
393
                    oldv = istack.pop()
429
394
                elif c == '[':
430
395
                    assert v not in dset
431
396
                    dset.add(v)
472
437
                    assert v not in istack
473
438
                    istack.append(v)
474
439
                elif c == '}':
475
 
                    istack.pop()
 
440
                    oldv = istack.pop()
 
441
                    assert oldv == v
476
442
                elif c == '[':
477
443
                    if v in included:
478
444
                        assert v not in dset
532
498
        return l
533
499
 
534
500
 
535
 
    def __len__(self):
536
 
        return self.numversions()
537
 
 
538
 
 
539
501
    def check(self, progress_bar=None):
540
502
        # check no circular inclusions
541
503
        for version in range(self.numversions()):
548
510
 
549
511
        # try extracting all versions; this is a bit slow and parallel
550
512
        # extraction could be used
 
513
        import sha
551
514
        nv = self.numversions()
552
515
        for version in range(nv):
553
516
            if progress_bar:
709
672
 
710
673
 
711
674
 
712
 
def weave_toc(w):
713
 
    """Show the weave's table-of-contents"""
714
 
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
715
 
    for i in (6, 50, 10, 10):
716
 
        print '-' * i,
717
 
    print
718
 
    for i in range(w.numversions()):
719
 
        sha1 = w._sha1s[i]
720
 
        name = w._names[i]
721
 
        parent_str = ' '.join(map(str, w._parents[i]))
722
 
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
723
 
 
724
 
 
725
 
 
726
 
def weave_stats(weave_file):
727
 
    from bzrlib.progress import ProgressBar
728
 
    from bzrlib.weavefile import read_weave
729
 
 
730
 
    pb = ProgressBar()
731
 
 
732
 
    wf = file(weave_file, 'rb')
 
675
def weave_info(filename, out):
 
676
    """Show some text information about the weave."""
 
677
    from weavefile import read_weave
 
678
    wf = file(filename, 'rb')
733
679
    w = read_weave(wf)
734
680
    # FIXME: doesn't work on pipes
735
681
    weave_size = wf.tell()
 
682
    print >>out, "weave file size %d bytes" % weave_size
 
683
    print >>out, "weave contains %d versions" % len(w._parents)
736
684
 
737
685
    total = 0
738
 
    vers = len(w)
739
 
    for i in range(vers):
740
 
        pb.update('checking sizes', i, vers)
741
 
        for line in w.get_iter(i):
742
 
            total += len(line)
743
 
 
744
 
    pb.clear()
745
 
 
746
 
    print 'versions          %9d' % vers
747
 
    print 'weave file        %9d bytes' % weave_size
748
 
    print 'total contents    %9d bytes' % total
749
 
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
750
 
    if vers:
751
 
        avg = total/vers
752
 
        print 'average size      %9d bytes' % avg
753
 
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
 
686
    print '%6s %6s %8s %40s %20s' % ('ver', 'lines', 'bytes', 'sha1', 'parents')
 
687
    for i in (6, 6, 8, 40, 20):
 
688
        print '-' * i,
 
689
    print
 
690
    for i in range(len(w._parents)):
 
691
        text = w.get(i)
 
692
        lines = len(text)
 
693
        bytes = sum((len(a) for a in text))
 
694
        sha1 = w._sha1s[i]
 
695
        print '%6d %6d %8d %40s' % (i, lines, bytes, sha1),
 
696
        for pv in w._parents[i]:
 
697
            print pv,
 
698
        print
 
699
        total += bytes
 
700
 
 
701
    print >>out, "versions total %d bytes" % total
 
702
    print >>out, "compression ratio %.3f" % (float(total)/float(weave_size))
754
703
 
755
704
 
756
705
def usage():
765
714
        Write out specified version.
766
715
    weave check WEAVEFILE
767
716
        Check consistency of all versions.
768
 
    weave toc WEAVEFILE
 
717
    weave info WEAVEFILE
769
718
        Display table of contents.
770
 
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
 
719
    weave add WEAVEFILE [BASE...] < NEWTEXT
771
720
        Add NEWTEXT, with specified parent versions.
772
721
    weave annotate WEAVEFILE VERSION
773
722
        Display origin of each line.
780
729
 
781
730
    % weave init foo.weave
782
731
    % vi foo.txt
783
 
    % weave add foo.weave ver0 < foo.txt
 
732
    % weave add foo.weave < foo.txt
784
733
    added version 0
785
734
 
786
735
    (create updated version)
787
736
    % vi foo.txt
788
737
    % weave get foo.weave 0 | diff -u - foo.txt
789
 
    % weave add foo.weave ver1 0 < foo.txt
 
738
    % weave add foo.weave 0 < foo.txt
790
739
    added version 1
791
740
 
792
741
    % weave get foo.weave 0 > foo.txt       (create forked version)
793
742
    % vi foo.txt
794
 
    % weave add foo.weave ver2 0 < foo.txt
 
743
    % weave add foo.weave 0 < foo.txt
795
744
    added version 2
796
745
 
797
746
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
798
747
    % vi foo.txt                            (resolve conflicts)
799
 
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
 
748
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
800
749
    
801
750
"""
802
751
    
808
757
    from weavefile import write_weave, read_weave
809
758
    from bzrlib.progress import ProgressBar
810
759
 
811
 
    try:
812
 
        import psyco
813
 
        psyco.full()
814
 
    except ImportError:
815
 
        pass
816
 
 
817
 
    if len(argv) < 2:
818
 
        usage()
819
 
        return 0
 
760
    #import psyco
 
761
    #psyco.full()
820
762
 
821
763
    cmd = argv[1]
822
764
 
828
770
    elif cmd == 'add':
829
771
        w = readit()
830
772
        # at the moment, based on everything in the file
831
 
        name = argv[3]
832
 
        parents = map(int, argv[4:])
 
773
        parents = map(int, argv[3:])
833
774
        lines = sys.stdin.readlines()
834
 
        ver = w.add(name, parents, lines)
 
775
        ver = w.add(parents, lines)
835
776
        write_weave(w, file(argv[2], 'wb'))
836
 
        print 'added version %r %d' % (name, ver)
 
777
        print 'added version %d' % ver
837
778
    elif cmd == 'init':
838
779
        fn = argv[2]
839
780
        if os.path.exists(fn):
861
802
                print '%5d | %s' % (origin, text)
862
803
                lasto = origin
863
804
                
864
 
    elif cmd == 'toc':
865
 
        weave_toc(readit())
866
 
 
867
 
    elif cmd == 'stats':
868
 
        weave_stats(argv[2])
 
805
    elif cmd == 'info':
 
806
        weave_info(argv[2], sys.stdout)
869
807
        
870
808
    elif cmd == 'check':
871
809
        w = readit()
917
855
        raise ValueError('unknown command %r' % cmd)
918
856
    
919
857
 
920
 
 
921
 
def profile_main(argv): 
922
 
    import tempfile, hotshot, hotshot.stats
923
 
 
924
 
    prof_f = tempfile.NamedTemporaryFile()
925
 
 
926
 
    prof = hotshot.Profile(prof_f.name)
927
 
 
928
 
    ret = prof.runcall(main, argv)
929
 
    prof.close()
930
 
 
931
 
    stats = hotshot.stats.load(prof_f.name)
932
 
    #stats.strip_dirs()
933
 
    stats.sort_stats('cumulative')
934
 
    ## XXX: Might like to write to stderr or the trace file instead but
935
 
    ## print_stats seems hardcoded to stdout
936
 
    stats.print_stats(20)
937
 
            
938
 
    return ret
939
 
 
940
 
 
941
858
if __name__ == '__main__':
942
859
    import sys
943
 
    if '--profile' in sys.argv:
944
 
        args = sys.argv[:]
945
 
        args.remove('--profile')
946
 
        sys.exit(profile_main(args))
947
 
    else:
948
 
        sys.exit(main(sys.argv))
949
 
 
 
860
    sys.exit(main(sys.argv))