1
# Copyright (C) 2006, 2007 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, errors
22
from bzrlib.dirstate import DirState
23
from bzrlib.memorytree import MemoryTree
24
from bzrlib.tests import TestCaseWithTransport
28
# test DirStateRevisionTree : test filtering out of deleted files does not
29
# filter out files called RECYCLED.BIN ;)
30
# test 0 parents, 1 parent, 4 parents.
31
# test unicode parents, non unicode parents
32
# test all change permutations in one and two parents.
33
# i.e. file in parent 1, dir in parent 2, symlink in tree.
34
# test that renames in the tree result in correct parent paths
35
# Test get state from a file, then asking for lines.
36
# write a smaller state, and check the file has been truncated.
37
# add a entry when its in state deleted
38
# revision attribute for root entries.
39
# test that utf8 strings are preserved in _row_to_line
40
# test parent manipulation
41
# test parents that are null in save : i.e. no record in the parent tree for this.
42
# todo: _set_data records ghost parents.
44
# general checks for NOT_IN_MEMORY error conditions.
45
# set_path_id on a NOT_IN_MEMORY dirstate
46
# set_path_id unicode support
47
# set_path_id setting id of a path not root
48
# set_path_id setting id when there are parents without the id in the parents
49
# set_path_id setting id when there are parents with the id in the parents
50
# set_path_id setting id when state is not in memory
51
# set_path_id setting id when state is in memory unmodified
52
# set_path_id setting id when state is in memory modified
54
class TestCaseWithDirState(TestCaseWithTransport):
55
"""Helper functions for creating DirState objects with various content."""
57
def create_empty_dirstate(self):
58
state = dirstate.DirState.initialize('dirstate')
61
def create_dirstate_with_root(self):
62
state = self.create_empty_dirstate()
63
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
64
root_entry_direntry = ('', '', 'a-root-value'), [
65
('d', '', 0, False, packed_stat),
68
dirblocks.append(('', [root_entry_direntry]))
69
dirblocks.append(('', []))
70
state._set_data([], dirblocks)
73
def create_dirstate_with_root_and_subdir(self):
74
state = self.create_dirstate_with_root()
75
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
76
dirblocks = list(state._dirblocks)
77
subdir_entry = ('', 'subdir', 'subdir-id'), [
78
('d', '', 0, False, packed_stat),
80
dirblocks[1][1].append(subdir_entry)
81
state._set_data([], dirblocks)
84
def create_complex_dirstate(self):
85
"""This dirstate contains multiple files and directories.
95
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
97
# Notice that a/e is an empty directory.
99
state = dirstate.DirState.initialize('dirstate')
100
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
101
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
102
root_entry = ('', '', 'a-root-value'), [
103
('d', '', 0, False, packed_stat),
105
a_entry = ('', 'a', 'a-dir'), [
106
('d', '', 0, False, packed_stat),
108
b_entry = ('', 'b', 'b-dir'), [
109
('d', '', 0, False, packed_stat),
111
c_entry = ('', 'c', 'c-file'), [
112
('f', null_sha, 10, False, packed_stat),
114
d_entry = ('', 'd', 'd-file'), [
115
('f', null_sha, 20, False, packed_stat),
117
e_entry = ('a', 'e', 'e-dir'), [
118
('d', '', 0, False, packed_stat),
120
f_entry = ('a', 'f', 'f-file'), [
121
('f', null_sha, 30, False, packed_stat),
123
g_entry = ('b', 'g', 'g-file'), [
124
('f', null_sha, 30, False, packed_stat),
126
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
127
('f', null_sha, 40, False, packed_stat),
130
dirblocks.append(('', [root_entry]))
131
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
132
dirblocks.append(('a', [e_entry, f_entry]))
133
dirblocks.append(('b', [g_entry, h_entry]))
134
state._set_data([], dirblocks)
137
def check_state_with_reopen(self, expected_result, state):
138
"""Check that state has current state expected_result.
140
This will check the current state, open the file anew and check it
143
self.assertEqual(expected_result[0], state.get_parent_ids())
144
# there should be no ghosts in this tree.
145
self.assertEqual([], state.get_ghosts())
146
# there should be one fileid in this tree - the root of the tree.
147
self.assertEqual(expected_result[1], list(state._iter_entries()))
149
state = dirstate.DirState.on_file('dirstate')
150
self.assertEqual(expected_result[1], list(state._iter_entries()))
153
class TestTreeToDirState(TestCaseWithDirState):
155
def test_empty_to_dirstate(self):
156
"""We should be able to create a dirstate for an empty tree."""
157
# There are no files on disk and no parents
158
tree = self.make_branch_and_tree('tree')
159
state = dirstate.DirState.from_tree(tree, 'dirstate')
160
expected_result = ([], [
161
(('', '', tree.path2id('')), # common details
162
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
164
self.check_state_with_reopen(expected_result, state)
166
def test_1_parents_empty_to_dirstate(self):
167
# create a parent by doing a commit
168
tree = self.make_branch_and_tree('tree')
169
rev_id = tree.commit('first post').encode('utf8')
170
state = dirstate.DirState.from_tree(tree, 'dirstate')
171
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
172
expected_result = ([rev_id], [
173
(('', '', tree.path2id('')), # common details
174
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
175
('d', '', 0, False, rev_id), # first parent details
177
self.check_state_with_reopen(expected_result, state)
179
def test_2_parents_empty_to_dirstate(self):
180
# create a parent by doing a commit
181
tree = self.make_branch_and_tree('tree')
182
rev_id = tree.commit('first post')
183
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
184
rev_id2 = tree2.commit('second post', allow_pointless=True)
185
tree.merge_from_branch(tree2.branch)
186
state = dirstate.DirState.from_tree(tree, 'dirstate')
187
expected_result = ([rev_id, rev_id2], [
188
(('', '', tree.path2id('')), # common details
189
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
190
('d', '', 0, False, rev_id), # first parent details
191
('d', '', 0, False, rev_id2), # second parent details
193
self.check_state_with_reopen(expected_result, state)
195
def test_empty_unknowns_are_ignored_to_dirstate(self):
196
"""We should be able to create a dirstate for an empty tree."""
197
# There are no files on disk and no parents
198
tree = self.make_branch_and_tree('tree')
199
self.build_tree(['tree/unknown'])
200
state = dirstate.DirState.from_tree(tree, 'dirstate')
201
expected_result = ([], [
202
(('', '', tree.path2id('')), # common details
203
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
205
self.check_state_with_reopen(expected_result, state)
207
def get_tree_with_a_file(self):
208
tree = self.make_branch_and_tree('tree')
209
self.build_tree(['tree/a file'])
210
tree.add('a file', 'a file id')
213
def test_non_empty_no_parents_to_dirstate(self):
214
"""We should be able to create a dirstate for an empty tree."""
215
# There are files on disk and no parents
216
tree = self.get_tree_with_a_file()
217
state = dirstate.DirState.from_tree(tree, 'dirstate')
218
expected_result = ([], [
219
(('', '', tree.path2id('')), # common details
220
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
222
(('', 'a file', 'a file id'), # common
223
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
226
self.check_state_with_reopen(expected_result, state)
228
def test_1_parents_not_empty_to_dirstate(self):
229
# create a parent by doing a commit
230
tree = self.get_tree_with_a_file()
231
rev_id = tree.commit('first post').encode('utf8')
232
# change the current content to be different this will alter stat, sha
234
self.build_tree_contents([('tree/a file', 'new content\n')])
235
state = dirstate.DirState.from_tree(tree, 'dirstate')
236
expected_result = ([rev_id], [
237
(('', '', tree.path2id('')), # common details
238
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
239
('d', '', 0, False, rev_id), # first parent details
241
(('', 'a file', 'a file id'), # common
242
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
243
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False, rev_id), # first parent
246
self.check_state_with_reopen(expected_result, state)
248
def test_2_parents_not_empty_to_dirstate(self):
249
# create a parent by doing a commit
250
tree = self.get_tree_with_a_file()
251
rev_id = tree.commit('first post').encode('utf8')
252
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
253
# change the current content to be different this will alter stat, sha
255
self.build_tree_contents([('tree2/a file', 'merge content\n')])
256
rev_id2 = tree2.commit('second post').encode('utf8')
257
tree.merge_from_branch(tree2.branch)
258
# change the current content to be different this will alter stat, sha
259
# and length again, giving us three distinct values:
260
self.build_tree_contents([('tree/a file', 'new content\n')])
261
state = dirstate.DirState.from_tree(tree, 'dirstate')
262
expected_result = ([rev_id, rev_id2], [
263
(('', '', tree.path2id('')), # common details
264
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
265
('d', '', 0, False, rev_id), # first parent details
266
('d', '', 0, False, rev_id2), # second parent details
268
(('', 'a file', 'a file id'), # common
269
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
270
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False, rev_id), # first parent
271
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False, rev_id2), # second parent
274
self.check_state_with_reopen(expected_result, state)
277
class TestDirStateOnFile(TestCaseWithDirState):
279
def test_construct_with_path(self):
280
tree = self.make_branch_and_tree('tree')
281
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
282
# we want to be able to get the lines of the dirstate that we will
284
lines = state.get_lines()
285
self.build_tree_contents([('dirstate', ''.join(lines))])
287
state = dirstate.DirState.on_file('dirstate')
288
# no parents, default tree content
289
expected_result = ([], [
290
(('', '', tree.path2id('')), # common details
291
# current tree details, but new from_tree skips statting, it
292
# uses set_state_from_inventory, and thus depends on the
294
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
297
self.check_state_with_reopen(expected_result, state)
299
def test_can_save_clean_on_file(self):
300
tree = self.make_branch_and_tree('tree')
301
state = dirstate.DirState.from_tree(tree, 'dirstate')
302
# doing a save should work here as there have been no changes.
304
# TODO: stat it and check it hasn't changed; may require waiting for
305
# the state accuracy window.
308
class TestDirStateInitialize(TestCaseWithDirState):
310
def test_initialize(self):
311
state = dirstate.DirState.initialize('dirstate')
312
self.assertIsInstance(state, dirstate.DirState)
313
lines = state.get_lines()
314
self.assertFileEqual(''.join(state.get_lines()),
316
expected_result = ([], [
317
(('', '', 'TREE_ROOT'), # common details
318
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
321
self.check_state_with_reopen(expected_result, state)
324
class TestDirStateManipulations(TestCaseWithDirState):
326
def test_set_state_from_inventory_no_content_no_parents(self):
327
# setting the current inventory is a slow but important api to support.
328
state = dirstate.DirState.initialize('dirstate')
329
tree1 = self.make_branch_and_memory_tree('tree1')
332
revid1 = tree1.commit('foo').encode('utf8')
333
root_id = tree1.inventory.root.file_id
334
state.set_state_from_inventory(tree1.inventory)
336
self.assertEqual(DirState.IN_MEMORY_UNMODIFIED, state._header_state)
337
self.assertEqual(DirState.IN_MEMORY_MODIFIED, state._dirblock_state)
338
expected_result = [], [
339
(('', '', root_id), [
340
('d', '', 0, False, DirState.NULLSTAT)])]
341
self.check_state_with_reopen(expected_result, state)
343
def test_set_path_id_no_parents(self):
344
"""The id of a path can be changed trivally with no parents."""
345
state = dirstate.DirState.initialize('dirstate')
346
# check precondition to be sure the state does change appropriately.
348
[(('', '', 'TREE_ROOT'), [('d', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
349
list(state._iter_entries()))
350
state.set_path_id('', 'foobarbaz')
352
(('', '', 'foobarbaz'), [('d', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
353
self.assertEqual(expected_rows, list(state._iter_entries()))
354
# should work across save too
356
state = dirstate.DirState.on_file('dirstate')
357
self.assertEqual(expected_rows, list(state._iter_entries()))
359
def test_set_parent_trees_no_content(self):
360
# set_parent_trees is a slow but important api to support.
361
state = dirstate.DirState.initialize('dirstate')
362
tree1 = self.make_branch_and_memory_tree('tree1')
365
revid1 = tree1.commit('foo')
367
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
368
tree2 = MemoryTree.create_on_branch(branch2)
370
revid2 = tree2.commit('foo')
371
root_id = tree2.inventory.root.file_id
372
state.set_path_id('', root_id)
374
state.set_parent_trees(
375
((revid1, tree1.branch.repository.revision_tree(revid1)),
376
(revid2, tree2.branch.repository.revision_tree(revid2)),
377
('ghost-rev', None)),
379
# check we can reopen and use the dirstate after setting parent trees.
381
state = dirstate.DirState.on_file('dirstate')
382
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
383
# iterating the entire state ensures that the state is parsable.
384
list(state._iter_entries())
385
# be sure that it sets not appends - change it
386
state.set_parent_trees(
387
((revid1, tree1.branch.repository.revision_tree(revid1)),
388
('ghost-rev', None)),
390
# and now put it back.
391
state.set_parent_trees(
392
((revid1, tree1.branch.repository.revision_tree(revid1)),
393
(revid2, tree2.branch.repository.revision_tree(revid2)),
394
('ghost-rev', tree2.branch.repository.revision_tree(None))),
396
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
397
# the ghost should be recorded as such by set_parent_trees.
398
self.assertEqual(['ghost-rev'], state.get_ghosts())
400
[(('', '', root_id), [
401
('d', '', 0, False, DirState.NULLSTAT),
402
('d', '', 0, False, revid1),
403
('d', '', 0, False, revid2)
405
list(state._iter_entries()))
407
def test_set_parent_trees_file_missing_from_tree(self):
408
# Adding a parent tree may reference files not in the current state.
409
# they should get listed just once by id, even if they are in two
411
# set_parent_trees is a slow but important api to support.
412
state = dirstate.DirState.initialize('dirstate')
413
tree1 = self.make_branch_and_memory_tree('tree1')
416
tree1.add(['a file'], ['file-id'], ['file'])
417
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
418
revid1 = tree1.commit('foo')
420
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
421
tree2 = MemoryTree.create_on_branch(branch2)
423
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
424
revid2 = tree2.commit('foo')
425
root_id = tree2.inventory.root.file_id
426
state.set_path_id('', root_id)
428
state.set_parent_trees(
429
((revid1, tree1.branch.repository.revision_tree(revid1)),
430
(revid2, tree2.branch.repository.revision_tree(revid2)),
432
# check the layout in memory
433
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
434
(('', '', root_id), [
435
('d', '', 0, False, DirState.NULLSTAT),
436
('d', '', 0, False, revid1.encode('utf8')),
437
('d', '', 0, False, revid2.encode('utf8'))]),
438
(('', 'a file', 'file-id'), [
439
('a', '', 0, False, ''),
440
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False, revid1.encode('utf8')),
441
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False, revid2.encode('utf8'))])
443
self.check_state_with_reopen(expected_result, state)
445
### add a path via _set_data - so we dont need delta work, just
446
# raw data in, and ensure that it comes out via get_lines happily.
448
def test_add_path_to_root_no_parents_all_data(self):
449
# The most trivial addition of a path is when there are no parents and
450
# its in the root and all data about the file is supplied
451
state = dirstate.DirState.initialize('dirstate')
452
self.build_tree(['a file'])
453
stat = os.lstat('a file')
454
# the 1*20 is the sha1 pretend value.
455
state.add('a file', 'a file id', 'file', stat, '1'*20)
456
# having added it, it should be in the output of iter_entries.
458
(('', '', 'TREE_ROOT'), [
459
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
461
(('', 'a file', 'a file id'), [
462
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree details
465
self.assertEqual(expected_entries, list(state._iter_entries()))
466
# saving and reloading should not affect this.
468
state = dirstate.DirState.on_file('dirstate')
469
self.assertEqual(expected_entries, list(state._iter_entries()))
471
def test_add_path_to_unversioned_directory(self):
472
"""Adding a path to an unversioned directory should error.
474
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
475
once dirstate is stable and if it is merged with WorkingTree3, consider
476
removing this copy of the test.
478
state = dirstate.DirState.initialize('dirstate')
479
self.build_tree(['unversioned/', 'unversioned/a file'])
480
self.assertRaises(errors.NotVersionedError, state.add,
481
'unversioned/a file', 'a file id', 'file', None, None)
483
def test_add_directory_to_root_no_parents_all_data(self):
484
# The most trivial addition of a dir is when there are no parents and
485
# its in the root and all data about the file is supplied
486
state = dirstate.DirState.initialize('dirstate')
487
self.build_tree(['a dir/'])
488
stat = os.lstat('a dir')
489
state.add('a dir', 'a dir id', 'directory', stat, None)
490
# having added it, it should be in the output of iter_entries.
492
(('', '', 'TREE_ROOT'), [
493
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
495
(('', 'a dir', 'a dir id'), [
496
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree details
499
self.assertEqual(expected_entries, list(state._iter_entries()))
500
# saving and reloading should not affect this.
502
state = dirstate.DirState.on_file('dirstate')
503
self.assertEqual(expected_entries, list(state._iter_entries()))
505
def test_add_symlink_to_root_no_parents_all_data(self):
506
# The most trivial addition of a symlink when there are no parents and
507
# its in the root and all data about the file is supplied
508
state = dirstate.DirState.initialize('dirstate')
509
## TODO: windows: dont fail this test. Also, how are symlinks meant to
510
# be represented on windows.
511
os.symlink('target', 'a link')
512
stat = os.lstat('a link')
513
state.add('a link', 'a link id', 'symlink', stat, 'target')
514
# having added it, it should be in the output of iter_entries.
516
(('', '', 'TREE_ROOT'), [
517
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
519
(('', 'a link', 'a link id'), [
520
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree details
523
self.assertEqual(expected_entries, list(state._iter_entries()))
524
# saving and reloading should not affect this.
526
state = dirstate.DirState.on_file('dirstate')
527
self.assertEqual(expected_entries, list(state._iter_entries()))
529
def test_add_directory_and_child_no_parents_all_data(self):
530
# after adding a directory, we should be able to add children to it.
531
state = dirstate.DirState.initialize('dirstate')
532
self.build_tree(['a dir/', 'a dir/a file'])
533
stat = os.lstat('a dir')
534
state.add('a dir', 'a dir id', 'directory', stat, None)
535
filestat = os.lstat('a dir/a file')
536
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
537
# having added it, it should be in the output of iter_entries.
539
(('', '', 'TREE_ROOT'), [
540
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree details
542
(('', 'a dir', 'a dir id'), [
543
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree details
545
(('a dir', 'a file', 'a file id'), [
546
('f', '1'*20, 25, False, dirstate.pack_stat(filestat)), # current tree details
549
self.assertEqual(expected_entries, list(state._iter_entries()))
550
# saving and reloading should not affect this.
552
state = dirstate.DirState.on_file('dirstate')
553
self.assertEqual(expected_entries, list(state._iter_entries()))
556
class TestGetLines(TestCaseWithDirState):
558
def test_get_line_with_2_rows(self):
559
state = self.create_dirstate_with_root_and_subdir()
560
self.assertEqual(['#bazaar dirstate flat format 2\n',
561
'adler32: -1327947603\n',
565
'\x00\x00a-root-value\x00'
566
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
567
'\x00subdir\x00subdir-id\x00'
568
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'],
571
def test_entry_to_line(self):
572
state = self.create_dirstate_with_root()
574
'\x00\x00a-root-value\x00d\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
575
state._entry_to_line(state._dirblocks[0][1][0]))
577
def test_entry_to_line_with_parent(self):
578
state = dirstate.DirState.initialize('dirstate')
579
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
580
root_entry = ('', '', 'a-root-value'), [
581
('d', '', 0, False, packed_stat), # current tree details
582
('a', 'dirname/basename', 0, False, ''), # first: a pointer to the current location
585
'\x00\x00a-root-value\x00'
586
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
587
'a\x00dirname/basename\x000\x00n\x00',
588
state._entry_to_line(root_entry))
590
def test_entry_to_line_with_two_parents_at_different_paths(self):
591
# / in the tree, at / in one parent and /dirname/basename in the other.
592
state = dirstate.DirState.initialize('dirstate')
593
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
594
root_entry = ('', '', 'a-root-value'), [
595
('d', '', 0, False, packed_stat), # current tree details
596
('d', '', 0, False, 'rev_id'), # first parent details
597
('a', 'dirname/basename', 0, False, ''), # second: a pointer to the current location
600
'\x00\x00a-root-value\x00'
601
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
602
'd\x00\x000\x00n\x00rev_id\x00'
603
'a\x00dirname/basename\x000\x00n\x00',
604
state._entry_to_line(root_entry))
606
def test_iter_entries(self):
607
# we should be able to iterate the dirstate entries from end to end
608
# this is for get_lines to be easy to read.
609
state = dirstate.DirState.initialize('dirstate')
610
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
612
root_entries = [(('', '', 'a-root-value'), [
613
('d', '', 0, False, packed_stat), # current tree details
615
dirblocks.append(('', root_entries))
616
# add two files in the root
617
subdir_entry = ('', 'subdir', 'subdir-id'), [
618
('d', '', 0, False, packed_stat), # current tree details
620
afile_entry = ('', 'afile', 'afile-id'), [
621
('f', 'sha1value', 34, False, packed_stat), # current tree details
623
dirblocks.append(('', [subdir_entry, afile_entry]))
625
file_entry2 = ('subdir', '2file', '2file-id'), [
626
('f', 'sha1value', 23, False, packed_stat), # current tree details
628
dirblocks.append(('subdir', [file_entry2]))
629
state._set_data([], dirblocks)
630
expected_entries = [root_entries[0], subdir_entry, afile_entry, file_entry2]
631
self.assertEqual(expected_entries, list(state._iter_entries()))
634
class TestGetBlockRowIndex(TestCaseWithDirState):
636
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
637
file_present, state, dirname, basename, tree_index):
638
self.assertEqual((block_index, row_index, dir_present, file_present),
639
state._get_block_entry_index(dirname, basename, tree_index))
641
block = state._dirblocks[block_index]
642
self.assertEqual(dirname, block[0])
643
if dir_present and file_present:
644
row = state._dirblocks[block_index][1][row_index]
645
self.assertEqual(dirname, row[0][0])
646
self.assertEqual(basename, row[0][1])
648
def test_simple_structure(self):
649
state = self.create_dirstate_with_root_and_subdir()
650
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
651
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
652
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
653
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
654
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'subdir', 'foo', 0)
656
def test_complex_structure_exists(self):
657
state = self.create_complex_dirstate()
658
# Make sure we can find everything that exists
659
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
660
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
661
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
662
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
663
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
664
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
665
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
666
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
667
self.assertBlockRowIndexEqual(3, 1, True, True, state, 'b', 'h\xc3\xa5', 0)
669
def test_complex_structure_missing(self):
670
state = self.create_complex_dirstate()
671
# Make sure things would be inserted in the right locations
672
# '_' comes before 'a'
673
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
674
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
675
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
676
self.assertBlockRowIndexEqual(1, 4, True, False, state, '', 'h\xc3\xa5', 0)
677
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
678
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
679
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
680
# This would be inserted between a/ and b/
681
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
683
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
686
class TestGetEntry(TestCaseWithDirState):
688
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
689
"""Check that the right entry is returned for a request to getEntry."""
690
entry = state._get_entry(index, path_utf8=path)
692
self.assertEqual((None, None), entry)
695
self.assertEqual((dirname, basename, file_id), cur[:3])
697
def test_simple_structure(self):
698
state = self.create_dirstate_with_root_and_subdir()
699
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
700
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
701
self.assertEntryEqual(None, None, None, state, 'missing', 0)
702
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
703
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
705
def test_complex_structure_exists(self):
706
state = self.create_complex_dirstate()
707
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
708
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
709
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
710
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
711
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
712
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
713
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
714
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
715
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state, 'b/h\xc3\xa5', 0)
717
def test_complex_structure_missing(self):
718
state = self.create_complex_dirstate()
719
self.assertEntryEqual(None, None, None, state, '_', 0)
720
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
721
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
722
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
724
def test_get_entry_uninitialized(self):
725
"""Calling get_entry will load data if it needs to"""
726
state = self.create_dirstate_with_root()
729
state = dirstate.DirState.on_file('dirstate')
730
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._header_state)
731
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._dirblock_state)
732
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)