~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-13 08:21:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050913082132-66279763a02e695c
- allow the same version to be repeatedly added to a weave

  (silently absorb them into a single version)

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