~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-12 08:45:58 UTC
  • Revision ID: mbp@sourcefrog.net-20050912084558-93fecb4c84386c17
doc

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 
 
48
# with all this and simplification of add code, 33s
 
49
 
 
50
 
 
51
 
49
52
 
50
53
 
51
54
# TODO: Perhaps have copy method for Weave instances?
59
62
# binaries, perhaps by naively splitting on \n or perhaps using
60
63
# something like a rolling checksum.
61
64
 
62
 
# TODO: Track version names as well as indexes. 
63
 
 
64
65
# TODO: End marker for each version so we can stop reading?
65
66
 
66
67
# TODO: Check that no insertion occurs inside a deletion that was
75
76
# description of which revisions include it.  Nice for checking all
76
77
# shas in parallel.
77
78
 
78
 
 
 
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
from cStringIO import StringIO
79
91
 
80
92
 
81
93
class WeaveError(Exception):
101
113
 
102
114
    * a nonnegative index number.
103
115
 
104
 
    * a version-id string.
 
116
    * a version-id string. (not implemented yet)
105
117
 
106
118
    Typically the index number will be valid only inside this weave and
107
119
    the version-id is used to reference it in the larger world.
118
130
    The instruction can be '{' or '}' for an insertion block, and '['
119
131
    and ']' for a deletion block respectively.  The version is the
120
132
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
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.
122
136
 
123
137
    Constraints/notes:
124
138
 
160
174
        each version; the parent's parents are implied.
161
175
 
162
176
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
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.
164
184
    """
165
185
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
186
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
167
187
    
168
188
    def __init__(self):
169
189
        self._weave = []
170
190
        self._parents = []
171
191
        self._sha1s = []
 
192
        self._names = []
 
193
        self._name_map = {}
172
194
 
173
195
 
174
196
    def __eq__(self, other):
175
197
        if not isinstance(other, Weave):
176
198
            return False
177
199
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
200
               and self._weave == other._weave \
 
201
               and self._sha1s == other._sha1s 
 
202
 
179
203
    
180
 
 
181
204
    def __ne__(self, other):
182
205
        return not self.__eq__(other)
183
206
 
 
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
 
184
214
        
185
 
    def add(self, parents, text):
 
215
    def add(self, name, parents, text):
186
216
        """Add a single text on top of the weave.
187
217
  
188
218
        Returns the index number of the newly added version.
189
219
 
 
220
        name
 
221
            Symbolic name for this version.
 
222
            (Typically the revision-id of the revision that added it.)
 
223
 
190
224
        parents
191
225
            List or set of direct parent version numbers.
192
226
            
193
227
        text
194
228
            Sequence of lines to be added in the new version."""
195
229
 
 
230
        assert isinstance(name, basestring)
 
231
        if name in self._name_map:
 
232
            raise WeaveError("name %r already present in weave" % name)
 
233
        
196
234
        self._check_versions(parents)
197
235
        ## self._check_lines(text)
198
236
        new_version = len(self._parents)
199
237
 
200
 
        import sha
201
238
        s = sha.new()
202
239
        map(s.update, text)
203
240
        sha1 = s.hexdigest()
204
241
        del s
205
242
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
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[:])
208
246
        self._sha1s.append(sha1)
 
247
        self._names.append(name)
 
248
        self._name_map[name] = new_version
209
249
 
210
250
            
211
251
        if not parents:
216
256
            if text:
217
257
                self._weave.append(('{', new_version))
218
258
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
259
                self._weave.append(('}', None))
220
260
        
221
261
            return new_version
222
262
 
238
278
            basis_lineno.append(lineno)
239
279
            basis_lines.append(line)
240
280
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
281
        # another small special case: a merge, producing the same text
 
282
        # as auto-merge
242
283
        if text == basis_lines:
243
284
            return new_version            
244
285
 
287
328
                # we don't destroy ourselves
288
329
                i = i2 + offset
289
330
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
331
                                    + text[j1:j2] 
 
332
                                    + [('}', None)])
292
333
                offset += 2 + (j2 - j1)
293
334
 
294
335
        return new_version
384
425
                if c == '{':
385
426
                    istack.append(v)
386
427
                elif c == '}':
387
 
                    oldv = istack.pop()
 
428
                    istack.pop()
388
429
                elif c == '[':
389
430
                    assert v not in dset
390
431
                    dset.add(v)
410
451
 
411
452
        The set typically but not necessarily corresponds to a version.
412
453
        """
 
454
        for i in versions:
 
455
            if not isinstance(i, int):
 
456
                raise ValueError(i)
 
457
            
413
458
        included = self.inclusions(versions)
414
459
 
415
460
        istack = []
431
476
                    assert v not in istack
432
477
                    istack.append(v)
433
478
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
479
                    istack.pop()
436
480
                elif c == '[':
437
481
                    if v in included:
438
482
                        assert v not in dset
467
511
            yield line
468
512
 
469
513
 
 
514
    def get_text(self, version):
 
515
        assert isinstance(version, int)
 
516
        s = StringIO()
 
517
        s.writelines(self.get_iter(version))
 
518
        return s.getvalue()
 
519
 
 
520
 
470
521
    def get(self, index):
471
522
        return list(self.get_iter(index))
472
523
 
508
559
 
509
560
        # try extracting all versions; this is a bit slow and parallel
510
561
        # extraction could be used
511
 
        import sha
512
562
        nv = self.numversions()
513
563
        for version in range(nv):
514
564
            if progress_bar:
670
720
 
671
721
 
672
722
 
673
 
def weave_info(w):
674
 
    """Show some text information about the weave."""
675
 
    print '%6s %40s %20s' % ('ver', 'sha1', 'parents')
676
 
    for i in (6, 40, 20):
 
723
def weave_toc(w):
 
724
    """Show the weave's table-of-contents"""
 
725
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
726
    for i in (6, 50, 10, 10):
677
727
        print '-' * i,
678
728
    print
679
729
    for i in range(w.numversions()):
680
730
        sha1 = w._sha1s[i]
681
 
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
 
731
        name = w._names[i]
 
732
        parent_str = ' '.join(map(str, w._parents[i]))
 
733
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
682
734
 
683
735
 
684
736
 
706
758
    print 'weave file        %9d bytes' % weave_size
707
759
    print 'total contents    %9d bytes' % total
708
760
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
761
    if vers:
 
762
        avg = total/vers
 
763
        print 'average size      %9d bytes' % avg
 
764
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
765
 
711
766
 
712
767
def usage():
721
776
        Write out specified version.
722
777
    weave check WEAVEFILE
723
778
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
779
    weave toc WEAVEFILE
725
780
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
781
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
782
        Add NEWTEXT, with specified parent versions.
728
783
    weave annotate WEAVEFILE VERSION
729
784
        Display origin of each line.
736
791
 
737
792
    % weave init foo.weave
738
793
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
794
    % weave add foo.weave ver0 < foo.txt
740
795
    added version 0
741
796
 
742
797
    (create updated version)
743
798
    % vi foo.txt
744
799
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
800
    % weave add foo.weave ver1 0 < foo.txt
746
801
    added version 1
747
802
 
748
803
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
804
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
805
    % weave add foo.weave ver2 0 < foo.txt
751
806
    added version 2
752
807
 
753
808
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
809
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
810
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
811
    
757
812
"""
758
813
    
764
819
    from weavefile import write_weave, read_weave
765
820
    from bzrlib.progress import ProgressBar
766
821
 
767
 
    #import psyco
768
 
    #psyco.full()
 
822
    try:
 
823
        import psyco
 
824
        psyco.full()
 
825
    except ImportError:
 
826
        pass
 
827
 
 
828
    if len(argv) < 2:
 
829
        usage()
 
830
        return 0
769
831
 
770
832
    cmd = argv[1]
771
833
 
777
839
    elif cmd == 'add':
778
840
        w = readit()
779
841
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
842
        name = argv[3]
 
843
        parents = map(int, argv[4:])
781
844
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
845
        ver = w.add(name, parents, lines)
783
846
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
847
        print 'added version %r %d' % (name, ver)
785
848
    elif cmd == 'init':
786
849
        fn = argv[2]
787
850
        if os.path.exists(fn):
809
872
                print '%5d | %s' % (origin, text)
810
873
                lasto = origin
811
874
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
875
    elif cmd == 'toc':
 
876
        weave_toc(readit())
814
877
 
815
878
    elif cmd == 'stats':
816
879
        weave_stats(argv[2])
865
928
        raise ValueError('unknown command %r' % cmd)
866
929
    
867
930
 
 
931
 
 
932
def profile_main(argv): 
 
933
    import tempfile, hotshot, hotshot.stats
 
934
 
 
935
    prof_f = tempfile.NamedTemporaryFile()
 
936
 
 
937
    prof = hotshot.Profile(prof_f.name)
 
938
 
 
939
    ret = prof.runcall(main, argv)
 
940
    prof.close()
 
941
 
 
942
    stats = hotshot.stats.load(prof_f.name)
 
943
    #stats.strip_dirs()
 
944
    stats.sort_stats('cumulative')
 
945
    ## XXX: Might like to write to stderr or the trace file instead but
 
946
    ## print_stats seems hardcoded to stdout
 
947
    stats.print_stats(20)
 
948
            
 
949
    return ret
 
950
 
 
951
 
868
952
if __name__ == '__main__':
869
953
    import sys
870
 
    sys.exit(main(sys.argv))
 
954
    if '--profile' in sys.argv:
 
955
        args = sys.argv[:]
 
956
        args.remove('--profile')
 
957
        sys.exit(profile_main(args))
 
958
    else:
 
959
        sys.exit(main(sys.argv))
 
960