/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/bzr/weave.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-05 23:32:39 UTC
  • mto: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200505233239-kdmnmscn8eisltk6
Add a breezy.__main__ module.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
# Author: Martin Pool <mbp@canonical.com>
18
18
 
19
 
 
20
19
"""Weave - storage of related text file versions"""
21
20
 
 
21
from __future__ import absolute_import
22
22
 
23
23
# XXX: If we do weaves this way, will a merge still behave the same
24
24
# way if it's done in a different order?  That's a pretty desirable
67
67
# FIXME: the conflict markers should be *7* characters
68
68
 
69
69
from copy import copy
70
 
from cStringIO import StringIO
71
70
import os
 
71
import patiencediff
72
72
 
73
 
from bzrlib.lazy_import import lazy_import
 
73
from ..lazy_import import lazy_import
74
74
lazy_import(globals(), """
75
 
from bzrlib import tsort
 
75
from breezy import tsort
76
76
""")
77
 
from bzrlib import (
 
77
from .. import (
78
78
    errors,
79
79
    osutils,
80
80
    )
81
 
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
 
        RevisionAlreadyPresent,
83
 
        RevisionNotPresent,
84
 
        UnavailableRepresentation,
85
 
        )
86
 
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
87
 
import bzrlib.patiencediff
88
 
from bzrlib.revision import NULL_REVISION
89
 
from bzrlib.symbol_versioning import *
90
 
from bzrlib.trace import mutter
91
 
from bzrlib.versionedfile import (
 
81
from ..errors import (
 
82
    RevisionAlreadyPresent,
 
83
    RevisionNotPresent,
 
84
    UnavailableRepresentation,
 
85
    )
 
86
from ..osutils import dirname, sha, sha_strings, split_lines
 
87
from ..revision import NULL_REVISION
 
88
from ..sixish import (
 
89
    BytesIO,
 
90
    )
 
91
from ..trace import mutter
 
92
from .versionedfile import (
92
93
    AbsentContentFactory,
93
94
    adapter_registry,
94
95
    ContentFactory,
95
96
    sort_groupcompress,
96
97
    VersionedFile,
97
98
    )
98
 
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
 
99
from .weavefile import _read_weave_v5, write_weave_v5
 
100
 
 
101
 
 
102
class WeaveError(errors.BzrError):
 
103
 
 
104
    _fmt = "Error in processing weave: %(msg)s"
 
105
 
 
106
    def __init__(self, msg=None):
 
107
        errors.BzrError.__init__(self)
 
108
        self.msg = msg
 
109
 
 
110
 
 
111
class WeaveRevisionAlreadyPresent(WeaveError):
 
112
 
 
113
    _fmt = "Revision {%(revision_id)s} already present in %(weave)s"
 
114
 
 
115
    def __init__(self, revision_id, weave):
 
116
 
 
117
        WeaveError.__init__(self)
 
118
        self.revision_id = revision_id
 
119
        self.weave = weave
 
120
 
 
121
 
 
122
class WeaveRevisionNotPresent(WeaveError):
 
123
 
 
124
    _fmt = "Revision {%(revision_id)s} not present in %(weave)s"
 
125
 
 
126
    def __init__(self, revision_id, weave):
 
127
        WeaveError.__init__(self)
 
128
        self.revision_id = revision_id
 
129
        self.weave = weave
 
130
 
 
131
 
 
132
class WeaveFormatError(WeaveError):
 
133
 
 
134
    _fmt = "Weave invariant violated: %(what)s"
 
135
 
 
136
    def __init__(self, what):
 
137
        WeaveError.__init__(self)
 
138
        self.what = what
 
139
 
 
140
 
 
141
class WeaveParentMismatch(WeaveError):
 
142
 
 
143
    _fmt = "Parents are mismatched between two revisions. %(msg)s"
 
144
 
 
145
 
 
146
class WeaveInvalidChecksum(WeaveError):
 
147
 
 
148
    _fmt = "Text did not match its checksum: %(msg)s"
 
149
 
 
150
 
 
151
class WeaveTextDiffers(WeaveError):
 
152
 
 
153
    _fmt = ("Weaves differ on text content. Revision:"
 
154
            " {%(revision_id)s}, %(weave_a)s, %(weave_b)s")
 
155
 
 
156
    def __init__(self, revision_id, weave_a, weave_b):
 
157
        WeaveError.__init__(self)
 
158
        self.revision_id = revision_id
 
159
        self.weave_a = weave_a
 
160
        self.weave_b = weave_b
99
161
 
100
162
 
101
163
class WeaveContentFactory(ContentFactory):
117
179
    def get_bytes_as(self, storage_kind):
118
180
        if storage_kind == 'fulltext':
119
181
            return self._weave.get_text(self.key[-1])
120
 
        elif storage_kind == 'chunked':
 
182
        elif storage_kind in ('chunked', 'lines'):
121
183
            return self._weave.get_lines(self.key[-1])
122
184
        else:
123
185
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
124
186
 
 
187
    def iter_bytes_as(self, storage_kind):
 
188
        if storage_kind in ('chunked', 'lines'):
 
189
            return iter(self._weave.get_lines(self.key[-1]))
 
190
        else:
 
191
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
 
192
 
125
193
 
126
194
class Weave(VersionedFile):
127
195
    """weave - versioned text file storage.
231
299
        self._name_map = {}
232
300
        self._weave_name = weave_name
233
301
        if matcher is None:
234
 
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
 
302
            self._matcher = patiencediff.PatienceSequenceMatcher
235
303
        else:
236
304
            self._matcher = matcher
237
305
        if get_scope is None:
238
 
            get_scope = lambda:None
 
306
            def get_scope():
 
307
                return None
239
308
        self._get_scope = get_scope
240
309
        self._scope = get_scope()
241
310
        self._access_mode = access_mode
268
337
        if not isinstance(other, Weave):
269
338
            return False
270
339
        return self._parents == other._parents \
271
 
               and self._weave == other._weave \
272
 
               and self._sha1s == other._sha1s
 
340
            and self._weave == other._weave \
 
341
            and self._sha1s == other._sha1s
273
342
 
274
343
    def __ne__(self, other):
275
344
        return not self.__eq__(other)
359
428
                raise RevisionNotPresent([record.key[0]], self)
360
429
            # adapt to non-tuple interface
361
430
            parents = [parent[0] for parent in record.parents]
362
 
            if (record.storage_kind == 'fulltext'
363
 
                or record.storage_kind == 'chunked'):
364
 
                self.add_lines(record.key[0], parents,
365
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
 
431
            if record.storage_kind in ('fulltext', 'chunked', 'lines'):
 
432
                self.add_lines(
 
433
                    record.key[0], parents,
 
434
                    record.get_bytes_as('lines'))
366
435
            else:
367
 
                adapter_key = record.storage_kind, 'fulltext'
 
436
                adapter_key = record.storage_kind, 'lines'
368
437
                try:
369
438
                    adapter = adapters[adapter_key]
370
439
                except KeyError:
371
440
                    adapter_factory = adapter_registry.get(adapter_key)
372
441
                    adapter = adapter_factory(self)
373
442
                    adapters[adapter_key] = adapter
374
 
                lines = split_lines(adapter.get_bytes(record))
 
443
                lines = adapter.get_bytes(record, 'lines')
375
444
                try:
376
445
                    self.add_lines(record.key[0], parents, lines)
377
446
                except RevisionAlreadyPresent:
384
453
        """
385
454
        idx = self._lookup(name)
386
455
        if sorted(self._parents[idx]) != sorted(parents) \
387
 
            or sha1 != self._sha1s[idx]:
 
456
                or sha1 != self._sha1s[idx]:
388
457
            raise RevisionAlreadyPresent(name, self._weave_name)
389
458
        return idx
390
459
 
391
460
    def _add_lines(self, version_id, parents, lines, parent_texts,
392
 
       left_matching_blocks, nostore_sha, random_id, check_content):
 
461
                   left_matching_blocks, nostore_sha, random_id,
 
462
                   check_content):
393
463
        """See VersionedFile.add_lines."""
394
 
        idx = self._add(version_id, lines, map(self._lookup, parents),
395
 
            nostore_sha=nostore_sha)
 
464
        idx = self._add(version_id, lines, list(map(self._lookup, parents)),
 
465
                        nostore_sha=nostore_sha)
396
466
        return sha_strings(lines), sum(map(len, lines)), idx
397
467
 
398
468
    def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
420
490
        if sha1 == nostore_sha:
421
491
            raise errors.ExistingContent
422
492
        if version_id is None:
423
 
            version_id = "sha1:" + sha1
 
493
            version_id = b"sha1:" + sha1
424
494
        if version_id in self._name_map:
425
495
            return self._check_repeated_add(version_id, parents, lines, sha1)
426
496
 
427
497
        self._check_versions(parents)
428
 
        ## self._check_lines(lines)
429
498
        new_version = len(self._parents)
430
499
 
431
 
        # if we abort after here the (in-memory) weave will be corrupt because only
432
 
        # some fields are updated
 
500
        # if we abort after here the (in-memory) weave will be corrupt because
 
501
        # only some fields are updated
433
502
        # XXX: FIXME implement a succeed-or-fail of the rest of this routine.
434
503
        #      - Robert Collins 20060226
435
504
        self._parents.append(parents[:])
437
506
        self._names.append(version_id)
438
507
        self._name_map[version_id] = new_version
439
508
 
440
 
 
441
509
        if not parents:
442
510
            # special case; adding with no parents revision; can do
443
511
            # this more quickly by just appending unconditionally.
444
512
            # even more specially, if we're adding an empty text we
445
513
            # need do nothing at all.
446
514
            if lines:
447
 
                self._weave.append(('{', new_version))
 
515
                self._weave.append((b'{', new_version))
448
516
                self._weave.extend(lines)
449
 
                self._weave.append(('}', None))
 
517
                self._weave.append((b'}', None))
450
518
            return new_version
451
519
 
452
520
        if len(parents) == 1:
455
523
                # special case: same as the single parent
456
524
                return new_version
457
525
 
458
 
 
459
526
        ancestors = self._inclusions(parents)
460
527
 
461
528
        l = self._weave
479
546
        # matches the end of the file?  the current code says it's the
480
547
        # last line of the weave?
481
548
 
482
 
        #print 'basis_lines:', basis_lines
483
 
        #print 'new_lines:  ', lines
 
549
        # print 'basis_lines:', basis_lines
 
550
        # print 'new_lines:  ', lines
484
551
 
485
552
        s = self._matcher(None, basis_lines, lines)
486
553
 
487
554
        # offset gives the number of lines that have been inserted
488
 
        # into the weave up to the current point; if the original edit instruction
489
 
        # says to change line A then we actually change (A+offset)
 
555
        # into the weave up to the current point; if the original edit
 
556
        # instruction says to change line A then we actually change (A+offset)
490
557
        offset = 0
491
558
 
492
559
        for tag, i1, i2, j1, j2 in s.get_opcodes():
493
 
            # i1,i2 are given in offsets within basis_lines; we need to map them
494
 
            # back to offsets within the entire weave
495
 
            #print 'raw match', tag, i1, i2, j1, j2
 
560
            # i1,i2 are given in offsets within basis_lines; we need to map
 
561
            # them back to offsets within the entire weave print 'raw match',
 
562
            # tag, i1, i2, j1, j2
496
563
            if tag == 'equal':
497
564
                continue
498
565
            i1 = basis_lineno[i1]
500
567
            # the deletion and insertion are handled separately.
501
568
            # first delete the region.
502
569
            if i1 != i2:
503
 
                self._weave.insert(i1+offset, ('[', new_version))
504
 
                self._weave.insert(i2+offset+1, (']', new_version))
 
570
                self._weave.insert(i1 + offset, (b'[', new_version))
 
571
                self._weave.insert(i2 + offset + 1, (b']', new_version))
505
572
                offset += 2
506
573
 
507
574
            if j1 != j2:
509
576
                # i2; we want to insert after this region to make sure
510
577
                # we don't destroy ourselves
511
578
                i = i2 + offset
512
 
                self._weave[i:i] = ([('{', new_version)]
513
 
                                    + lines[j1:j2]
514
 
                                    + [('}', None)])
 
579
                self._weave[i:i] = ([(b'{', new_version)] +
 
580
                                    lines[j1:j2] +
 
581
                                    [(b'}', None)])
515
582
                offset += 2 + (j2 - j1)
516
583
        return new_version
517
584
 
520
587
        if not len(versions):
521
588
            return []
522
589
        i = set(versions)
523
 
        for v in xrange(max(versions), 0, -1):
 
590
        for v in range(max(versions), 0, -1):
524
591
            if v in i:
525
592
                # include all its parents
526
593
                i.update(self._parents[v])
527
594
        return i
528
 
        ## except IndexError:
529
 
        ##     raise ValueError("version %d not present in weave" % v)
530
595
 
531
596
    def get_ancestry(self, version_ids, topo_sorted=True):
532
597
        """See VersionedFile.get_ancestry."""
533
 
        if isinstance(version_ids, basestring):
 
598
        if isinstance(version_ids, bytes):
534
599
            version_ids = [version_ids]
535
600
        i = self._inclusions([self._lookup(v) for v in version_ids])
536
601
        return [self._idx_to_name(v) for v in i]
537
602
 
538
 
    def _check_lines(self, text):
539
 
        if not isinstance(text, list):
540
 
            raise ValueError("text should be a list, not %s" % type(text))
541
 
 
542
 
        for l in text:
543
 
            if not isinstance(l, basestring):
544
 
                raise ValueError("text line should be a string or unicode, not %s"
545
 
                                 % type(l))
546
 
 
547
 
 
548
 
 
549
603
    def _check_versions(self, indexes):
550
604
        """Check everything in the sequence of indexes is valid"""
551
605
        for i in indexes:
568
622
        The index indicates when the line originated in the weave."""
569
623
        incls = [self._lookup(version_id)]
570
624
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
571
 
            self._extract(incls)]
 
625
                self._extract(incls)]
572
626
 
573
627
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
574
628
                                                pb=None):
576
630
        if version_ids is None:
577
631
            version_ids = self.versions()
578
632
        version_ids = set(version_ids)
579
 
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
580
 
            if inserted not in version_ids: continue
581
 
            if line[-1] != '\n':
582
 
                yield line + '\n', inserted
 
633
        for lineno, inserted, deletes, line in self._walk_internal(
 
634
                version_ids):
 
635
            if inserted not in version_ids:
 
636
                continue
 
637
            if not line.endswith(b'\n'):
 
638
                yield line + b'\n', inserted
583
639
            else:
584
640
                yield line, inserted
585
641
 
594
650
        for l in self._weave:
595
651
            if l.__class__ == tuple:
596
652
                c, v = l
597
 
                isactive = None
598
 
                if c == '{':
 
653
                if c == b'{':
599
654
                    istack.append(self._names[v])
600
 
                elif c == '}':
 
655
                elif c == b'}':
601
656
                    istack.pop()
602
 
                elif c == '[':
 
657
                elif c == b'[':
603
658
                    dset.add(self._names[v])
604
 
                elif c == ']':
 
659
                elif c == b']':
605
660
                    dset.remove(self._names[v])
606
661
                else:
607
662
                    raise WeaveFormatError('unexpected instruction %r' % v)
611
666
 
612
667
        if istack:
613
668
            raise WeaveFormatError("unclosed insertion blocks "
614
 
                    "at end of weave: %s" % istack)
 
669
                                   "at end of weave: %s" % istack)
615
670
        if dset:
616
 
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
617
 
                                   % dset)
 
671
            raise WeaveFormatError(
 
672
                "unclosed deletion blocks at end of weave: %s" % dset)
618
673
 
619
674
    def plan_merge(self, ver_a, ver_b):
620
675
        """Return pseudo-annotation indicating how the two versions merge.
628
683
        inc_b = set(self.get_ancestry([ver_b]))
629
684
        inc_c = inc_a & inc_b
630
685
 
631
 
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
 
686
        for lineno, insert, deleteset, line in self._walk_internal(
 
687
                [ver_a, ver_b]):
632
688
            if deleteset & inc_c:
633
689
                # killed in parent; can't be in either a or b
634
690
                # not relevant to our work
685
741
 
686
742
        result = []
687
743
 
688
 
        WFE = WeaveFormatError
689
 
 
690
744
        # wow.
691
 
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
 
745
        #  449       0   4474.6820   2356.5590   breezy.weave:556(_extract)
692
746
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
693
747
        # 1.6 seconds in 'isinstance'.
694
748
        # changing the first isinstance:
695
 
        #  449       0   2814.2660   1577.1760   bzrlib.weave:556(_extract)
 
749
        #  449       0   2814.2660   1577.1760   breezy.weave:556(_extract)
696
750
        #  +140414   0    762.8050    762.8050   +<isinstance>
697
751
        # note that the inline time actually dropped (less function calls)
698
752
        # and total processing time was halved.
699
753
        # we're still spending ~1/4 of the method in isinstance though.
700
754
        # so lets hard code the acceptable string classes we expect:
701
 
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
 
755
        #  449       0   1202.9420    786.2930   breezy.weave:556(_extract)
702
756
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list'
703
757
        #                                          objects>
704
758
        # yay, down to ~1/4 the initial extract time, and our inline time
705
759
        # has shrunk again, with isinstance no longer dominating.
706
760
        # tweaking the stack inclusion test to use a set gives:
707
 
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
 
761
        #  449       0   1122.8030    713.0080   breezy.weave:556(_extract)
708
762
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list'
709
763
        #                                          objects>
710
764
        # - a 5% win, or possibly just noise. However with large istacks that
711
 
        # 'in' test could dominate, so I'm leaving this change in place -
712
 
        # when its fast enough to consider profiling big datasets we can review.
713
 
 
714
 
 
715
 
 
 
765
        # 'in' test could dominate, so I'm leaving this change in place - when
 
766
        # its fast enough to consider profiling big datasets we can review.
716
767
 
717
768
        for l in self._weave:
718
769
            if l.__class__ == tuple:
719
770
                c, v = l
720
771
                isactive = None
721
 
                if c == '{':
 
772
                if c == b'{':
722
773
                    istack.append(v)
723
774
                    iset.add(v)
724
 
                elif c == '}':
 
775
                elif c == b'}':
725
776
                    iset.remove(istack.pop())
726
 
                elif c == '[':
 
777
                elif c == b'[':
727
778
                    if v in included:
728
779
                        dset.add(v)
729
 
                elif c == ']':
 
780
                elif c == b']':
730
781
                    if v in included:
731
782
                        dset.remove(v)
732
783
                else:
733
784
                    raise AssertionError()
734
785
            else:
735
786
                if isactive is None:
736
 
                    isactive = (not dset) and istack and (istack[-1] in included)
 
787
                    isactive = (not dset) and istack and (
 
788
                        istack[-1] in included)
737
789
                if isactive:
738
790
                    result.append((istack[-1], lineno, l))
739
791
            lineno += 1
740
792
        if istack:
741
793
            raise WeaveFormatError("unclosed insertion blocks "
742
 
                    "at end of weave: %s" % istack)
 
794
                                   "at end of weave: %s" % istack)
743
795
        if dset:
744
 
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
745
 
                                   % dset)
 
796
            raise WeaveFormatError(
 
797
                "unclosed deletion blocks at end of weave: %s" % dset)
746
798
        return result
747
799
 
748
800
    def _maybe_lookup(self, name_or_index):
750
802
 
751
803
        NOT FOR PUBLIC USE.
752
804
        """
753
 
        if isinstance(name_or_index, (int, long)):
 
805
        # GZ 2017-04-01: This used to check for long as well, but I don't think
 
806
        # there are python implementations with sys.maxsize > sys.maxint
 
807
        if isinstance(name_or_index, int):
754
808
            return name_or_index
755
809
        else:
756
810
            return self._lookup(name_or_index)
758
812
    def get_lines(self, version_id):
759
813
        """See VersionedFile.get_lines()."""
760
814
        int_index = self._maybe_lookup(version_id)
761
 
        result = [line for (origin, lineno, line) in self._extract([int_index])]
 
815
        result = [line for (origin, lineno, line)
 
816
                  in self._extract([int_index])]
762
817
        expected_sha1 = self._sha1s[int_index]
763
818
        measured_sha1 = sha_strings(result)
764
819
        if measured_sha1 != expected_sha1:
765
 
            raise errors.WeaveInvalidChecksum(
766
 
                    'file %s, revision %s, expected: %s, measured %s'
767
 
                    % (self._weave_name, version_id,
768
 
                       expected_sha1, measured_sha1))
 
820
            raise WeaveInvalidChecksum(
 
821
                'file %s, revision %s, expected: %s, measured %s'
 
822
                % (self._weave_name, version_id,
 
823
                   expected_sha1, measured_sha1))
769
824
        return result
770
825
 
771
826
    def get_sha1s(self, version_ids):
777
832
 
778
833
    def num_versions(self):
779
834
        """How many versions are in this weave?"""
780
 
        l = len(self._parents)
781
 
        return l
 
835
        return len(self._parents)
782
836
 
783
837
    __len__ = num_versions
784
838
 
791
845
            if inclusions:
792
846
                inclusions.sort()
793
847
                if inclusions[-1] >= version:
794
 
                    raise WeaveFormatError("invalid included version %d for index %d"
795
 
                                           % (inclusions[-1], version))
 
848
                    raise WeaveFormatError(
 
849
                        "invalid included version %d for index %d"
 
850
                        % (inclusions[-1], version))
796
851
 
797
852
        # try extracting all versions; parallel extraction is used
798
853
        nv = self.num_versions()
805
860
            name = self._idx_to_name(i)
806
861
            sha1s[name] = sha()
807
862
            texts[name] = []
808
 
            new_inc = set([name])
 
863
            new_inc = {name}
809
864
            for p in self._parents[i]:
810
865
                new_inc.update(inclusions[self._idx_to_name(p)])
811
866
 
831
886
                # The active inclusion must be an ancestor,
832
887
                # and no ancestors must have deleted this line,
833
888
                # because we don't support resurrection.
834
 
                if (insert in name_inclusions) and not (deleteset & name_inclusions):
 
889
                if ((insert in name_inclusions) and
 
890
                        not (deleteset & name_inclusions)):
835
891
                    sha1s[name].update(line)
836
892
 
837
893
        for i in range(nv):
838
894
            version = self._idx_to_name(i)
839
 
            hd = sha1s[version].hexdigest()
 
895
            hd = sha1s[version].hexdigest().encode()
840
896
            expected = self._sha1s[i]
841
897
            if hd != expected:
842
 
                raise errors.WeaveInvalidChecksum(
843
 
                        "mismatched sha1 for version %s: "
844
 
                        "got %s, expected %s"
845
 
                        % (version, hd, expected))
 
898
                raise WeaveInvalidChecksum(
 
899
                    "mismatched sha1 for version %s: "
 
900
                    "got %s, expected %s"
 
901
                    % (version, hd, expected))
846
902
 
847
903
        # TODO: check insertions are properly nested, that there are
848
904
        # no lines outside of insertion blocks, that deletions are
875
931
        this_idx = self._name_map.get(name, -1)
876
932
        if this_idx != -1:
877
933
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
878
 
                raise errors.WeaveTextDiffers(name, self, other)
 
934
                raise WeaveTextDiffers(name, self, other)
879
935
            self_parents = self._parents[this_idx]
880
936
            other_parents = other._parents[other_idx]
881
 
            n1 = set([self._names[i] for i in self_parents])
882
 
            n2 = set([other._names[i] for i in other_parents])
 
937
            n1 = {self._names[i] for i in self_parents}
 
938
            n2 = {other._names[i] for i in other_parents}
883
939
            if not self._compatible_parents(n1, n2):
884
 
                raise WeaveParentMismatch("inconsistent parents "
 
940
                raise WeaveParentMismatch(
 
941
                    "inconsistent parents "
885
942
                    "for version {%s}: %s vs %s" % (name, n1, n2))
886
943
            else:
887
944
                return True         # ok!
910
967
 
911
968
    WEAVE_SUFFIX = '.weave'
912
969
 
913
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
970
    def __init__(self, name, transport, filemode=None, create=False,
 
971
                 access_mode='w', get_scope=None):
914
972
        """Create a WeaveFile.
915
973
 
916
974
        :param create: If not True, only open an existing knit.
917
975
        """
918
976
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
919
 
            allow_reserved=False)
 
977
                                        allow_reserved=False)
920
978
        self._transport = transport
921
979
        self._filemode = filemode
922
980
        try:
923
 
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
 
981
            f = self._transport.get(name + WeaveFile.WEAVE_SUFFIX)
 
982
            _read_weave_v5(BytesIO(f.read()), self)
924
983
        except errors.NoSuchFile:
925
984
            if not create:
926
985
                raise
928
987
            self._save()
929
988
 
930
989
    def _add_lines(self, version_id, parents, lines, parent_texts,
931
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
990
                   left_matching_blocks, nostore_sha, random_id,
 
991
                   check_content):
932
992
        """Add a version and save the weave."""
933
993
        self.check_not_reserved_id(version_id)
934
 
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
935
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
936
 
            check_content)
 
994
        result = super(WeaveFile, self)._add_lines(
 
995
            version_id, parents, lines, parent_texts, left_matching_blocks,
 
996
            nostore_sha, random_id, check_content)
937
997
        self._save()
938
998
        return result
939
999
 
940
1000
    def copy_to(self, name, transport):
941
1001
        """See VersionedFile.copy_to()."""
942
1002
        # as we are all in memory always, just serialise to the new place.
943
 
        sio = StringIO()
 
1003
        sio = BytesIO()
944
1004
        write_weave_v5(self, sio)
945
1005
        sio.seek(0)
946
1006
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
948
1008
    def _save(self):
949
1009
        """Save the weave."""
950
1010
        self._check_write_ok()
951
 
        sio = StringIO()
 
1011
        sio = BytesIO()
952
1012
        write_weave_v5(self, sio)
953
1013
        sio.seek(0)
954
1014
        bytes = sio.getvalue()
985
1045
    :param msg: An optional message for the progress
986
1046
    """
987
1047
    wr = Weave()
988
 
    ia = ib = 0
989
 
    queue_a = range(wa.num_versions())
990
 
    queue_b = range(wb.num_versions())
991
1048
    # first determine combined parents of all versions
992
1049
    # map from version name -> all parent names
993
1050
    combined_parents = _reweave_parent_graphs(wa, wb)
994
1051
    mutter("combined parents: %r", combined_parents)
995
 
    order = tsort.topo_sort(combined_parents.iteritems())
 
1052
    order = tsort.topo_sort(combined_parents.items())
996
1053
    mutter("order to reweave: %r", order)
997
1054
 
998
1055
    if pb and not msg:
1010
1067
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
1011
1068
                    import difflib
1012
1069
                    lines = list(difflib.unified_diff(lines, lines_b,
1013
 
                            wa._weave_name, wb._weave_name))
 
1070
                                                      wa._weave_name, wb._weave_name))
1014
1071
                    mutter('lines:\n%s', ''.join(lines))
1015
 
                    raise errors.WeaveTextDiffers(name, wa, wb)
 
1072
                    raise WeaveTextDiffers(name, wa, wb)
1016
1073
        else:
1017
1074
            lines = wb.get_lines(name)
1018
1075
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])