~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

Handled simultaneous renames of parent and child better

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?
55
24
 
56
25
# XXX: If we do weaves this way, will a merge still behave the same
57
26
# way if it's done in a different order?  That's a pretty desirable
74
43
 
75
44
# TODO: Parallel-extract that passes back each line along with a
76
45
# description of which revisions include it.  Nice for checking all
77
 
# shas in parallel.
 
46
# shas or calculating stats in parallel.
78
47
 
79
48
# TODO: Using a single _extract routine and then processing the output
80
49
# is probably inefficient.  It's simple enough that we can afford to
81
50
# have slight specializations for different ways its used: annotate,
82
51
# basis for add, get, etc.
83
52
 
84
 
# TODO: Perhaps the API should work only in names to hide the integer
85
 
# indexes from the user?
86
 
 
87
 
 
88
 
 
 
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
89
71
import sha
90
 
from cStringIO import StringIO
91
 
 
92
 
 
93
 
class WeaveError(Exception):
94
 
    """Exception in processing weave"""
95
 
 
96
 
 
97
 
class WeaveFormatError(WeaveError):
98
 
    """Weave invariant violated"""
99
 
    
 
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
 
100
80
 
101
81
class Weave(object):
102
82
    """weave - versioned text file storage.
113
93
 
114
94
    * a nonnegative index number.
115
95
 
116
 
    * a version-id string. (not implemented yet)
 
96
    * a version-id string.
117
97
 
118
98
    Typically the index number will be valid only inside this weave and
119
99
    the version-id is used to reference it in the larger world.
198
178
        self._name_map = {}
199
179
        self._weave_name = weave_name
200
180
 
 
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
201
197
 
202
198
    def __eq__(self, other):
203
199
        if not isinstance(other, Weave):
210
206
    def __ne__(self, other):
211
207
        return not self.__eq__(other)
212
208
 
213
 
 
 
209
    def __contains__(self, name):
 
210
        return self._name_map.has_key(name)
 
211
 
 
212
    def maybe_lookup(self, name_or_index):
 
213
        """Convert possible symbolic name to index, or pass through indexes."""
 
214
        if isinstance(name_or_index, (int, long)):
 
215
            return name_or_index
 
216
        else:
 
217
            return self.lookup(name_or_index)
 
218
 
 
219
        
214
220
    def lookup(self, name):
 
221
        """Convert symbolic version name to index."""
215
222
        try:
216
223
            return self._name_map[name]
217
224
        except KeyError:
218
 
            raise WeaveError("name %s not present in weave %s" %
219
 
                             (name, self._weave_name))
220
 
 
 
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)
 
233
 
 
234
    def idx_to_name(self, version):
 
235
        return self._names[version]
 
236
 
 
237
    def _check_repeated_add(self, name, parents, text, sha1):
 
238
        """Check that a duplicated add is OK.
 
239
 
 
240
        If it is, return the (old) index; otherwise raise an exception.
 
241
        """
 
242
        idx = self.lookup(name)
 
243
        if sorted(self._parents[idx]) != sorted(parents) \
 
244
            or sha1 != self._sha1s[idx]:
 
245
            raise WeaveRevisionAlreadyPresent(name, self)
 
246
        return idx
221
247
        
222
 
    def add(self, name, parents, text):
 
248
    def add(self, name, parents, text, sha1=None):
223
249
        """Add a single text on top of the weave.
224
250
  
225
251
        Returns the index number of the newly added version.
232
258
            List or set of direct parent version numbers.
233
259
            
234
260
        text
235
 
            Sequence of lines to be added in the new version."""
 
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
236
267
 
237
268
        assert isinstance(name, basestring)
 
269
        if sha1 is None:
 
270
            sha1 = sha_strings(text)
238
271
        if name in self._name_map:
239
 
            raise WeaveError("name %r already present in weave" % name)
240
 
        
 
272
            return self._check_repeated_add(name, parents, text, sha1)
 
273
 
 
274
        parents = map(self.maybe_lookup, parents)
241
275
        self._check_versions(parents)
242
276
        ## self._check_lines(text)
243
277
        new_version = len(self._parents)
244
278
 
245
 
        s = sha.new()
246
 
        map(s.update, text)
247
 
        sha1 = s.hexdigest()
248
 
        del s
249
279
 
250
280
        # if we abort after here the (in-memory) weave will be corrupt because only
251
281
        # some fields are updated
300
330
        #print 'basis_lines:', basis_lines
301
331
        #print 'new_lines:  ', lines
302
332
 
303
 
        from difflib import SequenceMatcher
304
333
        s = SequenceMatcher(None, basis_lines, text)
305
334
 
306
335
        # offset gives the number of lines that have been inserted
341
370
 
342
371
        return new_version
343
372
 
 
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)
344
377
 
345
378
    def inclusions(self, versions):
346
379
        """Return set of all ancestors of given version(s)."""
347
380
        i = set(versions)
348
 
        v = max(versions)
349
 
        try:
350
 
            while v >= 0:
351
 
                if v in i:
352
 
                    # include all its parents
353
 
                    i.update(self._parents[v])
354
 
                v -= 1
355
 
            return i
356
 
        except IndexError:
357
 
            raise ValueError("version %d not present in weave" % v)
 
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)
 
388
 
 
389
 
 
390
    def parents(self, version):
 
391
        return self._parents[version]
 
392
 
 
393
 
 
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)])
358
397
 
359
398
 
360
399
    def minimal_parents(self, version):
400
439
                raise IndexError("invalid version number %r" % i)
401
440
 
402
441
    
403
 
    def annotate(self, index):
404
 
        return list(self.annotate_iter(index))
405
 
 
406
 
 
407
 
    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):
408
447
        """Yield list of (index-id, line) pairs for the specified version.
409
448
 
410
449
        The index indicates when the line originated in the weave."""
411
 
        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):
412
452
            yield origin, text
413
453
 
414
 
 
415
454
    def _walk(self):
416
455
        """Walk the weave.
417
456
 
439
478
                elif c == ']':
440
479
                    dset.remove(v)
441
480
                else:
442
 
                    raise WeaveFormatError('unexpected instruction %r'
443
 
                                           % v)
 
481
                    raise WeaveFormatError('unexpected instruction %r' % v)
444
482
            else:
445
483
                assert isinstance(l, basestring)
446
484
                assert istack
447
485
                yield lineno, istack[-1], dset, l
448
486
            lineno += 1
449
487
 
450
 
 
 
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)
451
494
 
452
495
    def _extract(self, versions):
453
496
        """Yield annotation of lines in included set.
500
543
                if isactive:
501
544
                    result.append((istack[-1], lineno, l))
502
545
            lineno += 1
503
 
 
504
546
        if istack:
505
 
            raise WFE("unclosed insertion blocks at end of weave",
506
 
                                   istack)
 
547
            raise WeaveFormatError("unclosed insertion blocks "
 
548
                    "at end of weave: %s" % istack)
507
549
        if dset:
508
 
            raise WFE("unclosed deletion blocks at end of weave",
509
 
                                   dset)
510
 
 
 
550
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
551
                                   % dset)
511
552
        return result
512
 
    
513
 
 
514
 
 
515
 
    def get_iter(self, version):
 
553
 
 
554
 
 
555
    def get_iter(self, name_or_index):
516
556
        """Yield lines for the specified version."""
517
 
        for origin, lineno, line in self._extract([version]):
 
557
        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
        for origin, lineno, line in self._extract(incls):
 
565
            if cur_sha:
 
566
                cur_sha.update(line)
518
567
            yield line
519
 
 
520
 
 
521
 
    def get_text(self, version):
 
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))
522
580
        assert isinstance(version, int)
523
 
        s = StringIO()
524
 
        s.writelines(self.get_iter(version))
525
 
        return s.getvalue()
526
 
 
527
 
 
528
 
    def get(self, index):
529
 
        return list(self.get_iter(index))
530
 
 
 
581
 
 
582
 
 
583
    def get_lines(self, name_or_index):
 
584
        return list(self.get_iter(name_or_index))
 
585
 
 
586
 
 
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)]
531
596
 
532
597
    def mash_iter(self, included):
533
598
        """Return composed version of multiple included versions."""
 
599
        included = map(self.maybe_lookup, included)
534
600
        for origin, lineno, text in self._extract(included):
535
601
            yield text
536
602
 
553
619
    def __len__(self):
554
620
        return self.numversions()
555
621
 
556
 
 
557
622
    def check(self, progress_bar=None):
558
623
        # check no circular inclusions
559
624
        for version in range(self.numversions()):
564
629
                    raise WeaveFormatError("invalid included version %d for index %d"
565
630
                                           % (inclusions[-1], version))
566
631
 
567
 
        # try extracting all versions; this is a bit slow and parallel
568
 
        # extraction could be used
 
632
        # try extracting all versions; parallel extraction is used
569
633
        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():
 
656
            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
 
570
666
        for version in range(nv):
571
 
            if progress_bar:
572
 
                progress_bar.update('checking text', version, nv)
573
 
            s = sha.new()
574
 
            for l in self.get_iter(version):
575
 
                s.update(l)
576
 
            hd = s.hexdigest()
 
667
            hd = sha1s[version].hexdigest()
577
668
            expected = self._sha1s[version]
578
669
            if hd != expected:
579
 
                raise WeaveError("mismatched sha1 for version %d; "
580
 
                                 "got %s, expected %s"
581
 
                                 % (version, hd, expected))
 
670
                raise errors.WeaveInvalidChecksum(
 
671
                        "mismatched sha1 for version %s: "
 
672
                        "got %s, expected %s"
 
673
                        % (self._names[version], hd, expected))
582
674
 
583
675
        # TODO: check insertions are properly nested, that there are
584
676
        # no lines outside of insertion blocks, that deletions are
585
677
        # properly paired, etc.
586
678
 
587
 
 
588
 
 
589
 
    def merge(self, merge_versions):
590
 
        """Automerge and mark conflicts between versions.
591
 
 
592
 
        This returns a sequence, each entry describing alternatives
593
 
        for a chunk of the file.  Each of the alternatives is given as
594
 
        a list of lines.
595
 
 
596
 
        If there is a chunk of the file where there's no diagreement,
597
 
        only one alternative is given.
598
 
        """
599
 
 
600
 
        # approach: find the included versions common to all the
601
 
        # merged versions
602
 
        raise NotImplementedError()
603
 
 
604
 
 
605
 
 
606
679
    def _delta(self, included, lines):
607
680
        """Return changes from basis to new revision.
608
681
 
622
695
        If line1=line2, this is a pure insert; if newlines=[] this is a
623
696
        pure delete.  (Similar to difflib.)
624
697
        """
625
 
 
 
698
        raise NotImplementedError()
626
699
 
627
700
            
628
701
    def plan_merge(self, ver_a, ver_b):
677
750
        lines_a = []
678
751
        lines_b = []
679
752
        ch_a = ch_b = False
680
 
 
 
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.
681
758
        for state, line in plan:
682
759
            if state == 'unchanged' or state == 'killed-both':
683
760
                # resync and flush queued conflicts changes if any
691
768
                elif lines_a == lines_b:
692
769
                    for l in lines_a: yield l
693
770
                else:
694
 
                    yield '<<<<\n'
 
771
                    yield '<<<<<<<\n'
695
772
                    for l in lines_a: yield l
696
 
                    yield '====\n'
 
773
                    yield '=======\n'
697
774
                    for l in lines_b: yield l
698
 
                    yield '>>>>\n'
 
775
                    yield '>>>>>>>\n'
699
776
 
700
777
                del lines_a[:]
701
778
                del lines_b[:]
721
798
                                 'killed-both'), \
722
799
                       state
723
800
 
724
 
                
725
 
 
726
 
 
727
 
 
 
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
728
974
 
729
975
 
730
976
def weave_toc(w):
741
987
 
742
988
 
743
989
 
744
 
def weave_stats(weave_file):
745
 
    from bzrlib.progress import ProgressBar
 
990
def weave_stats(weave_file, pb):
746
991
    from bzrlib.weavefile import read_weave
747
992
 
748
 
    pb = ProgressBar()
749
 
 
750
993
    wf = file(weave_file, 'rb')
751
994
    w = read_weave(wf)
752
995
    # FIXME: doesn't work on pipes
756
999
    vers = len(w)
757
1000
    for i in range(vers):
758
1001
        pb.update('checking sizes', i, vers)
759
 
        for line in w.get_iter(i):
 
1002
        for origin, lineno, line in w._extract([i]):
760
1003
            total += len(line)
761
1004
 
762
1005
    pb.clear()
793
1036
        Display composite of all selected versions.
794
1037
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
795
1038
        Auto-merge two versions and display conflicts.
 
1039
    weave diff WEAVEFILE VERSION1 VERSION2 
 
1040
        Show differences between two versions.
796
1041
 
797
1042
example:
798
1043
 
823
1068
def main(argv):
824
1069
    import sys
825
1070
    import os
826
 
    from weavefile import write_weave, read_weave
 
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
827
1078
    from bzrlib.progress import ProgressBar
828
1079
 
829
1080
    try:
866
1117
        w = readit()
867
1118
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
868
1119
 
 
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
            
869
1132
    elif cmd == 'annotate':
870
1133
        w = readit()
871
1134
        # newline is added to all lines regardless; too hard to get
883
1146
        weave_toc(readit())
884
1147
 
885
1148
    elif cmd == 'stats':
886
 
        weave_stats(argv[2])
 
1149
        weave_stats(argv[2], ProgressBar())
887
1150
        
888
1151
    elif cmd == 'check':
889
1152
        w = readit()
956
1219
    return ret
957
1220
 
958
1221
 
 
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
 
959
1230
if __name__ == '__main__':
960
1231
    import sys
961
1232
    if '--profile' in sys.argv:
962
1233
        args = sys.argv[:]
963
1234
        args.remove('--profile')
964
1235
        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))
965
1240
    else:
966
1241
        sys.exit(main(sys.argv))
967
1242