~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-07-12 01:44:23 UTC
  • Revision ID: mbp@sourcefrog.net-20050712014423-1d95eb47ce7ab510
- add simple test case for bzr status

- show_status takes to_file argument

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
"""Weave - storage of related text file versions"""
23
23
 
24
 
# before intset (r923) 2000 versions in 41.5s
25
 
# with intset (r926) 2000 versions in 93s !!!
26
 
# better to just use plain sets.
27
 
 
28
 
# making _extract build and return a list, rather than being a generator
29
 
# takes 37.94s
30
 
 
31
 
# with python -O, r923 does 2000 versions in 36.87s
32
 
 
33
 
# with optimizations to avoid mutating lists - 35.75!  I guess copying
34
 
# all the elements every time costs more than the small manipulations.
35
 
# a surprisingly small change.
36
 
 
37
 
# r931, which avoids using a generator for extract, does 36.98s
38
 
 
39
 
# with memoized inclusions, takes 41.49s; not very good
40
 
 
41
 
# with slots, takes 37.35s; without takes 39.16, a bit surprising
42
 
 
43
 
# with the delta calculation mixed in with the add method, rather than
44
 
# separated, takes 36.78s
45
 
 
46
 
# with delta folded in and mutation of the list, 36.13s
47
 
 
48
 
# with all this and simplification of add code, 33s
49
 
 
50
 
 
51
 
 
52
 
 
53
 
 
54
24
# TODO: Perhaps have copy method for Weave instances?
55
25
 
56
26
# XXX: If we do weaves this way, will a merge still behave the same
62
32
# binaries, perhaps by naively splitting on \n or perhaps using
63
33
# something like a rolling checksum.
64
34
 
 
35
# TODO: Track version names as well as indexes. 
 
36
 
65
37
# TODO: End marker for each version so we can stop reading?
66
38
 
67
39
# TODO: Check that no insertion occurs inside a deletion that was
72
44
# properly nested, that there is no text outside of an insertion, that
73
45
# insertions or deletions are not repeated, etc.
74
46
 
75
 
# TODO: Parallel-extract that passes back each line along with a
76
 
# description of which revisions include it.  Nice for checking all
77
 
# shas in parallel.
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
 
 
 
47
 
 
48
 
 
49
try:
 
50
    set
 
51
    frozenset
 
52
except NameError:
 
53
    from sets import Set, ImmutableSet
 
54
    set = Set
 
55
    frozenset = ImmutableSet
 
56
    del Set, ImmutableSet
91
57
 
92
58
 
93
59
class WeaveError(Exception):
113
79
 
114
80
    * a nonnegative index number.
115
81
 
116
 
    * a version-id string. (not implemented yet)
 
82
    * a version-id string.
117
83
 
118
84
    Typically the index number will be valid only inside this weave and
119
85
    the version-id is used to reference it in the larger world.
120
86
 
121
87
    The weave is represented as a list mixing edit instructions and
122
 
    literal text.  Each entry in _weave can be either a string (or
 
88
    literal text.  Each entry in _l can be either a string (or
123
89
    unicode), or a tuple.  If a string, it means that the given line
124
90
    should be output in the currently active revisions.
125
91
 
130
96
    The instruction can be '{' or '}' for an insertion block, and '['
131
97
    and ']' for a deletion block respectively.  The version is the
132
98
    integer version index.  There is no replace operator, only deletes
133
 
    and inserts.  For '}', the end of an insertion, there is no
134
 
    version parameter because it always closes the most recently
135
 
    opened insertion.
 
99
    and inserts.
136
100
 
137
101
    Constraints/notes:
138
102
 
165
129
      should be no way to get an earlier version deleting a later
166
130
      version.
167
131
 
168
 
    _weave
169
 
        Text of the weave; list of control instruction tuples and strings.
 
132
    _l
 
133
        Text of the weave.
170
134
 
171
 
    _parents
 
135
    _v
172
136
        List of parents, indexed by version number.
173
137
        It is only necessary to store the minimal set of parents for
174
138
        each version; the parent's parents are implied.
175
139
 
176
140
    _sha1s
177
 
        List of hex SHA-1 of each version.
178
 
 
179
 
    _names
180
 
        List of symbolic names for each version.  Each should be unique.
181
 
 
182
 
    _name_map
183
 
        For each name, the version number.
 
141
        List of hex SHA-1 of each version, or None if not recorded.
184
142
    """
185
 
 
186
 
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
187
 
    
188
143
    def __init__(self):
189
 
        self._weave = []
190
 
        self._parents = []
 
144
        self._l = []
 
145
        self._v = []
191
146
        self._sha1s = []
192
 
        self._names = []
193
 
        self._name_map = {}
194
147
 
195
148
 
196
149
    def __eq__(self, other):
197
150
        if not isinstance(other, Weave):
198
151
            return False
199
 
        return self._parents == other._parents \
200
 
               and self._weave == other._weave \
201
 
               and self._sha1s == other._sha1s 
 
152
        return self._v == other._v \
 
153
               and self._l == other._l
 
154
    
202
155
 
203
 
    
204
156
    def __ne__(self, other):
205
157
        return not self.__eq__(other)
206
158
 
207
 
 
208
 
    def lookup(self, name):
209
 
        try:
210
 
            return self._name_map[name]
211
 
        except KeyError:
212
 
            raise WeaveError("name %s not present in weave" % name)
213
 
 
214
159
        
215
 
    def add(self, name, parents, text):
 
160
    def add(self, parents, text):
216
161
        """Add a single text on top of the weave.
217
162
  
218
163
        Returns the index number of the newly added version.
219
164
 
220
 
        name
221
 
            Symbolic name for this version.
222
 
            (Typically the revision-id of the revision that added it.)
223
 
 
224
165
        parents
225
166
            List or set of direct parent version numbers.
226
167
            
227
168
        text
228
169
            Sequence of lines to be added in the new version."""
229
 
 
230
 
        assert isinstance(name, basestring)
231
 
        if name in self._name_map:
232
 
            raise WeaveError("name %r already present in weave" % name)
233
 
        
234
 
        self._check_versions(parents)
 
170
        ## self._check_versions(parents)
235
171
        ## self._check_lines(text)
236
 
        new_version = len(self._parents)
 
172
        idx = len(self._v)
237
173
 
 
174
        import sha
238
175
        s = sha.new()
239
 
        map(s.update, text)
 
176
        for l in text:
 
177
            s.update(l)
240
178
        sha1 = s.hexdigest()
241
179
        del s
242
180
 
243
 
        # if we abort after here the (in-memory) weave will be corrupt because only
244
 
        # some fields are updated
245
 
        self._parents.append(parents[:])
 
181
        if parents:
 
182
            ancestors = self.inclusions(parents)
 
183
            delta = self._delta(ancestors, text)
 
184
 
 
185
            # offset gives the number of lines that have been inserted
 
186
            # into the weave up to the current point; if the original edit instruction
 
187
            # says to change line A then we actually change (A+offset)
 
188
            offset = 0
 
189
 
 
190
            for i1, i2, newlines in delta:
 
191
                assert 0 <= i1
 
192
                assert i1 <= i2
 
193
                assert i2 <= len(self._l)
 
194
 
 
195
                # the deletion and insertion are handled separately.
 
196
                # first delete the region.
 
197
                if i1 != i2:
 
198
                    self._l.insert(i1+offset, ('[', idx))
 
199
                    self._l.insert(i2+offset+1, (']', idx))
 
200
                    offset += 2
 
201
                    # is this OK???
 
202
 
 
203
                if newlines:
 
204
                    # there may have been a deletion spanning up to
 
205
                    # i2; we want to insert after this region to make sure
 
206
                    # we don't destroy ourselves
 
207
                    i = i2 + offset
 
208
                    self._l[i:i] = [('{', idx)] \
 
209
                                   + newlines \
 
210
                                   + [('}', idx)]
 
211
                    offset += 2 + len(newlines)
 
212
 
 
213
            self._addversion(parents)
 
214
        else:
 
215
            # special case; adding with no parents revision; can do this
 
216
            # more quickly by just appending unconditionally
 
217
            self._l.append(('{', idx))
 
218
            self._l += text
 
219
            self._l.append(('}', idx))
 
220
 
 
221
            self._addversion(None)
 
222
 
246
223
        self._sha1s.append(sha1)
247
 
        self._names.append(name)
248
 
        self._name_map[name] = new_version
249
 
 
250
 
            
251
 
        if not parents:
252
 
            # special case; adding with no parents revision; can do
253
 
            # this more quickly by just appending unconditionally.
254
 
            # even more specially, if we're adding an empty text we
255
 
            # need do nothing at all.
256
 
            if text:
257
 
                self._weave.append(('{', new_version))
258
 
                self._weave.extend(text)
259
 
                self._weave.append(('}', None))
260
 
        
261
 
            return new_version
262
 
 
263
 
        if len(parents) == 1:
264
 
            pv = list(parents)[0]
265
 
            if sha1 == self._sha1s[pv]:
266
 
                # special case: same as the single parent
267
 
                return new_version
268
 
            
269
 
 
270
 
        ancestors = self.inclusions(parents)
271
 
 
272
 
        l = self._weave
273
 
 
274
 
        # basis a list of (origin, lineno, line)
275
 
        basis_lineno = []
276
 
        basis_lines = []
277
 
        for origin, lineno, line in self._extract(ancestors):
278
 
            basis_lineno.append(lineno)
279
 
            basis_lines.append(line)
280
 
 
281
 
        # another small special case: a merge, producing the same text
282
 
        # as auto-merge
283
 
        if text == basis_lines:
284
 
            return new_version            
285
 
 
286
 
        # add a sentinal, because we can also match against the final line
287
 
        basis_lineno.append(len(self._weave))
288
 
 
289
 
        # XXX: which line of the weave should we really consider
290
 
        # matches the end of the file?  the current code says it's the
291
 
        # last line of the weave?
292
 
 
293
 
        #print 'basis_lines:', basis_lines
294
 
        #print 'new_lines:  ', lines
295
 
 
296
 
        from difflib import SequenceMatcher
297
 
        s = SequenceMatcher(None, basis_lines, text)
298
 
 
299
 
        # offset gives the number of lines that have been inserted
300
 
        # into the weave up to the current point; if the original edit instruction
301
 
        # says to change line A then we actually change (A+offset)
302
 
        offset = 0
303
 
 
304
 
        for tag, i1, i2, j1, j2 in s.get_opcodes():
305
 
            # i1,i2 are given in offsets within basis_lines; we need to map them
306
 
            # back to offsets within the entire weave
307
 
            #print 'raw match', tag, i1, i2, j1, j2
308
 
            if tag == 'equal':
309
 
                continue
310
 
 
311
 
            i1 = basis_lineno[i1]
312
 
            i2 = basis_lineno[i2]
313
 
 
314
 
            assert 0 <= j1 <= j2 <= len(text)
315
 
 
316
 
            #print tag, i1, i2, j1, j2
317
 
 
318
 
            # the deletion and insertion are handled separately.
319
 
            # first delete the region.
320
 
            if i1 != i2:
321
 
                self._weave.insert(i1+offset, ('[', new_version))
322
 
                self._weave.insert(i2+offset+1, (']', new_version))
323
 
                offset += 2
324
 
 
325
 
            if j1 != j2:
326
 
                # there may have been a deletion spanning up to
327
 
                # i2; we want to insert after this region to make sure
328
 
                # we don't destroy ourselves
329
 
                i = i2 + offset
330
 
                self._weave[i:i] = ([('{', new_version)] 
331
 
                                    + text[j1:j2] 
332
 
                                    + [('}', None)])
333
 
                offset += 2 + (j2 - j1)
334
 
 
335
 
        return new_version
 
224
            
 
225
        return idx
336
226
 
337
227
 
338
228
    def inclusions(self, versions):
343
233
            while v >= 0:
344
234
                if v in i:
345
235
                    # include all its parents
346
 
                    i.update(self._parents[v])
 
236
                    i.update(self._v[v])
347
237
                v -= 1
348
238
            return i
349
239
        except IndexError:
352
242
 
353
243
    def minimal_parents(self, version):
354
244
        """Find the minimal set of parents for the version."""
355
 
        included = self._parents[version]
 
245
        included = self._v[version]
356
246
        if not included:
357
247
            return []
358
248
        
372
262
        return mininc
373
263
 
374
264
 
 
265
    def _addversion(self, parents):
 
266
        if parents:
 
267
            self._v.append(parents)
 
268
        else:
 
269
            self._v.append(frozenset())
 
270
 
375
271
 
376
272
    def _check_lines(self, text):
377
273
        if not isinstance(text, list):
388
284
        """Check everything in the sequence of indexes is valid"""
389
285
        for i in indexes:
390
286
            try:
391
 
                self._parents[i]
 
287
                self._v[i]
392
288
            except IndexError:
393
289
                raise IndexError("invalid version number %r" % i)
394
290
 
405
301
            yield origin, text
406
302
 
407
303
 
408
 
    def _walk(self):
409
 
        """Walk the weave.
410
 
 
411
 
        Yields sequence of
412
 
        (lineno, insert, deletes, text)
413
 
        for each literal line.
414
 
        """
415
 
        
416
 
        istack = []
417
 
        dset = set()
418
 
 
419
 
        lineno = 0         # line of weave, 0-based
420
 
 
421
 
        for l in self._weave:
422
 
            if isinstance(l, tuple):
423
 
                c, v = l
424
 
                isactive = None
425
 
                if c == '{':
426
 
                    istack.append(v)
427
 
                elif c == '}':
428
 
                    istack.pop()
429
 
                elif c == '[':
430
 
                    assert v not in dset
431
 
                    dset.add(v)
432
 
                elif c == ']':
433
 
                    dset.remove(v)
434
 
                else:
435
 
                    raise WeaveFormatError('unexpected instruction %r'
436
 
                                           % v)
437
 
            else:
438
 
                assert isinstance(l, basestring)
439
 
                assert istack
440
 
                yield lineno, istack[-1], dset, l
441
 
            lineno += 1
442
 
 
443
 
 
444
 
 
445
304
    def _extract(self, versions):
446
305
        """Yield annotation of lines in included set.
447
306
 
460
319
 
461
320
        isactive = None
462
321
 
463
 
        result = []
464
 
 
465
322
        WFE = WeaveFormatError
466
323
 
467
 
        for l in self._weave:
 
324
        for l in self._l:
468
325
            if isinstance(l, tuple):
469
326
                c, v = l
470
327
                isactive = None
472
329
                    assert v not in istack
473
330
                    istack.append(v)
474
331
                elif c == '}':
475
 
                    istack.pop()
 
332
                    oldv = istack.pop()
 
333
                    assert oldv == v
476
334
                elif c == '[':
477
335
                    if v in included:
478
336
                        assert v not in dset
487
345
                if isactive is None:
488
346
                    isactive = (not dset) and istack and (istack[-1] in included)
489
347
                if isactive:
490
 
                    result.append((istack[-1], lineno, l))
 
348
                    yield istack[-1], lineno, l
491
349
            lineno += 1
492
350
 
493
351
        if istack:
497
355
            raise WFE("unclosed deletion blocks at end of weave",
498
356
                                   dset)
499
357
 
500
 
        return result
501
 
    
502
 
 
503
358
 
504
359
    def get_iter(self, version):
505
360
        """Yield lines for the specified version."""
513
368
 
514
369
    def mash_iter(self, included):
515
370
        """Return composed version of multiple included versions."""
 
371
        included = frozenset(included)
516
372
        for origin, lineno, text in self._extract(included):
517
373
            yield text
518
374
 
519
375
 
520
376
    def dump(self, to_file):
521
377
        from pprint import pprint
522
 
        print >>to_file, "Weave._weave = ",
523
 
        pprint(self._weave, to_file)
524
 
        print >>to_file, "Weave._parents = ",
525
 
        pprint(self._parents, to_file)
 
378
        print >>to_file, "Weave._l = ",
 
379
        pprint(self._l, to_file)
 
380
        print >>to_file, "Weave._v = ",
 
381
        pprint(self._v, to_file)
526
382
 
527
383
 
528
384
 
529
385
    def numversions(self):
530
 
        l = len(self._parents)
 
386
        l = len(self._v)
531
387
        assert l == len(self._sha1s)
532
388
        return l
533
389
 
534
390
 
535
 
    def __len__(self):
536
 
        return self.numversions()
537
 
 
538
 
 
539
391
    def check(self, progress_bar=None):
540
392
        # check no circular inclusions
541
393
        for version in range(self.numversions()):
542
 
            inclusions = list(self._parents[version])
 
394
            inclusions = list(self._v[version])
543
395
            if inclusions:
544
396
                inclusions.sort()
545
397
                if inclusions[-1] >= version:
548
400
 
549
401
        # try extracting all versions; this is a bit slow and parallel
550
402
        # extraction could be used
 
403
        import sha
551
404
        nv = self.numversions()
552
405
        for version in range(nv):
553
406
            if progress_bar:
604
457
        If line1=line2, this is a pure insert; if newlines=[] this is a
605
458
        pure delete.  (Similar to difflib.)
606
459
        """
607
 
 
608
 
 
609
 
            
610
 
    def plan_merge(self, ver_a, ver_b):
611
 
        """Return pseudo-annotation indicating how the two versions merge.
612
 
 
613
 
        This is computed between versions a and b and their common
614
 
        base.
615
 
 
616
 
        Weave lines present in none of them are skipped entirely.
617
 
        """
618
 
        inc_a = self.inclusions([ver_a])
619
 
        inc_b = self.inclusions([ver_b])
620
 
        inc_c = inc_a & inc_b
621
 
 
622
 
        for lineno, insert, deleteset, line in self._walk():
623
 
            if deleteset & inc_c:
624
 
                # killed in parent; can't be in either a or b
625
 
                # not relevant to our work
626
 
                yield 'killed-base', line
627
 
            elif insert in inc_c:
628
 
                # was inserted in base
629
 
                killed_a = bool(deleteset & inc_a)
630
 
                killed_b = bool(deleteset & inc_b)
631
 
                if killed_a and killed_b:
632
 
                    yield 'killed-both', line
633
 
                elif killed_a:
634
 
                    yield 'killed-a', line
635
 
                elif killed_b:
636
 
                    yield 'killed-b', line
637
 
                else:
638
 
                    yield 'unchanged', line
639
 
            elif insert in inc_a:
640
 
                if deleteset & inc_a:
641
 
                    yield 'ghost-a', line
642
 
                else:
643
 
                    # new in A; not in B
644
 
                    yield 'new-a', line
645
 
            elif insert in inc_b:
646
 
                if deleteset & inc_b:
647
 
                    yield 'ghost-b', line
648
 
                else:
649
 
                    yield 'new-b', line
650
 
            else:
651
 
                # not in either revision
652
 
                yield 'irrelevant', line
653
 
 
654
 
        yield 'unchanged', ''           # terminator
655
 
 
656
 
 
657
 
 
658
 
    def weave_merge(self, plan):
659
 
        lines_a = []
660
 
        lines_b = []
661
 
        ch_a = ch_b = False
662
 
 
663
 
        for state, line in plan:
664
 
            if state == 'unchanged' or state == 'killed-both':
665
 
                # resync and flush queued conflicts changes if any
666
 
                if not lines_a and not lines_b:
667
 
                    pass
668
 
                elif ch_a and not ch_b:
669
 
                    # one-sided change:                    
670
 
                    for l in lines_a: yield l
671
 
                elif ch_b and not ch_a:
672
 
                    for l in lines_b: yield l
673
 
                elif lines_a == lines_b:
674
 
                    for l in lines_a: yield l
675
 
                else:
676
 
                    yield '<<<<\n'
677
 
                    for l in lines_a: yield l
678
 
                    yield '====\n'
679
 
                    for l in lines_b: yield l
680
 
                    yield '>>>>\n'
681
 
 
682
 
                del lines_a[:]
683
 
                del lines_b[:]
684
 
                ch_a = ch_b = False
685
 
                
686
 
            if state == 'unchanged':
687
 
                if line:
688
 
                    yield line
689
 
            elif state == 'killed-a':
690
 
                ch_a = True
691
 
                lines_b.append(line)
692
 
            elif state == 'killed-b':
693
 
                ch_b = True
694
 
                lines_a.append(line)
695
 
            elif state == 'new-a':
696
 
                ch_a = True
697
 
                lines_a.append(line)
698
 
            elif state == 'new-b':
699
 
                ch_b = True
700
 
                lines_b.append(line)
701
 
            else:
702
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
703
 
                                 'killed-both'), \
704
 
                       state
705
 
 
706
 
                
707
 
 
708
 
 
709
 
 
710
 
 
711
 
 
712
 
def weave_toc(w):
713
 
    """Show the weave's table-of-contents"""
714
 
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
715
 
    for i in (6, 50, 10, 10):
716
 
        print '-' * i,
717
 
    print
718
 
    for i in range(w.numversions()):
719
 
        sha1 = w._sha1s[i]
720
 
        name = w._names[i]
721
 
        parent_str = ' '.join(map(str, w._parents[i]))
722
 
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
723
 
 
724
 
 
725
 
 
726
 
def weave_stats(weave_file):
727
 
    from bzrlib.progress import ProgressBar
728
 
    from bzrlib.weavefile import read_weave
729
 
 
730
 
    pb = ProgressBar()
731
 
 
732
 
    wf = file(weave_file, 'rb')
 
460
        # basis a list of (origin, lineno, line)
 
461
        basis_lineno = []
 
462
        basis_lines = []
 
463
        for origin, lineno, line in self._extract(included):
 
464
            basis_lineno.append(lineno)
 
465
            basis_lines.append(line)
 
466
 
 
467
        # add a sentinal, because we can also match against the final line
 
468
        basis_lineno.append(len(self._l))
 
469
 
 
470
        # XXX: which line of the weave should we really consider
 
471
        # matches the end of the file?  the current code says it's the
 
472
        # last line of the weave?
 
473
 
 
474
        from difflib import SequenceMatcher
 
475
        s = SequenceMatcher(None, basis_lines, lines)
 
476
 
 
477
        # TODO: Perhaps return line numbers from composed weave as well?
 
478
 
 
479
        for tag, i1, i2, j1, j2 in s.get_opcodes():
 
480
            ##print tag, i1, i2, j1, j2
 
481
 
 
482
            if tag == 'equal':
 
483
                continue
 
484
 
 
485
            # i1,i2 are given in offsets within basis_lines; we need to map them
 
486
            # back to offsets within the entire weave
 
487
            real_i1 = basis_lineno[i1]
 
488
            real_i2 = basis_lineno[i2]
 
489
 
 
490
            assert 0 <= j1
 
491
            assert j1 <= j2
 
492
            assert j2 <= len(lines)
 
493
 
 
494
            yield real_i1, real_i2, lines[j1:j2]
 
495
 
 
496
 
 
497
 
 
498
def weave_info(filename, out):
 
499
    """Show some text information about the weave."""
 
500
    from weavefile import read_weave
 
501
    wf = file(filename, 'rb')
733
502
    w = read_weave(wf)
734
503
    # FIXME: doesn't work on pipes
735
504
    weave_size = wf.tell()
 
505
    print >>out, "weave file size %d bytes" % weave_size
 
506
    print >>out, "weave contains %d versions" % len(w._v)
736
507
 
737
508
    total = 0
738
 
    vers = len(w)
739
 
    for i in range(vers):
740
 
        pb.update('checking sizes', i, vers)
741
 
        for line in w.get_iter(i):
742
 
            total += len(line)
743
 
 
744
 
    pb.clear()
745
 
 
746
 
    print 'versions          %9d' % vers
747
 
    print 'weave file        %9d bytes' % weave_size
748
 
    print 'total contents    %9d bytes' % total
749
 
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
750
 
    if vers:
751
 
        avg = total/vers
752
 
        print 'average size      %9d bytes' % avg
753
 
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
 
509
    print '%6s %6s %8s %40s %20s' % ('ver', 'lines', 'bytes', 'sha1', 'parents')
 
510
    for i in (6, 6, 8, 40, 20):
 
511
        print '-' * i,
 
512
    print
 
513
    for i in range(len(w._v)):
 
514
        text = w.get(i)
 
515
        lines = len(text)
 
516
        bytes = sum((len(a) for a in text))
 
517
        sha1 = w._sha1s[i]
 
518
        print '%6d %6d %8d %40s' % (i, lines, bytes, sha1),
 
519
        for pv in w._v[i]:
 
520
            print pv,
 
521
        print
 
522
        total += bytes
 
523
 
 
524
    print >>out, "versions total %d bytes" % total
 
525
    print >>out, "compression ratio %.3f" % (float(total)/float(weave_size))
754
526
 
755
527
 
756
528
def usage():
765
537
        Write out specified version.
766
538
    weave check WEAVEFILE
767
539
        Check consistency of all versions.
768
 
    weave toc WEAVEFILE
 
540
    weave info WEAVEFILE
769
541
        Display table of contents.
770
 
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
 
542
    weave add WEAVEFILE [BASE...] < NEWTEXT
771
543
        Add NEWTEXT, with specified parent versions.
772
544
    weave annotate WEAVEFILE VERSION
773
545
        Display origin of each line.
780
552
 
781
553
    % weave init foo.weave
782
554
    % vi foo.txt
783
 
    % weave add foo.weave ver0 < foo.txt
 
555
    % weave add foo.weave < foo.txt
784
556
    added version 0
785
557
 
786
558
    (create updated version)
787
559
    % vi foo.txt
788
560
    % weave get foo.weave 0 | diff -u - foo.txt
789
 
    % weave add foo.weave ver1 0 < foo.txt
 
561
    % weave add foo.weave 0 < foo.txt
790
562
    added version 1
791
563
 
792
564
    % weave get foo.weave 0 > foo.txt       (create forked version)
793
565
    % vi foo.txt
794
 
    % weave add foo.weave ver2 0 < foo.txt
 
566
    % weave add foo.weave 0 < foo.txt
795
567
    added version 2
796
568
 
797
569
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
798
570
    % vi foo.txt                            (resolve conflicts)
799
 
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
 
571
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
800
572
    
801
573
"""
802
574
    
808
580
    from weavefile import write_weave, read_weave
809
581
    from bzrlib.progress import ProgressBar
810
582
 
811
 
    try:
812
 
        import psyco
813
 
        psyco.full()
814
 
    except ImportError:
815
 
        pass
816
 
 
817
 
    if len(argv) < 2:
818
 
        usage()
819
 
        return 0
 
583
    #import psyco
 
584
    #psyco.full()
820
585
 
821
586
    cmd = argv[1]
822
587
 
828
593
    elif cmd == 'add':
829
594
        w = readit()
830
595
        # at the moment, based on everything in the file
831
 
        name = argv[3]
832
 
        parents = map(int, argv[4:])
 
596
        parents = map(int, argv[3:])
833
597
        lines = sys.stdin.readlines()
834
 
        ver = w.add(name, parents, lines)
 
598
        ver = w.add(parents, lines)
835
599
        write_weave(w, file(argv[2], 'wb'))
836
 
        print 'added version %r %d' % (name, ver)
 
600
        print 'added version %d' % ver
837
601
    elif cmd == 'init':
838
602
        fn = argv[2]
839
603
        if os.path.exists(fn):
861
625
                print '%5d | %s' % (origin, text)
862
626
                lasto = origin
863
627
                
864
 
    elif cmd == 'toc':
865
 
        weave_toc(readit())
866
 
 
867
 
    elif cmd == 'stats':
868
 
        weave_stats(argv[2])
 
628
    elif cmd == 'info':
 
629
        weave_info(argv[2], sys.stdout)
869
630
        
870
631
    elif cmd == 'check':
871
632
        w = readit()
872
633
        pb = ProgressBar()
873
634
        w.check(pb)
874
635
        pb.clear()
875
 
        print '%d versions ok' % w.numversions()
876
636
 
877
637
    elif cmd == 'inclusions':
878
638
        w = readit()
880
640
 
881
641
    elif cmd == 'parents':
882
642
        w = readit()
883
 
        print ' '.join(map(str, w._parents[int(argv[3])]))
884
 
 
885
 
    elif cmd == 'plan-merge':
886
 
        w = readit()
887
 
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
888
 
            if line:
889
 
                print '%14s | %s' % (state, line),
 
643
        print ' '.join(map(str, w._v[int(argv[3])]))
890
644
 
891
645
    elif cmd == 'merge':
892
 
        w = readit()
893
 
        p = w.plan_merge(int(argv[3]), int(argv[4]))
894
 
        sys.stdout.writelines(w.weave_merge(p))
895
 
            
896
 
    elif cmd == 'mash-merge':
897
646
        if len(argv) != 5:
898
647
            usage()
899
648
            return 1
917
666
        raise ValueError('unknown command %r' % cmd)
918
667
    
919
668
 
920
 
 
921
 
def profile_main(argv): 
922
 
    import tempfile, hotshot, hotshot.stats
923
 
 
924
 
    prof_f = tempfile.NamedTemporaryFile()
925
 
 
926
 
    prof = hotshot.Profile(prof_f.name)
927
 
 
928
 
    ret = prof.runcall(main, argv)
929
 
    prof.close()
930
 
 
931
 
    stats = hotshot.stats.load(prof_f.name)
932
 
    #stats.strip_dirs()
933
 
    stats.sort_stats('cumulative')
934
 
    ## XXX: Might like to write to stderr or the trace file instead but
935
 
    ## print_stats seems hardcoded to stdout
936
 
    stats.print_stats(20)
937
 
            
938
 
    return ret
939
 
 
940
 
 
941
669
if __name__ == '__main__':
942
670
    import sys
943
 
    if '--profile' in sys.argv:
944
 
        args = sys.argv[:]
945
 
        args.remove('--profile')
946
 
        sys.exit(profile_main(args))
947
 
    else:
948
 
        sys.exit(main(sys.argv))
949
 
 
 
671
    sys.exit(main(sys.argv))