~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Aaron Bentley
  • Date: 2005-08-10 14:59:36 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1110.
  • Revision ID: abentley@panoramicfeedback.com-20050810145936-f1b0cf25e8b18f6c
Ensured that revert FILE only modifies that file.

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
49
 
 
50
 
 
51
 
 
 
48
# with all this and simplification of add code, 33s 
52
49
 
53
50
 
54
51
# TODO: Perhaps have copy method for Weave instances?
62
59
# binaries, perhaps by naively splitting on \n or perhaps using
63
60
# something like a rolling checksum.
64
61
 
 
62
# TODO: Track version names as well as indexes. 
 
63
 
65
64
# TODO: End marker for each version so we can stop reading?
66
65
 
67
66
# TODO: Check that no insertion occurs inside a deletion that was
76
75
# description of which revisions include it.  Nice for checking all
77
76
# shas in parallel.
78
77
 
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
 
78
 
94
79
 
95
80
 
96
81
class WeaveError(Exception):
116
101
 
117
102
    * a nonnegative index number.
118
103
 
119
 
    * a version-id string. (not implemented yet)
 
104
    * a version-id string.
120
105
 
121
106
    Typically the index number will be valid only inside this weave and
122
107
    the version-id is used to reference it in the larger world.
133
118
    The instruction can be '{' or '}' for an insertion block, and '['
134
119
    and ']' for a deletion block respectively.  The version is the
135
120
    integer version index.  There is no replace operator, only deletes
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.
 
121
    and inserts.
139
122
 
140
123
    Constraints/notes:
141
124
 
177
160
        each version; the parent's parents are implied.
178
161
 
179
162
    _sha1s
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.
 
163
        List of hex SHA-1 of each version, or None if not recorded.
191
164
    """
192
165
 
193
 
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
194
 
                 '_weave_name']
 
166
    __slots__ = ['_weave', '_parents', '_sha1s']
195
167
    
196
 
    def __init__(self, weave_name=None):
 
168
    def __init__(self):
197
169
        self._weave = []
198
170
        self._parents = []
199
171
        self._sha1s = []
200
 
        self._names = []
201
 
        self._name_map = {}
202
 
        self._weave_name = weave_name
203
172
 
204
173
 
205
174
    def __eq__(self, other):
206
175
        if not isinstance(other, Weave):
207
176
            return False
208
177
        return self._parents == other._parents \
209
 
               and self._weave == other._weave \
210
 
               and self._sha1s == other._sha1s 
 
178
               and self._weave == other._weave
 
179
    
211
180
 
212
 
    
213
181
    def __ne__(self, other):
214
182
        return not self.__eq__(other)
215
183
 
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):
 
184
        
 
185
    def add(self, parents, text):
247
186
        """Add a single text on top of the weave.
248
187
  
249
188
        Returns the index number of the newly added version.
250
189
 
251
 
        name
252
 
            Symbolic name for this version.
253
 
            (Typically the revision-id of the revision that added it.)
254
 
 
255
190
        parents
256
191
            List or set of direct parent version numbers.
257
192
            
258
193
        text
259
194
            Sequence of lines to be added in the new version."""
260
195
 
261
 
        assert isinstance(name, basestring)
262
 
        if name in self._name_map:
263
 
            return self._check_repeated_add(name, parents, text)
264
 
        
265
196
        self._check_versions(parents)
266
197
        ## self._check_lines(text)
267
198
        new_version = len(self._parents)
268
199
 
269
 
        sha1 = sha_strings(text)
 
200
        import sha
 
201
        s = sha.new()
 
202
        map(s.update, text)
 
203
        sha1 = s.hexdigest()
 
204
        del s
270
205
 
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[:])
 
206
        # if we abort after here the weave will be corrupt
 
207
        self._parents.append(frozenset(parents))
274
208
        self._sha1s.append(sha1)
275
 
        self._names.append(name)
276
 
        self._name_map[name] = new_version
277
209
 
278
210
            
279
211
        if not parents:
284
216
            if text:
285
217
                self._weave.append(('{', new_version))
286
218
                self._weave.extend(text)
287
 
                self._weave.append(('}', None))
 
219
                self._weave.append(('}', new_version))
288
220
        
289
221
            return new_version
290
222
 
306
238
            basis_lineno.append(lineno)
307
239
            basis_lines.append(line)
308
240
 
309
 
        # another small special case: a merge, producing the same text
310
 
        # as auto-merge
 
241
        # another small special case: a merge, producing the same text as auto-merge
311
242
        if text == basis_lines:
312
243
            return new_version            
313
244
 
356
287
                # we don't destroy ourselves
357
288
                i = i2 + offset
358
289
                self._weave[i:i] = ([('{', new_version)] 
359
 
                                    + text[j1:j2] 
360
 
                                    + [('}', None)])
 
290
                                + text[j1:j2] 
 
291
                                + [('}', new_version)])
361
292
                offset += 2 + (j2 - j1)
362
293
 
363
294
        return new_version
378
309
            raise ValueError("version %d not present in weave" % v)
379
310
 
380
311
 
381
 
    def parents(self, version):
382
 
        return self._parents[version]
383
 
 
384
 
 
385
312
    def minimal_parents(self, version):
386
313
        """Find the minimal set of parents for the version."""
387
314
        included = self._parents[version]
457
384
                if c == '{':
458
385
                    istack.append(v)
459
386
                elif c == '}':
460
 
                    istack.pop()
 
387
                    oldv = istack.pop()
461
388
                elif c == '[':
462
389
                    assert v not in dset
463
390
                    dset.add(v)
483
410
 
484
411
        The set typically but not necessarily corresponds to a version.
485
412
        """
486
 
        for i in versions:
487
 
            if not isinstance(i, int):
488
 
                raise ValueError(i)
489
 
            
490
413
        included = self.inclusions(versions)
491
414
 
492
415
        istack = []
508
431
                    assert v not in istack
509
432
                    istack.append(v)
510
433
                elif c == '}':
511
 
                    istack.pop()
 
434
                    oldv = istack.pop()
 
435
                    assert oldv == v
512
436
                elif c == '[':
513
437
                    if v in included:
514
438
                        assert v not in dset
543
467
            yield line
544
468
 
545
469
 
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
 
 
553
470
    def get(self, index):
554
471
        return list(self.get_iter(index))
555
472
 
591
508
 
592
509
        # try extracting all versions; this is a bit slow and parallel
593
510
        # extraction could be used
 
511
        import sha
594
512
        nv = self.numversions()
595
513
        for version in range(nv):
596
514
            if progress_bar:
752
670
 
753
671
 
754
672
 
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):
 
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):
759
677
        print '-' * i,
760
678
    print
761
679
    for i in range(w.numversions()):
762
680
        sha1 = w._sha1s[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)
 
681
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
766
682
 
767
683
 
768
684
 
790
706
    print 'weave file        %9d bytes' % weave_size
791
707
    print 'total contents    %9d bytes' % total
792
708
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
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))
 
709
 
797
710
 
798
711
 
799
712
def usage():
808
721
        Write out specified version.
809
722
    weave check WEAVEFILE
810
723
        Check consistency of all versions.
811
 
    weave toc WEAVEFILE
 
724
    weave info WEAVEFILE
812
725
        Display table of contents.
813
 
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
 
726
    weave add WEAVEFILE [BASE...] < NEWTEXT
814
727
        Add NEWTEXT, with specified parent versions.
815
728
    weave annotate WEAVEFILE VERSION
816
729
        Display origin of each line.
823
736
 
824
737
    % weave init foo.weave
825
738
    % vi foo.txt
826
 
    % weave add foo.weave ver0 < foo.txt
 
739
    % weave add foo.weave < foo.txt
827
740
    added version 0
828
741
 
829
742
    (create updated version)
830
743
    % vi foo.txt
831
744
    % weave get foo.weave 0 | diff -u - foo.txt
832
 
    % weave add foo.weave ver1 0 < foo.txt
 
745
    % weave add foo.weave 0 < foo.txt
833
746
    added version 1
834
747
 
835
748
    % weave get foo.weave 0 > foo.txt       (create forked version)
836
749
    % vi foo.txt
837
 
    % weave add foo.weave ver2 0 < foo.txt
 
750
    % weave add foo.weave 0 < foo.txt
838
751
    added version 2
839
752
 
840
753
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
841
754
    % vi foo.txt                            (resolve conflicts)
842
 
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
 
755
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
843
756
    
844
757
"""
845
758
    
851
764
    from weavefile import write_weave, read_weave
852
765
    from bzrlib.progress import ProgressBar
853
766
 
854
 
    try:
855
 
        import psyco
856
 
        psyco.full()
857
 
    except ImportError:
858
 
        pass
859
 
 
860
 
    if len(argv) < 2:
861
 
        usage()
862
 
        return 0
 
767
    #import psyco
 
768
    #psyco.full()
863
769
 
864
770
    cmd = argv[1]
865
771
 
871
777
    elif cmd == 'add':
872
778
        w = readit()
873
779
        # at the moment, based on everything in the file
874
 
        name = argv[3]
875
 
        parents = map(int, argv[4:])
 
780
        parents = map(int, argv[3:])
876
781
        lines = sys.stdin.readlines()
877
 
        ver = w.add(name, parents, lines)
 
782
        ver = w.add(parents, lines)
878
783
        write_weave(w, file(argv[2], 'wb'))
879
 
        print 'added version %r %d' % (name, ver)
 
784
        print 'added version %d' % ver
880
785
    elif cmd == 'init':
881
786
        fn = argv[2]
882
787
        if os.path.exists(fn):
904
809
                print '%5d | %s' % (origin, text)
905
810
                lasto = origin
906
811
                
907
 
    elif cmd == 'toc':
908
 
        weave_toc(readit())
 
812
    elif cmd == 'info':
 
813
        weave_info(readit())
909
814
 
910
815
    elif cmd == 'stats':
911
816
        weave_stats(argv[2])
960
865
        raise ValueError('unknown command %r' % cmd)
961
866
    
962
867
 
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
 
 
984
868
if __name__ == '__main__':
985
869
    import sys
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
 
 
 
870
    sys.exit(main(sys.argv))