~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

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
# TODO: Perhaps have copy method for Weave instances?
24
55
 
25
56
# XXX: If we do weaves this way, will a merge still behave the same
26
57
# way if it's done in a different order?  That's a pretty desirable
43
74
 
44
75
# TODO: Parallel-extract that passes back each line along with a
45
76
# description of which revisions include it.  Nice for checking all
46
 
# shas or calculating stats in parallel.
 
77
# shas in parallel.
47
78
 
48
79
# TODO: Using a single _extract routine and then processing the output
49
80
# is probably inefficient.  It's simple enough that we can afford to
50
81
# have slight specializations for different ways its used: annotate,
51
82
# basis for add, get, etc.
52
83
 
53
 
# TODO: Probably the API should work only in names to hide the integer
54
 
# indexes from the user.
55
 
 
56
 
# TODO: Is there any potential performance win by having an add()
57
 
# variant that is passed a pre-cooked version of the single basis
58
 
# version?
59
 
 
60
 
# TODO: Reweave can possibly be made faster by remembering diffs
61
 
# where the basis and destination are unchanged.
62
 
 
63
 
# FIXME: Sometimes we will be given a parents list for a revision
64
 
# that includes some redundant parents (i.e. already a parent of 
65
 
# something in the list.)  We should eliminate them.  This can 
66
 
# be done fairly efficiently because the sequence numbers constrain
67
 
# the possible relationships.
68
 
 
69
 
 
70
 
import os
 
84
# TODO: Perhaps the API should work only in names to hide the integer
 
85
# indexes from the user?
 
86
 
 
87
 
 
88
 
71
89
import sha
72
 
from difflib import SequenceMatcher
73
 
 
74
 
from bzrlib.trace import mutter
75
 
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
76
 
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
77
 
import bzrlib.errors as errors
78
 
from bzrlib.tsort import topo_sort
79
 
 
 
90
 
 
91
from cStringIO import StringIO
 
92
 
 
93
from bzrlib.osutils import sha_strings
 
94
 
 
95
 
 
96
class WeaveError(Exception):
 
97
    """Exception in processing weave"""
 
98
 
 
99
 
 
100
class WeaveFormatError(WeaveError):
 
101
    """Weave invariant violated"""
 
102
    
80
103
 
81
104
class Weave(object):
82
105
    """weave - versioned text file storage.
93
116
 
94
117
    * a nonnegative index number.
95
118
 
96
 
    * a version-id string.
 
119
    * a version-id string. (not implemented yet)
97
120
 
98
121
    Typically the index number will be valid only inside this weave and
99
122
    the version-id is used to reference it in the larger world.
178
201
        self._name_map = {}
179
202
        self._weave_name = weave_name
180
203
 
181
 
    def __repr__(self):
182
 
        return "Weave(%r)" % self._weave_name
183
 
 
184
 
 
185
 
    def copy(self):
186
 
        """Return a deep copy of self.
187
 
        
188
 
        The copy can be modified without affecting the original weave."""
189
 
        other = Weave()
190
 
        other._weave = self._weave[:]
191
 
        other._parents = self._parents[:]
192
 
        other._sha1s = self._sha1s[:]
193
 
        other._names = self._names[:]
194
 
        other._name_map = self._name_map.copy()
195
 
        other._weave_name = self._weave_name
196
 
        return other
197
204
 
198
205
    def __eq__(self, other):
199
206
        if not isinstance(other, Weave):
206
213
    def __ne__(self, other):
207
214
        return not self.__eq__(other)
208
215
 
209
 
    def __contains__(self, name):
210
 
        return self._name_map.has_key(name)
211
216
 
212
217
    def maybe_lookup(self, name_or_index):
213
218
        """Convert possible symbolic name to index, or pass through indexes."""
222
227
        try:
223
228
            return self._name_map[name]
224
229
        except KeyError:
225
 
            raise WeaveRevisionNotPresent(name, self)
226
 
 
227
 
    def names(self):
228
 
        return self._names[:]
229
 
 
230
 
    def iter_names(self):
231
 
        """Yield a list of all names in this weave."""
232
 
        return iter(self._names)
 
230
            raise WeaveError("name %r not present in weave %r" %
 
231
                             (name, self._weave_name))
 
232
 
233
233
 
234
234
    def idx_to_name(self, version):
235
235
        return self._names[version]
236
236
 
237
 
    def _check_repeated_add(self, name, parents, text, sha1):
 
237
 
 
238
    def _check_repeated_add(self, name, parents, text):
238
239
        """Check that a duplicated add is OK.
239
240
 
240
241
        If it is, return the (old) index; otherwise raise an exception.
241
242
        """
242
243
        idx = self.lookup(name)
243
 
        if sorted(self._parents[idx]) != sorted(parents) \
244
 
            or sha1 != self._sha1s[idx]:
245
 
            raise WeaveRevisionAlreadyPresent(name, self)
 
244
        if sorted(self._parents[idx]) != sorted(parents):
 
245
            raise WeaveError("name \"%s\" already present in weave "
 
246
                             "with different parents" % name)
 
247
        new_sha1 = sha_strings(text)
 
248
        if new_sha1 != self._sha1s[idx]:
 
249
            raise WeaveError("name \"%s\" already present in weave "
 
250
                             "with different text" % name)            
246
251
        return idx
247
252
        
248
 
    def add(self, name, parents, text, sha1=None):
 
253
 
 
254
        
 
255
    def add(self, name, parents, text):
249
256
        """Add a single text on top of the weave.
250
257
  
251
258
        Returns the index number of the newly added version.
258
265
            List or set of direct parent version numbers.
259
266
            
260
267
        text
261
 
            Sequence of lines to be added in the new version.
262
 
 
263
 
        sha -- SHA-1 of the file, if known.  This is trusted to be
264
 
            correct if supplied.
265
 
        """
266
 
        from bzrlib.osutils import sha_strings
 
268
            Sequence of lines to be added in the new version."""
267
269
 
268
270
        assert isinstance(name, basestring)
269
 
        if sha1 is None:
270
 
            sha1 = sha_strings(text)
271
271
        if name in self._name_map:
272
 
            return self._check_repeated_add(name, parents, text, sha1)
 
272
            return self._check_repeated_add(name, parents, text)
273
273
 
274
274
        parents = map(self.maybe_lookup, parents)
275
275
        self._check_versions(parents)
276
276
        ## self._check_lines(text)
277
277
        new_version = len(self._parents)
278
278
 
 
279
        sha1 = sha_strings(text)
279
280
 
280
281
        # if we abort after here the (in-memory) weave will be corrupt because only
281
282
        # some fields are updated
330
331
        #print 'basis_lines:', basis_lines
331
332
        #print 'new_lines:  ', lines
332
333
 
 
334
        from difflib import SequenceMatcher
333
335
        s = SequenceMatcher(None, basis_lines, text)
334
336
 
335
337
        # offset gives the number of lines that have been inserted
370
372
 
371
373
        return new_version
372
374
 
373
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
374
 
        """Add an identical text to old_rev_id as new_rev_id."""
375
 
        old_lines = self.get(self.lookup(old_rev_id))
376
 
        self.add(new_rev_id, parents, old_lines)
377
375
 
378
376
    def inclusions(self, versions):
379
377
        """Return set of all ancestors of given version(s)."""
380
378
        i = set(versions)
381
 
        for v in xrange(max(versions), 0, -1):
382
 
            if v in i:
383
 
                # include all its parents
384
 
                i.update(self._parents[v])
385
 
        return i
386
 
        ## except IndexError:
387
 
        ##     raise ValueError("version %d not present in weave" % v)
 
379
        v = max(versions)
 
380
        try:
 
381
            while v >= 0:
 
382
                if v in i:
 
383
                    # include all its parents
 
384
                    i.update(self._parents[v])
 
385
                v -= 1
 
386
            return i
 
387
        except IndexError:
 
388
            raise ValueError("version %d not present in weave" % v)
388
389
 
389
390
 
390
391
    def parents(self, version):
391
392
        return self._parents[version]
392
393
 
393
394
 
394
 
    def parent_names(self, version):
395
 
        """Return version names for parents of a version."""
396
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
397
 
 
398
 
 
399
395
    def minimal_parents(self, version):
400
396
        """Find the minimal set of parents for the version."""
401
397
        included = self._parents[version]
451
447
        for origin, lineno, text in self._extract(incls):
452
448
            yield origin, text
453
449
 
 
450
 
454
451
    def _walk(self):
455
452
        """Walk the weave.
456
453
 
478
475
                elif c == ']':
479
476
                    dset.remove(v)
480
477
                else:
481
 
                    raise WeaveFormatError('unexpected instruction %r' % v)
 
478
                    raise WeaveFormatError('unexpected instruction %r'
 
479
                                           % v)
482
480
            else:
483
481
                assert isinstance(l, basestring)
484
482
                assert istack
485
483
                yield lineno, istack[-1], dset, l
486
484
            lineno += 1
487
485
 
488
 
        if istack:
489
 
            raise WeaveFormatError("unclosed insertion blocks "
490
 
                    "at end of weave: %s" % istack)
491
 
        if dset:
492
 
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
493
 
                                   % dset)
 
486
 
494
487
 
495
488
    def _extract(self, versions):
496
489
        """Yield annotation of lines in included set.
543
536
                if isactive:
544
537
                    result.append((istack[-1], lineno, l))
545
538
            lineno += 1
 
539
 
546
540
        if istack:
547
 
            raise WeaveFormatError("unclosed insertion blocks "
548
 
                    "at end of weave: %s" % istack)
 
541
            raise WFE("unclosed insertion blocks at end of weave",
 
542
                                   istack)
549
543
        if dset:
550
 
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
551
 
                                   % dset)
 
544
            raise WFE("unclosed deletion blocks at end of weave",
 
545
                                   dset)
 
546
 
552
547
        return result
 
548
    
553
549
 
554
550
 
555
551
    def get_iter(self, name_or_index):
556
552
        """Yield lines for the specified version."""
557
553
        incls = [self.maybe_lookup(name_or_index)]
558
 
        if len(incls) == 1:
559
 
            index = incls[0]
560
 
            cur_sha = sha.new()
561
 
        else:
562
 
            # We don't have sha1 sums for multiple entries
563
 
            cur_sha = None
564
554
        for origin, lineno, line in self._extract(incls):
565
 
            if cur_sha:
566
 
                cur_sha.update(line)
567
555
            yield line
568
 
        if cur_sha:
569
 
            expected_sha1 = self._sha1s[index]
570
 
            measured_sha1 = cur_sha.hexdigest() 
571
 
            if measured_sha1 != expected_sha1:
572
 
                raise errors.WeaveInvalidChecksum(
573
 
                        'file %s, revision %s, expected: %s, measured %s' 
574
 
                        % (self._weave_name, self._names[index],
575
 
                           expected_sha1, measured_sha1))
576
 
 
577
 
 
578
 
    def get_text(self, name_or_index):
579
 
        return ''.join(self.get_iter(name_or_index))
 
556
 
 
557
 
 
558
    def get_text(self, version):
580
559
        assert isinstance(version, int)
581
 
 
582
 
 
583
 
    def get_lines(self, name_or_index):
 
560
        s = StringIO()
 
561
        s.writelines(self.get_iter(version))
 
562
        return s.getvalue()
 
563
 
 
564
 
 
565
    def get(self, name_or_index):
584
566
        return list(self.get_iter(name_or_index))
585
567
 
586
568
 
587
 
    get = get_lines
588
 
 
589
 
 
590
 
    def get_sha1(self, name):
591
 
        """Get the stored sha1 sum for the given revision.
592
 
        
593
 
        :param name: The name of the version to lookup
594
 
        """
595
 
        return self._sha1s[self.lookup(name)]
596
 
 
597
569
    def mash_iter(self, included):
598
570
        """Return composed version of multiple included versions."""
599
 
        included = map(self.maybe_lookup, included)
600
571
        for origin, lineno, text in self._extract(included):
601
572
            yield text
602
573
 
619
590
    def __len__(self):
620
591
        return self.numversions()
621
592
 
 
593
 
622
594
    def check(self, progress_bar=None):
623
595
        # check no circular inclusions
624
596
        for version in range(self.numversions()):
629
601
                    raise WeaveFormatError("invalid included version %d for index %d"
630
602
                                           % (inclusions[-1], version))
631
603
 
632
 
        # try extracting all versions; parallel extraction is used
 
604
        # try extracting all versions; this is a bit slow and parallel
 
605
        # extraction could be used
633
606
        nv = self.numversions()
634
 
        sha1s = [sha.new() for i in range(nv)]
635
 
        texts = [[] for i in range(nv)]
636
 
        inclusions = []
637
 
        for i in range(nv):
638
 
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
639
 
            # The problem is that set membership is much more expensive
640
 
            new_inc = set([i])
641
 
            for p in self._parents[i]:
642
 
                new_inc.update(inclusions[p])
643
 
 
644
 
            #assert set(new_inc) == self.inclusions([i]), 'failed %s != %s' % (new_inc, self.inclusions([i]))
645
 
            inclusions.append(new_inc)
646
 
 
647
 
        nlines = len(self._weave)
648
 
 
649
 
        update_text = 'checking weave'
650
 
        if self._weave_name:
651
 
            short_name = os.path.basename(self._weave_name)
652
 
            update_text = 'checking %s' % (short_name,)
653
 
            update_text = update_text[:25]
654
 
 
655
 
        for lineno, insert, deleteset, line in self._walk():
 
607
        for version in range(nv):
656
608
            if progress_bar:
657
 
                progress_bar.update(update_text, lineno, nlines)
658
 
 
659
 
            for j, j_inc in enumerate(inclusions):
660
 
                # The active inclusion must be an ancestor,
661
 
                # and no ancestors must have deleted this line,
662
 
                # because we don't support resurrection.
663
 
                if (insert in j_inc) and not (deleteset & j_inc):
664
 
                    sha1s[j].update(line)
665
 
 
666
 
        for version in range(nv):
667
 
            hd = sha1s[version].hexdigest()
 
609
                progress_bar.update('checking text', version, nv)
 
610
            s = sha.new()
 
611
            for l in self.get_iter(version):
 
612
                s.update(l)
 
613
            hd = s.hexdigest()
668
614
            expected = self._sha1s[version]
669
615
            if hd != expected:
670
 
                raise errors.WeaveInvalidChecksum(
671
 
                        "mismatched sha1 for version %s: "
672
 
                        "got %s, expected %s"
673
 
                        % (self._names[version], hd, expected))
 
616
                raise WeaveError("mismatched sha1 for version %d; "
 
617
                                 "got %s, expected %s"
 
618
                                 % (version, hd, expected))
674
619
 
675
620
        # TODO: check insertions are properly nested, that there are
676
621
        # no lines outside of insertion blocks, that deletions are
677
622
        # properly paired, etc.
678
623
 
 
624
 
 
625
 
 
626
    def merge(self, merge_versions):
 
627
        """Automerge and mark conflicts between versions.
 
628
 
 
629
        This returns a sequence, each entry describing alternatives
 
630
        for a chunk of the file.  Each of the alternatives is given as
 
631
        a list of lines.
 
632
 
 
633
        If there is a chunk of the file where there's no diagreement,
 
634
        only one alternative is given.
 
635
        """
 
636
 
 
637
        # approach: find the included versions common to all the
 
638
        # merged versions
 
639
        raise NotImplementedError()
 
640
 
 
641
 
 
642
 
679
643
    def _delta(self, included, lines):
680
644
        """Return changes from basis to new revision.
681
645
 
695
659
        If line1=line2, this is a pure insert; if newlines=[] this is a
696
660
        pure delete.  (Similar to difflib.)
697
661
        """
698
 
        raise NotImplementedError()
 
662
 
699
663
 
700
664
            
701
665
    def plan_merge(self, ver_a, ver_b):
750
714
        lines_a = []
751
715
        lines_b = []
752
716
        ch_a = ch_b = False
753
 
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
754
 
        # conflicted regions), rather than just inserting the markers.
755
 
        # 
756
 
        # TODO: Show some version information (e.g. author, date) on 
757
 
        # conflicted regions.
 
717
 
758
718
        for state, line in plan:
759
719
            if state == 'unchanged' or state == 'killed-both':
760
720
                # resync and flush queued conflicts changes if any
768
728
                elif lines_a == lines_b:
769
729
                    for l in lines_a: yield l
770
730
                else:
771
 
                    yield '<<<<<<<\n'
 
731
                    yield '<<<<\n'
772
732
                    for l in lines_a: yield l
773
 
                    yield '=======\n'
 
733
                    yield '====\n'
774
734
                    for l in lines_b: yield l
775
 
                    yield '>>>>>>>\n'
 
735
                    yield '>>>>\n'
776
736
 
777
737
                del lines_a[:]
778
738
                del lines_b[:]
798
758
                                 'killed-both'), \
799
759
                       state
800
760
 
801
 
 
802
 
    def join(self, other, pb=None, msg=None):
803
 
        import sys, time
804
 
        """Integrate versions from other into this weave.
805
 
 
806
 
        The resulting weave contains all the history of both weaves; 
807
 
        any version you could retrieve from either self or other can be 
808
 
        retrieved from self after this call.
809
 
 
810
 
        It is illegal for the two weaves to contain different values 
811
 
        or different parents for any version.  See also reweave().
812
 
 
813
 
        :param other: The other weave to pull into this one
814
 
        :param pb: An optional progress bar
815
 
        :param msg: An optional message to display for progress
816
 
        """
817
 
        if other.numversions() == 0:
818
 
            return          # nothing to update, easy
819
 
        # two loops so that we do not change ourselves before verifying it
820
 
        # will be ok
821
 
        # work through in index order to make sure we get all dependencies
822
 
        for other_idx, name in enumerate(other._names):
823
 
            self._check_version_consistent(other, other_idx, name)
824
 
 
825
 
        if pb and not msg:
826
 
            msg = 'weave join'
827
 
 
828
 
        merged = 0
829
 
        processed = 0
830
 
        time0 = time.time( )
831
 
        for other_idx, name in enumerate(other._names):
832
 
            # TODO: If all the parents of the other version are already
833
 
            # present then we can avoid some work by just taking the delta
834
 
            # and adjusting the offsets.
835
 
            new_parents = self._imported_parents(other, other_idx)
836
 
            sha1 = other._sha1s[other_idx]
837
 
 
838
 
            processed += 1
839
 
 
840
 
            if name in self._name_map:
841
 
                idx = self.lookup(name)
842
 
                n1 = set(map(other.idx_to_name, other._parents[other_idx]))
843
 
                n2 = set(map(self.idx_to_name, self._parents[idx]))
844
 
                if sha1 ==  self._sha1s[idx] and n1 == n2:
845
 
                        continue
846
 
 
847
 
            if pb:
848
 
                pb.update(msg, other_idx, len(other._names))
849
 
           
850
 
            merged += 1
851
 
            lines = other.get_lines(other_idx)
852
 
            self.add(name, new_parents, lines, sha1)
853
 
 
854
 
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
855
 
                merged,processed,self._weave_name, time.time( )-time0))
856
 
 
857
 
 
858
 
 
859
 
 
860
 
    def _imported_parents(self, other, other_idx):
861
 
        """Return list of parents in self corresponding to indexes in other."""
862
 
        new_parents = []
863
 
        for parent_idx in other._parents[other_idx]:
864
 
            parent_name = other._names[parent_idx]
865
 
            if parent_name not in self._names:
866
 
                # should not be possible
867
 
                raise WeaveError("missing parent {%s} of {%s} in %r" 
868
 
                                 % (parent_name, other._name_map[other_idx], self))
869
 
            new_parents.append(self._name_map[parent_name])
870
 
        return new_parents
871
 
 
872
 
    def _check_version_consistent(self, other, other_idx, name):
873
 
        """Check if a version in consistent in this and other.
874
 
 
875
 
        To be consistent it must have:
876
 
 
877
 
         * the same text
878
 
         * the same direct parents (by name, not index, and disregarding
879
 
           order)
880
 
        
881
 
        If present & correct return True;
882
 
        if not present in self return False; 
883
 
        if inconsistent raise error."""
884
 
        this_idx = self._name_map.get(name, -1)
885
 
        if this_idx != -1:
886
 
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
887
 
                raise WeaveError("inconsistent texts for version {%s} "
888
 
                                 "when joining weaves"
889
 
                                 % (name))
890
 
            self_parents = self._parents[this_idx]
891
 
            other_parents = other._parents[other_idx]
892
 
            n1 = set([self._names[i] for i in self_parents])
893
 
            n2 = set([other._names[i] for i in other_parents])
894
 
            if n1 != n2:
895
 
                raise WeaveParentMismatch("inconsistent parents "
896
 
                    "for version {%s}: %s vs %s" % (name, n1, n2))
897
 
            else:
898
 
                return True         # ok!
899
 
        else:
900
 
            return False
901
 
 
902
 
    def reweave(self, other, pb=None, msg=None):
903
 
        """Reweave self with other.
904
 
 
905
 
        :param other: The other weave to merge
906
 
        :param pb: An optional progress bar, indicating how far done we are
907
 
        :param msg: An optional message for the progress
908
 
        """
909
 
        new_weave = reweave(self, other, pb=pb, msg=msg)
910
 
        for attr in self.__slots__:
911
 
            setattr(self, attr, getattr(new_weave, attr))
912
 
 
913
 
 
914
 
def reweave(wa, wb, pb=None, msg=None):
915
 
    """Combine two weaves and return the result.
916
 
 
917
 
    This works even if a revision R has different parents in 
918
 
    wa and wb.  In the resulting weave all the parents are given.
919
 
 
920
 
    This is done by just building up a new weave, maintaining ordering 
921
 
    of the versions in the two inputs.  More efficient approaches
922
 
    might be possible but it should only be necessary to do 
923
 
    this operation rarely, when a new previously ghost version is 
924
 
    inserted.
925
 
 
926
 
    :param pb: An optional progress bar, indicating how far done we are
927
 
    :param msg: An optional message for the progress
928
 
    """
929
 
    wr = Weave()
930
 
    ia = ib = 0
931
 
    queue_a = range(wa.numversions())
932
 
    queue_b = range(wb.numversions())
933
 
    # first determine combined parents of all versions
934
 
    # map from version name -> all parent names
935
 
    combined_parents = _reweave_parent_graphs(wa, wb)
936
 
    mutter("combined parents: %r", combined_parents)
937
 
    order = topo_sort(combined_parents.iteritems())
938
 
    mutter("order to reweave: %r", order)
939
 
 
940
 
    if pb and not msg:
941
 
        msg = 'reweave'
942
 
 
943
 
    for idx, name in enumerate(order):
944
 
        if pb:
945
 
            pb.update(msg, idx, len(order))
946
 
        if name in wa._name_map:
947
 
            lines = wa.get_lines(name)
948
 
            if name in wb._name_map:
949
 
                lines_b = wb.get_lines(name)
950
 
                if lines != lines_b:
951
 
                    mutter('Weaves differ on content. rev_id {%s}', name)
952
 
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
953
 
                    import difflib
954
 
                    lines = list(difflib.unified_diff(lines, lines_b,
955
 
                            wa._weave_name, wb._weave_name))
956
 
                    mutter('lines:\n%s', ''.join(lines))
957
 
                    raise errors.WeaveTextDiffers(name, wa, wb)
958
 
        else:
959
 
            lines = wb.get_lines(name)
960
 
        wr.add(name, combined_parents[name], lines)
961
 
    return wr
962
 
 
963
 
 
964
 
def _reweave_parent_graphs(wa, wb):
965
 
    """Return combined parent ancestry for two weaves.
966
 
    
967
 
    Returned as a list of (version_name, set(parent_names))"""
968
 
    combined = {}
969
 
    for weave in [wa, wb]:
970
 
        for idx, name in enumerate(weave._names):
971
 
            p = combined.setdefault(name, set())
972
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
973
 
    return combined
 
761
                
 
762
 
 
763
 
 
764
 
974
765
 
975
766
 
976
767
def weave_toc(w):
987
778
 
988
779
 
989
780
 
990
 
def weave_stats(weave_file, pb):
 
781
def weave_stats(weave_file):
 
782
    from bzrlib.progress import ProgressBar
991
783
    from bzrlib.weavefile import read_weave
992
784
 
 
785
    pb = ProgressBar()
 
786
 
993
787
    wf = file(weave_file, 'rb')
994
788
    w = read_weave(wf)
995
789
    # FIXME: doesn't work on pipes
999
793
    vers = len(w)
1000
794
    for i in range(vers):
1001
795
        pb.update('checking sizes', i, vers)
1002
 
        for origin, lineno, line in w._extract([i]):
 
796
        for line in w.get_iter(i):
1003
797
            total += len(line)
1004
798
 
1005
799
    pb.clear()
1036
830
        Display composite of all selected versions.
1037
831
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
1038
832
        Auto-merge two versions and display conflicts.
1039
 
    weave diff WEAVEFILE VERSION1 VERSION2 
1040
 
        Show differences between two versions.
1041
833
 
1042
834
example:
1043
835
 
1068
860
def main(argv):
1069
861
    import sys
1070
862
    import os
1071
 
    try:
1072
 
        import bzrlib
1073
 
    except ImportError:
1074
 
        # in case we're run directly from the subdirectory
1075
 
        sys.path.append('..')
1076
 
        import bzrlib
1077
 
    from bzrlib.weavefile import write_weave, read_weave
 
863
    from weavefile import write_weave, read_weave
1078
864
    from bzrlib.progress import ProgressBar
1079
865
 
1080
866
    try:
1117
903
        w = readit()
1118
904
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1119
905
 
1120
 
    elif cmd == 'diff':
1121
 
        from difflib import unified_diff
1122
 
        w = readit()
1123
 
        fn = argv[2]
1124
 
        v1, v2 = map(int, argv[3:5])
1125
 
        lines1 = w.get(v1)
1126
 
        lines2 = w.get(v2)
1127
 
        diff_gen = unified_diff(lines1, lines2,
1128
 
                                '%s version %d' % (fn, v1),
1129
 
                                '%s version %d' % (fn, v2))
1130
 
        sys.stdout.writelines(diff_gen)
1131
 
            
1132
906
    elif cmd == 'annotate':
1133
907
        w = readit()
1134
908
        # newline is added to all lines regardless; too hard to get
1146
920
        weave_toc(readit())
1147
921
 
1148
922
    elif cmd == 'stats':
1149
 
        weave_stats(argv[2], ProgressBar())
 
923
        weave_stats(argv[2])
1150
924
        
1151
925
    elif cmd == 'check':
1152
926
        w = readit()
1219
993
    return ret
1220
994
 
1221
995
 
1222
 
def lsprofile_main(argv): 
1223
 
    from bzrlib.lsprof import profile
1224
 
    ret,stats = profile(main, argv)
1225
 
    stats.sort()
1226
 
    stats.pprint()
1227
 
    return ret
1228
 
 
1229
 
 
1230
996
if __name__ == '__main__':
1231
997
    import sys
1232
998
    if '--profile' in sys.argv:
1233
999
        args = sys.argv[:]
1234
1000
        args.remove('--profile')
1235
1001
        sys.exit(profile_main(args))
1236
 
    elif '--lsprof' in sys.argv:
1237
 
        args = sys.argv[:]
1238
 
        args.remove('--lsprof')
1239
 
        sys.exit(lsprofile_main(args))
1240
1002
    else:
1241
1003
        sys.exit(main(sys.argv))
1242
1004