/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: Robert Collins
  • Date: 2005-10-03 05:54:35 UTC
  • mto: (1393.1.30)
  • mto: This revision was merged to the branch mainline in revision 1400.
  • Revision ID: robertc@robertcollins.net-20051003055434-c8ebd30d1de10247
move exporting functionality into inventory.py - uncovers bug in symlink support

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
295
381
 
 
382
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
383
        """Add an identical text to old_rev_id as new_rev_id."""
 
384
        old_lines = self.get(self.lookup(old_rev_id))
 
385
        self.add(new_rev_id, parents, old_lines)
296
386
 
297
387
    def inclusions(self, versions):
298
388
        """Return set of all ancestors of given version(s)."""
299
389
        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)
 
390
        for v in xrange(max(versions), 0, -1):
 
391
            if v in i:
 
392
                # include all its parents
 
393
                i.update(self._parents[v])
 
394
        return i
 
395
        ## except IndexError:
 
396
        ##     raise ValueError("version %d not present in weave" % v)
 
397
 
 
398
 
 
399
    def parents(self, version):
 
400
        return self._parents[version]
310
401
 
311
402
 
312
403
    def minimal_parents(self, version):
352
443
                raise IndexError("invalid version number %r" % i)
353
444
 
354
445
    
355
 
    def annotate(self, index):
356
 
        return list(self.annotate_iter(index))
357
 
 
358
 
 
359
 
    def annotate_iter(self, version):
 
446
    def annotate(self, name_or_index):
 
447
        return list(self.annotate_iter(name_or_index))
 
448
 
 
449
 
 
450
    def annotate_iter(self, name_or_index):
360
451
        """Yield list of (index-id, line) pairs for the specified version.
361
452
 
362
453
        The index indicates when the line originated in the weave."""
363
 
        for origin, lineno, text in self._extract([version]):
 
454
        incls = [self.maybe_lookup(name_or_index)]
 
455
        for origin, lineno, text in self._extract(incls):
364
456
            yield origin, text
365
457
 
366
458
 
384
476
                if c == '{':
385
477
                    istack.append(v)
386
478
                elif c == '}':
387
 
                    oldv = istack.pop()
 
479
                    istack.pop()
388
480
                elif c == '[':
389
481
                    assert v not in dset
390
482
                    dset.add(v)
410
502
 
411
503
        The set typically but not necessarily corresponds to a version.
412
504
        """
 
505
        for i in versions:
 
506
            if not isinstance(i, int):
 
507
                raise ValueError(i)
 
508
            
413
509
        included = self.inclusions(versions)
414
510
 
415
511
        istack = []
431
527
                    assert v not in istack
432
528
                    istack.append(v)
433
529
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
530
                    istack.pop()
436
531
                elif c == '[':
437
532
                    if v in included:
438
533
                        assert v not in dset
461
556
    
462
557
 
463
558
 
464
 
    def get_iter(self, version):
 
559
    def get_iter(self, name_or_index):
465
560
        """Yield lines for the specified version."""
466
 
        for origin, lineno, line in self._extract([version]):
 
561
        incls = [self.maybe_lookup(name_or_index)]
 
562
        for origin, lineno, line in self._extract(incls):
467
563
            yield line
468
564
 
469
565
 
470
 
    def get(self, index):
471
 
        return list(self.get_iter(index))
 
566
    def get_text(self, name_or_index):
 
567
        return ''.join(self.get_iter(name_or_index))
 
568
        assert isinstance(version, int)
 
569
 
 
570
 
 
571
    def get_lines(self, name_or_index):
 
572
        return list(self.get_iter(name_or_index))
 
573
 
 
574
 
 
575
    get = get_lines
472
576
 
473
577
 
474
578
    def mash_iter(self, included):
475
579
        """Return composed version of multiple included versions."""
 
580
        included = map(self.maybe_lookup, included)
476
581
        for origin, lineno, text in self._extract(included):
477
582
            yield text
478
583
 
508
613
 
509
614
        # try extracting all versions; this is a bit slow and parallel
510
615
        # extraction could be used
511
 
        import sha
512
616
        nv = self.numversions()
513
617
        for version in range(nv):
514
618
            if progress_bar:
670
774
 
671
775
 
672
776
 
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):
 
777
def weave_toc(w):
 
778
    """Show the weave's table-of-contents"""
 
779
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
780
    for i in (6, 50, 10, 10):
677
781
        print '-' * i,
678
782
    print
679
783
    for i in range(w.numversions()):
680
784
        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
 
785
        name = w._names[i]
 
786
        parent_str = ' '.join(map(str, w._parents[i]))
 
787
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
788
 
 
789
 
 
790
 
 
791
def weave_stats(weave_file, pb):
687
792
    from bzrlib.weavefile import read_weave
688
793
 
689
 
    pb = ProgressBar()
690
 
 
691
794
    wf = file(weave_file, 'rb')
692
795
    w = read_weave(wf)
693
796
    # FIXME: doesn't work on pipes
706
809
    print 'weave file        %9d bytes' % weave_size
707
810
    print 'total contents    %9d bytes' % total
708
811
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
812
    if vers:
 
813
        avg = total/vers
 
814
        print 'average size      %9d bytes' % avg
 
815
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
816
 
711
817
 
712
818
def usage():
721
827
        Write out specified version.
722
828
    weave check WEAVEFILE
723
829
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
830
    weave toc WEAVEFILE
725
831
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
832
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
833
        Add NEWTEXT, with specified parent versions.
728
834
    weave annotate WEAVEFILE VERSION
729
835
        Display origin of each line.
731
837
        Display composite of all selected versions.
732
838
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
839
        Auto-merge two versions and display conflicts.
 
840
    weave diff WEAVEFILE VERSION1 VERSION2 
 
841
        Show differences between two versions.
734
842
 
735
843
example:
736
844
 
737
845
    % weave init foo.weave
738
846
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
847
    % weave add foo.weave ver0 < foo.txt
740
848
    added version 0
741
849
 
742
850
    (create updated version)
743
851
    % vi foo.txt
744
852
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
853
    % weave add foo.weave ver1 0 < foo.txt
746
854
    added version 1
747
855
 
748
856
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
857
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
858
    % weave add foo.weave ver2 0 < foo.txt
751
859
    added version 2
752
860
 
753
861
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
862
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
863
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
864
    
757
865
"""
758
866
    
761
869
def main(argv):
762
870
    import sys
763
871
    import os
764
 
    from weavefile import write_weave, read_weave
 
872
    try:
 
873
        import bzrlib
 
874
    except ImportError:
 
875
        # in case we're run directly from the subdirectory
 
876
        sys.path.append('..')
 
877
        import bzrlib
 
878
    from bzrlib.weavefile import write_weave, read_weave
765
879
    from bzrlib.progress import ProgressBar
766
880
 
767
 
    #import psyco
768
 
    #psyco.full()
 
881
    try:
 
882
        import psyco
 
883
        psyco.full()
 
884
    except ImportError:
 
885
        pass
 
886
 
 
887
    if len(argv) < 2:
 
888
        usage()
 
889
        return 0
769
890
 
770
891
    cmd = argv[1]
771
892
 
777
898
    elif cmd == 'add':
778
899
        w = readit()
779
900
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
901
        name = argv[3]
 
902
        parents = map(int, argv[4:])
781
903
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
904
        ver = w.add(name, parents, lines)
783
905
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
906
        print 'added version %r %d' % (name, ver)
785
907
    elif cmd == 'init':
786
908
        fn = argv[2]
787
909
        if os.path.exists(fn):
796
918
        w = readit()
797
919
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
798
920
 
 
921
    elif cmd == 'diff':
 
922
        from difflib import unified_diff
 
923
        w = readit()
 
924
        fn = argv[2]
 
925
        v1, v2 = map(int, argv[3:5])
 
926
        lines1 = w.get(v1)
 
927
        lines2 = w.get(v2)
 
928
        diff_gen = unified_diff(lines1, lines2,
 
929
                                '%s version %d' % (fn, v1),
 
930
                                '%s version %d' % (fn, v2))
 
931
        sys.stdout.writelines(diff_gen)
 
932
            
799
933
    elif cmd == 'annotate':
800
934
        w = readit()
801
935
        # newline is added to all lines regardless; too hard to get
809
943
                print '%5d | %s' % (origin, text)
810
944
                lasto = origin
811
945
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
946
    elif cmd == 'toc':
 
947
        weave_toc(readit())
814
948
 
815
949
    elif cmd == 'stats':
816
 
        weave_stats(argv[2])
 
950
        weave_stats(argv[2], ProgressBar())
817
951
        
818
952
    elif cmd == 'check':
819
953
        w = readit()
865
999
        raise ValueError('unknown command %r' % cmd)
866
1000
    
867
1001
 
 
1002
 
 
1003
def profile_main(argv): 
 
1004
    import tempfile, hotshot, hotshot.stats
 
1005
 
 
1006
    prof_f = tempfile.NamedTemporaryFile()
 
1007
 
 
1008
    prof = hotshot.Profile(prof_f.name)
 
1009
 
 
1010
    ret = prof.runcall(main, argv)
 
1011
    prof.close()
 
1012
 
 
1013
    stats = hotshot.stats.load(prof_f.name)
 
1014
    #stats.strip_dirs()
 
1015
    stats.sort_stats('cumulative')
 
1016
    ## XXX: Might like to write to stderr or the trace file instead but
 
1017
    ## print_stats seems hardcoded to stdout
 
1018
    stats.print_stats(20)
 
1019
            
 
1020
    return ret
 
1021
 
 
1022
 
868
1023
if __name__ == '__main__':
869
1024
    import sys
870
 
    sys.exit(main(sys.argv))
 
1025
    if '--profile' in sys.argv:
 
1026
        args = sys.argv[:]
 
1027
        args.remove('--profile')
 
1028
        sys.exit(profile_main(args))
 
1029
    else:
 
1030
        sys.exit(main(sys.argv))
 
1031