~bzr-pqm/bzr/bzr.dev

« 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:
84
84
# TODO: Perhaps the API should work only in names to hide the integer
85
85
# indexes from the user?
86
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
 
87
91
 
88
92
 
89
93
import sha
90
 
 
 
94
from difflib import SequenceMatcher
 
95
 
 
96
 
 
97
from bzrlib.osutils import sha_strings
91
98
 
92
99
 
93
100
class WeaveError(Exception):
113
120
 
114
121
    * a nonnegative index number.
115
122
 
116
 
    * a version-id string. (not implemented yet)
 
123
    * a version-id string.
117
124
 
118
125
    Typically the index number will be valid only inside this weave and
119
126
    the version-id is used to reference it in the larger world.
181
188
 
182
189
    _name_map
183
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.
184
195
    """
185
196
 
186
 
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
 
197
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
198
                 '_weave_name']
187
199
    
188
 
    def __init__(self):
 
200
    def __init__(self, weave_name=None):
189
201
        self._weave = []
190
202
        self._parents = []
191
203
        self._sha1s = []
192
204
        self._names = []
193
205
        self._name_map = {}
 
206
        self._weave_name = weave_name
194
207
 
195
208
 
196
209
    def __eq__(self, other):
205
218
        return not self.__eq__(other)
206
219
 
207
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
        
208
229
    def lookup(self, name):
 
230
        """Convert symbolic version name to index."""
209
231
        try:
210
232
            return self._name_map[name]
211
233
        except KeyError:
212
 
            raise WeaveError("name %s not present in weave" % name)
213
 
 
214
 
        
215
 
    def add(self, name, parents, text):
 
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):
216
259
        """Add a single text on top of the weave.
217
260
  
218
261
        Returns the index number of the newly added version.
225
268
            List or set of direct parent version numbers.
226
269
            
227
270
        text
228
 
            Sequence of lines to be added in the new version."""
 
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
        """
229
276
 
230
277
        assert isinstance(name, basestring)
 
278
        if sha1 is None:
 
279
            sha1 = sha_strings(text)
231
280
        if name in self._name_map:
232
 
            raise WeaveError("name %r already present in weave" % name)
233
 
        
 
281
            return self._check_repeated_add(name, parents, text, sha1)
 
282
 
 
283
        parents = map(self.maybe_lookup, parents)
234
284
        self._check_versions(parents)
235
285
        ## self._check_lines(text)
236
286
        new_version = len(self._parents)
237
287
 
238
 
        s = sha.new()
239
 
        map(s.update, text)
240
 
        sha1 = s.hexdigest()
241
 
        del s
242
288
 
243
289
        # if we abort after here the (in-memory) weave will be corrupt because only
244
290
        # some fields are updated
293
339
        #print 'basis_lines:', basis_lines
294
340
        #print 'new_lines:  ', lines
295
341
 
296
 
        from difflib import SequenceMatcher
297
342
        s = SequenceMatcher(None, basis_lines, text)
298
343
 
299
344
        # offset gives the number of lines that have been inserted
338
383
    def inclusions(self, versions):
339
384
        """Return set of all ancestors of given version(s)."""
340
385
        i = set(versions)
341
 
        v = max(versions)
342
 
        try:
343
 
            while v >= 0:
344
 
                if v in i:
345
 
                    # include all its parents
346
 
                    i.update(self._parents[v])
347
 
                v -= 1
348
 
            return i
349
 
        except IndexError:
350
 
            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]
351
397
 
352
398
 
353
399
    def minimal_parents(self, version):
393
439
                raise IndexError("invalid version number %r" % i)
394
440
 
395
441
    
396
 
    def annotate(self, index):
397
 
        return list(self.annotate_iter(index))
398
 
 
399
 
 
400
 
    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):
401
447
        """Yield list of (index-id, line) pairs for the specified version.
402
448
 
403
449
        The index indicates when the line originated in the weave."""
404
 
        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):
405
452
            yield origin, text
406
453
 
407
454
 
451
498
 
452
499
        The set typically but not necessarily corresponds to a version.
453
500
        """
 
501
        for i in versions:
 
502
            if not isinstance(i, int):
 
503
                raise ValueError(i)
 
504
            
454
505
        included = self.inclusions(versions)
455
506
 
456
507
        istack = []
501
552
    
502
553
 
503
554
 
504
 
    def get_iter(self, version):
 
555
    def get_iter(self, name_or_index):
505
556
        """Yield lines for the specified version."""
506
 
        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):
507
559
            yield line
508
560
 
509
561
 
510
 
    def get(self, index):
511
 
        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
512
572
 
513
573
 
514
574
    def mash_iter(self, included):
515
575
        """Return composed version of multiple included versions."""
 
576
        included = map(self.maybe_lookup, included)
516
577
        for origin, lineno, text in self._extract(included):
517
578
            yield text
518
579
 
775
836
        Display composite of all selected versions.
776
837
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
777
838
        Auto-merge two versions and display conflicts.
 
839
    weave diff WEAVEFILE VERSION1 VERSION2 
 
840
        Show differences between two versions.
778
841
 
779
842
example:
780
843
 
805
868
def main(argv):
806
869
    import sys
807
870
    import os
808
 
    from weavefile import write_weave, read_weave
 
871
    from bzrlib.weavefile import write_weave, read_weave
809
872
    from bzrlib.progress import ProgressBar
810
873
 
811
874
    try:
848
911
        w = readit()
849
912
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
850
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
            
851
926
    elif cmd == 'annotate':
852
927
        w = readit()
853
928
        # newline is added to all lines regardless; too hard to get