/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-29 12:49:59 UTC
  • mto: (1185.14.2)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: mbp@sourcefrog.net-20050929124959-df3f175844fc90ed
- remove redundant import

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
 
 
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
# TODO: Is there any potential performance win by having an add()
 
88
# variant that is passed a pre-cooked version of the single basis
 
89
# version?
 
90
 
 
91
 
 
92
 
 
93
import sha
 
94
from difflib import SequenceMatcher
 
95
 
78
96
 
79
97
 
80
98
 
118
136
    The instruction can be '{' or '}' for an insertion block, and '['
119
137
    and ']' for a deletion block respectively.  The version is the
120
138
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
139
    and inserts.  For '}', the end of an insertion, there is no
 
140
    version parameter because it always closes the most recently
 
141
    opened insertion.
122
142
 
123
143
    Constraints/notes:
124
144
 
160
180
        each version; the parent's parents are implied.
161
181
 
162
182
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
183
        List of hex SHA-1 of each version.
 
184
 
 
185
    _names
 
186
        List of symbolic names for each version.  Each should be unique.
 
187
 
 
188
    _name_map
 
189
        For each name, the version number.
 
190
 
 
191
    _weave_name
 
192
        Descriptive name of this weave; typically the filename if known.
 
193
        Set by read_weave.
164
194
    """
165
195
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
196
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
197
                 '_weave_name']
167
198
    
168
 
    def __init__(self):
 
199
    def __init__(self, weave_name=None):
169
200
        self._weave = []
170
201
        self._parents = []
171
202
        self._sha1s = []
 
203
        self._names = []
 
204
        self._name_map = {}
 
205
        self._weave_name = weave_name
172
206
 
173
207
 
174
208
    def __eq__(self, other):
175
209
        if not isinstance(other, Weave):
176
210
            return False
177
211
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
212
               and self._weave == other._weave \
 
213
               and self._sha1s == other._sha1s 
 
214
 
179
215
    
180
 
 
181
216
    def __ne__(self, other):
182
217
        return not self.__eq__(other)
183
218
 
184
 
        
185
 
    def add(self, parents, text):
 
219
 
 
220
    def maybe_lookup(self, name_or_index):
 
221
        """Convert possible symbolic name to index, or pass through indexes."""
 
222
        if isinstance(name_or_index, (int, long)):
 
223
            return name_or_index
 
224
        else:
 
225
            return self.lookup(name_or_index)
 
226
 
 
227
        
 
228
    def lookup(self, name):
 
229
        """Convert symbolic version name to index."""
 
230
        try:
 
231
            return self._name_map[name]
 
232
        except KeyError:
 
233
            raise WeaveError("name %r not present in weave %r" %
 
234
                             (name, self._weave_name))
 
235
 
 
236
 
 
237
    def idx_to_name(self, version):
 
238
        return self._names[version]
 
239
 
 
240
 
 
241
    def _check_repeated_add(self, name, parents, text, sha1):
 
242
        """Check that a duplicated add is OK.
 
243
 
 
244
        If it is, return the (old) index; otherwise raise an exception.
 
245
        """
 
246
        idx = self.lookup(name)
 
247
        if sorted(self._parents[idx]) != sorted(parents):
 
248
            raise WeaveError("name \"%s\" already present in weave "
 
249
                             "with different parents" % name)
 
250
        if sha1 != self._sha1s[idx]:
 
251
            raise WeaveError("name \"%s\" already present in weave "
 
252
                             "with different text" % name)            
 
253
        return idx
 
254
        
 
255
 
 
256
        
 
257
    def add(self, name, parents, text, sha1=None):
186
258
        """Add a single text on top of the weave.
187
259
  
188
260
        Returns the index number of the newly added version.
189
261
 
 
262
        name
 
263
            Symbolic name for this version.
 
264
            (Typically the revision-id of the revision that added it.)
 
265
 
190
266
        parents
191
267
            List or set of direct parent version numbers.
192
268
            
193
269
        text
194
 
            Sequence of lines to be added in the new version."""
195
 
 
 
270
            Sequence of lines to be added in the new version.
 
271
 
 
272
        sha -- SHA-1 of the file, if known.  This is trusted to be
 
273
            correct if supplied.
 
274
        """
 
275
        from bzrlib.osutils import sha_strings
 
276
 
 
277
        assert isinstance(name, basestring)
 
278
        if sha1 is None:
 
279
            sha1 = sha_strings(text)
 
280
        if name in self._name_map:
 
281
            return self._check_repeated_add(name, parents, text, sha1)
 
282
 
 
283
        parents = map(self.maybe_lookup, parents)
196
284
        self._check_versions(parents)
197
285
        ## self._check_lines(text)
198
286
        new_version = len(self._parents)
199
287
 
200
 
        import sha
201
 
        s = sha.new()
202
 
        map(s.update, text)
203
 
        sha1 = s.hexdigest()
204
 
        del s
205
288
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
289
        # if we abort after here the (in-memory) weave will be corrupt because only
 
290
        # some fields are updated
 
291
        self._parents.append(parents[:])
208
292
        self._sha1s.append(sha1)
 
293
        self._names.append(name)
 
294
        self._name_map[name] = new_version
209
295
 
210
296
            
211
297
        if not parents:
216
302
            if text:
217
303
                self._weave.append(('{', new_version))
218
304
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
305
                self._weave.append(('}', None))
220
306
        
221
307
            return new_version
222
308
 
238
324
            basis_lineno.append(lineno)
239
325
            basis_lines.append(line)
240
326
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
327
        # another small special case: a merge, producing the same text
 
328
        # as auto-merge
242
329
        if text == basis_lines:
243
330
            return new_version            
244
331
 
252
339
        #print 'basis_lines:', basis_lines
253
340
        #print 'new_lines:  ', lines
254
341
 
255
 
        from difflib import SequenceMatcher
256
342
        s = SequenceMatcher(None, basis_lines, text)
257
343
 
258
344
        # offset gives the number of lines that have been inserted
287
373
                # we don't destroy ourselves
288
374
                i = i2 + offset
289
375
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
376
                                    + text[j1:j2] 
 
377
                                    + [('}', None)])
292
378
                offset += 2 + (j2 - j1)
293
379
 
294
380
        return new_version
297
383
    def inclusions(self, versions):
298
384
        """Return set of all ancestors of given version(s)."""
299
385
        i = set(versions)
300
 
        v = max(versions)
301
 
        try:
302
 
            while v >= 0:
303
 
                if v in i:
304
 
                    # include all its parents
305
 
                    i.update(self._parents[v])
306
 
                v -= 1
307
 
            return i
308
 
        except IndexError:
309
 
            raise ValueError("version %d not present in weave" % v)
 
386
        for v in xrange(max(versions), 0, -1):
 
387
            if v in i:
 
388
                # include all its parents
 
389
                i.update(self._parents[v])
 
390
        return i
 
391
        ## except IndexError:
 
392
        ##     raise ValueError("version %d not present in weave" % v)
 
393
 
 
394
 
 
395
    def parents(self, version):
 
396
        return self._parents[version]
310
397
 
311
398
 
312
399
    def minimal_parents(self, version):
352
439
                raise IndexError("invalid version number %r" % i)
353
440
 
354
441
    
355
 
    def annotate(self, index):
356
 
        return list(self.annotate_iter(index))
357
 
 
358
 
 
359
 
    def annotate_iter(self, version):
 
442
    def annotate(self, name_or_index):
 
443
        return list(self.annotate_iter(name_or_index))
 
444
 
 
445
 
 
446
    def annotate_iter(self, name_or_index):
360
447
        """Yield list of (index-id, line) pairs for the specified version.
361
448
 
362
449
        The index indicates when the line originated in the weave."""
363
 
        for origin, lineno, text in self._extract([version]):
 
450
        incls = [self.maybe_lookup(name_or_index)]
 
451
        for origin, lineno, text in self._extract(incls):
364
452
            yield origin, text
365
453
 
366
454
 
384
472
                if c == '{':
385
473
                    istack.append(v)
386
474
                elif c == '}':
387
 
                    oldv = istack.pop()
 
475
                    istack.pop()
388
476
                elif c == '[':
389
477
                    assert v not in dset
390
478
                    dset.add(v)
410
498
 
411
499
        The set typically but not necessarily corresponds to a version.
412
500
        """
 
501
        for i in versions:
 
502
            if not isinstance(i, int):
 
503
                raise ValueError(i)
 
504
            
413
505
        included = self.inclusions(versions)
414
506
 
415
507
        istack = []
431
523
                    assert v not in istack
432
524
                    istack.append(v)
433
525
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
526
                    istack.pop()
436
527
                elif c == '[':
437
528
                    if v in included:
438
529
                        assert v not in dset
461
552
    
462
553
 
463
554
 
464
 
    def get_iter(self, version):
 
555
    def get_iter(self, name_or_index):
465
556
        """Yield lines for the specified version."""
466
 
        for origin, lineno, line in self._extract([version]):
 
557
        incls = [self.maybe_lookup(name_or_index)]
 
558
        for origin, lineno, line in self._extract(incls):
467
559
            yield line
468
560
 
469
561
 
470
 
    def get(self, index):
471
 
        return list(self.get_iter(index))
 
562
    def get_text(self, name_or_index):
 
563
        return ''.join(self.get_iter(name_or_index))
 
564
        assert isinstance(version, int)
 
565
 
 
566
 
 
567
    def get_lines(self, name_or_index):
 
568
        return list(self.get_iter(name_or_index))
 
569
 
 
570
 
 
571
    get = get_lines
472
572
 
473
573
 
474
574
    def mash_iter(self, included):
475
575
        """Return composed version of multiple included versions."""
 
576
        included = map(self.maybe_lookup, included)
476
577
        for origin, lineno, text in self._extract(included):
477
578
            yield text
478
579
 
508
609
 
509
610
        # try extracting all versions; this is a bit slow and parallel
510
611
        # extraction could be used
511
 
        import sha
512
612
        nv = self.numversions()
513
613
        for version in range(nv):
514
614
            if progress_bar:
670
770
 
671
771
 
672
772
 
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):
 
773
def weave_toc(w):
 
774
    """Show the weave's table-of-contents"""
 
775
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
776
    for i in (6, 50, 10, 10):
677
777
        print '-' * i,
678
778
    print
679
779
    for i in range(w.numversions()):
680
780
        sha1 = w._sha1s[i]
681
 
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
682
 
 
683
 
 
684
 
 
685
 
def weave_stats(weave_file):
686
 
    from bzrlib.progress import ProgressBar
 
781
        name = w._names[i]
 
782
        parent_str = ' '.join(map(str, w._parents[i]))
 
783
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
784
 
 
785
 
 
786
 
 
787
def weave_stats(weave_file, pb):
687
788
    from bzrlib.weavefile import read_weave
688
789
 
689
 
    pb = ProgressBar()
690
 
 
691
790
    wf = file(weave_file, 'rb')
692
791
    w = read_weave(wf)
693
792
    # FIXME: doesn't work on pipes
706
805
    print 'weave file        %9d bytes' % weave_size
707
806
    print 'total contents    %9d bytes' % total
708
807
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
808
    if vers:
 
809
        avg = total/vers
 
810
        print 'average size      %9d bytes' % avg
 
811
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
812
 
711
813
 
712
814
def usage():
721
823
        Write out specified version.
722
824
    weave check WEAVEFILE
723
825
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
826
    weave toc WEAVEFILE
725
827
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
828
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
829
        Add NEWTEXT, with specified parent versions.
728
830
    weave annotate WEAVEFILE VERSION
729
831
        Display origin of each line.
731
833
        Display composite of all selected versions.
732
834
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
835
        Auto-merge two versions and display conflicts.
 
836
    weave diff WEAVEFILE VERSION1 VERSION2 
 
837
        Show differences between two versions.
734
838
 
735
839
example:
736
840
 
737
841
    % weave init foo.weave
738
842
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
843
    % weave add foo.weave ver0 < foo.txt
740
844
    added version 0
741
845
 
742
846
    (create updated version)
743
847
    % vi foo.txt
744
848
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
849
    % weave add foo.weave ver1 0 < foo.txt
746
850
    added version 1
747
851
 
748
852
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
853
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
854
    % weave add foo.weave ver2 0 < foo.txt
751
855
    added version 2
752
856
 
753
857
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
858
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
859
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
860
    
757
861
"""
758
862
    
761
865
def main(argv):
762
866
    import sys
763
867
    import os
764
 
    from weavefile import write_weave, read_weave
 
868
    try:
 
869
        import bzrlib
 
870
    except ImportError:
 
871
        # in case we're run directly from the subdirectory
 
872
        sys.path.append('..')
 
873
        import bzrlib
 
874
    from bzrlib.weavefile import write_weave, read_weave
765
875
    from bzrlib.progress import ProgressBar
766
876
 
767
 
    #import psyco
768
 
    #psyco.full()
 
877
    try:
 
878
        import psyco
 
879
        psyco.full()
 
880
    except ImportError:
 
881
        pass
 
882
 
 
883
    if len(argv) < 2:
 
884
        usage()
 
885
        return 0
769
886
 
770
887
    cmd = argv[1]
771
888
 
777
894
    elif cmd == 'add':
778
895
        w = readit()
779
896
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
897
        name = argv[3]
 
898
        parents = map(int, argv[4:])
781
899
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
900
        ver = w.add(name, parents, lines)
783
901
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
902
        print 'added version %r %d' % (name, ver)
785
903
    elif cmd == 'init':
786
904
        fn = argv[2]
787
905
        if os.path.exists(fn):
796
914
        w = readit()
797
915
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
798
916
 
 
917
    elif cmd == 'diff':
 
918
        from difflib import unified_diff
 
919
        w = readit()
 
920
        fn = argv[2]
 
921
        v1, v2 = map(int, argv[3:5])
 
922
        lines1 = w.get(v1)
 
923
        lines2 = w.get(v2)
 
924
        diff_gen = unified_diff(lines1, lines2,
 
925
                                '%s version %d' % (fn, v1),
 
926
                                '%s version %d' % (fn, v2))
 
927
        sys.stdout.writelines(diff_gen)
 
928
            
799
929
    elif cmd == 'annotate':
800
930
        w = readit()
801
931
        # newline is added to all lines regardless; too hard to get
809
939
                print '%5d | %s' % (origin, text)
810
940
                lasto = origin
811
941
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
942
    elif cmd == 'toc':
 
943
        weave_toc(readit())
814
944
 
815
945
    elif cmd == 'stats':
816
 
        weave_stats(argv[2])
 
946
        weave_stats(argv[2], ProgressBar())
817
947
        
818
948
    elif cmd == 'check':
819
949
        w = readit()
865
995
        raise ValueError('unknown command %r' % cmd)
866
996
    
867
997
 
 
998
 
 
999
def profile_main(argv): 
 
1000
    import tempfile, hotshot, hotshot.stats
 
1001
 
 
1002
    prof_f = tempfile.NamedTemporaryFile()
 
1003
 
 
1004
    prof = hotshot.Profile(prof_f.name)
 
1005
 
 
1006
    ret = prof.runcall(main, argv)
 
1007
    prof.close()
 
1008
 
 
1009
    stats = hotshot.stats.load(prof_f.name)
 
1010
    #stats.strip_dirs()
 
1011
    stats.sort_stats('cumulative')
 
1012
    ## XXX: Might like to write to stderr or the trace file instead but
 
1013
    ## print_stats seems hardcoded to stdout
 
1014
    stats.print_stats(20)
 
1015
            
 
1016
    return ret
 
1017
 
 
1018
 
868
1019
if __name__ == '__main__':
869
1020
    import sys
870
 
    sys.exit(main(sys.argv))
 
1021
    if '--profile' in sys.argv:
 
1022
        args = sys.argv[:]
 
1023
        args.remove('--profile')
 
1024
        sys.exit(profile_main(args))
 
1025
    else:
 
1026
        sys.exit(main(sys.argv))
 
1027