1
# Copyright (C) 2005, 2006 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for Knit data structure"""
23
from bzrlib.errors import KnitError, RevisionAlreadyPresent
24
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory, KnitAnnotateFactory
25
from bzrlib.osutils import split_lines
26
from bzrlib.tests import TestCaseInTempDir
27
from bzrlib.transport.local import LocalTransport
28
from bzrlib.transactions import PassThroughTransaction
31
class KnitTests(TestCaseInTempDir):
33
def add_stock_one_and_one_a(self, k):
34
k.add_lines('text-1', [], split_lines(TEXT_1))
35
k.add_lines('text-1a', ['text-1'], split_lines(TEXT_1A))
37
def test_knit_constructor(self):
38
"""Construct empty k"""
39
knit = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
41
def test_knit_add(self):
42
"""Store one text in knit and retrieve"""
43
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
44
k.add_lines('text-1', [], split_lines(TEXT_1))
45
self.assertTrue(k.has_version('text-1'))
46
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
48
def test_knit_reload(self):
49
"""Store and reload a knit"""
50
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
51
k.add_lines('text-1', [], split_lines(TEXT_1))
53
k2 = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'r', KnitPlainFactory())
54
self.assertTrue(k2.has_version('text-1'))
55
self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
57
def test_knit_several(self):
58
"""Store several texts in a knit"""
59
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
60
k.add_lines('text-1', [], split_lines(TEXT_1))
61
k.add_lines('text-2', [], split_lines(TEXT_2))
62
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
63
self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
65
def test_repeated_add(self):
66
"""Knit traps attempt to replace existing version"""
67
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
68
k.add_lines('text-1', [], split_lines(TEXT_1))
69
self.assertRaises(RevisionAlreadyPresent,
71
'text-1', [], split_lines(TEXT_1))
74
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
75
k.add_lines('text-1', [], [])
76
self.assertEquals(k.get_lines('text-1'), [])
78
def test_incomplete(self):
79
"""Test if texts without a ending line-end can be inserted and
81
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
83
k.add_lines('text-1', [], ['a\n', 'b' ])
84
k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
85
self.assertEquals(k.get_lines('text-1'), ['a\n', 'b' ])
86
self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
89
"""Expression of knit delta as lines"""
90
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
91
td = list(line_delta(TEXT_1.splitlines(True),
92
TEXT_1A.splitlines(True)))
93
self.assertEqualDiff(''.join(td), delta_1_1a)
94
out = apply_line_delta(TEXT_1.splitlines(True), td)
95
self.assertEqualDiff(''.join(out), TEXT_1A)
97
def test_add_with_parents(self):
98
"""Store in knit with parents"""
99
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
100
self.add_stock_one_and_one_a(k)
101
self.assertEquals(k.get_parents('text-1'), [])
102
self.assertEquals(k.get_parents('text-1a'), ['text-1'])
104
def test_ancestry(self):
105
"""Store in knit with parents"""
106
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory())
107
self.add_stock_one_and_one_a(k)
108
self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
110
def test_add_delta(self):
111
"""Store in knit with parents"""
112
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(),
114
self.add_stock_one_and_one_a(k)
115
self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
117
def test_annotate(self):
119
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
121
self.insert_and_test_small_annotate(k)
123
def insert_and_test_small_annotate(self, k):
124
"""test annotation with k works correctly."""
125
k.add_lines('text-1', [], ['a\n', 'b\n'])
126
k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
128
origins = k.annotate('text-2')
129
self.assertEquals(origins[0], ('text-1', 'a\n'))
130
self.assertEquals(origins[1], ('text-2', 'c\n'))
132
def test_annotate_fulltext(self):
134
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(),
136
self.insert_and_test_small_annotate(k)
138
def test_annotate_merge_1(self):
139
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
140
k.add_lines('text-a1', [], ['a\n', 'b\n'])
141
k.add_lines('text-a2', [], ['d\n', 'c\n'])
142
k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
143
origins = k.annotate('text-am')
144
self.assertEquals(origins[0], ('text-a2', 'd\n'))
145
self.assertEquals(origins[1], ('text-a1', 'b\n'))
147
def test_annotate_merge_2(self):
148
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
149
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
150
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
151
k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
152
origins = k.annotate('text-am')
153
self.assertEquals(origins[0], ('text-a1', 'a\n'))
154
self.assertEquals(origins[1], ('text-a2', 'y\n'))
155
self.assertEquals(origins[2], ('text-a1', 'c\n'))
157
def test_annotate_merge_9(self):
158
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
159
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
160
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
161
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
162
origins = k.annotate('text-am')
163
self.assertEquals(origins[0], ('text-am', 'k\n'))
164
self.assertEquals(origins[1], ('text-a2', 'y\n'))
165
self.assertEquals(origins[2], ('text-a1', 'c\n'))
167
def test_annotate_merge_3(self):
168
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
169
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
170
k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
171
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
172
origins = k.annotate('text-am')
173
self.assertEquals(origins[0], ('text-am', 'k\n'))
174
self.assertEquals(origins[1], ('text-a2', 'y\n'))
175
self.assertEquals(origins[2], ('text-a2', 'z\n'))
177
def test_annotate_merge_4(self):
178
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
179
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
180
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
181
k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
182
k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
183
origins = k.annotate('text-am')
184
self.assertEquals(origins[0], ('text-a1', 'a\n'))
185
self.assertEquals(origins[1], ('text-a1', 'b\n'))
186
self.assertEquals(origins[2], ('text-a2', 'z\n'))
188
def test_annotate_merge_5(self):
189
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
190
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
191
k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
192
k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
193
k.add_lines('text-am',
194
['text-a1', 'text-a2', 'text-a3'],
195
['a\n', 'e\n', 'z\n'])
196
origins = k.annotate('text-am')
197
self.assertEquals(origins[0], ('text-a1', 'a\n'))
198
self.assertEquals(origins[1], ('text-a2', 'e\n'))
199
self.assertEquals(origins[2], ('text-a3', 'z\n'))
201
def test_annotate_file_cherry_pick(self):
202
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory())
203
k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
204
k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
205
k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
206
origins = k.annotate('text-3')
207
self.assertEquals(origins[0], ('text-1', 'a\n'))
208
self.assertEquals(origins[1], ('text-1', 'b\n'))
209
self.assertEquals(origins[2], ('text-1', 'c\n'))
211
def test_knit_join(self):
212
"""Store in knit with parents"""
213
k1 = KnitVersionedFile(LocalTransport('.'), 'test1.knit', 'w', KnitPlainFactory())
214
k1.add_lines('text-a', [], split_lines(TEXT_1))
215
k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
217
k1.add_lines('text-c', [], split_lines(TEXT_1))
218
k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
220
k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
222
k2 = KnitVersionedFile(LocalTransport('.'), 'test2.knit', 'w', KnitPlainFactory())
223
count = k2.join(k1, version_ids=['text-m'])
224
self.assertEquals(count, 5)
225
self.assertTrue(k2.has_version('text-a'))
226
self.assertTrue(k2.has_version('text-c'))
228
def test_reannotate(self):
229
k1 = KnitVersionedFile(LocalTransport('.'), 'test1', 'w',
230
KnitAnnotateFactory())
232
k1.add_lines('text-a', [], ['a\n', 'b\n'])
234
k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
236
k2 = KnitVersionedFile(LocalTransport('.'), 'test2', 'w',
237
KnitAnnotateFactory())
238
k2.join(k1, version_ids=['text-b'])
241
k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
243
k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
245
k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
247
# test-c will have index 3
248
k1.join(k2, version_ids=['text-c'])
250
lines = k1.get_lines('text-c')
251
self.assertEquals(lines, ['z\n', 'c\n'])
253
origins = k1.annotate('text-c')
254
self.assertEquals(origins[0], ('text-c', 'z\n'))
255
self.assertEquals(origins[1], ('text-b', 'c\n'))
267
Banana cup cake recipe
278
Banana cup cake recipe
294
def line_delta(from_lines, to_lines):
295
"""Generate line-based delta from one text to another"""
296
s = difflib.SequenceMatcher(None, from_lines, to_lines)
297
for op in s.get_opcodes():
300
yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
301
for i in range(op[3], op[4]):
305
def apply_line_delta(basis_lines, delta_lines):
306
"""Apply a line-based perfect diff
308
basis_lines -- text to apply the patch to
309
delta_lines -- diff instructions and content
314
while i < len(delta_lines):
316
a, b, c = map(long, l.split(','))
318
out[offset+a:offset+b] = delta_lines[i:i+c]
320
offset = offset + (b - a) + c