/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-09-30 02:54:51 UTC
  • mfrom: (1395)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050930025451-47b9e412202be44b
symlink and weaves, whaddya know

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
# 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
 
 
96
 
 
97
from bzrlib.osutils import sha_strings
79
98
 
80
99
 
81
100
class WeaveError(Exception):
118
137
    The instruction can be '{' or '}' for an insertion block, and '['
119
138
    and ']' for a deletion block respectively.  The version is the
120
139
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
140
    and inserts.  For '}', the end of an insertion, there is no
 
141
    version parameter because it always closes the most recently
 
142
    opened insertion.
122
143
 
123
144
    Constraints/notes:
124
145
 
160
181
        each version; the parent's parents are implied.
161
182
 
162
183
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
184
        List of hex SHA-1 of each version.
 
185
 
 
186
    _names
 
187
        List of symbolic names for each version.  Each should be unique.
 
188
 
 
189
    _name_map
 
190
        For each name, the version number.
 
191
 
 
192
    _weave_name
 
193
        Descriptive name of this weave; typically the filename if known.
 
194
        Set by read_weave.
164
195
    """
165
196
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
197
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
198
                 '_weave_name']
167
199
    
168
 
    def __init__(self):
 
200
    def __init__(self, weave_name=None):
169
201
        self._weave = []
170
202
        self._parents = []
171
203
        self._sha1s = []
 
204
        self._names = []
 
205
        self._name_map = {}
 
206
        self._weave_name = weave_name
172
207
 
173
208
 
174
209
    def __eq__(self, other):
175
210
        if not isinstance(other, Weave):
176
211
            return False
177
212
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
213
               and self._weave == other._weave \
 
214
               and self._sha1s == other._sha1s 
 
215
 
179
216
    
180
 
 
181
217
    def __ne__(self, other):
182
218
        return not self.__eq__(other)
183
219
 
184
 
        
185
 
    def add(self, parents, text):
 
220
 
 
221
    def maybe_lookup(self, name_or_index):
 
222
        """Convert possible symbolic name to index, or pass through indexes."""
 
223
        if isinstance(name_or_index, (int, long)):
 
224
            return name_or_index
 
225
        else:
 
226
            return self.lookup(name_or_index)
 
227
 
 
228
        
 
229
    def lookup(self, name):
 
230
        """Convert symbolic version name to index."""
 
231
        try:
 
232
            return self._name_map[name]
 
233
        except KeyError:
 
234
            raise WeaveError("name %r not present in weave %r" %
 
235
                             (name, self._weave_name))
 
236
 
 
237
 
 
238
    def idx_to_name(self, version):
 
239
        return self._names[version]
 
240
 
 
241
 
 
242
    def _check_repeated_add(self, name, parents, text, sha1):
 
243
        """Check that a duplicated add is OK.
 
244
 
 
245
        If it is, return the (old) index; otherwise raise an exception.
 
246
        """
 
247
        idx = self.lookup(name)
 
248
        if sorted(self._parents[idx]) != sorted(parents):
 
249
            raise WeaveError("name \"%s\" already present in weave "
 
250
                             "with different parents" % name)
 
251
        if sha1 != self._sha1s[idx]:
 
252
            raise WeaveError("name \"%s\" already present in weave "
 
253
                             "with different text" % name)            
 
254
        return idx
 
255
        
 
256
 
 
257
        
 
258
    def add(self, name, parents, text, sha1=None):
186
259
        """Add a single text on top of the weave.
187
260
  
188
261
        Returns the index number of the newly added version.
189
262
 
 
263
        name
 
264
            Symbolic name for this version.
 
265
            (Typically the revision-id of the revision that added it.)
 
266
 
190
267
        parents
191
268
            List or set of direct parent version numbers.
192
269
            
193
270
        text
194
 
            Sequence of lines to be added in the new version."""
195
 
 
 
271
            Sequence of lines to be added in the new version.
 
272
 
 
273
        sha -- SHA-1 of the file, if known.  This is trusted to be
 
274
            correct if supplied.
 
275
        """
 
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])))
 
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)
682
784
 
683
785
 
684
786
 
706
808
    print 'weave file        %9d bytes' % weave_size
707
809
    print 'total contents    %9d bytes' % total
708
810
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
811
    if vers:
 
812
        avg = total/vers
 
813
        print 'average size      %9d bytes' % avg
 
814
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
815
 
711
816
 
712
817
def usage():
721
826
        Write out specified version.
722
827
    weave check WEAVEFILE
723
828
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
829
    weave toc WEAVEFILE
725
830
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
831
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
832
        Add NEWTEXT, with specified parent versions.
728
833
    weave annotate WEAVEFILE VERSION
729
834
        Display origin of each line.
731
836
        Display composite of all selected versions.
732
837
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
838
        Auto-merge two versions and display conflicts.
 
839
    weave diff WEAVEFILE VERSION1 VERSION2 
 
840
        Show differences between two versions.
734
841
 
735
842
example:
736
843
 
737
844
    % weave init foo.weave
738
845
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
846
    % weave add foo.weave ver0 < foo.txt
740
847
    added version 0
741
848
 
742
849
    (create updated version)
743
850
    % vi foo.txt
744
851
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
852
    % weave add foo.weave ver1 0 < foo.txt
746
853
    added version 1
747
854
 
748
855
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
856
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
857
    % weave add foo.weave ver2 0 < foo.txt
751
858
    added version 2
752
859
 
753
860
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
861
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
862
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
863
    
757
864
"""
758
865
    
761
868
def main(argv):
762
869
    import sys
763
870
    import os
764
 
    from weavefile import write_weave, read_weave
 
871
    from bzrlib.weavefile import write_weave, read_weave
765
872
    from bzrlib.progress import ProgressBar
766
873
 
767
 
    #import psyco
768
 
    #psyco.full()
 
874
    try:
 
875
        import psyco
 
876
        psyco.full()
 
877
    except ImportError:
 
878
        pass
 
879
 
 
880
    if len(argv) < 2:
 
881
        usage()
 
882
        return 0
769
883
 
770
884
    cmd = argv[1]
771
885
 
777
891
    elif cmd == 'add':
778
892
        w = readit()
779
893
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
894
        name = argv[3]
 
895
        parents = map(int, argv[4:])
781
896
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
897
        ver = w.add(name, parents, lines)
783
898
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
899
        print 'added version %r %d' % (name, ver)
785
900
    elif cmd == 'init':
786
901
        fn = argv[2]
787
902
        if os.path.exists(fn):
796
911
        w = readit()
797
912
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
798
913
 
 
914
    elif cmd == 'diff':
 
915
        from difflib import unified_diff
 
916
        w = readit()
 
917
        fn = argv[2]
 
918
        v1, v2 = map(int, argv[3:5])
 
919
        lines1 = w.get(v1)
 
920
        lines2 = w.get(v2)
 
921
        diff_gen = unified_diff(lines1, lines2,
 
922
                                '%s version %d' % (fn, v1),
 
923
                                '%s version %d' % (fn, v2))
 
924
        sys.stdout.writelines(diff_gen)
 
925
            
799
926
    elif cmd == 'annotate':
800
927
        w = readit()
801
928
        # newline is added to all lines regardless; too hard to get
809
936
                print '%5d | %s' % (origin, text)
810
937
                lasto = origin
811
938
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
939
    elif cmd == 'toc':
 
940
        weave_toc(readit())
814
941
 
815
942
    elif cmd == 'stats':
816
943
        weave_stats(argv[2])
865
992
        raise ValueError('unknown command %r' % cmd)
866
993
    
867
994
 
 
995
 
 
996
def profile_main(argv): 
 
997
    import tempfile, hotshot, hotshot.stats
 
998
 
 
999
    prof_f = tempfile.NamedTemporaryFile()
 
1000
 
 
1001
    prof = hotshot.Profile(prof_f.name)
 
1002
 
 
1003
    ret = prof.runcall(main, argv)
 
1004
    prof.close()
 
1005
 
 
1006
    stats = hotshot.stats.load(prof_f.name)
 
1007
    #stats.strip_dirs()
 
1008
    stats.sort_stats('cumulative')
 
1009
    ## XXX: Might like to write to stderr or the trace file instead but
 
1010
    ## print_stats seems hardcoded to stdout
 
1011
    stats.print_stats(20)
 
1012
            
 
1013
    return ret
 
1014
 
 
1015
 
868
1016
if __name__ == '__main__':
869
1017
    import sys
870
 
    sys.exit(main(sys.argv))
 
1018
    if '--profile' in sys.argv:
 
1019
        args = sys.argv[:]
 
1020
        args.remove('--profile')
 
1021
        sys.exit(profile_main(args))
 
1022
    else:
 
1023
        sys.exit(main(sys.argv))
 
1024