~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to weave.py

  • Committer: Martin Pool
  • Date: 2005-06-30 08:40:59 UTC
  • mto: This revision was merged to the branch mainline in revision 852.
  • Revision ID: mbp@sourcefrog.net-20050630084059-d6eb6cb46972365b
Rename Weave.get_included to inclusions and getiter to get_iter

Refactor annotate() code 

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
 
46
46
# TODO: Separate out some code to read and write weaves.
47
47
 
48
 
# TODO: End marker for each version so we can stop reading?
 
48
# TODO: End marker for each version?
49
49
 
50
50
# TODO: Check that no insertion occurs inside a deletion that was
51
51
# active in the version of the insertion.
52
52
 
53
 
# TODO: Perhaps a special slower check() method that verifies more
54
 
# nesting constraints and the MD5 of each version?
55
 
 
56
 
 
57
53
 
58
54
try:
59
55
    set
149
145
        versions included in those versions are included transitively.
150
146
        So new versions created from nothing list []; most versions
151
147
        have a single entry; some have more.
152
 
 
153
 
    _sha1s
154
 
        List of hex SHA-1 of each version, or None if not recorded.
155
148
    """
156
149
    def __init__(self):
157
150
        self._l = []
158
151
        self._v = []
159
 
        self._sha1s = []
 
152
 
160
153
 
161
154
 
162
155
    def __eq__(self, other):
181
174
 
182
175
        text
183
176
            Sequence of lines to be added in the new version."""
184
 
        ## self._check_versions(parents)
185
 
        ## self._check_lines(text)
 
177
        self._check_versions(parents)
 
178
        self._check_lines(text)
 
179
 
186
180
        idx = len(self._v)
187
181
 
188
 
        import sha
189
 
        s = sha.new()
190
 
        for l in text:
191
 
            s.update(l)
192
 
        sha1 = s.hexdigest()
193
 
        del s
194
 
 
195
182
        if parents:
196
183
            delta = self._delta(self.inclusions(parents), text)
197
184
 
223
210
                                   + [('}', idx)]
224
211
                    offset += 2 + len(newlines)
225
212
 
 
213
            # TODO: Could eliminate any parents that are implied by
 
214
            # the others
 
215
                    
226
216
            self._addversion(parents)
227
217
        else:
228
218
            # special case; adding with no parents revision; can do this
232
222
            self._l.append(('}', idx))
233
223
 
234
224
            self._addversion(None)
235
 
 
236
 
        self._sha1s.append(sha1)
237
225
            
238
226
        return idx
239
227
 
298
286
 
299
287
        dset = set()         # versions for which a deletion block is current
300
288
 
301
 
        isactive = None
 
289
        isactive = False
302
290
 
303
291
        lineno = 0         # line of weave, 0-based
304
292
 
308
296
        # the stack and the other that processes the results -- but
309
297
        # I'm not sure it's really needed.
310
298
 
311
 
        # TODO: In fact, I think we only need to store the *count* of
312
 
        # active insertions and deletions, and we can maintain that by
313
 
        # just by just counting as we go along.
314
 
 
315
299
        WFE = WeaveFormatError
316
 
 
 
300
        
317
301
        for l in self._l:
318
302
            if isinstance(l, tuple):
319
 
                isactive = None         # recalculate
320
303
                c, v = l
321
304
                if c == '{':
322
305
                    if istack and (istack[-1] >= v):
341
324
                        if istack[-1] == v:
342
325
                            raise WFE("version %d deletes own text on line %d"
343
326
                                      % (v, lineno))
344
 
                        # XXX
345
327
                        dset.add(v)
346
328
                elif c == ']':
347
329
                    if v in dset:
357
339
                if not istack:
358
340
                    raise WFE("literal at top level on line %d"
359
341
                              % lineno)
360
 
                if isactive == None:
361
 
                    isactive = (istack[-1] in included) \
362
 
                               and not included.intersection(dset)
 
342
                isactive = (istack[-1] in included) \
 
343
                           and not included.intersection(dset)
363
344
                if isactive:
364
345
                    origin = istack[-1]
365
346
                    yield origin, lineno, l
383
364
        return list(self.get_iter(index))
384
365
 
385
366
 
386
 
    def mash_iter(self, included):
 
367
    def merge_iter(self, included):
387
368
        """Return composed version of multiple included versions."""
388
369
        included = frozenset(included)
389
370
        for origin, lineno, text in self._extract(included):
398
379
        pprint(self._v, to_file)
399
380
 
400
381
 
401
 
 
402
 
    def numversions(self):
403
 
        l = len(self._v)
404
 
        assert l == len(self._sha1s)
405
 
        return l
406
 
 
407
 
 
408
382
    def check(self):
409
 
        # check no circular inclusions
410
 
        for version in range(self.numversions()):
411
 
            inclusions = list(self._v[version])
412
 
            if inclusions:
413
 
                inclusions.sort()
414
 
                if inclusions[-1] >= version:
 
383
        for vers_info in self._v:
 
384
            included = set()
 
385
            for vi in vers_info[0]:
 
386
                if vi < 0 or vi >= index:
415
387
                    raise WeaveFormatError("invalid included version %d for index %d"
416
 
                                           % (inclusions[-1], version))
417
 
 
418
 
        # try extracting all versions; this is a bit slow and parallel
419
 
        # extraction could be used
420
 
        import sha
421
 
        for version in range(self.numversions()):
422
 
            s = sha.new()
423
 
            for l in self.get_iter(version):
424
 
                s.update(l)
425
 
            hd = s.hexdigest()
426
 
            expected = self._sha1s[version]
427
 
            if hd != expected:
428
 
                raise WeaveError("mismatched sha1 for version %d; "
429
 
                                 "got %s, expected %s"
430
 
                                 % (version, hd, expected))
431
 
 
432
 
 
433
 
 
434
 
    def merge(self, merge_versions):
435
 
        """Automerge and mark conflicts between versions.
436
 
 
437
 
        This returns a sequence, each entry describing alternatives
438
 
        for a chunk of the file.  Each of the alternatives is given as
439
 
        a list of lines.
440
 
 
441
 
        If there is a chunk of the file where there's no diagreement,
442
 
        only one alternative is given.
443
 
        """
444
 
 
445
 
        # approach: find the included versions common to all the
446
 
        # merged versions
447
 
        raise NotImplementedError()
 
388
                                               % (vi, index))
 
389
                if vi in included:
 
390
                    raise WeaveFormatError("repeated included version %d for index %d"
 
391
                                               % (vi, index))
 
392
                included.add(vi)
448
393
 
449
394
 
450
395
 
467
412
        If line1=line2, this is a pure insert; if newlines=[] this is a
468
413
        pure delete.  (Similar to difflib.)
469
414
        """
 
415
 
 
416
        self._check_versions(included)
 
417
 
 
418
        ##from pprint import pprint
 
419
 
 
420
        # first get basis for comparison
 
421
        # basis holds (lineno, origin, line)
 
422
        basis = []
 
423
 
 
424
        ##print 'my lines:'
 
425
        ##pprint(self._l)
 
426
 
470
427
        # basis a list of (origin, lineno, line)
471
 
        basis_lineno = []
472
 
        basis_lines = []
473
 
        for origin, lineno, line in self._extract(included):
474
 
            basis_lineno.append(lineno)
475
 
            basis_lines.append(line)
 
428
        basis = list(self._extract(included))
 
429
 
 
430
        # now make a parallel list with only the text, to pass to the differ
 
431
        basis_lines = [line for (origin, lineno, line) in basis]
476
432
 
477
433
        # add a sentinal, because we can also match against the final line
478
 
        basis_lineno.append(len(self._l))
 
434
        basis.append((None, len(self._l), None))
479
435
 
480
436
        # XXX: which line of the weave should we really consider
481
437
        # matches the end of the file?  the current code says it's the
484
440
        from difflib import SequenceMatcher
485
441
        s = SequenceMatcher(None, basis_lines, lines)
486
442
 
 
443
        ##print 'basis sequence:'
 
444
        ##pprint(basis)
 
445
 
487
446
        # TODO: Perhaps return line numbers from composed weave as well?
488
447
 
489
448
        for tag, i1, i2, j1, j2 in s.get_opcodes():
494
453
 
495
454
            # i1,i2 are given in offsets within basis_lines; we need to map them
496
455
            # back to offsets within the entire weave
497
 
            real_i1 = basis_lineno[i1]
498
 
            real_i2 = basis_lineno[i2]
 
456
            real_i1 = basis[i1][1]
 
457
            real_i2 = basis[i2][1]
499
458
 
500
459
            assert 0 <= j1
501
460
            assert j1 <= j2
505
464
 
506
465
 
507
466
 
508
 
def weave_info(filename, out):
509
 
    """Show some text information about the weave."""
510
 
    from weavefile import read_weave
511
 
    wf = file(filename, 'rb')
512
 
    w = read_weave(wf)
513
 
    # FIXME: doesn't work on pipes
514
 
    weave_size = wf.tell()
515
 
    print >>out, "weave file size %d bytes" % weave_size
516
 
    print >>out, "weave contains %d versions" % len(w._v)
517
 
 
518
 
    total = 0
519
 
    print ' %8s %8s %8s %s' % ('version', 'lines', 'bytes', 'sha1')
520
 
    print ' -------- -------- -------- ----------------------------------------'
521
 
    for i in range(len(w._v)):
522
 
        text = w.get(i)
523
 
        lines = len(text)
524
 
        bytes = sum((len(a) for a in text))
525
 
        sha1 = w._sha1s[i]
526
 
        print ' %8d %8d %8d %s' % (i, lines, bytes, sha1)
527
 
        total += bytes
528
 
 
529
 
    print >>out, "versions total %d bytes" % total
530
 
    print >>out, "compression ratio %.3f" % (float(total)/float(weave_size))
531
 
    
532
 
 
533
467
 
534
468
def main(argv):
535
469
    import sys
536
470
    import os
537
 
    from weavefile import write_weave_v1, read_weave
 
471
    from weavefile import write_weave_v1, read_weave_v1
538
472
    cmd = argv[1]
539
473
    if cmd == 'add':
540
 
        w = read_weave(file(argv[2], 'rb'))
 
474
        w = read_weave_v1(file(argv[2], 'rb'))
541
475
        # at the moment, based on everything in the file
542
476
        parents = set(range(len(w._v)))
543
477
        lines = sys.stdin.readlines()
551
485
        w = Weave()
552
486
        write_weave_v1(w, file(fn, 'wb'))
553
487
    elif cmd == 'get':
554
 
        w = read_weave(file(argv[2], 'rb'))
555
 
        sys.stdout.writelines(w.get_iter(int(argv[3])))
 
488
        w = read_weave_v1(file(argv[2], 'rb'))
 
489
        sys.stdout.writelines(w.getiter(int(argv[3])))
556
490
    elif cmd == 'annotate':
557
 
        w = read_weave(file(argv[2], 'rb'))
 
491
        w = read_weave_v1(file(argv[2], 'rb'))
558
492
        # newline is added to all lines regardless; too hard to get
559
493
        # reasonable formatting otherwise
560
494
        lasto = None
565
499
            else:
566
500
                print '%5d | %s' % (origin, text)
567
501
                lasto = origin
568
 
    elif cmd == 'info':
569
 
        weave_info(argv[2], sys.stdout)
570
 
    elif cmd == 'check':
571
 
        w = read_weave(file(argv[2], 'rb'))
572
 
        w.check()
573
502
    else:
574
503
        raise ValueError('unknown command %r' % cmd)
575
504