104
115
f = self.reopen_file()
107
def test_get_delta(self):
109
sha1s = self._setup_for_deltas(f)
110
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
111
[(0, 0, 1, [('base', 'line\n')])])
112
self.assertEqual(expected_delta, f.get_delta('base'))
114
text_name = 'chain1-'
115
for depth in range(26):
116
new_version = text_name + '%s' % depth
117
expected_delta = (next_parent, sha1s[depth],
119
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
120
self.assertEqual(expected_delta, f.get_delta(new_version))
121
next_parent = new_version
123
text_name = 'chain2-'
124
for depth in range(26):
125
new_version = text_name + '%s' % depth
126
expected_delta = (next_parent, sha1s[depth], False,
127
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
128
self.assertEqual(expected_delta, f.get_delta(new_version))
129
next_parent = new_version
130
# smoke test for eol support
131
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
132
self.assertEqual(['line'], f.get_lines('noeol'))
133
self.assertEqual(expected_delta, f.get_delta('noeol'))
135
def test_get_deltas(self):
137
sha1s = self._setup_for_deltas(f)
138
deltas = f.get_deltas(f.versions())
139
expected_delta = (None, '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
140
[(0, 0, 1, [('base', 'line\n')])])
141
self.assertEqual(expected_delta, deltas['base'])
143
text_name = 'chain1-'
144
for depth in range(26):
145
new_version = text_name + '%s' % depth
146
expected_delta = (next_parent, sha1s[depth],
148
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
149
self.assertEqual(expected_delta, deltas[new_version])
150
next_parent = new_version
152
text_name = 'chain2-'
153
for depth in range(26):
154
new_version = text_name + '%s' % depth
155
expected_delta = (next_parent, sha1s[depth], False,
156
[(depth + 1, depth + 1, 1, [(new_version, 'line\n')])])
157
self.assertEqual(expected_delta, deltas[new_version])
158
next_parent = new_version
159
# smoke tests for eol support
160
expected_delta = ('base', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True, [])
161
self.assertEqual(['line'], f.get_lines('noeol'))
162
self.assertEqual(expected_delta, deltas['noeol'])
163
# smoke tests for eol support - two noeol in a row same content
164
expected_deltas = (('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
165
[(0, 1, 2, [(u'noeolsecond', 'line\n'), (u'noeolsecond', 'line\n')])]),
166
('noeol', '3ad7ee82dbd8f29ecba073f96e43e414b3f70a4d', True,
167
[(0, 0, 1, [('noeolsecond', 'line\n')]), (1, 1, 0, [])]))
168
self.assertEqual(['line\n', 'line'], f.get_lines('noeolsecond'))
169
self.assertTrue(deltas['noeolsecond'] in expected_deltas)
170
# two no-eol in a row, different content
171
expected_delta = ('noeolsecond', '8bb553a84e019ef1149db082d65f3133b195223b', True,
172
[(1, 2, 1, [(u'noeolnotshared', 'phone\n')])])
173
self.assertEqual(['line\n', 'phone'], f.get_lines('noeolnotshared'))
174
self.assertEqual(expected_delta, deltas['noeolnotshared'])
175
# eol folling a no-eol with content change
176
expected_delta = ('noeol', 'a61f6fb6cfc4596e8d88c34a308d1e724caf8977', False,
177
[(0, 1, 1, [(u'eol', 'phone\n')])])
178
self.assertEqual(['phone\n'], f.get_lines('eol'))
179
self.assertEqual(expected_delta, deltas['eol'])
180
# eol folling a no-eol with content change
181
expected_delta = ('noeol', '6bfa09d82ce3e898ad4641ae13dd4fdb9cf0d76b', False,
182
[(0, 1, 1, [(u'eolline', 'line\n')])])
183
self.assertEqual(['line\n'], f.get_lines('eolline'))
184
self.assertEqual(expected_delta, deltas['eolline'])
185
# eol with no parents
186
expected_delta = (None, '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
187
[(0, 0, 1, [(u'noeolbase', 'line\n')])])
188
self.assertEqual(['line'], f.get_lines('noeolbase'))
189
self.assertEqual(expected_delta, deltas['noeolbase'])
190
# eol with two parents, in inverse insertion order
191
expected_deltas = (('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
192
[(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]),
193
('noeolbase', '264f39cab871e4cfd65b3a002f7255888bb5ed97', True,
194
[(0, 1, 1, [(u'eolbeforefirstparent', 'line\n')])]))
195
self.assertEqual(['line'], f.get_lines('eolbeforefirstparent'))
196
#self.assertTrue(deltas['eolbeforefirstparent'] in expected_deltas)
118
def test_add_unicode_content(self):
119
# unicode content is not permitted in versioned files.
120
# versioned files version sequences of bytes only.
122
self.assertRaises(errors.BzrBadParameterUnicode,
123
vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
125
(errors.BzrBadParameterUnicode, NotImplementedError),
126
vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
128
def test_add_follows_left_matching_blocks(self):
129
"""If we change left_matching_blocks, delta changes
131
Note: There are multiple correct deltas in this case, because
132
we start with 1 "a" and we get 3.
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(['a\n', 'a\n', 'a\n'], vf.get_lines('2'))
141
vf.add_lines('3', ['1'], ['a\n', 'a\n', 'a\n'],
142
left_matching_blocks=[(0, 2, 1), (1, 3, 0)])
143
self.assertEqual(['a\n', 'a\n', 'a\n'], vf.get_lines('3'))
145
def test_inline_newline_throws(self):
146
# \r characters are not permitted in lines being added
148
self.assertRaises(errors.BzrBadParameterContainsNewline,
149
vf.add_lines, 'a', [], ['a\n\n'])
151
(errors.BzrBadParameterContainsNewline, NotImplementedError),
152
vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
153
# but inline CR's are allowed
154
vf.add_lines('a', [], ['a\r\n'])
156
vf.add_lines_with_ghosts('b', [], ['a\r\n'])
157
except NotImplementedError:
160
def test_add_reserved(self):
162
self.assertRaises(errors.ReservedId,
163
vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
165
def test_add_lines_nostoresha(self):
166
"""When nostore_sha is supplied using old content raises."""
168
empty_text = ('a', [])
169
sample_text_nl = ('b', ["foo\n", "bar\n"])
170
sample_text_no_nl = ('c', ["foo\n", "bar"])
172
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
173
sha, _, _ = vf.add_lines(version, [], lines)
175
# we now have a copy of all the lines in the vf.
176
for sha, (version, lines) in zip(
177
shas, (empty_text, sample_text_nl, sample_text_no_nl)):
178
self.assertRaises(errors.ExistingContent,
179
vf.add_lines, version + "2", [], lines,
181
# and no new version should have been added.
182
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
185
def test_add_lines_with_ghosts_nostoresha(self):
186
"""When nostore_sha is supplied using old content raises."""
188
empty_text = ('a', [])
189
sample_text_nl = ('b', ["foo\n", "bar\n"])
190
sample_text_no_nl = ('c', ["foo\n", "bar"])
192
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
193
sha, _, _ = vf.add_lines(version, [], lines)
195
# we now have a copy of all the lines in the vf.
196
# is the test applicable to this vf implementation?
198
vf.add_lines_with_ghosts('d', [], [])
199
except NotImplementedError:
200
raise TestSkipped("add_lines_with_ghosts is optional")
201
for sha, (version, lines) in zip(
202
shas, (empty_text, sample_text_nl, sample_text_no_nl)):
203
self.assertRaises(errors.ExistingContent,
204
vf.add_lines_with_ghosts, version + "2", [], lines,
206
# and no new version should have been added.
207
self.assertRaises(errors.RevisionNotPresent, vf.get_lines,
210
def test_add_lines_return_value(self):
211
# add_lines should return the sha1 and the text size.
213
empty_text = ('a', [])
214
sample_text_nl = ('b', ["foo\n", "bar\n"])
215
sample_text_no_nl = ('c', ["foo\n", "bar"])
216
# check results for the three cases:
217
for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
218
# the first two elements are the same for all versioned files:
219
# - the digest and the size of the text. For some versioned files
220
# additional data is returned in additional tuple elements.
221
result = vf.add_lines(version, [], lines)
222
self.assertEqual(3, len(result))
223
self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
225
# parents should not affect the result:
226
lines = sample_text_nl[1]
227
self.assertEqual((osutils.sha_strings(lines), sum(map(len, lines))),
228
vf.add_lines('d', ['b', 'c'], lines)[0:2])
230
def test_get_reserved(self):
232
self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
233
self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
234
self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
236
def test_make_mpdiffs(self):
237
from bzrlib import multiparent
238
vf = self.get_file('foo')
239
sha1s = self._setup_for_deltas(vf)
240
new_vf = self.get_file('bar')
241
for version in multiparent.topo_iter(vf):
242
mpdiff = vf.make_mpdiffs([version])[0]
243
new_vf.add_mpdiffs([(version, vf.get_parents(version),
244
vf.get_sha1(version), mpdiff)])
245
self.assertEqualDiff(vf.get_text(version),
246
new_vf.get_text(version))
198
248
def _setup_for_deltas(self, f):
199
self.assertRaises(errors.RevisionNotPresent, f.get_delta, 'base')
249
self.assertFalse(f.has_version('base'))
200
250
# add texts that should trip the knit maximum delta chain threshold
201
251
# as well as doing parallel chains of data in knits.
202
252
# this is done by two chains of 25 insertions
570
616
self.assertRaises(NotImplementedError, vf.get_parents_with_ghosts, 'foo')
571
617
self.assertRaises(NotImplementedError, vf.get_graph_with_ghosts)
619
vf = self.reopen_file()
573
620
# test key graph related apis: getncestry, _graph, get_parents
575
622
# - these are ghost unaware and must not be reflect ghosts
576
self.assertEqual([u'notbxbfse'], vf.get_ancestry(u'notbxbfse'))
577
self.assertEqual([], vf.get_parents(u'notbxbfse'))
578
self.assertEqual({u'notbxbfse':[]}, vf.get_graph())
579
self.assertFalse(vf.has_version(u'b\xbfse'))
623
self.assertEqual(['notbxbfse'], vf.get_ancestry('notbxbfse'))
624
self.assertEqual([], vf.get_parents('notbxbfse'))
625
self.assertEqual({'notbxbfse':()}, vf.get_graph())
626
self.assertFalse(vf.has_version(parent_id_utf8))
580
627
# we have _with_ghost apis to give us ghost information.
581
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
582
self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
583
self.assertEqual({u'notbxbfse':[u'b\xbfse']}, vf.get_graph_with_ghosts())
584
self.assertTrue(vf.has_ghost(u'b\xbfse'))
628
self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry_with_ghosts(['notbxbfse']))
629
self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
630
self.assertEqual({'notbxbfse':[parent_id_utf8]}, vf.get_graph_with_ghosts())
631
self.assertTrue(vf.has_ghost(parent_id_utf8))
585
632
# if we add something that is a ghost of another, it should correct the
586
633
# results of the prior apis
587
vf.add_lines(u'b\xbfse', [], [])
588
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry([u'notbxbfse']))
589
self.assertEqual([u'b\xbfse'], vf.get_parents(u'notbxbfse'))
590
self.assertEqual({u'b\xbfse':[],
591
u'notbxbfse':[u'b\xbfse'],
634
vf.add_lines(parent_id_utf8, [], [])
635
self.assertEqual([parent_id_utf8, 'notbxbfse'], vf.get_ancestry(['notbxbfse']))
636
self.assertEqual([parent_id_utf8], vf.get_parents('notbxbfse'))
637
self.assertEqual({parent_id_utf8:(),
638
'notbxbfse':(parent_id_utf8, ),
594
self.assertTrue(vf.has_version(u'b\xbfse'))
641
self.assertTrue(vf.has_version(parent_id_utf8))
595
642
# we have _with_ghost apis to give us ghost information.
596
self.assertEqual([u'b\xbfse', u'notbxbfse'], vf.get_ancestry_with_ghosts([u'notbxbfse']))
597
self.assertEqual([u'b\xbfse'], vf.get_parents_with_ghosts(u'notbxbfse'))
598
self.assertEqual({u'b\xbfse':[],
599
u'notbxbfse':[u'b\xbfse'],
643
self.assertEqual([parent_id_utf8, 'notbxbfse'],
644
vf.get_ancestry_with_ghosts(['notbxbfse']))
645
self.assertEqual([parent_id_utf8], vf.get_parents_with_ghosts('notbxbfse'))
646
self.assertEqual({parent_id_utf8:[],
647
'notbxbfse':[parent_id_utf8],
601
649
vf.get_graph_with_ghosts())
602
self.assertFalse(vf.has_ghost(u'b\xbfse'))
650
self.assertFalse(vf.has_ghost(parent_id_utf8))
604
652
def test_add_lines_with_ghosts_after_normal_revs(self):
605
653
# some versioned file formats allow lines to be added with parent
783
864
versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
865
# now we should get the default InterVersionedFile object again.
785
866
self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
869
class TestReadonlyHttpMixin(object):
871
def test_readonly_http_works(self):
872
# we should be able to read from http with a versioned file.
874
# try an empty file access
875
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
876
self.assertEqual([], readonly_vf.versions())
878
vf.add_lines('1', [], ['a\n'])
879
vf.add_lines('2', ['1'], ['b\n', 'a\n'])
880
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
881
self.assertEqual(['1', '2'], vf.versions())
882
for version in readonly_vf.versions():
883
readonly_vf.get_lines(version)
886
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
889
return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
891
def get_factory(self):
895
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
898
return KnitVersionedFile('foo', get_transport(self.get_url('.')),
899
delta=True, create=True)
901
def get_factory(self):
902
return KnitVersionedFile
905
class MergeCasesMixin(object):
907
def doMerge(self, base, a, b, mp):
908
from cStringIO import StringIO
909
from textwrap import dedent
915
w.add_lines('text0', [], map(addcrlf, base))
916
w.add_lines('text1', ['text0'], map(addcrlf, a))
917
w.add_lines('text2', ['text0'], map(addcrlf, b))
921
self.log('merge plan:')
922
p = list(w.plan_merge('text1', 'text2'))
923
for state, line in p:
925
self.log('%12s | %s' % (state, line[:-1]))
929
mt.writelines(w.weave_merge(p))
931
self.log(mt.getvalue())
933
mp = map(addcrlf, mp)
934
self.assertEqual(mt.readlines(), mp)
937
def testOneInsert(self):
943
def testSeparateInserts(self):
944
self.doMerge(['aaa', 'bbb', 'ccc'],
945
['aaa', 'xxx', 'bbb', 'ccc'],
946
['aaa', 'bbb', 'yyy', 'ccc'],
947
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
949
def testSameInsert(self):
950
self.doMerge(['aaa', 'bbb', 'ccc'],
951
['aaa', 'xxx', 'bbb', 'ccc'],
952
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
953
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
954
overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
955
def testOverlappedInsert(self):
956
self.doMerge(['aaa', 'bbb'],
957
['aaa', 'xxx', 'yyy', 'bbb'],
958
['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
960
# really it ought to reduce this to
961
# ['aaa', 'xxx', 'yyy', 'bbb']
964
def testClashReplace(self):
965
self.doMerge(['aaa'],
968
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
971
def testNonClashInsert1(self):
972
self.doMerge(['aaa'],
975
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
978
def testNonClashInsert2(self):
979
self.doMerge(['aaa'],
985
def testDeleteAndModify(self):
986
"""Clashing delete and modification.
988
If one side modifies a region and the other deletes it then
989
there should be a conflict with one side blank.
992
#######################################
993
# skippd, not working yet
996
self.doMerge(['aaa', 'bbb', 'ccc'],
997
['aaa', 'ddd', 'ccc'],
999
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
1001
def _test_merge_from_strings(self, base, a, b, expected):
1003
w.add_lines('text0', [], base.splitlines(True))
1004
w.add_lines('text1', ['text0'], a.splitlines(True))
1005
w.add_lines('text2', ['text0'], b.splitlines(True))
1006
self.log('merge plan:')
1007
p = list(w.plan_merge('text1', 'text2'))
1008
for state, line in p:
1010
self.log('%12s | %s' % (state, line[:-1]))
1011
self.log('merge result:')
1012
result_text = ''.join(w.weave_merge(p))
1013
self.log(result_text)
1014
self.assertEqualDiff(result_text, expected)
1016
def test_weave_merge_conflicts(self):
1017
# does weave merge properly handle plans that end with unchanged?
1018
result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1019
self.assertEqual(result, 'hello\n')
1021
def test_deletion_extended(self):
1022
"""One side deletes, the other deletes more.
1039
self._test_merge_from_strings(base, a, b, result)
1041
def test_deletion_overlap(self):
1042
"""Delete overlapping regions with no other conflict.
1044
Arguably it'd be better to treat these as agreement, rather than
1045
conflict, but for now conflict is safer.
1073
self._test_merge_from_strings(base, a, b, result)
1075
def test_agreement_deletion(self):
1076
"""Agree to delete some lines, without conflicts."""
1098
self._test_merge_from_strings(base, a, b, result)
1100
def test_sync_on_deletion(self):
1101
"""Specific case of merge where we can synchronize incorrectly.
1103
A previous version of the weave merge concluded that the two versions
1104
agreed on deleting line 2, and this could be a synchronization point.
1105
Line 1 was then considered in isolation, and thought to be deleted on
1108
It's better to consider the whole thing as a disagreement region.
1119
a's replacement line 2
1132
a's replacement line 2
1139
self._test_merge_from_strings(base, a, b, result)
1142
class TestKnitMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1144
def get_file(self, name='foo'):
1145
return KnitVersionedFile(name, get_transport(self.get_url('.')),
1146
delta=True, create=True)
1148
def log_contents(self, w):
1152
class TestWeaveMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1154
def get_file(self, name='foo'):
1155
return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1157
def log_contents(self, w):
1158
self.log('weave is:')
1160
write_weave(w, tmpf)
1161
self.log(tmpf.getvalue())
1163
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1164
'xxx', '>>>>>>> ', 'bbb']
1167
class TestFormatSignatures(TestCaseWithMemoryTransport):
1169
def get_knit_file(self, name, annotated):
1171
factory = KnitAnnotateFactory()
1173
factory = KnitPlainFactory()
1174
return KnitVersionedFile(
1175
name, get_transport(self.get_url('.')), create=True,
1178
def test_knit_format_signatures(self):
1179
"""Different formats of knit have different signature strings."""
1180
knit = self.get_knit_file('a', True)
1181
self.assertEqual('knit-annotated', knit.get_format_signature())
1182
knit = self.get_knit_file('p', False)
1183
self.assertEqual('knit-plain', knit.get_format_signature())