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
self.t = PassThroughTransaction()
40
knit = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
42
def test_knit_add(self):
43
"""Store one text in knit and retrieve"""
44
self.t = PassThroughTransaction()
45
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
46
k.add_lines('text-1', [], split_lines(TEXT_1))
47
self.assertTrue(k.has_version('text-1'))
48
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
50
def test_knit_reload(self):
51
"""Store and reload a knit"""
52
self.t = PassThroughTransaction()
53
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
54
k.add_lines('text-1', [], split_lines(TEXT_1))
56
k2 = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'r', KnitPlainFactory(), self.t)
57
self.assertTrue(k2.has_version('text-1'))
58
self.assertEqualDiff(''.join(k2.get_lines('text-1')), TEXT_1)
60
def test_knit_several(self):
61
"""Store several texts in a knit"""
62
self.t = PassThroughTransaction()
63
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
64
k.add_lines('text-1', [], split_lines(TEXT_1))
65
k.add_lines('text-2', [], split_lines(TEXT_2))
66
self.assertEqualDiff(''.join(k.get_lines('text-1')), TEXT_1)
67
self.assertEqualDiff(''.join(k.get_lines('text-2')), TEXT_2)
69
def test_repeated_add(self):
70
"""Knit traps attempt to replace existing version"""
71
self.t = PassThroughTransaction()
72
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
73
k.add_lines('text-1', [], split_lines(TEXT_1))
74
self.assertRaises(RevisionAlreadyPresent,
76
'text-1', [], split_lines(TEXT_1))
79
self.t = PassThroughTransaction()
80
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
81
k.add_lines('text-1', [], [])
82
self.assertEquals(k.get_lines('text-1'), [])
84
def test_incomplete(self):
85
"""Test if texts without a ending line-end can be inserted and
87
self.t = PassThroughTransaction()
88
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
90
k.add_lines('text-1', [], ['a\n', 'b' ])
91
k.add_lines('text-2', ['text-1'], ['a\rb\n', 'b\n'])
92
self.assertEquals(k.get_lines('text-1'), ['a\n', 'b' ])
93
self.assertEquals(k.get_lines('text-2'), ['a\rb\n', 'b\n'])
96
"""Expression of knit delta as lines"""
97
self.t = PassThroughTransaction()
98
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
99
td = list(line_delta(TEXT_1.splitlines(True),
100
TEXT_1A.splitlines(True)))
101
self.assertEqualDiff(''.join(td), delta_1_1a)
102
out = apply_line_delta(TEXT_1.splitlines(True), td)
103
self.assertEqualDiff(''.join(out), TEXT_1A)
105
def test_add_with_parents(self):
106
"""Store in knit with parents"""
107
self.t = PassThroughTransaction()
108
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
109
self.add_stock_one_and_one_a(k)
110
self.assertEquals(k.get_parents('text-1'), [])
111
self.assertEquals(k.get_parents('text-1a'), ['text-1'])
113
def test_ancestry(self):
114
"""Store in knit with parents"""
115
self.t = PassThroughTransaction()
116
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t)
117
self.add_stock_one_and_one_a(k)
118
self.assertEquals(set(k.get_ancestry(['text-1a'])), set(['text-1a', 'text-1']))
120
def test_add_delta(self):
121
"""Store in knit with parents"""
122
self.t = PassThroughTransaction()
123
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitPlainFactory(), self.t,
125
self.add_stock_one_and_one_a(k)
126
self.assertEqualDiff(''.join(k.get_lines('text-1a')), TEXT_1A)
128
def test_annotate(self):
130
self.t = PassThroughTransaction()
131
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
133
self.insert_and_test_small_annotate(k)
135
def insert_and_test_small_annotate(self, k):
136
"""test annotation with k works correctly."""
137
k.add_lines('text-1', [], ['a\n', 'b\n'])
138
k.add_lines('text-2', ['text-1'], ['a\n', 'c\n'])
140
origins = k.annotate('text-2')
141
self.assertEquals(origins[0], ('text-1', 'a\n'))
142
self.assertEquals(origins[1], ('text-2', 'c\n'))
144
def test_annotate_fulltext(self):
146
self.t = PassThroughTransaction()
147
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t,
149
self.insert_and_test_small_annotate(k)
151
def test_annotate_merge_1(self):
152
self.t = PassThroughTransaction()
153
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
154
k.add_lines('text-a1', [], ['a\n', 'b\n'])
155
k.add_lines('text-a2', [], ['d\n', 'c\n'])
156
k.add_lines('text-am', ['text-a1', 'text-a2'], ['d\n', 'b\n'])
157
origins = k.annotate('text-am')
158
self.assertEquals(origins[0], ('text-a2', 'd\n'))
159
self.assertEquals(origins[1], ('text-a1', 'b\n'))
161
def test_annotate_merge_2(self):
162
self.t = PassThroughTransaction()
163
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
164
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
165
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
166
k.add_lines('text-am', ['text-a1', 'text-a2'], ['a\n', 'y\n', 'c\n'])
167
origins = k.annotate('text-am')
168
self.assertEquals(origins[0], ('text-a1', 'a\n'))
169
self.assertEquals(origins[1], ('text-a2', 'y\n'))
170
self.assertEquals(origins[2], ('text-a1', 'c\n'))
172
def test_annotate_merge_9(self):
173
self.t = PassThroughTransaction()
174
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
175
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
176
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
177
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'c\n'])
178
origins = k.annotate('text-am')
179
self.assertEquals(origins[0], ('text-am', 'k\n'))
180
self.assertEquals(origins[1], ('text-a2', 'y\n'))
181
self.assertEquals(origins[2], ('text-a1', 'c\n'))
183
def test_annotate_merge_3(self):
184
self.t = PassThroughTransaction()
185
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
186
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
187
k.add_lines('text-a2', [] ,['x\n', 'y\n', 'z\n'])
188
k.add_lines('text-am', ['text-a1', 'text-a2'], ['k\n', 'y\n', 'z\n'])
189
origins = k.annotate('text-am')
190
self.assertEquals(origins[0], ('text-am', 'k\n'))
191
self.assertEquals(origins[1], ('text-a2', 'y\n'))
192
self.assertEquals(origins[2], ('text-a2', 'z\n'))
194
def test_annotate_merge_4(self):
195
self.t = PassThroughTransaction()
196
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
197
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
198
k.add_lines('text-a2', [], ['x\n', 'y\n', 'z\n'])
199
k.add_lines('text-a3', ['text-a1'], ['a\n', 'b\n', 'p\n'])
200
k.add_lines('text-am', ['text-a2', 'text-a3'], ['a\n', 'b\n', 'z\n'])
201
origins = k.annotate('text-am')
202
self.assertEquals(origins[0], ('text-a1', 'a\n'))
203
self.assertEquals(origins[1], ('text-a1', 'b\n'))
204
self.assertEquals(origins[2], ('text-a2', 'z\n'))
206
def test_annotate_merge_5(self):
207
self.t = PassThroughTransaction()
208
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
209
k.add_lines('text-a1', [], ['a\n', 'b\n', 'c\n'])
210
k.add_lines('text-a2', [], ['d\n', 'e\n', 'f\n'])
211
k.add_lines('text-a3', [], ['x\n', 'y\n', 'z\n'])
212
k.add_lines('text-am',
213
['text-a1', 'text-a2', 'text-a3'],
214
['a\n', 'e\n', 'z\n'])
215
origins = k.annotate('text-am')
216
self.assertEquals(origins[0], ('text-a1', 'a\n'))
217
self.assertEquals(origins[1], ('text-a2', 'e\n'))
218
self.assertEquals(origins[2], ('text-a3', 'z\n'))
220
def test_annotate_file_cherry_pick(self):
221
self.t = PassThroughTransaction()
222
k = KnitVersionedFile(LocalTransport('.'), 'test.knit', 'w', KnitAnnotateFactory(), self.t)
223
k.add_lines('text-1', [], ['a\n', 'b\n', 'c\n'])
224
k.add_lines('text-2', ['text-1'], ['d\n', 'e\n', 'f\n'])
225
k.add_lines('text-3', ['text-2', 'text-1'], ['a\n', 'b\n', 'c\n'])
226
origins = k.annotate('text-3')
227
self.assertEquals(origins[0], ('text-1', 'a\n'))
228
self.assertEquals(origins[1], ('text-1', 'b\n'))
229
self.assertEquals(origins[2], ('text-1', 'c\n'))
231
def test_knit_join(self):
232
"""Store in knit with parents"""
233
self.t = PassThroughTransaction()
234
k1 = KnitVersionedFile(LocalTransport('.'), 'test1.knit', 'w', KnitPlainFactory(), self.t)
235
k1.add_lines('text-a', [], split_lines(TEXT_1))
236
k1.add_lines('text-b', ['text-a'], split_lines(TEXT_1))
238
k1.add_lines('text-c', [], split_lines(TEXT_1))
239
k1.add_lines('text-d', ['text-c'], split_lines(TEXT_1))
241
k1.add_lines('text-m', ['text-b', 'text-d'], split_lines(TEXT_1))
243
k2 = KnitVersionedFile(LocalTransport('.'), 'test2.knit', 'w', KnitPlainFactory(), self.t)
244
count = k2.join(k1, version_ids=['text-m'])
245
self.assertEquals(count, 5)
246
self.assertTrue(k2.has_version('text-a'))
247
self.assertTrue(k2.has_version('text-c'))
249
def test_reannotate(self):
250
self.t = PassThroughTransaction()
251
k1 = KnitVersionedFile(LocalTransport('.'), 'test1', 'w',
252
KnitAnnotateFactory(), self.t)
254
k1.add_lines('text-a', [], ['a\n', 'b\n'])
256
k1.add_lines('text-b', ['text-a'], ['a\n', 'c\n'])
258
k2 = KnitVersionedFile(LocalTransport('.'), 'test2', 'w',
259
KnitAnnotateFactory(), self.t)
260
k2.join(k1, version_ids=['text-b'])
263
k1.add_lines('text-X', ['text-b'], ['a\n', 'b\n'])
265
k2.add_lines('text-c', ['text-b'], ['z\n', 'c\n'])
267
k2.add_lines('text-Y', ['text-b'], ['b\n', 'c\n'])
269
# test-c will have index 3
270
k1.join(k2, version_ids=['text-c'])
272
lines = k1.get_lines('text-c')
273
self.assertEquals(lines, ['z\n', 'c\n'])
275
origins = k1.annotate('text-c')
276
self.assertEquals(origins[0], ('text-c', 'z\n'))
277
self.assertEquals(origins[1], ('text-b', 'c\n'))
289
Banana cup cake recipe
300
Banana cup cake recipe
316
def line_delta(from_lines, to_lines):
317
"""Generate line-based delta from one text to another"""
318
s = difflib.SequenceMatcher(None, from_lines, to_lines)
319
for op in s.get_opcodes():
322
yield '%d,%d,%d\n' % (op[1], op[2], op[4]-op[3])
323
for i in range(op[3], op[4]):
327
def apply_line_delta(basis_lines, delta_lines):
328
"""Apply a line-based perfect diff
330
basis_lines -- text to apply the patch to
331
delta_lines -- diff instructions and content
336
while i < len(delta_lines):
338
a, b, c = map(long, l.split(','))
340
out[offset+a:offset+b] = delta_lines[i:i+c]
342
offset = offset + (b - a) + c