/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2005 Canonical Ltd
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
2
#
3
# Authors:
4
#   Johan Rydberg <jrydberg@gnu.org>
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
10
#
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
15
#
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
1704.2.15 by Martin Pool
Remove TODO about knit testing printed from test suite
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
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
24
from StringIO import StringIO
25
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
26
import bzrlib
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
27
from bzrlib import (
28
    errors,
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
29
    osutils,
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
30
    progress,
31
    )
1563.2.11 by Robert Collins
Consolidate reweave and join as we have no separate usage, make reweave tests apply to all versionedfile implementations and deprecate the old reweave apis.
32
from bzrlib.errors import (
33
                           RevisionNotPresent, 
34
                           RevisionAlreadyPresent,
35
                           WeaveParentMismatch
36
                           )
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
37
from bzrlib.knit import (
38
    KnitVersionedFile,
39
    KnitAnnotateFactory,
2770.1.10 by Aaron Bentley
Merge bzr.dev
40
    KnitPlainFactory,
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
41
    )
2670.3.3 by Andrew Bennetts
Merge from bzr.dev.
42
from bzrlib.tests import TestCaseWithMemoryTransport, TestSkipped
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
43
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
44
from bzrlib.trace import mutter
1563.2.16 by Robert Collins
Change WeaveStore into VersionedFileStore and make its versoined file class parameterisable.
45
from bzrlib.transport import get_transport
1563.2.13 by Robert Collins
InterVersionedFile implemented.
46
from bzrlib.transport.memory import MemoryTransport
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
47
from bzrlib.tsort import topo_sort
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
48
import bzrlib.versionedfile as versionedfile
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
49
from bzrlib.weave import WeaveFile
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
50
from bzrlib.weavefile import read_weave, write_weave
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
51
52
53
class VersionedFileTestMixIn(object):
54
    """A mixin test class for testing VersionedFiles.
55
56
    This is not an adaptor-style test at this point because
57
    theres no dynamic substitution of versioned file implementations,
58
    they are strictly controlled by their owning repositories.
59
    """
60
61
    def test_add(self):
62
        f = self.get_file()
63
        f.add_lines('r0', [], ['a\n', 'b\n'])
64
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
65
        def verify_file(f):
66
            versions = f.versions()
67
            self.assertTrue('r0' in versions)
68
            self.assertTrue('r1' in versions)
69
            self.assertEquals(f.get_lines('r0'), ['a\n', 'b\n'])
70
            self.assertEquals(f.get_text('r0'), 'a\nb\n')
71
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
1563.2.18 by Robert Collins
get knit repositories really using knits for text storage.
72
            self.assertEqual(2, len(f))
73
            self.assertEqual(2, f.num_versions())
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
74
    
75
            self.assertRaises(RevisionNotPresent,
76
                f.add_lines, 'r2', ['foo'], [])
77
            self.assertRaises(RevisionAlreadyPresent,
78
                f.add_lines, 'r1', [], [])
79
        verify_file(f)
1666.1.6 by Robert Collins
Make knit the default format.
80
        # this checks that reopen with create=True does not break anything.
81
        f = self.reopen_file(create=True)
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
82
        verify_file(f)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
83
1596.2.32 by Robert Collins
Reduce re-extraction of texts during weave to knit joins by providing a memoisation facility.
84
    def test_adds_with_parent_texts(self):
85
        f = self.get_file()
86
        parent_texts = {}
2776.1.1 by Robert Collins
* The ``add_lines`` methods on ``VersionedFile`` implementations has changed
87
        _, _, parent_texts['r0'] = f.add_lines('r0', [], ['a\n', 'b\n'])
1596.2.32 by Robert Collins
Reduce re-extraction of texts during weave to knit joins by providing a memoisation facility.
88
        try:
2776.1.1 by Robert Collins
* The ``add_lines`` methods on ``VersionedFile`` implementations has changed
89
            _, _, parent_texts['r1'] = f.add_lines_with_ghosts('r1',
90
                ['r0', 'ghost'], ['b\n', 'c\n'], parent_texts=parent_texts)
1596.2.32 by Robert Collins
Reduce re-extraction of texts during weave to knit joins by providing a memoisation facility.
91
        except NotImplementedError:
92
            # if the format doesn't support ghosts, just add normally.
2776.1.1 by Robert Collins
* The ``add_lines`` methods on ``VersionedFile`` implementations has changed
93
            _, _, parent_texts['r1'] = f.add_lines('r1',
94
                ['r0'], ['b\n', 'c\n'], parent_texts=parent_texts)
1596.2.32 by Robert Collins
Reduce re-extraction of texts during weave to knit joins by providing a memoisation facility.
95
        f.add_lines('r2', ['r1'], ['c\n', 'd\n'], parent_texts=parent_texts)
96
        self.assertNotEqual(None, parent_texts['r0'])
97
        self.assertNotEqual(None, parent_texts['r1'])
98
        def verify_file(f):
99
            versions = f.versions()
100
            self.assertTrue('r0' in versions)
101
            self.assertTrue('r1' in versions)
102
            self.assertTrue('r2' in versions)
103
            self.assertEquals(f.get_lines('r0'), ['a\n', 'b\n'])
104
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
105
            self.assertEquals(f.get_lines('r2'), ['c\n', 'd\n'])
106
            self.assertEqual(3, f.num_versions())
107
            origins = f.annotate('r1')
108
            self.assertEquals(origins[0][0], 'r0')
109
            self.assertEquals(origins[1][0], 'r1')
110
            origins = f.annotate('r2')
111
            self.assertEquals(origins[0][0], 'r1')
112
            self.assertEquals(origins[1][0], 'r2')
113
114
        verify_file(f)
115
        f = self.reopen_file()
116
        verify_file(f)
117
1666.1.6 by Robert Collins
Make knit the default format.
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
2520.4.150 by Aaron Bentley
Test that non-Weave uses left_matching_blocks for add_lines
128
    def test_add_follows_left_matching_blocks(self):
129
        """If we change left_matching_blocks, delta changes
130
131
        Note: There are multiple correct deltas in this case, because
132
        we start with 1 "a" and we get 3.
133
        """
134
        vf = self.get_file()
135
        if isinstance(vf, WeaveFile):
136
            raise TestSkipped("WeaveFile ignores left_matching_blocks")
137
        vf.add_lines('1', [], ['a\n'])
138
        vf.add_lines('2', ['1'], ['a\n', 'a\n', 'a\n'],
139
                     left_matching_blocks=[(0, 0, 1), (1, 3, 0)])
140
        self.assertEqual([(1, 1, 2, [('2', 'a\n'), ('2', 'a\n')])],
141
                         vf.get_delta('2')[3])
142
        vf.add_lines('3', ['1'], ['a\n', 'a\n', 'a\n'],
143
                     left_matching_blocks=[(0, 2, 1), (1, 3, 0)])
144
        self.assertEqual([(0, 0, 2, [('3', 'a\n'), ('3', 'a\n')])],
145
                         vf.get_delta('3')[3])
146
1666.1.6 by Robert Collins
Make knit the default format.
147
    def test_inline_newline_throws(self):
148
        # \r characters are not permitted in lines being added
149
        vf = self.get_file()
150
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
151
            vf.add_lines, 'a', [], ['a\n\n'])
152
        self.assertRaises(
153
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
154
            vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
155
        # but inline CR's are allowed
156
        vf.add_lines('a', [], ['a\r\n'])
157
        try:
158
            vf.add_lines_with_ghosts('b', [], ['a\r\n'])
159
        except NotImplementedError:
160
            pass
161
2229.2.1 by Aaron Bentley
Reject reserved ids in versiondfile, tree, branch and repository
162
    def test_add_reserved(self):
163
        vf = self.get_file()
164
        self.assertRaises(errors.ReservedId,
165
            vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
166
167
        self.assertRaises(errors.ReservedId,
168
            vf.add_delta, 'a:', [], None, 'sha1', False, ((0, 0, 0, []),))
169
2776.1.1 by Robert Collins
* The ``add_lines`` methods on ``VersionedFile`` implementations has changed
170
    def test_add_lines_return_value(self):
171
        # add_lines should return the sha1 and the text size.
172
        vf = self.get_file()
173
        empty_text = ('a', [])
174
        sample_text_nl = ('b', ["foo\n", "bar\n"])
175
        sample_text_no_nl = ('c', ["foo\n", "bar"])
176
        # check results for the three cases:
177
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
178
            # the first two elements are the same for all versioned files:
179
            # - the digest and the size of the text. For some versioned files
180
            #   additional data is returned in additional tuple elements.
181
            result = vf.add_lines(version, [], lines)
182
            self.assertEqual(3, len(result))
183
            self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
184
                result[0:2])
185
        # parents should not affect the result:
186
        lines = sample_text_nl[1]
187
        self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
188
            vf.add_lines('d', ['b', 'c'], lines)[0:2])
189
2229.2.1 by Aaron Bentley
Reject reserved ids in versiondfile, tree, branch and repository
190
    def test_get_reserved(self):
191
        vf = self.get_file()
192
        self.assertRaises(errors.ReservedId, vf.get_delta, 'b:')
193
        self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
194
        self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
195
        self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
196
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
197
    def test_get_delta(self):
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
198
        f = self.get_file()
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
199
        sha1s = self._setup_for_deltas(f)
200
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
201
                          [(0, 0, 1, [('base', 'line\n')])])
202
        self.assertEqual(expected_delta, f.get_delta('base'))
203
        next_parent = 'base'
204
        text_name = 'chain1-'
205
        for depth in range(26):
206
            new_version = text_name + '%s' % depth
207
            expected_delta = (next_parent, sha1s[depth], 
208
                              False,
209
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
210
            self.assertEqual(expected_delta, f.get_delta(new_version))
211
            next_parent = new_version
212
        next_parent = 'base'
213
        text_name = 'chain2-'
214
        for depth in range(26):
215
            new_version = text_name + '%s' % depth
216
            expected_delta = (next_parent, sha1s[depth], False,
217
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
218
            self.assertEqual(expected_delta, f.get_delta(new_version))
219
            next_parent = new_version
220
        # smoke test for eol support
221
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
222
        self.assertEqual(['line'], f.get_lines('noeol'))
223
        self.assertEqual(expected_delta, f.get_delta('noeol'))
224
225
    def test_get_deltas(self):
226
        f = self.get_file()
227
        sha1s = self._setup_for_deltas(f)
228
        deltas = f.get_deltas(f.versions())
229
        expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
230
                          [(0, 0, 1, [('base', 'line\n')])])
231
        self.assertEqual(expected_delta, deltas['base'])
232
        next_parent = 'base'
233
        text_name = 'chain1-'
234
        for depth in range(26):
235
            new_version = text_name + '%s' % depth
236
            expected_delta = (next_parent, sha1s[depth], 
237
                              False,
238
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
239
            self.assertEqual(expected_delta, deltas[new_version])
240
            next_parent = new_version
241
        next_parent = 'base'
242
        text_name = 'chain2-'
243
        for depth in range(26):
244
            new_version = text_name + '%s' % depth
245
            expected_delta = (next_parent, sha1s[depth], False,
246
                              [(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
247
            self.assertEqual(expected_delta, deltas[new_version])
248
            next_parent = new_version
249
        # smoke tests for eol support
250
        expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
251
        self.assertEqual(['line'], f.get_lines('noeol'))
252
        self.assertEqual(expected_delta, deltas['noeol'])
253
        # smoke tests for eol support - two noeol in a row same content
254
        expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
255
                          [(0, 1, 2, [('noeolsecond', 'line\n'), ('noeolsecond', 'line\n')])]),
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
256
                          ('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True, 
257
                           [(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
258
        self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
259
        self.assertTrue(deltas['noeolsecond'] in expected_deltas)
260
        # two no-eol in a row, different content
261
        expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True, 
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
262
                          [(1, 2, 1, [('noeolnotshared', 'phone\n')])])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
263
        self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
264
        self.assertEqual(expected_delta, deltas['noeolnotshared'])
265
        # eol folling a no-eol with content change
266
        expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False, 
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
267
                          [(0, 1, 1, [('eol', 'phone\n')])])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
268
        self.assertEqual(['phone\n'], f.get_lines('eol'))
269
        self.assertEqual(expected_delta, deltas['eol'])
270
        # eol folling a no-eol with content change
271
        expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False, 
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
272
                          [(0, 1, 1, [('eolline', 'line\n')])])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
273
        self.assertEqual(['line\n'], f.get_lines('eolline'))
274
        self.assertEqual(expected_delta, deltas['eolline'])
275
        # eol with no parents
276
        expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, 
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
277
                          [(0, 0, 1, [('noeolbase', 'line\n')])])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
278
        self.assertEqual(['line'], f.get_lines('noeolbase'))
279
        self.assertEqual(expected_delta, deltas['noeolbase'])
280
        # eol with two parents, in inverse insertion order
281
        expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
282
                            [(0, 1, 1, [('eolbeforefirstparent', 'line\n')])]),
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
283
                           ('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
284
                            [(0, 1, 1, [('eolbeforefirstparent', 'line\n')])]))
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
285
        self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
286
        #self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
287
2520.4.85 by Aaron Bentley
Get all test passing (which just proves there aren't enough tests!)
288
    def test_make_mpdiffs(self):
2520.4.3 by Aaron Bentley
Implement plain strategy for extracting and installing multiparent diffs
289
        from bzrlib import multiparent
290
        vf = self.get_file('foo')
291
        sha1s = self._setup_for_deltas(vf)
292
        new_vf = self.get_file('bar')
293
        for version in multiparent.topo_iter(vf):
2520.4.85 by Aaron Bentley
Get all test passing (which just proves there aren't enough tests!)
294
            mpdiff = vf.make_mpdiffs([version])[0]
295
            new_vf.add_mpdiffs([(version, vf.get_parents(version),
296
                                 vf.get_sha1(version), mpdiff)])
2520.4.3 by Aaron Bentley
Implement plain strategy for extracting and installing multiparent diffs
297
            self.assertEqualDiff(vf.get_text(version),
298
                                 new_vf.get_text(version))
299
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
300
    def _setup_for_deltas(self, f):
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
301
        self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
302
        # add texts that should trip the knit maximum delta chain threshold
303
        # as well as doing parallel chains of data in knits.
304
        # this is done by two chains of 25 insertions
305
        f.add_lines('base', [], ['line\n'])
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
306
        f.add_lines('noeol', ['base'], ['line'])
307
        # detailed eol tests:
308
        # shared last line with parent no-eol
309
        f.add_lines('noeolsecond', ['noeol'], ['line\n', 'line'])
310
        # differing last line with parent, both no-eol
311
        f.add_lines('noeolnotshared', ['noeolsecond'], ['line\n', 'phone'])
312
        # add eol following a noneol parent, change content
313
        f.add_lines('eol', ['noeol'], ['phone\n'])
314
        # add eol following a noneol parent, no change content
315
        f.add_lines('eolline', ['noeol'], ['line\n'])
316
        # noeol with no parents:
317
        f.add_lines('noeolbase', [], ['line'])
318
        # noeol preceeding its leftmost parent in the output:
319
        # this is done by making it a merge of two parents with no common
320
        # anestry: noeolbase and noeol with the 
321
        # later-inserted parent the leftmost.
322
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
323
        # two identical eol texts
324
        f.add_lines('noeoldup', ['noeol'], ['line'])
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
325
        next_parent = 'base'
326
        text_name = 'chain1-'
327
        text = ['line\n']
328
        sha1s = {0 :'da6d3141cb4a5e6f464bf6e0518042ddc7bfd079',
329
                 1 :'45e21ea146a81ea44a821737acdb4f9791c8abe7',
330
                 2 :'e1f11570edf3e2a070052366c582837a4fe4e9fa',
331
                 3 :'26b4b8626da827088c514b8f9bbe4ebf181edda1',
332
                 4 :'e28a5510be25ba84d31121cff00956f9970ae6f6',
333
                 5 :'d63ec0ce22e11dcf65a931b69255d3ac747a318d',
334
                 6 :'2c2888d288cb5e1d98009d822fedfe6019c6a4ea',
335
                 7 :'95c14da9cafbf828e3e74a6f016d87926ba234ab',
336
                 8 :'779e9a0b28f9f832528d4b21e17e168c67697272',
337
                 9 :'1f8ff4e5c6ff78ac106fcfe6b1e8cb8740ff9a8f',
338
                 10:'131a2ae712cf51ed62f143e3fbac3d4206c25a05',
339
                 11:'c5a9d6f520d2515e1ec401a8f8a67e6c3c89f199',
340
                 12:'31a2286267f24d8bedaa43355f8ad7129509ea85',
341
                 13:'dc2a7fe80e8ec5cae920973973a8ee28b2da5e0a',
342
                 14:'2c4b1736566b8ca6051e668de68650686a3922f2',
343
                 15:'5912e4ecd9b0c07be4d013e7e2bdcf9323276cde',
344
                 16:'b0d2e18d3559a00580f6b49804c23fea500feab3',
345
                 17:'8e1d43ad72f7562d7cb8f57ee584e20eb1a69fc7',
346
                 18:'5cf64a3459ae28efa60239e44b20312d25b253f3',
347
                 19:'1ebed371807ba5935958ad0884595126e8c4e823',
348
                 20:'2aa62a8b06fb3b3b892a3292a068ade69d5ee0d3',
349
                 21:'01edc447978004f6e4e962b417a4ae1955b6fe5d',
350
                 22:'d8d8dc49c4bf0bab401e0298bb5ad827768618bb',
351
                 23:'c21f62b1c482862983a8ffb2b0c64b3451876e3f',
352
                 24:'c0593fe795e00dff6b3c0fe857a074364d5f04fc',
353
                 25:'dd1a1cf2ba9cc225c3aff729953e6364bf1d1855',
354
                 }
355
        for depth in range(26):
356
            new_version = text_name + '%s' % depth
357
            text = text + ['line\n']
358
            f.add_lines(new_version, [next_parent], text)
359
            next_parent = new_version
360
        next_parent = 'base'
361
        text_name = 'chain2-'
362
        text = ['line\n']
363
        for depth in range(26):
364
            new_version = text_name + '%s' % depth
365
            text = text + ['line\n']
366
            f.add_lines(new_version, [next_parent], text)
367
            next_parent = new_version
1596.2.38 by Robert Collins
rollback from using deltas to using fulltexts - deltas need more work to be ready.
368
        return sha1s
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
369
370
    def test_add_delta(self):
371
        # tests for the add-delta facility.
372
        # at this point, optimising for speed, we assume no checks when deltas are inserted.
373
        # this may need to be revisited.
374
        source = self.get_file('source')
375
        source.add_lines('base', [], ['line\n'])
376
        next_parent = 'base'
377
        text_name = 'chain1-'
378
        text = ['line\n']
379
        for depth in range(26):
380
            new_version = text_name + '%s' % depth
381
            text = text + ['line\n']
382
            source.add_lines(new_version, [next_parent], text)
383
            next_parent = new_version
384
        next_parent = 'base'
385
        text_name = 'chain2-'
386
        text = ['line\n']
387
        for depth in range(26):
388
            new_version = text_name + '%s' % depth
389
            text = text + ['line\n']
390
            source.add_lines(new_version, [next_parent], text)
391
            next_parent = new_version
392
        source.add_lines('noeol', ['base'], ['line'])
393
        
394
        target = self.get_file('target')
395
        for version in source.versions():
396
            parent, sha1, noeol, delta = source.get_delta(version)
397
            target.add_delta(version,
398
                             source.get_parents(version),
399
                             parent,
400
                             sha1,
401
                             noeol,
402
                             delta)
403
        self.assertRaises(RevisionAlreadyPresent,
404
                          target.add_delta, 'base', [], None, '', False, [])
405
        for version in source.versions():
406
            self.assertEqual(source.get_lines(version),
407
                             target.get_lines(version))
1596.2.36 by Robert Collins
add a get_delta api to versioned_file.
408
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
409
    def test_ancestry(self):
410
        f = self.get_file()
1563.2.29 by Robert Collins
Remove all but fetch references to repository.revision_store.
411
        self.assertEqual([], f.get_ancestry([]))
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
412
        f.add_lines('r0', [], ['a\n', 'b\n'])
413
        f.add_lines('r1', ['r0'], ['b\n', 'c\n'])
414
        f.add_lines('r2', ['r0'], ['b\n', 'c\n'])
415
        f.add_lines('r3', ['r2'], ['b\n', 'c\n'])
416
        f.add_lines('rM', ['r1', 'r2'], ['b\n', 'c\n'])
1563.2.29 by Robert Collins
Remove all but fetch references to repository.revision_store.
417
        self.assertEqual([], f.get_ancestry([]))
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
418
        versions = f.get_ancestry(['rM'])
419
        # there are some possibilities:
420
        # r0 r1 r2 rM r3
421
        # r0 r1 r2 r3 rM
422
        # etc
423
        # so we check indexes
424
        r0 = versions.index('r0')
425
        r1 = versions.index('r1')
426
        r2 = versions.index('r2')
427
        self.assertFalse('r3' in versions)
428
        rM = versions.index('rM')
429
        self.assertTrue(r0 < r1)
430
        self.assertTrue(r0 < r2)
431
        self.assertTrue(r1 < rM)
432
        self.assertTrue(r2 < rM)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
433
434
        self.assertRaises(RevisionNotPresent,
435
            f.get_ancestry, ['rM', 'rX'])
1594.2.21 by Robert Collins
Teach versioned files to prevent mutation after finishing.
436
2530.1.1 by Aaron Bentley
Make topological sorting optional for get_ancestry
437
        self.assertEqual(set(f.get_ancestry('rM')),
438
            set(f.get_ancestry('rM', topo_sorted=False)))
439
1594.2.21 by Robert Collins
Teach versioned files to prevent mutation after finishing.
440
    def test_mutate_after_finish(self):
441
        f = self.get_file()
442
        f.transaction_finished()
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
443
        self.assertRaises(errors.OutSideTransaction, f.add_delta, '', [], '', '', False, [])
1594.2.21 by Robert Collins
Teach versioned files to prevent mutation after finishing.
444
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
445
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
446
        self.assertRaises(errors.OutSideTransaction, f.fix_parents, '', [])
447
        self.assertRaises(errors.OutSideTransaction, f.join, '')
1594.2.24 by Robert Collins
Make use of the transaction finalisation warning support to implement in-knit caching.
448
        self.assertRaises(errors.OutSideTransaction, f.clone_text, 'base', 'bar', ['foo'])
1563.2.7 by Robert Collins
add versioned file clear_cache entry.
449
        
450
    def test_clear_cache(self):
451
        f = self.get_file()
452
        # on a new file it should not error
453
        f.clear_cache()
454
        # and after adding content, doing a clear_cache and a get should work.
455
        f.add_lines('0', [], ['a'])
456
        f.clear_cache()
457
        self.assertEqual(['a'], f.get_lines('0'))
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
458
459
    def test_clone_text(self):
460
        f = self.get_file()
461
        f.add_lines('r0', [], ['a\n', 'b\n'])
1563.2.5 by Robert Collins
Remove unused transaction references from knit.py and the versionedfile interface.
462
        f.clone_text('r1', 'r0', ['r0'])
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
463
        def verify_file(f):
464
            self.assertEquals(f.get_lines('r1'), f.get_lines('r0'))
465
            self.assertEquals(f.get_lines('r1'), ['a\n', 'b\n'])
466
            self.assertEquals(f.get_parents('r1'), ['r0'])
467
    
468
            self.assertRaises(RevisionNotPresent,
469
                f.clone_text, 'r2', 'rX', [])
470
            self.assertRaises(RevisionAlreadyPresent,
471
                f.clone_text, 'r1', 'r0', [])
472
        verify_file(f)
473
        verify_file(self.reopen_file())
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
474
1563.2.13 by Robert Collins
InterVersionedFile implemented.
475
    def test_create_empty(self):
476
        f = self.get_file()
477
        f.add_lines('0', [], ['a\n'])
478
        new_f = f.create_empty('t', MemoryTransport())
479
        # smoke test, specific types should check it is honoured correctly for
480
        # non type attributes
481
        self.assertEqual([], new_f.versions())
482
        self.assertTrue(isinstance(new_f, f.__class__))
483
1563.2.15 by Robert Collins
remove the weavestore assumptions about the number and nature of files it manages.
484
    def test_copy_to(self):
485
        f = self.get_file()
486
        f.add_lines('0', [], ['a\n'])
487
        t = MemoryTransport()
488
        f.copy_to('foo', t)
489
        for suffix in f.__class__.get_suffixes():
490
            self.assertTrue(t.has('foo' + suffix))
491
492
    def test_get_suffixes(self):
493
        f = self.get_file()
494
        # should be the same
495
        self.assertEqual(f.__class__.get_suffixes(), f.__class__.get_suffixes())
496
        # and should be a list
497
        self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
498
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
499
    def build_graph(self, file, graph):
500
        for node in topo_sort(graph.items()):
501
            file.add_lines(node, graph[node], [])
502
1563.2.13 by Robert Collins
InterVersionedFile implemented.
503
    def test_get_graph(self):
504
        f = self.get_file()
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
505
        graph = {
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
506
            'v1': (),
507
            'v2': ('v1', ),
508
            'v3': ('v2', )}
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
509
        self.build_graph(f, graph)
510
        self.assertEqual(graph, f.get_graph())
511
    
512
    def test_get_graph_partial(self):
513
        f = self.get_file()
514
        complex_graph = {}
515
        simple_a = {
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
516
            'c': (),
517
            'b': ('c', ),
518
            'a': ('b', ),
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
519
            }
520
        complex_graph.update(simple_a)
521
        simple_b = {
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
522
            'c': (),
523
            'b': ('c', ),
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
524
            }
525
        complex_graph.update(simple_b)
526
        simple_gam = {
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
527
            'c': (),
528
            'oo': (),
529
            'bar': ('oo', 'c'),
530
            'gam': ('bar', ),
1684.3.1 by Robert Collins
Fix versioned file joins with empty targets.
531
            }
532
        complex_graph.update(simple_gam)
533
        simple_b_gam = {}
534
        simple_b_gam.update(simple_gam)
535
        simple_b_gam.update(simple_b)
536
        self.build_graph(f, complex_graph)
537
        self.assertEqual(simple_a, f.get_graph(['a']))
538
        self.assertEqual(simple_b, f.get_graph(['b']))
539
        self.assertEqual(simple_gam, f.get_graph(['gam']))
540
        self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
1563.2.13 by Robert Collins
InterVersionedFile implemented.
541
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
542
    def test_get_parents(self):
543
        f = self.get_file()
544
        f.add_lines('r0', [], ['a\n', 'b\n'])
545
        f.add_lines('r1', [], ['a\n', 'b\n'])
546
        f.add_lines('r2', [], ['a\n', 'b\n'])
547
        f.add_lines('r3', [], ['a\n', 'b\n'])
548
        f.add_lines('m', ['r0', 'r1', 'r2', 'r3'], ['a\n', 'b\n'])
549
        self.assertEquals(f.get_parents('m'), ['r0', 'r1', 'r2', 'r3'])
550
551
        self.assertRaises(RevisionNotPresent,
552
            f.get_parents, 'y')
553
554
    def test_annotate(self):
555
        f = self.get_file()
556
        f.add_lines('r0', [], ['a\n', 'b\n'])
557
        f.add_lines('r1', ['r0'], ['c\n', 'b\n'])
558
        origins = f.annotate('r1')
559
        self.assertEquals(origins[0][0], 'r1')
560
        self.assertEquals(origins[1][0], 'r0')
561
562
        self.assertRaises(RevisionNotPresent,
563
            f.annotate, 'foo')
564
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
565
    def test_detection(self):
566
        # Test weaves detect corruption.
567
        #
568
        # Weaves contain a checksum of their texts.
569
        # When a text is extracted, this checksum should be
570
        # verified.
571
572
        w = self.get_file_corrupted_text()
573
574
        self.assertEqual('hello\n', w.get_text('v1'))
575
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
576
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
577
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
578
579
        w = self.get_file_corrupted_checksum()
580
581
        self.assertEqual('hello\n', w.get_text('v1'))
582
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_text, 'v2')
583
        self.assertRaises(errors.WeaveInvalidChecksum, w.get_lines, 'v2')
584
        self.assertRaises(errors.WeaveInvalidChecksum, w.check)
585
586
    def get_file_corrupted_text(self):
587
        """Return a versioned file with corrupt text but valid metadata."""
588
        raise NotImplementedError(self.get_file_corrupted_text)
589
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
590
    def reopen_file(self, name='foo'):
591
        """Open the versioned file from disk again."""
592
        raise NotImplementedError(self.reopen_file)
593
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
594
    def test_iter_parents(self):
595
        """iter_parents returns the parents for many nodes."""
596
        f = self.get_file()
597
        # sample data:
598
        # no parents
599
        f.add_lines('r0', [], ['a\n', 'b\n'])
600
        # 1 parents
601
        f.add_lines('r1', ['r0'], ['a\n', 'b\n'])
602
        # 2 parents
603
        f.add_lines('r2', ['r1', 'r0'], ['a\n', 'b\n'])
604
        # XXX TODO a ghost
605
        # cases: each sample data individually:
606
        self.assertEqual(set([('r0', ())]),
607
            set(f.iter_parents(['r0'])))
608
        self.assertEqual(set([('r1', ('r0', ))]),
609
            set(f.iter_parents(['r1'])))
610
        self.assertEqual(set([('r2', ('r1', 'r0'))]),
611
            set(f.iter_parents(['r2'])))
612
        # no nodes returned for a missing node
613
        self.assertEqual(set(),
614
            set(f.iter_parents(['missing'])))
615
        # 1 node returned with missing nodes skipped
616
        self.assertEqual(set([('r1', ('r0', ))]),
617
            set(f.iter_parents(['ghost1', 'r1', 'ghost'])))
618
        # 2 nodes returned
619
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
620
            set(f.iter_parents(['r0', 'r1'])))
621
        # 2 nodes returned, missing skipped
622
        self.assertEqual(set([('r0', ()), ('r1', ('r0', ))]),
623
            set(f.iter_parents(['a', 'r0', 'b', 'r1', 'c'])))
624
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
625
    def test_iter_lines_added_or_present_in_versions(self):
626
        # test that we get at least an equalset of the lines added by
627
        # versions in the weave 
628
        # the ordering here is to make a tree so that dumb searches have
629
        # more changes to muck up.
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
630
631
        class InstrumentedProgress(progress.DummyProgress):
632
633
            def __init__(self):
634
635
                progress.DummyProgress.__init__(self)
636
                self.updates = []
637
638
            def update(self, msg=None, current=None, total=None):
639
                self.updates.append((msg, current, total))
640
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
641
        vf = self.get_file()
642
        # add a base to get included
643
        vf.add_lines('base', [], ['base\n'])
644
        # add a ancestor to be included on one side
645
        vf.add_lines('lancestor', [], ['lancestor\n'])
646
        # add a ancestor to be included on the other side
647
        vf.add_lines('rancestor', ['base'], ['rancestor\n'])
648
        # add a child of rancestor with no eofile-nl
649
        vf.add_lines('child', ['rancestor'], ['base\n', 'child\n'])
650
        # add a child of lancestor and base to join the two roots
651
        vf.add_lines('otherchild',
652
                     ['lancestor', 'base'],
653
                     ['base\n', 'lancestor\n', 'otherchild\n'])
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
654
        def iter_with_versions(versions, expected):
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
655
            # now we need to see what lines are returned, and how often.
656
            lines = {'base\n':0,
657
                     'lancestor\n':0,
658
                     'rancestor\n':0,
659
                     'child\n':0,
660
                     'otherchild\n':0,
661
                     }
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
662
            progress = InstrumentedProgress()
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
663
            # iterate over the lines
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
664
            for line in vf.iter_lines_added_or_present_in_versions(versions, 
665
                pb=progress):
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
666
                lines[line] += 1
2039.1.2 by Aaron Bentley
Tweak test to avoid catching assert
667
            if []!= progress.updates: 
668
                self.assertEqual(expected, progress.updates)
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
669
            return lines
2147.1.3 by John Arbash Meinel
In knit.py we were re-using a variable in 2 loops, causing bogus progress messages to be generated.
670
        lines = iter_with_versions(['child', 'otherchild'],
671
                                   [('Walking content.', 0, 2),
672
                                    ('Walking content.', 1, 2),
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
673
                                    ('Walking content.', 2, 2)])
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
674
        # we must see child and otherchild
675
        self.assertTrue(lines['child\n'] > 0)
676
        self.assertTrue(lines['otherchild\n'] > 0)
677
        # we dont care if we got more than that.
678
        
679
        # test all lines
2147.1.3 by John Arbash Meinel
In knit.py we were re-using a variable in 2 loops, causing bogus progress messages to be generated.
680
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
681
                                          ('Walking content.', 1, 5),
682
                                          ('Walking content.', 2, 5),
683
                                          ('Walking content.', 3, 5),
684
                                          ('Walking content.', 4, 5),
2039.1.1 by Aaron Bentley
Clean up progress properly when interrupted during fetch (#54000)
685
                                          ('Walking content.', 5, 5)])
1594.2.6 by Robert Collins
Introduce a api specifically for looking at lines in some versions of the inventory, for fileid_involved.
686
        # all lines must be seen at least once
687
        self.assertTrue(lines['base\n'] > 0)
688
        self.assertTrue(lines['lancestor\n'] > 0)
689
        self.assertTrue(lines['rancestor\n'] > 0)
690
        self.assertTrue(lines['child\n'] > 0)
691
        self.assertTrue(lines['otherchild\n'] > 0)
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
692
693
    def test_fix_parents(self):
694
        # some versioned files allow incorrect parents to be corrected after
695
        # insertion - this may not fix ancestry..
696
        # if they do not supported, they just do not implement it.
1594.2.8 by Robert Collins
add ghost aware apis to knits.
697
        # we test this as an interface test to ensure that those that *do*
698
        # implementent it get it right.
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
699
        vf = self.get_file()
700
        vf.add_lines('notbase', [], [])
701
        vf.add_lines('base', [], [])
702
        try:
703
            vf.fix_parents('notbase', ['base'])
704
        except NotImplementedError:
705
            return
706
        self.assertEqual(['base'], vf.get_parents('notbase'))
707
        # open again, check it stuck.
708
        vf = self.get_file()
709
        self.assertEqual(['base'], vf.get_parents('notbase'))
710
1594.2.8 by Robert Collins
add ghost aware apis to knits.
711
    def test_fix_parents_with_ghosts(self):
712
        # when fixing parents, ghosts that are listed should not be ghosts
713
        # anymore.
714
        vf = self.get_file()
715
716
        try:
717
            vf.add_lines_with_ghosts('notbase', ['base', 'stillghost'], [])
718
        except NotImplementedError:
719
            return
720
        vf.add_lines('base', [], [])
721
        vf.fix_parents('notbase', ['base', 'stillghost'])
722
        self.assertEqual(['base'], vf.get_parents('notbase'))
723
        # open again, check it stuck.
724
        vf = self.get_file()
725
        self.assertEqual(['base'], vf.get_parents('notbase'))
726
        # and check the ghosts
727
        self.assertEqual(['base', 'stillghost'],
728
                         vf.get_parents_with_ghosts('notbase'))
729
730
    def test_add_lines_with_ghosts(self):
731
        # some versioned file formats allow lines to be added with parent
732
        # information that is > than that in the format. Formats that do
733
        # not support this need to raise NotImplementedError on the
734
        # add_lines_with_ghosts api.
735
        vf = self.get_file()
736
        # add a revision with ghost parents
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
737
        # The preferred form is utf8, but we should translate when needed
738
        parent_id_unicode = u'b\xbfse'
739
        parent_id_utf8 = parent_id_unicode.encode('utf8')
1594.2.8 by Robert Collins
add ghost aware apis to knits.
740
        try:
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
741
            vf.add_lines_with_ghosts('notbxbfse', [parent_id_utf8], [])
1594.2.8 by Robert Collins
add ghost aware apis to knits.
742
        except NotImplementedError:
743
            # check the other ghost apis are also not implemented
744
            self.assertRaises(NotImplementedError, vf.has_ghost, 'foo')
745
            self.assertRaises(NotImplementedError, vf.get_ancestry_with_ghosts, ['foo'])
746
            self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
747
            self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
748
            return
2150.2.1 by Robert Collins
Correctly decode utf8 revision ids from knits when parsing, fixes a regression where a unicode revision id is stored correctly, but then indexed by the utf8 value on the next invocation of bzr, rather than the unicode value.
749
        vf = self.reopen_file()
1594.2.8 by Robert Collins
add ghost aware apis to knits.
750
        # test key graph related apis: getncestry, _graph, get_parents
751
        # has_version
752
        # - these are ghost unaware and must not be reflect ghosts
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
753
        self.assertEqual(['notbxbfse'], vf.get_ancestry('notbxbfse'))
754
        self.assertEqual([], vf.get_parents('notbxbfse'))
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
755
        self.assertEqual({'notbxbfse':()}, vf.get_graph())
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
756
        self.assertFalse(self.callDeprecated([osutils._revision_id_warning],
757
                         vf.has_version, parent_id_unicode))
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
758
        self.assertFalse(vf.has_version(parent_id_utf8))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
759
        # we have _with_ghost apis to give us ghost information.
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
760
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
761
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
762
        self.assertEqual({'notbxbfse':[parent_id_utf8]}, vf.get_graph_with_ghosts())
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
763
        self.assertTrue(self.callDeprecated([osutils._revision_id_warning],
764
                        vf.has_ghost, parent_id_unicode))
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
765
        self.assertTrue(vf.has_ghost(parent_id_utf8))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
766
        # if we add something that is a ghost of another, it should correct the
767
        # results of the prior apis
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
768
        self.callDeprecated([osutils._revision_id_warning],
769
                            vf.add_lines, parent_id_unicode, [], [])
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
770
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry(['notbxbfse']))
771
        self.assertEqual([parent_id_utf8], vf.get_parents('notbxbfse'))
2625.8.1 by Robert Collins
LIBRARY API BREAKS:
772
        self.assertEqual({parent_id_utf8:(),
773
                          'notbxbfse':(parent_id_utf8, ),
1594.2.8 by Robert Collins
add ghost aware apis to knits.
774
                          },
775
                         vf.get_graph())
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
776
        self.assertTrue(self.callDeprecated([osutils._revision_id_warning],
777
                        vf.has_version, parent_id_unicode))
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
778
        self.assertTrue(vf.has_version(parent_id_utf8))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
779
        # we have _with_ghost apis to give us ghost information.
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
780
        self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
781
        self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
782
        self.assertEqual({parent_id_utf8:[],
783
                          'notbxbfse':[parent_id_utf8],
1594.2.8 by Robert Collins
add ghost aware apis to knits.
784
                          },
785
                         vf.get_graph_with_ghosts())
2309.4.7 by John Arbash Meinel
Update VersionedFile tests to ensure that they can take Unicode,
786
        self.assertFalse(self.callDeprecated([osutils._revision_id_warning],
787
                         vf.has_ghost, parent_id_unicode))
2249.5.12 by John Arbash Meinel
Change the APIs for VersionedFile, Store, and some of Repository into utf-8
788
        self.assertFalse(vf.has_ghost(parent_id_utf8))
1594.2.8 by Robert Collins
add ghost aware apis to knits.
789
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
790
    def test_add_lines_with_ghosts_after_normal_revs(self):
791
        # some versioned file formats allow lines to be added with parent
792
        # information that is > than that in the format. Formats that do
793
        # not support this need to raise NotImplementedError on the
794
        # add_lines_with_ghosts api.
795
        vf = self.get_file()
796
        # probe for ghost support
797
        try:
798
            vf.has_ghost('hoo')
799
        except NotImplementedError:
800
            return
801
        vf.add_lines_with_ghosts('base', [], ['line\n', 'line_b\n'])
802
        vf.add_lines_with_ghosts('references_ghost',
803
                                 ['base', 'a_ghost'],
804
                                 ['line\n', 'line_b\n', 'line_c\n'])
805
        origins = vf.annotate('references_ghost')
806
        self.assertEquals(('base', 'line\n'), origins[0])
807
        self.assertEquals(('base', 'line_b\n'), origins[1])
808
        self.assertEquals(('references_ghost', 'line_c\n'), origins[2])
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
809
810
    def test_readonly_mode(self):
811
        transport = get_transport(self.get_url('.'))
812
        factory = self.get_factory()
813
        vf = factory('id', transport, 0777, create=True, access_mode='w')
814
        vf = factory('id', transport, access_mode='r')
1596.2.37 by Robert Collins
Switch to delta based content copying in the generic versioned file copier.
815
        self.assertRaises(errors.ReadOnlyError, vf.add_delta, '', [], '', '', False, [])
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
816
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
817
        self.assertRaises(errors.ReadOnlyError,
818
                          vf.add_lines_with_ghosts,
819
                          'base',
820
                          [],
821
                          [])
822
        self.assertRaises(errors.ReadOnlyError, vf.fix_parents, 'base', [])
823
        self.assertRaises(errors.ReadOnlyError, vf.join, 'base')
1594.2.24 by Robert Collins
Make use of the transaction finalisation warning support to implement in-knit caching.
824
        self.assertRaises(errors.ReadOnlyError, vf.clone_text, 'base', 'bar', ['foo'])
1666.1.6 by Robert Collins
Make knit the default format.
825
    
826
    def test_get_sha1(self):
827
        # check the sha1 data is available
828
        vf = self.get_file()
829
        # a simple file
830
        vf.add_lines('a', [], ['a\n'])
831
        # the same file, different metadata
832
        vf.add_lines('b', ['a'], ['a\n'])
833
        # a file differing only in last newline.
834
        vf.add_lines('c', [], ['a'])
835
        self.assertEqual(
836
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('a'))
837
        self.assertEqual(
838
            '3f786850e387550fdab836ed7e6dc881de23001b', vf.get_sha1('b'))
839
        self.assertEqual(
840
            '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', vf.get_sha1('c'))
2520.4.89 by Aaron Bentley
Add get_sha1s to weaves
841
842
        self.assertEqual(['3f786850e387550fdab836ed7e6dc881de23001b',
843
                          '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8',
844
                          '3f786850e387550fdab836ed7e6dc881de23001b'],
845
                          vf.get_sha1s(['a', 'c', 'b']))
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
846
        
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
847
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
848
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
849
850
    def get_file(self, name='foo'):
1563.2.25 by Robert Collins
Merge in upstream.
851
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
852
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
853
    def get_file_corrupted_text(self):
1563.2.25 by Robert Collins
Merge in upstream.
854
        w = WeaveFile('foo', get_transport(self.get_url('.')), create=True)
1563.2.13 by Robert Collins
InterVersionedFile implemented.
855
        w.add_lines('v1', [], ['hello\n'])
856
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
857
        
858
        # We are going to invasively corrupt the text
859
        # Make sure the internals of weave are the same
860
        self.assertEqual([('{', 0)
861
                        , 'hello\n'
862
                        , ('}', None)
863
                        , ('{', 1)
864
                        , 'there\n'
865
                        , ('}', None)
866
                        ], w._weave)
867
        
868
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
869
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
870
                        ], w._sha1s)
871
        w.check()
872
        
873
        # Corrupted
874
        w._weave[4] = 'There\n'
875
        return w
876
877
    def get_file_corrupted_checksum(self):
878
        w = self.get_file_corrupted_text()
879
        # Corrected
880
        w._weave[4] = 'there\n'
881
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
882
        
883
        #Invalid checksum, first digit changed
884
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
885
        return w
886
1666.1.6 by Robert Collins
Make knit the default format.
887
    def reopen_file(self, name='foo', create=False):
888
        return WeaveFile(name, get_transport(self.get_url('.')), create=create)
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
889
1563.2.25 by Robert Collins
Merge in upstream.
890
    def test_no_implicit_create(self):
891
        self.assertRaises(errors.NoSuchFile,
892
                          WeaveFile,
893
                          'foo',
894
                          get_transport(self.get_url('.')))
895
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
896
    def get_factory(self):
897
        return WeaveFile
898
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
899
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
900
class TestKnit(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
1563.2.4 by Robert Collins
First cut at including the knit implementation of versioned_file.
901
902
    def get_file(self, name='foo'):
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
903
        return self.get_factory()(name, get_transport(self.get_url('.')),
904
                                  delta=True, create=True)
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
905
1594.2.23 by Robert Collins
Test versioned file storage handling of clean/dirty status for accessed versioned files.
906
    def get_factory(self):
907
        return KnitVersionedFile
908
1563.2.6 by Robert Collins
Start check tests for knits (pending), and remove dead code.
909
    def get_file_corrupted_text(self):
910
        knit = self.get_file()
911
        knit.add_lines('v1', [], ['hello\n'])
912
        knit.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
913
        return knit
1563.2.9 by Robert Collins
Update versionedfile api tests to ensure that data is available after every operation.
914
1666.1.6 by Robert Collins
Make knit the default format.
915
    def reopen_file(self, name='foo', create=False):
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
916
        return self.get_factory()(name, get_transport(self.get_url('.')),
1666.1.6 by Robert Collins
Make knit the default format.
917
            delta=True,
918
            create=create)
1563.2.10 by Robert Collins
Change weave store to be a versioned store, using WeaveFiles which maintain integrity without needing explicit 'put' operations.
919
920
    def test_detection(self):
1563.2.19 by Robert Collins
stub out a check for knits.
921
        knit = self.get_file()
922
        knit.check()
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
923
1563.2.25 by Robert Collins
Merge in upstream.
924
    def test_no_implicit_create(self):
925
        self.assertRaises(errors.NoSuchFile,
926
                          KnitVersionedFile,
927
                          'foo',
928
                          get_transport(self.get_url('.')))
929
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
930
2770.1.1 by Aaron Bentley
Initial implmentation of plain knit annotation
931
class TestPlaintextKnit(TestKnit):
932
    """Test a knit with no cached annotations"""
933
934
    def _factory(self, name, transport, file_mode=None, access_mode=None,
935
                 delta=True, create=False):
936
        return KnitVersionedFile(name, transport, file_mode, access_mode,
937
                                 KnitPlainFactory(), delta=delta,
938
                                 create=create)
939
940
    def get_factory(self):
941
        return self._factory
942
943
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
944
class InterString(versionedfile.InterVersionedFile):
945
    """An inter-versionedfile optimised code path for strings.
946
947
    This is for use during testing where we use strings as versionedfiles
948
    so that none of the default regsitered interversionedfile classes will
949
    match - which lets us test the match logic.
950
    """
951
952
    @staticmethod
953
    def is_compatible(source, target):
954
        """InterString is compatible with strings-as-versionedfiles."""
955
        return isinstance(source, str) and isinstance(target, str)
956
957
958
# TODO this and the InterRepository core logic should be consolidatable
959
# if we make the registry a separate class though we still need to 
960
# test the behaviour in the active registry to catch failure-to-handle-
961
# stange-objects
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
962
class TestInterVersionedFile(TestCaseWithMemoryTransport):
1563.2.12 by Robert Collins
Checkpointing: created InterObject to factor out common inter object worker code, added InterVersionedFile and tests to allow making join work between any versionedfile.
963
964
    def test_get_default_inter_versionedfile(self):
965
        # test that the InterVersionedFile.get(a, b) probes
966
        # for a class where is_compatible(a, b) returns
967
        # true and returns a default interversionedfile otherwise.
968
        # This also tests that the default registered optimised interversionedfile
969
        # classes do not barf inappropriately when a surprising versionedfile type
970
        # is handed to them.
971
        dummy_a = "VersionedFile 1."
972
        dummy_b = "VersionedFile 2."
973
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
974
975
    def assertGetsDefaultInterVersionedFile(self, a, b):
976
        """Asserts that InterVersionedFile.get(a, b) -> the default."""
977
        inter = versionedfile.InterVersionedFile.get(a, b)
978
        self.assertEqual(versionedfile.InterVersionedFile,
979
                         inter.__class__)
980
        self.assertEqual(a, inter.source)
981
        self.assertEqual(b, inter.target)
982
983
    def test_register_inter_versionedfile_class(self):
984
        # test that a optimised code path provider - a
985
        # InterVersionedFile subclass can be registered and unregistered
986
        # and that it is correctly selected when given a versionedfile
987
        # pair that it returns true on for the is_compatible static method
988
        # check
989
        dummy_a = "VersionedFile 1."
990
        dummy_b = "VersionedFile 2."
991
        versionedfile.InterVersionedFile.register_optimiser(InterString)
992
        try:
993
            # we should get the default for something InterString returns False
994
            # to
995
            self.assertFalse(InterString.is_compatible(dummy_a, None))
996
            self.assertGetsDefaultInterVersionedFile(dummy_a, None)
997
            # and we should get an InterString for a pair it 'likes'
998
            self.assertTrue(InterString.is_compatible(dummy_a, dummy_b))
999
            inter = versionedfile.InterVersionedFile.get(dummy_a, dummy_b)
1000
            self.assertEqual(InterString, inter.__class__)
1001
            self.assertEqual(dummy_a, inter.source)
1002
            self.assertEqual(dummy_b, inter.target)
1003
        finally:
1004
            versionedfile.InterVersionedFile.unregister_optimiser(InterString)
1005
        # now we should get the default InterVersionedFile object again.
1006
        self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
1007
1008
1009
class TestReadonlyHttpMixin(object):
1010
1011
    def test_readonly_http_works(self):
1012
        # we should be able to read from http with a versioned file.
1013
        vf = self.get_file()
1666.1.6 by Robert Collins
Make knit the default format.
1014
        # try an empty file access
1015
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
1016
        self.assertEqual([], readonly_vf.versions())
1017
        # now with feeling.
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
1018
        vf.add_lines('1', [], ['a\n'])
1019
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
1020
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
1666.1.6 by Robert Collins
Make knit the default format.
1021
        self.assertEqual(['1', '2'], vf.versions())
1666.1.1 by Robert Collins
Add trivial http-using test for versioned files.
1022
        for version in readonly_vf.versions():
1023
            readonly_vf.get_lines(version)
1024
1025
1026
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
1027
1028
    def get_file(self):
1029
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
1030
1031
    def get_factory(self):
1032
        return WeaveFile
1033
1034
1035
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
1036
1037
    def get_file(self):
1038
        return KnitVersionedFile('foo', get_transport(self.get_url('.')),
1039
                                 delta=True, create=True)
1040
1041
    def get_factory(self):
1042
        return KnitVersionedFile
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
1043
1044
1045
class MergeCasesMixin(object):
1046
1047
    def doMerge(self, base, a, b, mp):
1048
        from cStringIO import StringIO
1049
        from textwrap import dedent
1050
1051
        def addcrlf(x):
1052
            return x + '\n'
1053
        
1054
        w = self.get_file()
1055
        w.add_lines('text0', [], map(addcrlf, base))
1056
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1057
        w.add_lines('text2', ['text0'], map(addcrlf, b))
1058
1059
        self.log_contents(w)
1060
1061
        self.log('merge plan:')
1062
        p = list(w.plan_merge('text1', 'text2'))
1063
        for state, line in p:
1064
            if line:
1065
                self.log('%12s | %s' % (state, line[:-1]))
1066
1067
        self.log('merge:')
1068
        mt = StringIO()
1069
        mt.writelines(w.weave_merge(p))
1070
        mt.seek(0)
1071
        self.log(mt.getvalue())
1072
1073
        mp = map(addcrlf, mp)
1074
        self.assertEqual(mt.readlines(), mp)
1075
        
1076
        
1077
    def testOneInsert(self):
1078
        self.doMerge([],
1079
                     ['aa'],
1080
                     [],
1081
                     ['aa'])
1082
1083
    def testSeparateInserts(self):
1084
        self.doMerge(['aaa', 'bbb', 'ccc'],
1085
                     ['aaa', 'xxx', 'bbb', 'ccc'],
1086
                     ['aaa', 'bbb', 'yyy', 'ccc'],
1087
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
1088
1089
    def testSameInsert(self):
1090
        self.doMerge(['aaa', 'bbb', 'ccc'],
1091
                     ['aaa', 'xxx', 'bbb', 'ccc'],
1092
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
1093
                     ['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
1094
    overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
1095
    def testOverlappedInsert(self):
1096
        self.doMerge(['aaa', 'bbb'],
1097
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1098
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1099
1100
        # really it ought to reduce this to 
1101
        # ['aaa', 'xxx', 'yyy', 'bbb']
1102
1103
1104
    def testClashReplace(self):
1105
        self.doMerge(['aaa'],
1106
                     ['xxx'],
1107
                     ['yyy', 'zzz'],
1108
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
1109
                      '>>>>>>> '])
1110
1111
    def testNonClashInsert1(self):
1112
        self.doMerge(['aaa'],
1113
                     ['xxx', 'aaa'],
1114
                     ['yyy', 'zzz'],
1115
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
1116
                      '>>>>>>> '])
1117
1118
    def testNonClashInsert2(self):
1119
        self.doMerge(['aaa'],
1120
                     ['aaa'],
1121
                     ['yyy', 'zzz'],
1122
                     ['yyy', 'zzz'])
1123
1124
1125
    def testDeleteAndModify(self):
1126
        """Clashing delete and modification.
1127
1128
        If one side modifies a region and the other deletes it then
1129
        there should be a conflict with one side blank.
1130
        """
1131
1132
        #######################################
1133
        # skippd, not working yet
1134
        return
1135
        
1136
        self.doMerge(['aaa', 'bbb', 'ccc'],
1137
                     ['aaa', 'ddd', 'ccc'],
1138
                     ['aaa', 'ccc'],
1139
                     ['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
1140
1141
    def _test_merge_from_strings(self, base, a, b, expected):
1142
        w = self.get_file()
1143
        w.add_lines('text0', [], base.splitlines(True))
1144
        w.add_lines('text1', ['text0'], a.splitlines(True))
1145
        w.add_lines('text2', ['text0'], b.splitlines(True))
1146
        self.log('merge plan:')
1147
        p = list(w.plan_merge('text1', 'text2'))
1148
        for state, line in p:
1149
            if line:
1150
                self.log('%12s | %s' % (state, line[:-1]))
1151
        self.log('merge result:')
1152
        result_text = ''.join(w.weave_merge(p))
1153
        self.log(result_text)
1154
        self.assertEqualDiff(result_text, expected)
1155
1156
    def test_weave_merge_conflicts(self):
1157
        # does weave merge properly handle plans that end with unchanged?
1158
        result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1159
        self.assertEqual(result, 'hello\n')
1160
1161
    def test_deletion_extended(self):
1162
        """One side deletes, the other deletes more.
1163
        """
1164
        base = """\
1165
            line 1
1166
            line 2
1167
            line 3
1168
            """
1169
        a = """\
1170
            line 1
1171
            line 2
1172
            """
1173
        b = """\
1174
            line 1
1175
            """
1176
        result = """\
1177
            line 1
1178
            """
1179
        self._test_merge_from_strings(base, a, b, result)
1180
1181
    def test_deletion_overlap(self):
1182
        """Delete overlapping regions with no other conflict.
1183
1184
        Arguably it'd be better to treat these as agreement, rather than 
1185
        conflict, but for now conflict is safer.
1186
        """
1187
        base = """\
1188
            start context
1189
            int a() {}
1190
            int b() {}
1191
            int c() {}
1192
            end context
1193
            """
1194
        a = """\
1195
            start context
1196
            int a() {}
1197
            end context
1198
            """
1199
        b = """\
1200
            start context
1201
            int c() {}
1202
            end context
1203
            """
1204
        result = """\
1205
            start context
1206
<<<<<<< 
1207
            int a() {}
1208
=======
1209
            int c() {}
1210
>>>>>>> 
1211
            end context
1212
            """
1213
        self._test_merge_from_strings(base, a, b, result)
1214
1215
    def test_agreement_deletion(self):
1216
        """Agree to delete some lines, without conflicts."""
1217
        base = """\
1218
            start context
1219
            base line 1
1220
            base line 2
1221
            end context
1222
            """
1223
        a = """\
1224
            start context
1225
            base line 1
1226
            end context
1227
            """
1228
        b = """\
1229
            start context
1230
            base line 1
1231
            end context
1232
            """
1233
        result = """\
1234
            start context
1235
            base line 1
1236
            end context
1237
            """
1238
        self._test_merge_from_strings(base, a, b, result)
1239
1240
    def test_sync_on_deletion(self):
1241
        """Specific case of merge where we can synchronize incorrectly.
1242
        
1243
        A previous version of the weave merge concluded that the two versions
1244
        agreed on deleting line 2, and this could be a synchronization point.
1245
        Line 1 was then considered in isolation, and thought to be deleted on 
1246
        both sides.
1247
1248
        It's better to consider the whole thing as a disagreement region.
1249
        """
1250
        base = """\
1251
            start context
1252
            base line 1
1253
            base line 2
1254
            end context
1255
            """
1256
        a = """\
1257
            start context
1258
            base line 1
1259
            a's replacement line 2
1260
            end context
1261
            """
1262
        b = """\
1263
            start context
1264
            b replaces
1265
            both lines
1266
            end context
1267
            """
1268
        result = """\
1269
            start context
1270
<<<<<<< 
1271
            base line 1
1272
            a's replacement line 2
1273
=======
1274
            b replaces
1275
            both lines
1276
>>>>>>> 
1277
            end context
1278
            """
1279
        self._test_merge_from_strings(base, a, b, result)
1280
1281
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
1282
class TestKnitMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
1283
1284
    def get_file(self, name='foo'):
1285
        return KnitVersionedFile(name, get_transport(self.get_url('.')),
1286
                                 delta=True, create=True)
1287
1288
    def log_contents(self, w):
1289
        pass
1290
1291
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
1292
class TestWeaveMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1664.2.9 by Aaron Bentley
Ported weave merge test to versionedfile
1293
1294
    def get_file(self, name='foo'):
1295
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1296
1297
    def log_contents(self, w):
1298
        self.log('weave is:')
1299
        tmpf = StringIO()
1300
        write_weave(w, tmpf)
1301
        self.log(tmpf.getvalue())
1302
1303
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1304
                                'xxx', '>>>>>>> ', 'bbb']
2670.3.1 by Andrew Bennetts
Add get_data_stream/insert_data_stream to KnitVersionedFile.
1305
1306
1307
class TestFormatSignatures(TestCaseWithMemoryTransport):
1308
1309
    def get_knit_file(self, name, annotated):
1310
        if annotated:
1311
            factory = KnitAnnotateFactory()
1312
        else:
1313
            factory = KnitPlainFactory()
1314
        return KnitVersionedFile(
1315
            name, get_transport(self.get_url('.')), create=True,
1316
            factory=factory)
1317
1318
    def test_knit_format_signatures(self):
1319
        """Different formats of knit have different signature strings."""
1320
        knit = self.get_knit_file('a', True)
1321
        self.assertEqual('knit-annotated', knit.get_format_signature())
1322
        knit = self.get_knit_file('p', False)
1323
        self.assertEqual('knit-plain', knit.get_format_signature())
1324