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