/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 bzrlib/tests/test_versionedfile.py

  • Committer: Aaron Bentley
  • Date: 2007-01-17 15:39:21 UTC
  • mfrom: (1551.9.35 Aaron's mergeable stuff)
  • mto: This revision was merged to the branch mainline in revision 2239.
  • Revision ID: abentley@panoramicfeedback.com-20070117153921-6pp9ssa2r8n5izoo
Merge bzr.ab, to avoid conflicts submitting

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
7
7
# it under the terms of the GNU General Public License as published by
8
8
# the Free Software Foundation; either version 2 of the License, or
9
9
# (at your option) any later version.
10
 
 
 
10
#
11
11
# This program is distributed in the hope that it will be useful,
12
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
14
# GNU General Public License for more details.
15
 
 
 
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
20
20
 
 
21
# TODO: might be nice to create a versionedfile with some type of corruption
 
22
# considered typical and check that it can be detected/corrected.
 
23
 
 
24
from StringIO import StringIO
 
25
 
21
26
import bzrlib
22
 
import bzrlib.errors as errors
 
27
from bzrlib import (
 
28
    errors,
 
29
    progress,
 
30
    )
23
31
from bzrlib.errors import (
24
32
                           RevisionNotPresent, 
25
33
                           RevisionAlreadyPresent,
28
36
from bzrlib.knit import KnitVersionedFile, \
29
37
     KnitAnnotateFactory
30
38
from bzrlib.tests import TestCaseWithTransport
 
39
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
31
40
from bzrlib.trace import mutter
32
41
from bzrlib.transport import get_transport
33
42
from bzrlib.transport.memory import MemoryTransport
 
43
from bzrlib.tsort import topo_sort
34
44
import bzrlib.versionedfile as versionedfile
35
45
from bzrlib.weave import WeaveFile
36
 
from bzrlib.weavefile import read_weave
 
46
from bzrlib.weavefile import read_weave, write_weave
37
47
 
38
48
 
39
49
class VersionedFileTestMixIn(object):
63
73
            self.assertRaises(RevisionAlreadyPresent,
64
74
                f.add_lines, 'r1', [], [])
65
75
        verify_file(f)
66
 
        f = self.reopen_file()
 
76
        # this checks that reopen with create=True does not break anything.
 
77
        f = self.reopen_file(create=True)
67
78
        verify_file(f)
68
79
 
69
80
    def test_adds_with_parent_texts(self):
104
115
        f = self.reopen_file()
105
116
        verify_file(f)
106
117
 
 
118
    def test_add_unicode_content(self):
 
119
        # unicode content is not permitted in versioned files. 
 
120
        # versioned files version sequences of bytes only.
 
121
        vf = self.get_file()
 
122
        self.assertRaises(errors.BzrBadParameterUnicode,
 
123
            vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
 
124
        self.assertRaises(
 
125
            (errors.BzrBadParameterUnicode, NotImplementedError),
 
126
            vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
 
127
 
 
128
    def test_inline_newline_throws(self):
 
129
        # \r characters are not permitted in lines being added
 
130
        vf = self.get_file()
 
131
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
 
132
            vf.add_lines, 'a', [], ['a\n\n'])
 
133
        self.assertRaises(
 
134
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
 
135
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
 
136
        # but inline CR's are allowed
 
137
        vf.add_lines('a', [], ['a\r\n'])
 
138
        try:
 
139
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
 
140
        except NotImplementedError:
 
141
            pass
 
142
 
 
143
    def test_add_reserved(self):
 
144
        vf = self.get_file()
 
145
        self.assertRaises(errors.ReservedId,
 
146
            vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
 
147
 
 
148
        self.assertRaises(errors.ReservedId,
 
149
            vf.add_delta, 'a:', [], None, 'sha1', False, ((0, 0, 0, []),))
 
150
 
 
151
    def test_get_reserved(self):
 
152
        vf = self.get_file()
 
153
        self.assertRaises(errors.ReservedId, vf.get_delta, 'b:')
 
154
        self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
 
155
        self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
 
156
        self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
 
157
 
107
158
    def test_get_delta(self):
108
159
        f = self.get_file()
109
160
        sha1s = self._setup_for_deltas(f)
391
442
        # and should be a list
392
443
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
393
444
 
 
445
    def build_graph(self, file, graph):
 
446
        for node in topo_sort(graph.items()):
 
447
            file.add_lines(node, graph[node], [])
 
448
 
394
449
    def test_get_graph(self):
395
450
        f = self.get_file()
396
 
        f.add_lines('v1', [], ['hello\n'])
397
 
        f.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
398
 
        f.add_lines('v3', ['v2'], ['hello\n', 'cruel\n', 'world\n'])
399
 
        self.assertEqual({'v1': [],
400
 
                          'v2': ['v1'],
401
 
                          'v3': ['v2']},
402
 
                         f.get_graph())
 
451
        graph = {
 
452
            'v1': [],
 
453
            'v2': ['v1'],
 
454
            'v3': ['v2']}
 
455
        self.build_graph(f, graph)
 
456
        self.assertEqual(graph, f.get_graph())
 
457
    
 
458
    def test_get_graph_partial(self):
 
459
        f = self.get_file()
 
460
        complex_graph = {}
 
461
        simple_a = {
 
462
            'c': [],
 
463
            'b': ['c'],
 
464
            'a': ['b'],
 
465
            }
 
466
        complex_graph.update(simple_a)
 
467
        simple_b = {
 
468
            'c': [],
 
469
            'b': ['c'],
 
470
            }
 
471
        complex_graph.update(simple_b)
 
472
        simple_gam = {
 
473
            'c': [],
 
474
            'oo': [],
 
475
            'bar': ['oo', 'c'],
 
476
            'gam': ['bar'],
 
477
            }
 
478
        complex_graph.update(simple_gam)
 
479
        simple_b_gam = {}
 
480
        simple_b_gam.update(simple_gam)
 
481
        simple_b_gam.update(simple_b)
 
482
        self.build_graph(f, complex_graph)
 
483
        self.assertEqual(simple_a, f.get_graph(['a']))
 
484
        self.assertEqual(simple_b, f.get_graph(['b']))
 
485
        self.assertEqual(simple_gam, f.get_graph(['gam']))
 
486
        self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
403
487
 
404
488
    def test_get_parents(self):
405
489
        f = self.get_file()
477
561
        # versions in the weave 
478
562
        # the ordering here is to make a tree so that dumb searches have
479
563
        # more changes to muck up.
 
564
 
 
565
        class InstrumentedProgress(progress.DummyProgress):
 
566
 
 
567
            def __init__(self):
 
568
 
 
569
                progress.DummyProgress.__init__(self)
 
570
                self.updates = []
 
571
 
 
572
            def update(self, msg=None, current=None, total=None):
 
573
                self.updates.append((msg, current, total))
 
574
 
480
575
        vf = self.get_file()
481
576
        # add a base to get included
482
577
        vf.add_lines('base', [], ['base\n'])
490
585
        vf.add_lines('otherchild',
491
586
                     ['lancestor', 'base'],
492
587
                     ['base\n', 'lancestor\n', 'otherchild\n'])
493
 
        def iter_with_versions(versions):
 
588
        def iter_with_versions(versions, expected):
494
589
            # now we need to see what lines are returned, and how often.
495
590
            lines = {'base\n':0,
496
591
                     'lancestor\n':0,
498
593
                     'child\n':0,
499
594
                     'otherchild\n':0,
500
595
                     }
 
596
            progress = InstrumentedProgress()
501
597
            # iterate over the lines
502
 
            for line in vf.iter_lines_added_or_present_in_versions(versions):
 
598
            for line in vf.iter_lines_added_or_present_in_versions(versions, 
 
599
                pb=progress):
503
600
                lines[line] += 1
 
601
            if []!= progress.updates: 
 
602
                self.assertEqual(expected, progress.updates)
504
603
            return lines
505
 
        lines = iter_with_versions(['child', 'otherchild'])
 
604
        lines = iter_with_versions(['child', 'otherchild'],
 
605
                                   [('Walking content.', 0, 2),
 
606
                                    ('Walking content.', 1, 2),
 
607
                                    ('Walking content.', 2, 2)])
506
608
        # we must see child and otherchild
507
609
        self.assertTrue(lines['child\n'] > 0)
508
610
        self.assertTrue(lines['otherchild\n'] > 0)
509
611
        # we dont care if we got more than that.
510
612
        
511
613
        # test all lines
512
 
        lines = iter_with_versions(None)
 
614
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
 
615
                                          ('Walking content.', 1, 5),
 
616
                                          ('Walking content.', 2, 5),
 
617
                                          ('Walking content.', 3, 5),
 
618
                                          ('Walking content.', 4, 5),
 
619
                                          ('Walking content.', 5, 5)])
513
620
        # all lines must be seen at least once
514
621
        self.assertTrue(lines['base\n'] > 0)
515
622
        self.assertTrue(lines['lancestor\n'] > 0)
570
677
            self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
571
678
            self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
572
679
            return
 
680
        vf = self.reopen_file()
573
681
        # test key graph related apis: getncestry, _graph, get_parents
574
682
        # has_version
575
683
        # - these are ghost unaware and must not be reflect ghosts
636
744
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
637
745
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
638
746
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
 
747
    
 
748
    def test_get_sha1(self):
 
749
        # check the sha1 data is available
 
750
        vf = self.get_file()
 
751
        # a simple file
 
752
        vf.add_lines('a', [], ['a\n'])
 
753
        # the same file, different metadata
 
754
        vf.add_lines('b', ['a'], ['a\n'])
 
755
        # a file differing only in last newline.
 
756
        vf.add_lines('c', [], ['a'])
 
757
        self.assertEqual(
 
758
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
 
759
        self.assertEqual(
 
760
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
 
761
        self.assertEqual(
 
762
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
639
763
        
640
764
 
641
765
class TestWeave(TestCaseWithTransport, VersionedFileTestMixIn):
677
801
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
678
802
        return w
679
803
 
680
 
    def reopen_file(self, name='foo'):
681
 
        return WeaveFile(name, get_transport(self.get_url('.')))
 
804
    def reopen_file(self, name='foo', create=False):
 
805
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
682
806
 
683
807
    def test_no_implicit_create(self):
684
808
        self.assertRaises(errors.NoSuchFile,
705
829
        knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
706
830
        return knit
707
831
 
708
 
    def reopen_file(self, name='foo'):
709
 
        return KnitVersionedFile(name, get_transport(self.get_url('.')), delta=True)
 
832
    def reopen_file(self, name='foo', create=False):
 
833
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
834
            delta=True,
 
835
            create=create)
710
836
 
711
837
    def test_detection(self):
712
 
        print "TODO for merging: create a corrupted knit."
713
838
        knit = self.get_file()
714
839
        knit.check()
715
840
 
783
908
            versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
909
        # now we should get the default InterVersionedFile object again.
785
910
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
 
911
 
 
912
 
 
913
class TestReadonlyHttpMixin(object):
 
914
 
 
915
    def test_readonly_http_works(self):
 
916
        # we should be able to read from http with a versioned file.
 
917
        vf = self.get_file()
 
918
        # try an empty file access
 
919
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
920
        self.assertEqual([], readonly_vf.versions())
 
921
        # now with feeling.
 
922
        vf.add_lines('1', [], ['a\n'])
 
923
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
 
924
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
 
925
        self.assertEqual(['1', '2'], vf.versions())
 
926
        for version in readonly_vf.versions():
 
927
            readonly_vf.get_lines(version)
 
928
 
 
929
 
 
930
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
931
 
 
932
    def get_file(self):
 
933
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
 
934
 
 
935
    def get_factory(self):
 
936
        return WeaveFile
 
937
 
 
938
 
 
939
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
 
940
 
 
941
    def get_file(self):
 
942
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
 
943
                                 delta=True, create=True)
 
944
 
 
945
    def get_factory(self):
 
946
        return KnitVersionedFile
 
947
 
 
948
 
 
949
class MergeCasesMixin(object):
 
950
 
 
951
    def doMerge(self, base, a, b, mp):
 
952
        from cStringIO import StringIO
 
953
        from textwrap import dedent
 
954
 
 
955
        def addcrlf(x):
 
956
            return x + '\n'
 
957
        
 
958
        w = self.get_file()
 
959
        w.add_lines('text0', [], map(addcrlf, base))
 
960
        w.add_lines('text1', ['text0'], map(addcrlf, a))
 
961
        w.add_lines('text2', ['text0'], map(addcrlf, b))
 
962
 
 
963
        self.log_contents(w)
 
964
 
 
965
        self.log('merge plan:')
 
966
        p = list(w.plan_merge('text1', 'text2'))
 
967
        for state, line in p:
 
968
            if line:
 
969
                self.log('%12s | %s' % (state, line[:-1]))
 
970
 
 
971
        self.log('merge:')
 
972
        mt = StringIO()
 
973
        mt.writelines(w.weave_merge(p))
 
974
        mt.seek(0)
 
975
        self.log(mt.getvalue())
 
976
 
 
977
        mp = map(addcrlf, mp)
 
978
        self.assertEqual(mt.readlines(), mp)
 
979
        
 
980
        
 
981
    def testOneInsert(self):
 
982
        self.doMerge([],
 
983
                     ['aa'],
 
984
                     [],
 
985
                     ['aa'])
 
986
 
 
987
    def testSeparateInserts(self):
 
988
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
989
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
990
                     ['aaa', 'bbb', 'yyy', 'ccc'],
 
991
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
992
 
 
993
    def testSameInsert(self):
 
994
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
995
                     ['aaa', 'xxx', 'bbb', 'ccc'],
 
996
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
 
997
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
 
998
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
 
999
    def testOverlappedInsert(self):
 
1000
        self.doMerge(['aaa', 'bbb'],
 
1001
                     ['aaa', 'xxx', 'yyy', 'bbb'],
 
1002
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
 
1003
 
 
1004
        # really it ought to reduce this to 
 
1005
        # ['aaa', 'xxx', 'yyy', 'bbb']
 
1006
 
 
1007
 
 
1008
    def testClashReplace(self):
 
1009
        self.doMerge(['aaa'],
 
1010
                     ['xxx'],
 
1011
                     ['yyy', 'zzz'],
 
1012
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
1013
                      '>>>>>>> '])
 
1014
 
 
1015
    def testNonClashInsert1(self):
 
1016
        self.doMerge(['aaa'],
 
1017
                     ['xxx', 'aaa'],
 
1018
                     ['yyy', 'zzz'],
 
1019
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
1020
                      '>>>>>>> '])
 
1021
 
 
1022
    def testNonClashInsert2(self):
 
1023
        self.doMerge(['aaa'],
 
1024
                     ['aaa'],
 
1025
                     ['yyy', 'zzz'],
 
1026
                     ['yyy', 'zzz'])
 
1027
 
 
1028
 
 
1029
    def testDeleteAndModify(self):
 
1030
        """Clashing delete and modification.
 
1031
 
 
1032
        If one side modifies a region and the other deletes it then
 
1033
        there should be a conflict with one side blank.
 
1034
        """
 
1035
 
 
1036
        #######################################
 
1037
        # skippd, not working yet
 
1038
        return
 
1039
        
 
1040
        self.doMerge(['aaa', 'bbb', 'ccc'],
 
1041
                     ['aaa', 'ddd', 'ccc'],
 
1042
                     ['aaa', 'ccc'],
 
1043
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
 
1044
 
 
1045
    def _test_merge_from_strings(self, base, a, b, expected):
 
1046
        w = self.get_file()
 
1047
        w.add_lines('text0', [], base.splitlines(True))
 
1048
        w.add_lines('text1', ['text0'], a.splitlines(True))
 
1049
        w.add_lines('text2', ['text0'], b.splitlines(True))
 
1050
        self.log('merge plan:')
 
1051
        p = list(w.plan_merge('text1', 'text2'))
 
1052
        for state, line in p:
 
1053
            if line:
 
1054
                self.log('%12s | %s' % (state, line[:-1]))
 
1055
        self.log('merge result:')
 
1056
        result_text = ''.join(w.weave_merge(p))
 
1057
        self.log(result_text)
 
1058
        self.assertEqualDiff(result_text, expected)
 
1059
 
 
1060
    def test_weave_merge_conflicts(self):
 
1061
        # does weave merge properly handle plans that end with unchanged?
 
1062
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
 
1063
        self.assertEqual(result, 'hello\n')
 
1064
 
 
1065
    def test_deletion_extended(self):
 
1066
        """One side deletes, the other deletes more.
 
1067
        """
 
1068
        base = """\
 
1069
            line 1
 
1070
            line 2
 
1071
            line 3
 
1072
            """
 
1073
        a = """\
 
1074
            line 1
 
1075
            line 2
 
1076
            """
 
1077
        b = """\
 
1078
            line 1
 
1079
            """
 
1080
        result = """\
 
1081
            line 1
 
1082
            """
 
1083
        self._test_merge_from_strings(base, a, b, result)
 
1084
 
 
1085
    def test_deletion_overlap(self):
 
1086
        """Delete overlapping regions with no other conflict.
 
1087
 
 
1088
        Arguably it'd be better to treat these as agreement, rather than 
 
1089
        conflict, but for now conflict is safer.
 
1090
        """
 
1091
        base = """\
 
1092
            start context
 
1093
            int a() {}
 
1094
            int b() {}
 
1095
            int c() {}
 
1096
            end context
 
1097
            """
 
1098
        a = """\
 
1099
            start context
 
1100
            int a() {}
 
1101
            end context
 
1102
            """
 
1103
        b = """\
 
1104
            start context
 
1105
            int c() {}
 
1106
            end context
 
1107
            """
 
1108
        result = """\
 
1109
            start context
 
1110
<<<<<<< 
 
1111
            int a() {}
 
1112
=======
 
1113
            int c() {}
 
1114
>>>>>>> 
 
1115
            end context
 
1116
            """
 
1117
        self._test_merge_from_strings(base, a, b, result)
 
1118
 
 
1119
    def test_agreement_deletion(self):
 
1120
        """Agree to delete some lines, without conflicts."""
 
1121
        base = """\
 
1122
            start context
 
1123
            base line 1
 
1124
            base line 2
 
1125
            end context
 
1126
            """
 
1127
        a = """\
 
1128
            start context
 
1129
            base line 1
 
1130
            end context
 
1131
            """
 
1132
        b = """\
 
1133
            start context
 
1134
            base line 1
 
1135
            end context
 
1136
            """
 
1137
        result = """\
 
1138
            start context
 
1139
            base line 1
 
1140
            end context
 
1141
            """
 
1142
        self._test_merge_from_strings(base, a, b, result)
 
1143
 
 
1144
    def test_sync_on_deletion(self):
 
1145
        """Specific case of merge where we can synchronize incorrectly.
 
1146
        
 
1147
        A previous version of the weave merge concluded that the two versions
 
1148
        agreed on deleting line 2, and this could be a synchronization point.
 
1149
        Line 1 was then considered in isolation, and thought to be deleted on 
 
1150
        both sides.
 
1151
 
 
1152
        It's better to consider the whole thing as a disagreement region.
 
1153
        """
 
1154
        base = """\
 
1155
            start context
 
1156
            base line 1
 
1157
            base line 2
 
1158
            end context
 
1159
            """
 
1160
        a = """\
 
1161
            start context
 
1162
            base line 1
 
1163
            a's replacement line 2
 
1164
            end context
 
1165
            """
 
1166
        b = """\
 
1167
            start context
 
1168
            b replaces
 
1169
            both lines
 
1170
            end context
 
1171
            """
 
1172
        result = """\
 
1173
            start context
 
1174
<<<<<<< 
 
1175
            base line 1
 
1176
            a's replacement line 2
 
1177
=======
 
1178
            b replaces
 
1179
            both lines
 
1180
>>>>>>> 
 
1181
            end context
 
1182
            """
 
1183
        self._test_merge_from_strings(base, a, b, result)
 
1184
 
 
1185
 
 
1186
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
 
1187
 
 
1188
    def get_file(self, name='foo'):
 
1189
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
 
1190
                                 delta=True, create=True)
 
1191
 
 
1192
    def log_contents(self, w):
 
1193
        pass
 
1194
 
 
1195
 
 
1196
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
 
1197
 
 
1198
    def get_file(self, name='foo'):
 
1199
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
 
1200
 
 
1201
    def log_contents(self, w):
 
1202
        self.log('weave is:')
 
1203
        tmpf = StringIO()
 
1204
        write_weave(w, tmpf)
 
1205
        self.log(tmpf.getvalue())
 
1206
 
 
1207
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1208
                                'xxx', '>>>>>>> ', 'bbb']