~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

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
 
 
91
from cStringIO import StringIO
 
92
 
 
93
from bzrlib.osutils import sha_strings
79
94
 
80
95
 
81
96
class WeaveError(Exception):
101
116
 
102
117
    * a nonnegative index number.
103
118
 
104
 
    * a version-id string.
 
119
    * a version-id string. (not implemented yet)
105
120
 
106
121
    Typically the index number will be valid only inside this weave and
107
122
    the version-id is used to reference it in the larger world.
118
133
    The instruction can be '{' or '}' for an insertion block, and '['
119
134
    and ']' for a deletion block respectively.  The version is the
120
135
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
136
    and inserts.  For '}', the end of an insertion, there is no
 
137
    version parameter because it always closes the most recently
 
138
    opened insertion.
122
139
 
123
140
    Constraints/notes:
124
141
 
160
177
        each version; the parent's parents are implied.
161
178
 
162
179
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
180
        List of hex SHA-1 of each version.
 
181
 
 
182
    _names
 
183
        List of symbolic names for each version.  Each should be unique.
 
184
 
 
185
    _name_map
 
186
        For each name, the version number.
 
187
 
 
188
    _weave_name
 
189
        Descriptive name of this weave; typically the filename if known.
 
190
        Set by read_weave.
164
191
    """
165
192
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
193
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
194
                 '_weave_name']
167
195
    
168
 
    def __init__(self):
 
196
    def __init__(self, weave_name=None):
169
197
        self._weave = []
170
198
        self._parents = []
171
199
        self._sha1s = []
 
200
        self._names = []
 
201
        self._name_map = {}
 
202
        self._weave_name = weave_name
172
203
 
173
204
 
174
205
    def __eq__(self, other):
175
206
        if not isinstance(other, Weave):
176
207
            return False
177
208
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
209
               and self._weave == other._weave \
 
210
               and self._sha1s == other._sha1s 
 
211
 
179
212
    
180
 
 
181
213
    def __ne__(self, other):
182
214
        return not self.__eq__(other)
183
215
 
184
 
        
185
 
    def add(self, parents, text):
 
216
 
 
217
    def maybe_lookup(self, name_or_index):
 
218
        """Convert possible symbolic name to index, or pass through indexes."""
 
219
        if isinstance(name_or_index, (int, long)):
 
220
            return name_or_index
 
221
        else:
 
222
            return self.lookup(name_or_index)
 
223
 
 
224
        
 
225
    def lookup(self, name):
 
226
        """Convert symbolic version name to index."""
 
227
        try:
 
228
            return self._name_map[name]
 
229
        except KeyError:
 
230
            raise WeaveError("name %r not present in weave %r" %
 
231
                             (name, self._weave_name))
 
232
 
 
233
 
 
234
    def idx_to_name(self, version):
 
235
        return self._names[version]
 
236
 
 
237
 
 
238
    def _check_repeated_add(self, name, parents, text):
 
239
        """Check that a duplicated add is OK.
 
240
 
 
241
        If it is, return the (old) index; otherwise raise an exception.
 
242
        """
 
243
        idx = self.lookup(name)
 
244
        if sorted(self._parents[idx]) != sorted(parents):
 
245
            raise WeaveError("name \"%s\" already present in weave "
 
246
                             "with different parents" % name)
 
247
        new_sha1 = sha_strings(text)
 
248
        if new_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):
186
256
        """Add a single text on top of the weave.
187
257
  
188
258
        Returns the index number of the newly added version.
189
259
 
 
260
        name
 
261
            Symbolic name for this version.
 
262
            (Typically the revision-id of the revision that added it.)
 
263
 
190
264
        parents
191
265
            List or set of direct parent version numbers.
192
266
            
193
267
        text
194
268
            Sequence of lines to be added in the new version."""
195
269
 
 
270
        assert isinstance(name, basestring)
 
271
        if name in self._name_map:
 
272
            return self._check_repeated_add(name, parents, text)
 
273
 
 
274
        parents = map(self.maybe_lookup, parents)
196
275
        self._check_versions(parents)
197
276
        ## self._check_lines(text)
198
277
        new_version = len(self._parents)
199
278
 
200
 
        import sha
201
 
        s = sha.new()
202
 
        map(s.update, text)
203
 
        sha1 = s.hexdigest()
204
 
        del s
 
279
        sha1 = sha_strings(text)
205
280
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
281
        # if we abort after here the (in-memory) weave will be corrupt because only
 
282
        # some fields are updated
 
283
        self._parents.append(parents[:])
208
284
        self._sha1s.append(sha1)
 
285
        self._names.append(name)
 
286
        self._name_map[name] = new_version
209
287
 
210
288
            
211
289
        if not parents:
216
294
            if text:
217
295
                self._weave.append(('{', new_version))
218
296
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
297
                self._weave.append(('}', None))
220
298
        
221
299
            return new_version
222
300
 
238
316
            basis_lineno.append(lineno)
239
317
            basis_lines.append(line)
240
318
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
319
        # another small special case: a merge, producing the same text
 
320
        # as auto-merge
242
321
        if text == basis_lines:
243
322
            return new_version            
244
323
 
287
366
                # we don't destroy ourselves
288
367
                i = i2 + offset
289
368
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
369
                                    + text[j1:j2] 
 
370
                                    + [('}', None)])
292
371
                offset += 2 + (j2 - j1)
293
372
 
294
373
        return new_version
309
388
            raise ValueError("version %d not present in weave" % v)
310
389
 
311
390
 
 
391
    def parents(self, version):
 
392
        return self._parents[version]
 
393
 
 
394
 
312
395
    def minimal_parents(self, version):
313
396
        """Find the minimal set of parents for the version."""
314
397
        included = self._parents[version]
352
435
                raise IndexError("invalid version number %r" % i)
353
436
 
354
437
    
355
 
    def annotate(self, index):
356
 
        return list(self.annotate_iter(index))
357
 
 
358
 
 
359
 
    def annotate_iter(self, version):
 
438
    def annotate(self, name_or_index):
 
439
        return list(self.annotate_iter(name_or_index))
 
440
 
 
441
 
 
442
    def annotate_iter(self, name_or_index):
360
443
        """Yield list of (index-id, line) pairs for the specified version.
361
444
 
362
445
        The index indicates when the line originated in the weave."""
363
 
        for origin, lineno, text in self._extract([version]):
 
446
        incls = [self.maybe_lookup(name_or_index)]
 
447
        for origin, lineno, text in self._extract(incls):
364
448
            yield origin, text
365
449
 
366
450
 
384
468
                if c == '{':
385
469
                    istack.append(v)
386
470
                elif c == '}':
387
 
                    oldv = istack.pop()
 
471
                    istack.pop()
388
472
                elif c == '[':
389
473
                    assert v not in dset
390
474
                    dset.add(v)
410
494
 
411
495
        The set typically but not necessarily corresponds to a version.
412
496
        """
 
497
        for i in versions:
 
498
            if not isinstance(i, int):
 
499
                raise ValueError(i)
 
500
            
413
501
        included = self.inclusions(versions)
414
502
 
415
503
        istack = []
431
519
                    assert v not in istack
432
520
                    istack.append(v)
433
521
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
522
                    istack.pop()
436
523
                elif c == '[':
437
524
                    if v in included:
438
525
                        assert v not in dset
461
548
    
462
549
 
463
550
 
464
 
    def get_iter(self, version):
 
551
    def get_iter(self, name_or_index):
465
552
        """Yield lines for the specified version."""
466
 
        for origin, lineno, line in self._extract([version]):
 
553
        incls = [self.maybe_lookup(name_or_index)]
 
554
        for origin, lineno, line in self._extract(incls):
467
555
            yield line
468
556
 
469
557
 
470
 
    def get(self, index):
471
 
        return list(self.get_iter(index))
 
558
    def get_text(self, version):
 
559
        assert isinstance(version, int)
 
560
        s = StringIO()
 
561
        s.writelines(self.get_iter(version))
 
562
        return s.getvalue()
 
563
 
 
564
 
 
565
    def get(self, name_or_index):
 
566
        return list(self.get_iter(name_or_index))
472
567
 
473
568
 
474
569
    def mash_iter(self, included):
508
603
 
509
604
        # try extracting all versions; this is a bit slow and parallel
510
605
        # extraction could be used
511
 
        import sha
512
606
        nv = self.numversions()
513
607
        for version in range(nv):
514
608
            if progress_bar:
670
764
 
671
765
 
672
766
 
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):
 
767
def weave_toc(w):
 
768
    """Show the weave's table-of-contents"""
 
769
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
770
    for i in (6, 50, 10, 10):
677
771
        print '-' * i,
678
772
    print
679
773
    for i in range(w.numversions()):
680
774
        sha1 = w._sha1s[i]
681
 
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
 
775
        name = w._names[i]
 
776
        parent_str = ' '.join(map(str, w._parents[i]))
 
777
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
682
778
 
683
779
 
684
780
 
706
802
    print 'weave file        %9d bytes' % weave_size
707
803
    print 'total contents    %9d bytes' % total
708
804
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
805
    if vers:
 
806
        avg = total/vers
 
807
        print 'average size      %9d bytes' % avg
 
808
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
809
 
711
810
 
712
811
def usage():
721
820
        Write out specified version.
722
821
    weave check WEAVEFILE
723
822
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
823
    weave toc WEAVEFILE
725
824
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
825
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
826
        Add NEWTEXT, with specified parent versions.
728
827
    weave annotate WEAVEFILE VERSION
729
828
        Display origin of each line.
736
835
 
737
836
    % weave init foo.weave
738
837
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
838
    % weave add foo.weave ver0 < foo.txt
740
839
    added version 0
741
840
 
742
841
    (create updated version)
743
842
    % vi foo.txt
744
843
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
844
    % weave add foo.weave ver1 0 < foo.txt
746
845
    added version 1
747
846
 
748
847
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
848
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
849
    % weave add foo.weave ver2 0 < foo.txt
751
850
    added version 2
752
851
 
753
852
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
853
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
854
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
855
    
757
856
"""
758
857
    
764
863
    from weavefile import write_weave, read_weave
765
864
    from bzrlib.progress import ProgressBar
766
865
 
767
 
    #import psyco
768
 
    #psyco.full()
 
866
    try:
 
867
        import psyco
 
868
        psyco.full()
 
869
    except ImportError:
 
870
        pass
 
871
 
 
872
    if len(argv) < 2:
 
873
        usage()
 
874
        return 0
769
875
 
770
876
    cmd = argv[1]
771
877
 
777
883
    elif cmd == 'add':
778
884
        w = readit()
779
885
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
886
        name = argv[3]
 
887
        parents = map(int, argv[4:])
781
888
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
889
        ver = w.add(name, parents, lines)
783
890
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
891
        print 'added version %r %d' % (name, ver)
785
892
    elif cmd == 'init':
786
893
        fn = argv[2]
787
894
        if os.path.exists(fn):
809
916
                print '%5d | %s' % (origin, text)
810
917
                lasto = origin
811
918
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
919
    elif cmd == 'toc':
 
920
        weave_toc(readit())
814
921
 
815
922
    elif cmd == 'stats':
816
923
        weave_stats(argv[2])
865
972
        raise ValueError('unknown command %r' % cmd)
866
973
    
867
974
 
 
975
 
 
976
def profile_main(argv): 
 
977
    import tempfile, hotshot, hotshot.stats
 
978
 
 
979
    prof_f = tempfile.NamedTemporaryFile()
 
980
 
 
981
    prof = hotshot.Profile(prof_f.name)
 
982
 
 
983
    ret = prof.runcall(main, argv)
 
984
    prof.close()
 
985
 
 
986
    stats = hotshot.stats.load(prof_f.name)
 
987
    #stats.strip_dirs()
 
988
    stats.sort_stats('cumulative')
 
989
    ## XXX: Might like to write to stderr or the trace file instead but
 
990
    ## print_stats seems hardcoded to stdout
 
991
    stats.print_stats(20)
 
992
            
 
993
    return ret
 
994
 
 
995
 
868
996
if __name__ == '__main__':
869
997
    import sys
870
 
    sys.exit(main(sys.argv))
 
998
    if '--profile' in sys.argv:
 
999
        args = sys.argv[:]
 
1000
        args.remove('--profile')
 
1001
        sys.exit(profile_main(args))
 
1002
    else:
 
1003
        sys.exit(main(sys.argv))
 
1004