1
# Copyright (C) 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 of the dirstate functionality being built for WorkingTreeFormat4."""
21
from bzrlib import dirstate
22
from bzrlib.tests import TestCaseWithTransport
26
# test 0 parents, 1 parent, 4 parents.
27
# test unicode parents, non unicode parents
28
# test all change permutations in one and two parents.
29
# i.e. file in parent 1, dir in parent 2, symlink in tree.
30
# test that renames in the tree result in correct parent paths
31
# Test get state from a file, then asking for lines.
32
# write a smaller state, and check the file has been truncated.
33
# add a entry when its in state deleted
34
# revision attribute for root entries.
35
# test that utf8 strings are preserved in _row_to_line
36
# test parent manipulation
37
# test parents that are null in save : i.e. no record in the parent tree for this.
38
# todo: _set_data records ghost parents.
40
class TestTreeToDirstate(TestCaseWithTransport):
42
def test_empty_to_dirstate(self):
43
"""We should be able to create a dirstate for an empty tree."""
44
# There are no files on disk and no parents
45
tree = self.make_branch_and_tree('tree')
46
state = dirstate.DirState.from_tree(tree, 'dirstate')
48
# an inner function because there is no parameterisation at this point
49
# if we make it reusable that would be a good thing.
50
self.assertEqual([], state.get_parent_ids())
51
# there should be no ghosts in this tree.
52
self.assertEqual([], state.get_ghosts())
53
# there should be one fileid in this tree - the root of the tree.
54
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
56
[(['', '', 'directory', tree.inventory.root.file_id, 0, root_stat_pack, ''], [])],
57
list(state._iter_rows()))
59
state = dirstate.DirState.on_file('dirstate')
62
def test_1_parents_empty_to_dirstate(self):
63
# create a parent by doing a commit
64
tree = self.make_branch_and_tree('tree')
65
rev_id = tree.commit('first post')
66
state = dirstate.DirState.from_tree(tree, 'dirstate')
67
# we want to be able to get the lines of the dirstate that we will
69
lines = state.get_lines()
70
# we now have parent revisions, and all the files in the tree were
71
# last modified in the parent.
73
'#bazaar dirstate flat format 1\n'
74
'adler32: [0-9-][0-9]*\n'
78
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
79
'\x00$') % rev_id.encode('utf8')
80
self.assertContainsRe(''.join(lines), expected_lines_re)
82
def test_2_parents_empty_to_dirstate(self):
83
# create a parent by doing a commit
84
tree = self.make_branch_and_tree('tree')
85
rev_id = tree.commit('first post')
86
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
87
rev_id2 = tree2.commit('second post', allow_pointless=True)
88
tree.merge_from_branch(tree2.branch)
89
state = dirstate.DirState.from_tree(tree, 'dirstate')
90
# we want to be able to get the lines of the dirstate that we will
92
lines = state.get_lines()
93
# we now have parent revisions, and all the files in the tree were
94
# last modified in the parent.
96
'#bazaar dirstate flat format 1\n'
97
'adler32: [0-9-][0-9]*\n'
99
'2\x00.*\x00.*\x00\n\x00'
101
'\x00\x00d\x00TREE_ROOT\x000\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
102
'\x00$') % (rev_id.encode('utf8'), rev_id2.encode('utf8'))
103
self.assertContainsRe(''.join(lines), expected_lines_re)
105
def test_empty_unknowns_are_ignored_to_dirstate(self):
106
"""We should be able to create a dirstate for an empty tree."""
107
# There are no files on disk and no parents
108
tree = self.make_branch_and_tree('tree')
109
self.build_tree(['tree/unknown'])
110
state = dirstate.DirState.from_tree(tree, 'dirstate')
111
# we want to be able to get the lines of the dirstate that we will
113
lines = state.get_lines()
114
expected_lines_re = (
115
'#bazaar dirstate flat format 1\n'
116
'adler32: [0-9-][0-9]*\n'
120
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00\n'
122
self.assertContainsRe(''.join(lines), expected_lines_re)
124
def get_tree_with_a_file(self):
125
tree = self.make_branch_and_tree('tree')
126
self.build_tree(['tree/a file'])
127
tree.add('a file', 'a file id')
130
def test_non_empty_no_parents_to_dirstate(self):
131
"""We should be able to create a dirstate for an empty tree."""
132
# There are files on disk and no parents
133
tree = self.get_tree_with_a_file()
134
state = dirstate.DirState.from_tree(tree, 'dirstate')
135
# we want to be able to get the lines of the dirstate that we will
137
lines = state.get_lines()
138
expected_lines_re = (
139
'#bazaar dirstate flat format 1\n'
140
'adler32: [0-9-][0-9]*\n'
144
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00\n'
145
'\x00\x00a file\x00f\x00a file id\x0024\x00[0-9a-zA-Z+/]{32}\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00'
147
self.assertContainsRe(''.join(lines), expected_lines_re)
149
def test_1_parents_not_empty_to_dirstate(self):
150
# create a parent by doing a commit
151
tree = self.get_tree_with_a_file()
152
rev_id = tree.commit('first post')
153
# change the current content to be different this will alter stat, sha
155
self.build_tree_contents([('tree/a file', 'new content\n')])
156
state = dirstate.DirState.from_tree(tree, 'dirstate')
157
# we want to be able to get the lines of the dirstate that we will
159
lines = state.get_lines()
160
# we now have parent revisions, and all the files in the tree were
161
# last modified in the parent.
162
expected_lines_re = (
163
'#bazaar dirstate flat format 1\n'
164
'adler32: [0-9-][0-9]*\n'
168
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
169
'\x00\x00a file\x00f\x00a file id\x0012\x00[0-9a-zA-Z+/]{32}\x008b787bd9293c8b962c7a637a9fdbf627fe68610e\x00%s\x00f\x00\x00a file\x0024\x00n\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00\n'
170
'\x00$') % (rev_id.encode('utf8'), rev_id.encode('utf8'))
171
self.assertContainsRe(''.join(lines), expected_lines_re)
173
def test_2_parents_not_empty_to_dirstate(self):
174
# create a parent by doing a commit
175
tree = self.get_tree_with_a_file()
176
rev_id = tree.commit('first post')
177
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
178
# change the current content to be different this will alter stat, sha
180
self.build_tree_contents([('tree2/a file', 'merge content\n')])
181
rev_id2 = tree2.commit('second post')
182
tree.merge_from_branch(tree2.branch)
183
# change the current content to be different this will alter stat, sha
184
# and length again, giving us three distinct values:
185
self.build_tree_contents([('tree/a file', 'new content\n')])
186
state = dirstate.DirState.from_tree(tree, 'dirstate')
187
# we want to be able to get the lines of the dirstate that we will
189
lines = state.get_lines()
190
# we now have parent revisions, and all the files in the tree were
191
# last modified in the parent.
192
expected_lines_re = (
193
'#bazaar dirstate flat format 1\n'
194
'adler32: [0-9-][0-9]*\n'
196
'2\x00.*\x00.*\x00\n\x00'
198
'\x00\x00d\x00TREE_ROOT\x000\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n\x00'
199
'\x00a file\x00f\x00a file id\x0012\x00[0-9a-zA-Z+/]{32}\x008b787bd9293c8b962c7a637a9fdbf627fe68610e\x00%s\x00f\x00\x00a file\x0024\x00n\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00%s\x00f\x00\x00a file\x0014\x00n\x00314d796174c9412647c3ce07dfb5d36a94e72958\x00\n\x00$'
200
% (rev_id.encode('utf8'), rev_id2.encode('utf8'),
201
rev_id.encode('utf8'), rev_id2.encode('utf8')))
202
self.assertContainsRe(''.join(lines), expected_lines_re)
205
class TestDirStateOnFile(TestCaseWithTransport):
207
def test_construct_with_path(self):
208
tree = self.make_branch_and_tree('tree')
209
state = dirstate.DirState.from_tree(tree, 'dirstate')
210
# we want to be able to get the lines of the dirstate that we will
212
lines = state.get_lines()
213
self.build_tree_contents([('dirstate', ''.join(lines))])
215
state = dirstate.DirState.on_file('dirstate')
216
# ask it for a parents list
217
self.assertEqual([], state.get_parent_ids())
218
# doing a save should work here as there have been no changes.
221
class TestDirStateInitialize(TestCaseWithTransport):
223
def test_initialize(self):
224
state = dirstate.DirState.initialize('dirstate')
225
self.assertIsInstance(state, dirstate.DirState)
226
self.assertFileEqual(
227
'#bazaar dirstate flat format 1\n'
228
'adler32: -455929114\n'
232
# after the 0 parent count, there is the \x00\n\x00 line delim
233
# then '' for dir, '' for basame, and then 'd' for directory.
234
# then the root value, 0 size, our constant xxxx packed stat, and
235
# an empty sha value. Finally a new \x00\n\x00 delimiter
236
'\x00\x00d\x00TREE_ROOT\x000\x00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x00\x00\n'
241
class TestDirstateManipulations(TestCaseWithTransport):
243
def test_add_ghost_tree(self):
244
state = dirstate.DirState.initialize('dirstate')
245
state.add_parent_tree('a-ghost', None)
246
# now the parent list should be changed:
247
self.assertEqual(['a-ghost'], state.get_parent_ids())
248
self.assertEqual(['a-ghost'], state.get_ghosts())
249
# save the state and reopen to check its persistent
251
state = dirstate.DirState.on_file('dirstate')
252
self.assertEqual(['a-ghost'], state.get_parent_ids())
253
self.assertEqual(['a-ghost'], state.get_ghosts())
255
def test_set_parent_trees_no_content(self):
256
# set_parent_trees is a slow but important api to support.
257
state = dirstate.DirState.initialize('dirstate')
258
tree1 = self.make_branch_and_memory_tree('tree1')
261
revid1 = tree1.commit('foo')
263
tree2 = self.make_branch_and_memory_tree('tree2')
266
revid2 = tree2.commit('foo')
268
state.set_parent_trees(
269
((revid1, tree1.branch.repository.revision_tree(revid1)),
270
(revid2, tree2.branch.repository.revision_tree(revid2)),
271
('ghost-rev', None)),
273
# check we can reopen and use the dirstate after setting parent trees.
275
state = dirstate.DirState.on_file('dirstate')
276
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
277
# iterating the entire state ensures that the state is parsable.
278
list(state._iter_rows())
279
# be sure that it sets not appends - change it
280
state.set_parent_trees(
281
((revid1, tree1.branch.repository.revision_tree(revid1)),
282
('ghost-rev', None)),
284
# and now put it back.
285
state.set_parent_trees(
286
((revid1, tree1.branch.repository.revision_tree(revid1)),
287
(revid2, tree2.branch.repository.revision_tree(revid2)),
288
('ghost-rev', tree2.branch.repository.revision_tree(None))),
290
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
291
# the ghost should be recorded as such by set_parent_trees.
292
self.assertEqual(['ghost-rev'], state.get_ghosts())
294
[(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), [])],
295
list(state._iter_rows()))
297
### add a path via _set_data - so we dont need delta work, just
298
# raw data in, and ensure that it comes out via get_lines happily.
300
def test_add_path_to_root_no_parents_all_data(self):
301
# The most trivial addition of a path is when there are no parents and
302
# its in the root and all data about the file is supplied
303
state = dirstate.DirState.initialize('dirstate')
304
self.build_tree(['a file'])
305
stat = os.lstat('a file')
306
# the 1*20 is the sha1 pretend value.
307
state.add('a file', 'a file id', 'file', stat, '1'*20)
310
class TestGetLines(TestCaseWithTransport):
312
def test_adding_ghost_tree_sets_ghosts_line(self):
313
state = dirstate.DirState.initialize('dirstate')
314
state.add_parent_tree('a-ghost', None)
315
self.assertEqual(['#bazaar dirstate flat format 1\n',
316
'adler32: 1202264142\n',
318
'1\x00a-ghost\x00\n\x00'
319
'1\x00a-ghost\x00\n\x00'
320
'\x00\x00d\x00TREE_ROOT\x000\x00'
321
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x00\x00\n\x00'],
324
def test_adding_tree_changes_lines(self):
325
state = dirstate.DirState.initialize('dirstate')
326
lines = list(state.get_lines())
327
state.add_parent_tree('a-ghost', None)
328
self.assertNotEqual(lines, state.get_lines())
330
def test_get_line_with_2_rows(self):
331
state = dirstate.DirState.initialize('dirstate')
332
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
333
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
334
root_row = (root_row_direntry, [])
336
# add a file in the root
337
subdir_row = (['', 'subdir', 'directory', 'subdir-id', 0, packed_stat, ''], [])
338
dirblocks.append(('', [subdir_row]))
339
state._set_data([], root_row, dirblocks)
340
self.assertEqual(['#bazaar dirstate flat format 1\n',
341
'adler32: 1283137489\n',
345
'\x00\x00d\x00a-root-value\x000'
346
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\x00\n\x00\x00subdir\x00'
347
'd\x00subdir-id\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\x00'
351
def test_row_to_line(self):
352
state = dirstate.DirState.initialize('dirstate')
353
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
354
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
355
root_parent_direntries = []
356
root_row = (root_row_direntry, root_parent_direntries)
357
self.assertEqual('\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00', state._row_to_line(root_row))
359
def test_row_to_line_with_parent(self):
360
state = dirstate.DirState.initialize('dirstate')
361
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
362
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
363
# one parent that was a file at path /dirname/basename
364
root_parent_direntries = [('revid', 'file', 'dirname', 'basename', 0, False, '')]
365
root_row = (root_row_direntry, root_parent_direntries)
367
'\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
368
'\x00revid\x00f\x00dirname\x00basename\x000\x00n\x00',
369
state._row_to_line(root_row))
371
def test_row_to_line_with_two_parents(self):
372
state = dirstate.DirState.initialize('dirstate')
373
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
374
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
375
# two parent entires: one that was a file at path /dirname/basename
376
# and one that was a directory at /
377
root_parent_direntries = [('revid', 'file', 'dirname', 'basename', 0, False, ''),
378
('revid2', 'directory', '', '', 0, False, '')]
379
root_row = (root_row_direntry, root_parent_direntries)
381
'\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
382
'\x00revid\x00f\x00dirname\x00basename\x000\x00n\x00'
383
'\x00revid2\x00d\x00\x00\x000\x00n\x00',
384
state._row_to_line(root_row))
386
def test_iter_rows(self):
387
# we should be able to iterate the dirstate rows from end to end
388
# this is for get_lines to be easy to read.
389
state = dirstate.DirState.initialize('dirstate')
390
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
391
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
392
root_row = (root_row_direntry, [])
394
# add two files in the root
395
subdir_row = (['', 'subdir', 'directory', 'subdir-id', 0, packed_stat, ''], [])
396
afile_row = (['', 'afile', 'file', 'afile-id', 34, packed_stat, 'sha1value'], [])
397
dirblocks.append(('', [subdir_row, afile_row]))
399
file_row2 = (['', '2file', 'file', '2file-id', 23, packed_stat, 'sha1value'], [])
400
dirblocks.append(('subdir', [file_row2]))
401
state._set_data([], root_row, dirblocks)
402
expected_rows = [root_row, subdir_row, afile_row, file_row2]
403
self.assertEqual(expected_rows, list(state._iter_rows()))