/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-16 04:47:12 UTC
  • Revision ID: mbp@sourcefrog.net-20050916044711-e5a7bfc6ead3800b
- is_ancestor now works by looking at the Branch's stored ancestry

- some tests related to this

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 lookup(self, name):
 
218
        assert isinstance(name, basestring), type(name)
 
219
        try:
 
220
            return self._name_map[name]
 
221
        except KeyError:
 
222
            raise WeaveError("name %r not present in weave %r" %
 
223
                             (name, self._weave_name))
 
224
 
 
225
 
 
226
    def idx_to_name(self, version):
 
227
        return self._names[version]
 
228
 
 
229
 
 
230
    def _check_repeated_add(self, name, parents, text):
 
231
        """Check that a duplicated add is OK.
 
232
 
 
233
        If it is, return the (old) index; otherwise raise an exception.
 
234
        """
 
235
        idx = self.lookup(name)
 
236
        if sorted(self._parents[idx]) != sorted(parents):
 
237
            raise WeaveError("name \"%s\" already present in weave "
 
238
                             "with different parents" % name)
 
239
        new_sha1 = sha_strings(text)
 
240
        if new_sha1 != self._sha1s[idx]:
 
241
            raise WeaveError("name \"%s\" already present in weave "
 
242
                             "with different text" % name)            
 
243
        return idx
 
244
        
 
245
 
 
246
        
 
247
    def add(self, name, parents, text):
186
248
        """Add a single text on top of the weave.
187
249
  
188
250
        Returns the index number of the newly added version.
189
251
 
 
252
        name
 
253
            Symbolic name for this version.
 
254
            (Typically the revision-id of the revision that added it.)
 
255
 
190
256
        parents
191
257
            List or set of direct parent version numbers.
192
258
            
193
259
        text
194
260
            Sequence of lines to be added in the new version."""
195
261
 
 
262
        assert isinstance(name, basestring)
 
263
        if name in self._name_map:
 
264
            return self._check_repeated_add(name, parents, text)
 
265
        
196
266
        self._check_versions(parents)
197
267
        ## self._check_lines(text)
198
268
        new_version = len(self._parents)
199
269
 
200
 
        import sha
201
 
        s = sha.new()
202
 
        map(s.update, text)
203
 
        sha1 = s.hexdigest()
204
 
        del s
 
270
        sha1 = sha_strings(text)
205
271
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
272
        # if we abort after here the (in-memory) weave will be corrupt because only
 
273
        # some fields are updated
 
274
        self._parents.append(parents[:])
208
275
        self._sha1s.append(sha1)
 
276
        self._names.append(name)
 
277
        self._name_map[name] = new_version
209
278
 
210
279
            
211
280
        if not parents:
216
285
            if text:
217
286
                self._weave.append(('{', new_version))
218
287
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
288
                self._weave.append(('}', None))
220
289
        
221
290
            return new_version
222
291
 
238
307
            basis_lineno.append(lineno)
239
308
            basis_lines.append(line)
240
309
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
310
        # another small special case: a merge, producing the same text
 
311
        # as auto-merge
242
312
        if text == basis_lines:
243
313
            return new_version            
244
314
 
287
357
                # we don't destroy ourselves
288
358
                i = i2 + offset
289
359
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
360
                                    + text[j1:j2] 
 
361
                                    + [('}', None)])
292
362
                offset += 2 + (j2 - j1)
293
363
 
294
364
        return new_version
309
379
            raise ValueError("version %d not present in weave" % v)
310
380
 
311
381
 
 
382
    def parents(self, version):
 
383
        return self._parents[version]
 
384
 
 
385
 
312
386
    def minimal_parents(self, version):
313
387
        """Find the minimal set of parents for the version."""
314
388
        included = self._parents[version]
384
458
                if c == '{':
385
459
                    istack.append(v)
386
460
                elif c == '}':
387
 
                    oldv = istack.pop()
 
461
                    istack.pop()
388
462
                elif c == '[':
389
463
                    assert v not in dset
390
464
                    dset.add(v)
410
484
 
411
485
        The set typically but not necessarily corresponds to a version.
412
486
        """
 
487
        for i in versions:
 
488
            if not isinstance(i, int):
 
489
                raise ValueError(i)
 
490
            
413
491
        included = self.inclusions(versions)
414
492
 
415
493
        istack = []
431
509
                    assert v not in istack
432
510
                    istack.append(v)
433
511
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
512
                    istack.pop()
436
513
                elif c == '[':
437
514
                    if v in included:
438
515
                        assert v not in dset
467
544
            yield line
468
545
 
469
546
 
 
547
    def get_text(self, version):
 
548
        assert isinstance(version, int)
 
549
        s = StringIO()
 
550
        s.writelines(self.get_iter(version))
 
551
        return s.getvalue()
 
552
 
 
553
 
470
554
    def get(self, index):
471
555
        return list(self.get_iter(index))
472
556
 
508
592
 
509
593
        # try extracting all versions; this is a bit slow and parallel
510
594
        # extraction could be used
511
 
        import sha
512
595
        nv = self.numversions()
513
596
        for version in range(nv):
514
597
            if progress_bar:
670
753
 
671
754
 
672
755
 
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):
 
756
def weave_toc(w):
 
757
    """Show the weave's table-of-contents"""
 
758
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
759
    for i in (6, 50, 10, 10):
677
760
        print '-' * i,
678
761
    print
679
762
    for i in range(w.numversions()):
680
763
        sha1 = w._sha1s[i]
681
 
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
 
764
        name = w._names[i]
 
765
        parent_str = ' '.join(map(str, w._parents[i]))
 
766
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
682
767
 
683
768
 
684
769
 
706
791
    print 'weave file        %9d bytes' % weave_size
707
792
    print 'total contents    %9d bytes' % total
708
793
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
794
    if vers:
 
795
        avg = total/vers
 
796
        print 'average size      %9d bytes' % avg
 
797
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
798
 
711
799
 
712
800
def usage():
721
809
        Write out specified version.
722
810
    weave check WEAVEFILE
723
811
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
812
    weave toc WEAVEFILE
725
813
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
814
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
815
        Add NEWTEXT, with specified parent versions.
728
816
    weave annotate WEAVEFILE VERSION
729
817
        Display origin of each line.
736
824
 
737
825
    % weave init foo.weave
738
826
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
827
    % weave add foo.weave ver0 < foo.txt
740
828
    added version 0
741
829
 
742
830
    (create updated version)
743
831
    % vi foo.txt
744
832
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
833
    % weave add foo.weave ver1 0 < foo.txt
746
834
    added version 1
747
835
 
748
836
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
837
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
838
    % weave add foo.weave ver2 0 < foo.txt
751
839
    added version 2
752
840
 
753
841
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
842
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
843
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
844
    
757
845
"""
758
846
    
764
852
    from weavefile import write_weave, read_weave
765
853
    from bzrlib.progress import ProgressBar
766
854
 
767
 
    #import psyco
768
 
    #psyco.full()
 
855
    try:
 
856
        import psyco
 
857
        psyco.full()
 
858
    except ImportError:
 
859
        pass
 
860
 
 
861
    if len(argv) < 2:
 
862
        usage()
 
863
        return 0
769
864
 
770
865
    cmd = argv[1]
771
866
 
777
872
    elif cmd == 'add':
778
873
        w = readit()
779
874
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
875
        name = argv[3]
 
876
        parents = map(int, argv[4:])
781
877
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
878
        ver = w.add(name, parents, lines)
783
879
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
880
        print 'added version %r %d' % (name, ver)
785
881
    elif cmd == 'init':
786
882
        fn = argv[2]
787
883
        if os.path.exists(fn):
809
905
                print '%5d | %s' % (origin, text)
810
906
                lasto = origin
811
907
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
908
    elif cmd == 'toc':
 
909
        weave_toc(readit())
814
910
 
815
911
    elif cmd == 'stats':
816
912
        weave_stats(argv[2])
865
961
        raise ValueError('unknown command %r' % cmd)
866
962
    
867
963
 
 
964
 
 
965
def profile_main(argv): 
 
966
    import tempfile, hotshot, hotshot.stats
 
967
 
 
968
    prof_f = tempfile.NamedTemporaryFile()
 
969
 
 
970
    prof = hotshot.Profile(prof_f.name)
 
971
 
 
972
    ret = prof.runcall(main, argv)
 
973
    prof.close()
 
974
 
 
975
    stats = hotshot.stats.load(prof_f.name)
 
976
    #stats.strip_dirs()
 
977
    stats.sort_stats('cumulative')
 
978
    ## XXX: Might like to write to stderr or the trace file instead but
 
979
    ## print_stats seems hardcoded to stdout
 
980
    stats.print_stats(20)
 
981
            
 
982
    return ret
 
983
 
 
984
 
868
985
if __name__ == '__main__':
869
986
    import sys
870
 
    sys.exit(main(sys.argv))
 
987
    if '--profile' in sys.argv:
 
988
        args = sys.argv[:]
 
989
        args.remove('--profile')
 
990
        sys.exit(profile_main(args))
 
991
    else:
 
992
        sys.exit(main(sys.argv))
 
993