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."""
28
from bzrlib.memorytree import MemoryTree
29
from bzrlib.tests import TestCase, TestCaseWithTransport
33
# test DirStateRevisionTree : test filtering out of deleted files does not
34
# filter out files called RECYCLED.BIN ;)
35
# test 0 parents, 1 parent, 4 parents.
36
# test unicode parents, non unicode parents
37
# test all change permutations in one and two parents.
38
# i.e. file in parent 1, dir in parent 2, symlink in tree.
39
# test that renames in the tree result in correct parent paths
40
# Test get state from a file, then asking for lines.
41
# write a smaller state, and check the file has been truncated.
42
# add a entry when its in state deleted
43
# revision attribute for root entries.
44
# test that utf8 strings are preserved in _row_to_line
45
# test parent manipulation
46
# test parents that are null in save : i.e. no record in the parent tree for this.
47
# todo: _set_data records ghost parents.
49
# general checks for NOT_IN_MEMORY error conditions.
50
# set_path_id on a NOT_IN_MEMORY dirstate
51
# set_path_id unicode support
52
# set_path_id setting id of a path not root
53
# set_path_id setting id when there are parents without the id in the parents
54
# set_path_id setting id when there are parents with the id in the parents
55
# set_path_id setting id when state is not in memory
56
# set_path_id setting id when state is in memory unmodified
57
# set_path_id setting id when state is in memory modified
59
class TestCaseWithDirState(TestCaseWithTransport):
60
"""Helper functions for creating DirState objects with various content."""
62
def create_empty_dirstate(self):
63
"""Return a locked but empty dirstate"""
64
state = dirstate.DirState.initialize('dirstate')
67
def create_dirstate_with_root(self):
68
"""Return a write-locked state with a single root entry."""
69
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
70
root_entry_direntry = ('', '', 'a-root-value'), [
71
('d', '', 0, False, packed_stat),
74
dirblocks.append(('', [root_entry_direntry]))
75
dirblocks.append(('', []))
76
state = self.create_empty_dirstate()
78
state._set_data([], dirblocks)
85
def create_dirstate_with_root_and_subdir(self):
86
"""Return a locked DirState with a root and a subdir"""
87
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
88
subdir_entry = ('', 'subdir', 'subdir-id'), [
89
('d', '', 0, False, packed_stat),
91
state = self.create_dirstate_with_root()
93
dirblocks = list(state._dirblocks)
94
dirblocks[1][1].append(subdir_entry)
95
state._set_data([], dirblocks)
101
def create_complex_dirstate(self):
102
"""This dirstate contains multiple files and directories.
112
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
114
# Notice that a/e is an empty directory.
116
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
117
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
118
root_entry = ('', '', 'a-root-value'), [
119
('d', '', 0, False, packed_stat),
121
a_entry = ('', 'a', 'a-dir'), [
122
('d', '', 0, False, packed_stat),
124
b_entry = ('', 'b', 'b-dir'), [
125
('d', '', 0, False, packed_stat),
127
c_entry = ('', 'c', 'c-file'), [
128
('f', null_sha, 10, False, packed_stat),
130
d_entry = ('', 'd', 'd-file'), [
131
('f', null_sha, 20, False, packed_stat),
133
e_entry = ('a', 'e', 'e-dir'), [
134
('d', '', 0, False, packed_stat),
136
f_entry = ('a', 'f', 'f-file'), [
137
('f', null_sha, 30, False, packed_stat),
139
g_entry = ('b', 'g', 'g-file'), [
140
('f', null_sha, 30, False, packed_stat),
142
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
143
('f', null_sha, 40, False, packed_stat),
146
dirblocks.append(('', [root_entry]))
147
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
148
dirblocks.append(('a', [e_entry, f_entry]))
149
dirblocks.append(('b', [g_entry, h_entry]))
150
state = dirstate.DirState.initialize('dirstate')
153
state._set_data([], dirblocks)
159
def check_state_with_reopen(self, expected_result, state):
160
"""Check that state has current state expected_result.
162
This will check the current state, open the file anew and check it
164
This function expects the current state to be locked for writing, and
165
will unlock it before re-opening.
166
This is required because we can't open a lock_read() while something
167
else has a lock_write().
168
write => mutually exclusive lock
171
# The state should already be write locked, since we just had to do
172
# some operation to get here.
173
assert state._lock_token is not None
175
self.assertEqual(expected_result[0], state.get_parent_ids())
176
# there should be no ghosts in this tree.
177
self.assertEqual([], state.get_ghosts())
178
# there should be one fileid in this tree - the root of the tree.
179
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
del state # Callers should unlock
184
state = dirstate.DirState.on_file('dirstate')
187
self.assertEqual(expected_result[1], list(state._iter_entries()))
192
class TestTreeToDirState(TestCaseWithDirState):
194
def test_empty_to_dirstate(self):
195
"""We should be able to create a dirstate for an empty tree."""
196
# There are no files on disk and no parents
197
tree = self.make_branch_and_tree('tree')
198
expected_result = ([], [
199
(('', '', tree.path2id('')), # common details
200
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
202
state = dirstate.DirState.from_tree(tree, 'dirstate')
204
self.check_state_with_reopen(expected_result, state)
206
def test_1_parents_empty_to_dirstate(self):
207
# create a parent by doing a commit
208
tree = self.make_branch_and_tree('tree')
209
rev_id = tree.commit('first post').encode('utf8')
210
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
211
expected_result = ([rev_id], [
212
(('', '', tree.path2id('')), # common details
213
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
214
('d', '', 0, False, rev_id), # first parent details
216
state = dirstate.DirState.from_tree(tree, 'dirstate')
217
self.check_state_with_reopen(expected_result, state)
220
def test_2_parents_empty_to_dirstate(self):
221
# create a parent by doing a commit
222
tree = self.make_branch_and_tree('tree')
223
rev_id = tree.commit('first post')
224
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
225
rev_id2 = tree2.commit('second post', allow_pointless=True)
226
tree.merge_from_branch(tree2.branch)
227
expected_result = ([rev_id, rev_id2], [
228
(('', '', tree.path2id('')), # common details
229
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
230
('d', '', 0, False, rev_id), # first parent details
231
('d', '', 0, False, rev_id2), # second parent details
233
state = dirstate.DirState.from_tree(tree, 'dirstate')
234
self.check_state_with_reopen(expected_result, state)
237
def test_empty_unknowns_are_ignored_to_dirstate(self):
238
"""We should be able to create a dirstate for an empty tree."""
239
# There are no files on disk and no parents
240
tree = self.make_branch_and_tree('tree')
241
self.build_tree(['tree/unknown'])
242
expected_result = ([], [
243
(('', '', tree.path2id('')), # common details
244
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
246
state = dirstate.DirState.from_tree(tree, 'dirstate')
247
self.check_state_with_reopen(expected_result, state)
249
def get_tree_with_a_file(self):
250
tree = self.make_branch_and_tree('tree')
251
self.build_tree(['tree/a file'])
252
tree.add('a file', 'a file id')
255
def test_non_empty_no_parents_to_dirstate(self):
256
"""We should be able to create a dirstate for an empty tree."""
257
# There are files on disk and no parents
258
tree = self.get_tree_with_a_file()
259
expected_result = ([], [
260
(('', '', tree.path2id('')), # common details
261
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
263
(('', 'a file', 'a file id'), # common
264
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
267
state = dirstate.DirState.from_tree(tree, 'dirstate')
268
self.check_state_with_reopen(expected_result, state)
270
def test_1_parents_not_empty_to_dirstate(self):
271
# create a parent by doing a commit
272
tree = self.get_tree_with_a_file()
273
rev_id = tree.commit('first post').encode('utf8')
274
# change the current content to be different this will alter stat, sha
276
self.build_tree_contents([('tree/a file', 'new content\n')])
277
expected_result = ([rev_id], [
278
(('', '', tree.path2id('')), # common details
279
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
280
('d', '', 0, False, rev_id), # first parent details
282
(('', 'a file', 'a file id'), # common
283
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
284
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
285
rev_id), # first parent
288
state = dirstate.DirState.from_tree(tree, 'dirstate')
289
self.check_state_with_reopen(expected_result, state)
291
def test_2_parents_not_empty_to_dirstate(self):
292
# create a parent by doing a commit
293
tree = self.get_tree_with_a_file()
294
rev_id = tree.commit('first post').encode('utf8')
295
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
296
# change the current content to be different this will alter stat, sha
298
self.build_tree_contents([('tree2/a file', 'merge content\n')])
299
rev_id2 = tree2.commit('second post').encode('utf8')
300
tree.merge_from_branch(tree2.branch)
301
# change the current content to be different this will alter stat, sha
302
# and length again, giving us three distinct values:
303
self.build_tree_contents([('tree/a file', 'new content\n')])
304
expected_result = ([rev_id, rev_id2], [
305
(('', '', tree.path2id('')), # common details
306
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
307
('d', '', 0, False, rev_id), # first parent details
308
('d', '', 0, False, rev_id2), # second parent details
310
(('', 'a file', 'a file id'), # common
311
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
312
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
313
rev_id), # first parent
314
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
315
rev_id2), # second parent
318
state = dirstate.DirState.from_tree(tree, 'dirstate')
319
self.check_state_with_reopen(expected_result, state)
321
def test_colliding_fileids(self):
322
# test insertion of parents creating several entries at the same path.
323
# we used to have a bug where they could cause the dirstate to break
324
# its ordering invariants.
325
# create some trees to test from
328
tree = self.make_branch_and_tree('tree%d' % i)
329
self.build_tree(['tree%d/name' % i,])
330
tree.add(['name'], ['file-id%d' % i])
331
revision_id = 'revid-%d' % i
332
tree.commit('message', rev_id=revision_id)
333
parents.append((revision_id,
334
tree.branch.repository.revision_tree(revision_id)))
335
# now fold these trees into a dirstate
336
state = dirstate.DirState.initialize('dirstate')
338
state.set_parent_trees(parents, [])
344
class TestDirStateOnFile(TestCaseWithDirState):
346
def test_construct_with_path(self):
347
tree = self.make_branch_and_tree('tree')
348
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
349
# we want to be able to get the lines of the dirstate that we will
351
lines = state.get_lines()
353
self.build_tree_contents([('dirstate', ''.join(lines))])
355
# no parents, default tree content
356
expected_result = ([], [
357
(('', '', tree.path2id('')), # common details
358
# current tree details, but new from_tree skips statting, it
359
# uses set_state_from_inventory, and thus depends on the
361
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
364
state = dirstate.DirState.on_file('dirstate')
365
state.lock_write() # check_state_with_reopen will save() and unlock it
366
self.check_state_with_reopen(expected_result, state)
368
def test_can_save_clean_on_file(self):
369
tree = self.make_branch_and_tree('tree')
370
state = dirstate.DirState.from_tree(tree, 'dirstate')
372
# doing a save should work here as there have been no changes.
374
# TODO: stat it and check it hasn't changed; may require waiting
375
# for the state accuracy window.
380
class TestDirStateInitialize(TestCaseWithDirState):
382
def test_initialize(self):
383
expected_result = ([], [
384
(('', '', 'TREE_ROOT'), # common details
385
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
388
state = dirstate.DirState.initialize('dirstate')
390
self.assertIsInstance(state, dirstate.DirState)
391
lines = state.get_lines()
392
self.assertFileEqual(''.join(state.get_lines()),
394
self.check_state_with_reopen(expected_result, state)
400
class TestDirStateManipulations(TestCaseWithDirState):
402
def test_set_state_from_inventory_no_content_no_parents(self):
403
# setting the current inventory is a slow but important api to support.
404
tree1 = self.make_branch_and_memory_tree('tree1')
408
revid1 = tree1.commit('foo').encode('utf8')
409
root_id = tree1.inventory.root.file_id
410
inv = tree1.inventory
413
expected_result = [], [
414
(('', '', root_id), [
415
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
416
state = dirstate.DirState.initialize('dirstate')
418
state.set_state_from_inventory(inv)
419
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
421
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
422
state._dirblock_state)
427
# This will unlock it
428
self.check_state_with_reopen(expected_result, state)
430
def test_set_path_id_no_parents(self):
431
"""The id of a path can be changed trivally with no parents."""
432
state = dirstate.DirState.initialize('dirstate')
434
# check precondition to be sure the state does change appropriately.
436
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
437
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
438
list(state._iter_entries()))
439
state.set_path_id('', 'foobarbaz')
441
(('', '', 'foobarbaz'), [('d', '', 0, False,
442
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
443
self.assertEqual(expected_rows, list(state._iter_entries()))
444
# should work across save too
448
state = dirstate.DirState.on_file('dirstate')
452
self.assertEqual(expected_rows, list(state._iter_entries()))
456
def test_set_path_id_with_parents(self):
457
"""Set the root file id in a dirstate with parents"""
458
mt = self.make_branch_and_tree('mt')
459
# in case the default tree format uses a different root id
460
mt.set_root_id('TREE_ROOT')
461
mt.commit('foo', rev_id='parent-revid')
462
rt = mt.branch.repository.revision_tree('parent-revid')
463
state = dirstate.DirState.initialize('dirstate')
466
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
467
state.set_path_id('', 'foobarbaz')
469
# now see that it is what we expected
471
(('', '', 'TREE_ROOT'),
472
[('a', '', 0, False, ''),
473
('d', '', 0, False, 'parent-revid'),
475
(('', '', 'foobarbaz'),
476
[('d', '', 0, False, ''),
477
('a', '', 0, False, ''),
481
self.assertEqual(expected_rows, list(state._iter_entries()))
482
# should work across save too
486
# now flush & check we get the same
487
state = dirstate.DirState.on_file('dirstate')
491
self.assertEqual(expected_rows, list(state._iter_entries()))
494
# now change within an existing file-backed state
498
state.set_path_id('', 'tree-root-2')
504
def test_set_parent_trees_no_content(self):
505
# set_parent_trees is a slow but important api to support.
506
tree1 = self.make_branch_and_memory_tree('tree1')
510
revid1 = tree1.commit('foo')
513
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
514
tree2 = MemoryTree.create_on_branch(branch2)
517
revid2 = tree2.commit('foo')
518
root_id = tree2.inventory.root.file_id
521
state = dirstate.DirState.initialize('dirstate')
523
state.set_path_id('', root_id)
524
state.set_parent_trees(
525
((revid1, tree1.branch.repository.revision_tree(revid1)),
526
(revid2, tree2.branch.repository.revision_tree(revid2)),
527
('ghost-rev', None)),
529
# check we can reopen and use the dirstate after setting parent
536
state = dirstate.DirState.on_file('dirstate')
539
self.assertEqual([revid1, revid2, 'ghost-rev'],
540
state.get_parent_ids())
541
# iterating the entire state ensures that the state is parsable.
542
list(state._iter_entries())
543
# be sure that it sets not appends - change it
544
state.set_parent_trees(
545
((revid1, tree1.branch.repository.revision_tree(revid1)),
546
('ghost-rev', None)),
548
# and now put it back.
549
state.set_parent_trees(
550
((revid1, tree1.branch.repository.revision_tree(revid1)),
551
(revid2, tree2.branch.repository.revision_tree(revid2)),
552
('ghost-rev', tree2.branch.repository.revision_tree(None))),
554
self.assertEqual([revid1, revid2, 'ghost-rev'],
555
state.get_parent_ids())
556
# the ghost should be recorded as such by set_parent_trees.
557
self.assertEqual(['ghost-rev'], state.get_ghosts())
559
[(('', '', root_id), [
560
('d', '', 0, False, dirstate.DirState.NULLSTAT),
561
('d', '', 0, False, revid1),
562
('d', '', 0, False, revid2)
564
list(state._iter_entries()))
568
def test_set_parent_trees_file_missing_from_tree(self):
569
# Adding a parent tree may reference files not in the current state.
570
# they should get listed just once by id, even if they are in two
572
# set_parent_trees is a slow but important api to support.
573
tree1 = self.make_branch_and_memory_tree('tree1')
577
tree1.add(['a file'], ['file-id'], ['file'])
578
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
579
revid1 = tree1.commit('foo')
582
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
583
tree2 = MemoryTree.create_on_branch(branch2)
586
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
587
revid2 = tree2.commit('foo')
588
root_id = tree2.inventory.root.file_id
591
# check the layout in memory
592
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
593
(('', '', root_id), [
594
('d', '', 0, False, dirstate.DirState.NULLSTAT),
595
('d', '', 0, False, revid1.encode('utf8')),
596
('d', '', 0, False, revid2.encode('utf8'))
598
(('', 'a file', 'file-id'), [
599
('a', '', 0, False, ''),
600
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
601
revid1.encode('utf8')),
602
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
603
revid2.encode('utf8'))
606
state = dirstate.DirState.initialize('dirstate')
608
state.set_path_id('', root_id)
609
state.set_parent_trees(
610
((revid1, tree1.branch.repository.revision_tree(revid1)),
611
(revid2, tree2.branch.repository.revision_tree(revid2)),
617
# check_state_with_reopen will unlock
618
self.check_state_with_reopen(expected_result, state)
620
### add a path via _set_data - so we dont need delta work, just
621
# raw data in, and ensure that it comes out via get_lines happily.
623
def test_add_path_to_root_no_parents_all_data(self):
624
# The most trivial addition of a path is when there are no parents and
625
# its in the root and all data about the file is supplied
626
self.build_tree(['a file'])
627
stat = os.lstat('a file')
628
# the 1*20 is the sha1 pretend value.
629
state = dirstate.DirState.initialize('dirstate')
631
(('', '', 'TREE_ROOT'), [
632
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
634
(('', 'a file', 'a file id'), [
635
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
639
state.add('a file', 'a file id', 'file', stat, '1'*20)
640
# having added it, it should be in the output of iter_entries.
641
self.assertEqual(expected_entries, list(state._iter_entries()))
642
# saving and reloading should not affect this.
646
state = dirstate.DirState.on_file('dirstate')
649
self.assertEqual(expected_entries, list(state._iter_entries()))
653
def test_add_path_to_unversioned_directory(self):
654
"""Adding a path to an unversioned directory should error.
656
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
657
once dirstate is stable and if it is merged with WorkingTree3, consider
658
removing this copy of the test.
660
self.build_tree(['unversioned/', 'unversioned/a file'])
661
state = dirstate.DirState.initialize('dirstate')
663
self.assertRaises(errors.NotVersionedError, state.add,
664
'unversioned/a file', 'a file id', 'file', None, None)
668
def test_add_directory_to_root_no_parents_all_data(self):
669
# The most trivial addition of a dir is when there are no parents and
670
# its in the root and all data about the file is supplied
671
self.build_tree(['a dir/'])
672
stat = os.lstat('a dir')
674
(('', '', 'TREE_ROOT'), [
675
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
677
(('', 'a dir', 'a dir id'), [
678
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
681
state = dirstate.DirState.initialize('dirstate')
683
state.add('a dir', 'a dir id', 'directory', stat, None)
684
# having added it, it should be in the output of iter_entries.
685
self.assertEqual(expected_entries, list(state._iter_entries()))
686
# saving and reloading should not affect this.
690
state = dirstate.DirState.on_file('dirstate')
694
self.assertEqual(expected_entries, list(state._iter_entries()))
698
def test_add_symlink_to_root_no_parents_all_data(self):
699
# The most trivial addition of a symlink when there are no parents and
700
# its in the root and all data about the file is supplied
701
## TODO: windows: dont fail this test. Also, how are symlinks meant to
702
# be represented on windows.
703
os.symlink('target', 'a link')
704
stat = os.lstat('a link')
706
(('', '', 'TREE_ROOT'), [
707
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
709
(('', 'a link', 'a link id'), [
710
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
713
state = dirstate.DirState.initialize('dirstate')
715
state.add('a link', 'a link id', 'symlink', stat, 'target')
716
# having added it, it should be in the output of iter_entries.
717
self.assertEqual(expected_entries, list(state._iter_entries()))
718
# saving and reloading should not affect this.
722
state = dirstate.DirState.on_file('dirstate')
725
self.assertEqual(expected_entries, list(state._iter_entries()))
729
def test_add_directory_and_child_no_parents_all_data(self):
730
# after adding a directory, we should be able to add children to it.
731
self.build_tree(['a dir/', 'a dir/a file'])
732
dirstat = os.lstat('a dir')
733
filestat = os.lstat('a dir/a file')
735
(('', '', 'TREE_ROOT'), [
736
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
738
(('', 'a dir', 'a dir id'), [
739
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
741
(('a dir', 'a file', 'a file id'), [
742
('f', '1'*20, 25, False,
743
dirstate.pack_stat(filestat)), # current tree details
746
state = dirstate.DirState.initialize('dirstate')
748
state.add('a dir', 'a dir id', 'directory', dirstat, None)
749
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
750
# added it, it should be in the output of iter_entries.
751
self.assertEqual(expected_entries, list(state._iter_entries()))
752
# saving and reloading should not affect this.
756
state = dirstate.DirState.on_file('dirstate')
759
self.assertEqual(expected_entries, list(state._iter_entries()))
763
def test_add_tree_reference(self):
764
# make a dirstate and add a tree reference
765
state = dirstate.DirState.initialize('dirstate')
767
('', 'subdir', 'subdir-id'),
768
[('t', 'subtree-123123', 0, False,
769
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
772
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
773
entry = state._get_entry(0, 'subdir-id', 'subdir')
774
self.assertEqual(entry, expected_entry)
779
# now check we can read it back
783
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
784
self.assertEqual(entry, entry2)
785
self.assertEqual(entry, expected_entry)
786
# and lookup by id should work too
787
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
788
self.assertEqual(entry, expected_entry)
793
class TestGetLines(TestCaseWithDirState):
795
def test_get_line_with_2_rows(self):
796
state = self.create_dirstate_with_root_and_subdir()
798
self.assertEqual(['#bazaar dirstate flat format 3\n',
799
'adler32: -1327947603\n',
803
'\x00\x00a-root-value\x00'
804
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
805
'\x00subdir\x00subdir-id\x00'
806
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
807
], state.get_lines())
811
def test_entry_to_line(self):
812
state = self.create_dirstate_with_root()
815
'\x00\x00a-root-value\x00d\x00\x000\x00n'
816
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
817
state._entry_to_line(state._dirblocks[0][1][0]))
821
def test_entry_to_line_with_parent(self):
822
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
823
root_entry = ('', '', 'a-root-value'), [
824
('d', '', 0, False, packed_stat), # current tree details
825
# first: a pointer to the current location
826
('a', 'dirname/basename', 0, False, ''),
828
state = dirstate.DirState.initialize('dirstate')
831
'\x00\x00a-root-value\x00'
832
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
833
'a\x00dirname/basename\x000\x00n\x00',
834
state._entry_to_line(root_entry))
838
def test_entry_to_line_with_two_parents_at_different_paths(self):
839
# / in the tree, at / in one parent and /dirname/basename in the other.
840
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
841
root_entry = ('', '', 'a-root-value'), [
842
('d', '', 0, False, packed_stat), # current tree details
843
('d', '', 0, False, 'rev_id'), # first parent details
844
# second: a pointer to the current location
845
('a', 'dirname/basename', 0, False, ''),
847
state = dirstate.DirState.initialize('dirstate')
850
'\x00\x00a-root-value\x00'
851
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
852
'd\x00\x000\x00n\x00rev_id\x00'
853
'a\x00dirname/basename\x000\x00n\x00',
854
state._entry_to_line(root_entry))
858
def test_iter_entries(self):
859
# we should be able to iterate the dirstate entries from end to end
860
# this is for get_lines to be easy to read.
861
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
863
root_entries = [(('', '', 'a-root-value'), [
864
('d', '', 0, False, packed_stat), # current tree details
866
dirblocks.append(('', root_entries))
867
# add two files in the root
868
subdir_entry = ('', 'subdir', 'subdir-id'), [
869
('d', '', 0, False, packed_stat), # current tree details
871
afile_entry = ('', 'afile', 'afile-id'), [
872
('f', 'sha1value', 34, False, packed_stat), # current tree details
874
dirblocks.append(('', [subdir_entry, afile_entry]))
876
file_entry2 = ('subdir', '2file', '2file-id'), [
877
('f', 'sha1value', 23, False, packed_stat), # current tree details
879
dirblocks.append(('subdir', [file_entry2]))
880
state = dirstate.DirState.initialize('dirstate')
882
state._set_data([], dirblocks)
883
expected_entries = [root_entries[0], subdir_entry, afile_entry,
885
self.assertEqual(expected_entries, list(state._iter_entries()))
890
class TestGetBlockRowIndex(TestCaseWithDirState):
892
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
893
file_present, state, dirname, basename, tree_index):
894
self.assertEqual((block_index, row_index, dir_present, file_present),
895
state._get_block_entry_index(dirname, basename, tree_index))
897
block = state._dirblocks[block_index]
898
self.assertEqual(dirname, block[0])
899
if dir_present and file_present:
900
row = state._dirblocks[block_index][1][row_index]
901
self.assertEqual(dirname, row[0][0])
902
self.assertEqual(basename, row[0][1])
904
def test_simple_structure(self):
905
state = self.create_dirstate_with_root_and_subdir()
906
self.addCleanup(state.unlock)
907
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
908
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
909
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
910
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
911
self.assertBlockRowIndexEqual(2, 0, False, False, state,
914
def test_complex_structure_exists(self):
915
state = self.create_complex_dirstate()
916
self.addCleanup(state.unlock)
917
# Make sure we can find everything that exists
918
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
919
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
920
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
921
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
922
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
923
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
924
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
925
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
926
self.assertBlockRowIndexEqual(3, 1, True, True, state,
929
def test_complex_structure_missing(self):
930
state = self.create_complex_dirstate()
931
self.addCleanup(state.unlock)
932
# Make sure things would be inserted in the right locations
933
# '_' comes before 'a'
934
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
935
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
936
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
937
self.assertBlockRowIndexEqual(1, 4, True, False, state,
939
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
940
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
941
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
942
# This would be inserted between a/ and b/
943
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
945
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
948
class TestGetEntry(TestCaseWithDirState):
950
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
951
"""Check that the right entry is returned for a request to getEntry."""
952
entry = state._get_entry(index, path_utf8=path)
954
self.assertEqual((None, None), entry)
957
self.assertEqual((dirname, basename, file_id), cur[:3])
959
def test_simple_structure(self):
960
state = self.create_dirstate_with_root_and_subdir()
961
self.addCleanup(state.unlock)
962
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
963
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
964
self.assertEntryEqual(None, None, None, state, 'missing', 0)
965
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
966
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
968
def test_complex_structure_exists(self):
969
state = self.create_complex_dirstate()
970
self.addCleanup(state.unlock)
971
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
972
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
973
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
974
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
975
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
976
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
977
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
978
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
979
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
982
def test_complex_structure_missing(self):
983
state = self.create_complex_dirstate()
984
self.addCleanup(state.unlock)
985
self.assertEntryEqual(None, None, None, state, '_', 0)
986
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
987
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
988
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
990
def test_get_entry_uninitialized(self):
991
"""Calling get_entry will load data if it needs to"""
992
state = self.create_dirstate_with_root()
998
state = dirstate.DirState.on_file('dirstate')
1001
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1002
state._header_state)
1003
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1004
state._dirblock_state)
1005
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1010
class TestDirstateSortOrder(TestCaseWithTransport):
1011
"""Test that DirState adds entries in the right order."""
1013
def test_add_sorting(self):
1014
"""Add entries in lexicographical order, we get path sorted order.
1016
This tests it to a depth of 4, to make sure we don't just get it right
1017
at a single depth. 'a/a' should come before 'a-a', even though it
1018
doesn't lexicographically.
1020
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1021
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1024
state = dirstate.DirState.initialize('dirstate')
1025
self.addCleanup(state.unlock)
1027
fake_stat = os.stat('dirstate')
1029
d_id = d.replace('/', '_')+'-id'
1030
file_path = d + '/f'
1031
file_id = file_path.replace('/', '_')+'-id'
1032
state.add(d, d_id, 'directory', fake_stat, null_sha)
1033
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1035
expected = ['', '', 'a',
1036
'a/a', 'a/a/a', 'a/a/a/a',
1037
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1039
split = lambda p:p.split('/')
1040
self.assertEqual(sorted(expected, key=split), expected)
1041
dirblock_names = [d[0] for d in state._dirblocks]
1042
self.assertEqual(expected, dirblock_names)
1044
def test_set_parent_trees_correct_order(self):
1045
"""After calling set_parent_trees() we should maintain the order."""
1046
dirs = ['a', 'a-a', 'a/a']
1048
state = dirstate.DirState.initialize('dirstate')
1049
self.addCleanup(state.unlock)
1051
fake_stat = os.stat('dirstate')
1053
d_id = d.replace('/', '_')+'-id'
1054
file_path = d + '/f'
1055
file_id = file_path.replace('/', '_')+'-id'
1056
state.add(d, d_id, 'directory', fake_stat, null_sha)
1057
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1059
expected = ['', '', 'a', 'a/a', 'a-a']
1060
dirblock_names = [d[0] for d in state._dirblocks]
1061
self.assertEqual(expected, dirblock_names)
1063
# *really* cheesy way to just get an empty tree
1064
repo = self.make_repository('repo')
1065
empty_tree = repo.revision_tree(None)
1066
state.set_parent_trees([('null:', empty_tree)], [])
1068
dirblock_names = [d[0] for d in state._dirblocks]
1069
self.assertEqual(expected, dirblock_names)
1072
class InstrumentedDirState(dirstate.DirState):
1073
"""An DirState with instrumented sha1 functionality."""
1075
def __init__(self, path):
1076
super(InstrumentedDirState, self).__init__(path)
1077
self._time_offset = 0
1080
def _sha_cutoff_time(self):
1081
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1082
self._cutoff_time = timestamp + self._time_offset
1084
def _sha1_file(self, abspath, entry):
1085
self._log.append(('sha1', abspath))
1086
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1088
def _read_link(self, abspath, old_link):
1089
self._log.append(('read_link', abspath, old_link))
1090
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1092
def _lstat(self, abspath, entry):
1093
self._log.append(('lstat', abspath))
1094
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1096
def _is_executable(self, mode, old_executable):
1097
self._log.append(('is_exec', mode, old_executable))
1098
return super(InstrumentedDirState, self)._is_executable(mode,
1101
def adjust_time(self, secs):
1102
"""Move the clock forward or back.
1104
:param secs: The amount to adjust the clock by. Positive values make it
1105
seem as if we are in the future, negative values make it seem like we
1108
self._time_offset += secs
1109
self._cutoff_time = None
1112
class _FakeStat(object):
1113
"""A class with the same attributes as a real stat result."""
1115
def __init__(self, size, mtime, ctime, dev, ino, mode):
1117
self.st_mtime = mtime
1118
self.st_ctime = ctime
1124
class TestUpdateEntry(TestCaseWithDirState):
1125
"""Test the DirState.update_entry functions"""
1127
def get_state_with_a(self):
1128
"""Create a DirState tracking a single object named 'a'"""
1129
state = InstrumentedDirState.initialize('dirstate')
1130
self.addCleanup(state.unlock)
1131
state.add('a', 'a-id', 'file', None, '')
1132
entry = state._get_entry(0, path_utf8='a')
1135
def test_update_entry(self):
1136
state, entry = self.get_state_with_a()
1137
self.build_tree(['a'])
1138
# Add one where we don't provide the stat or sha already
1139
self.assertEqual(('', 'a', 'a-id'), entry[0])
1140
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1142
# Flush the buffers to disk
1144
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1145
state._dirblock_state)
1147
stat_value = os.lstat('a')
1148
packed_stat = dirstate.pack_stat(stat_value)
1149
link_or_sha1 = state.update_entry(entry, abspath='a',
1150
stat_value=stat_value)
1151
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1154
# The dirblock entry should be updated with the new info
1155
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1157
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1158
state._dirblock_state)
1159
mode = stat_value.st_mode
1160
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1163
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1164
state._dirblock_state)
1166
# If we do it again right away, we don't know if the file has changed
1167
# so we will re-read the file. Roll the clock back so the file is
1168
# guaranteed to look too new.
1169
state.adjust_time(-10)
1171
link_or_sha1 = state.update_entry(entry, abspath='a',
1172
stat_value=stat_value)
1173
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1174
('sha1', 'a'), ('is_exec', mode, False),
1176
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1178
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1179
state._dirblock_state)
1182
# However, if we move the clock forward so the file is considered
1183
# "stable", it should just returned the cached value.
1184
state.adjust_time(20)
1185
link_or_sha1 = state.update_entry(entry, abspath='a',
1186
stat_value=stat_value)
1187
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1189
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1190
('sha1', 'a'), ('is_exec', mode, False),
1193
def test_update_entry_no_stat_value(self):
1194
"""Passing the stat_value is optional."""
1195
state, entry = self.get_state_with_a()
1196
state.adjust_time(-10) # Make sure the file looks new
1197
self.build_tree(['a'])
1198
# Add one where we don't provide the stat or sha already
1199
link_or_sha1 = state.update_entry(entry, abspath='a')
1200
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1202
stat_value = os.lstat('a')
1203
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1204
('is_exec', stat_value.st_mode, False),
1207
def test_update_entry_symlink(self):
1208
"""Update entry should read symlinks."""
1209
if not osutils.has_symlinks():
1210
return # PlatformDeficiency / TestSkipped
1211
state, entry = self.get_state_with_a()
1213
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1214
state._dirblock_state)
1215
os.symlink('target', 'a')
1217
state.adjust_time(-10) # Make the symlink look new
1218
stat_value = os.lstat('a')
1219
packed_stat = dirstate.pack_stat(stat_value)
1220
link_or_sha1 = state.update_entry(entry, abspath='a',
1221
stat_value=stat_value)
1222
self.assertEqual('target', link_or_sha1)
1223
self.assertEqual([('read_link', 'a', '')], state._log)
1224
# Dirblock is updated
1225
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1227
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1228
state._dirblock_state)
1230
# Because the stat_value looks new, we should re-read the target
1231
link_or_sha1 = state.update_entry(entry, abspath='a',
1232
stat_value=stat_value)
1233
self.assertEqual('target', link_or_sha1)
1234
self.assertEqual([('read_link', 'a', ''),
1235
('read_link', 'a', 'target'),
1237
state.adjust_time(+20) # Skip into the future, all files look old
1238
link_or_sha1 = state.update_entry(entry, abspath='a',
1239
stat_value=stat_value)
1240
self.assertEqual('target', link_or_sha1)
1241
# There should not be a new read_link call.
1242
# (this is a weak assertion, because read_link is fairly inexpensive,
1243
# versus the number of symlinks that we would have)
1244
self.assertEqual([('read_link', 'a', ''),
1245
('read_link', 'a', 'target'),
1248
def test_update_entry_dir(self):
1249
state, entry = self.get_state_with_a()
1250
self.build_tree(['a/'])
1251
self.assertIs(None, state.update_entry(entry, 'a'))
1253
def create_and_test_file(self, state, entry):
1254
"""Create a file at 'a' and verify the state finds it.
1256
The state should already be versioning *something* at 'a'. This makes
1257
sure that state.update_entry recognizes it as a file.
1259
self.build_tree(['a'])
1260
stat_value = os.lstat('a')
1261
packed_stat = dirstate.pack_stat(stat_value)
1263
link_or_sha1 = state.update_entry(entry, abspath='a')
1264
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1266
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1270
def create_and_test_dir(self, state, entry):
1271
"""Create a directory at 'a' and verify the state finds it.
1273
The state should already be versioning *something* at 'a'. This makes
1274
sure that state.update_entry recognizes it as a directory.
1276
self.build_tree(['a/'])
1277
stat_value = os.lstat('a')
1278
packed_stat = dirstate.pack_stat(stat_value)
1280
link_or_sha1 = state.update_entry(entry, abspath='a')
1281
self.assertIs(None, link_or_sha1)
1282
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1286
def create_and_test_symlink(self, state, entry):
1287
"""Create a symlink at 'a' and verify the state finds it.
1289
The state should already be versioning *something* at 'a'. This makes
1290
sure that state.update_entry recognizes it as a symlink.
1292
This should not be called if this platform does not have symlink
1295
os.symlink('path/to/foo', 'a')
1297
stat_value = os.lstat('a')
1298
packed_stat = dirstate.pack_stat(stat_value)
1300
link_or_sha1 = state.update_entry(entry, abspath='a')
1301
self.assertEqual('path/to/foo', link_or_sha1)
1302
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1306
def test_update_missing_file(self):
1307
state, entry = self.get_state_with_a()
1308
packed_stat = self.create_and_test_file(state, entry)
1309
# Now if we delete the file, update_entry should recover and
1312
self.assertIs(None, state.update_entry(entry, abspath='a'))
1313
# And the record shouldn't be changed.
1314
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1315
self.assertEqual([('f', digest, 14, False, packed_stat)],
1318
def test_update_missing_dir(self):
1319
state, entry = self.get_state_with_a()
1320
packed_stat = self.create_and_test_dir(state, entry)
1321
# Now if we delete the directory, update_entry should recover and
1324
self.assertIs(None, state.update_entry(entry, abspath='a'))
1325
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1327
def test_update_missing_symlink(self):
1328
if not osutils.has_symlinks():
1329
return # PlatformDeficiency / TestSkipped
1330
state, entry = self.get_state_with_a()
1331
packed_stat = self.create_and_test_symlink(state, entry)
1333
self.assertIs(None, state.update_entry(entry, abspath='a'))
1334
# And the record shouldn't be changed.
1335
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1338
def test_update_file_to_dir(self):
1339
"""If a file changes to a directory we return None for the sha.
1340
We also update the inventory record.
1342
state, entry = self.get_state_with_a()
1343
self.create_and_test_file(state, entry)
1345
self.create_and_test_dir(state, entry)
1347
def test_update_file_to_symlink(self):
1348
"""File becomes a symlink"""
1349
if not osutils.has_symlinks():
1350
return # PlatformDeficiency / TestSkipped
1351
state, entry = self.get_state_with_a()
1352
self.create_and_test_file(state, entry)
1354
self.create_and_test_symlink(state, entry)
1356
def test_update_dir_to_file(self):
1357
"""Directory becoming a file updates the entry."""
1358
state, entry = self.get_state_with_a()
1359
self.create_and_test_dir(state, entry)
1361
self.create_and_test_file(state, entry)
1363
def test_update_dir_to_symlink(self):
1364
"""Directory becomes a symlink"""
1365
if not osutils.has_symlinks():
1366
return # PlatformDeficiency / TestSkipped
1367
state, entry = self.get_state_with_a()
1368
self.create_and_test_dir(state, entry)
1370
self.create_and_test_symlink(state, entry)
1372
def test_update_symlink_to_file(self):
1373
"""Symlink becomes a file"""
1374
state, entry = self.get_state_with_a()
1375
self.create_and_test_symlink(state, entry)
1377
self.create_and_test_file(state, entry)
1379
def test_update_symlink_to_dir(self):
1380
"""Symlink becomes a directory"""
1381
state, entry = self.get_state_with_a()
1382
self.create_and_test_symlink(state, entry)
1384
self.create_and_test_dir(state, entry)
1386
def test__is_executable_win32(self):
1387
state, entry = self.get_state_with_a()
1388
self.build_tree(['a'])
1390
# Make sure we are using the win32 implementation of _is_executable
1391
state._is_executable = state._is_executable_win32
1393
# The file on disk is not executable, but we are marking it as though
1394
# it is. With _is_executable_win32 we ignore what is on disk.
1395
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1397
stat_value = os.lstat('a')
1398
packed_stat = dirstate.pack_stat(stat_value)
1400
state.adjust_time(-10) # Make sure everything is new
1401
# Make sure it wants to kkkkkkkk
1402
state.update_entry(entry, abspath='a', stat_value=stat_value)
1404
# The row is updated, but the executable bit stays set.
1405
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1406
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1409
class TestPackStat(TestCaseWithTransport):
1411
def assertPackStat(self, expected, stat_value):
1412
"""Check the packed and serialized form of a stat value."""
1413
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1415
def test_pack_stat_int(self):
1416
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1417
# Make sure that all parameters have an impact on the packed stat.
1418
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1421
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1422
st.st_mtime = 1172758620
1424
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1425
st.st_ctime = 1172758630
1427
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1430
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1431
st.st_ino = 6499540L
1433
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1434
st.st_mode = 0100744
1436
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1438
def test_pack_stat_float(self):
1439
"""On some platforms mtime and ctime are floats.
1441
Make sure we don't get warnings or errors, and that we ignore changes <
1444
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1445
777L, 6499538L, 0100644)
1446
# These should all be the same as the integer counterparts
1447
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1448
st.st_mtime = 1172758620.0
1450
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1451
st.st_ctime = 1172758630.0
1453
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1454
# fractional seconds are discarded, so no change from above
1455
st.st_mtime = 1172758620.453
1456
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1457
st.st_ctime = 1172758630.228
1458
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1461
class TestBisect(TestCaseWithTransport):
1462
"""Test the ability to bisect into the disk format."""
1464
def create_basic_dirstate(self):
1465
"""Create a dirstate with a few files and directories.
1474
tree = self.make_branch_and_tree('tree')
1475
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1476
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1477
self.build_tree(['tree/' + p for p in paths])
1478
tree.set_root_id('TREE_ROOT')
1479
tree.add([p.rstrip('/') for p in paths], file_ids)
1480
tree.commit('initial', rev_id='rev-1')
1481
revision_id = 'rev-1'
1482
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1483
t = self.get_transport().clone('tree')
1484
a_text = t.get_bytes('a')
1485
a_sha = osutils.sha_string(a_text)
1487
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1488
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1489
c_text = t.get_bytes('b/c')
1490
c_sha = osutils.sha_string(c_text)
1492
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1493
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1494
e_text = t.get_bytes('b/d/e')
1495
e_sha = osutils.sha_string(e_text)
1497
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1498
f_text = t.get_bytes('f')
1499
f_sha = osutils.sha_string(f_text)
1501
null_stat = dirstate.DirState.NULLSTAT
1503
'':(('', '', 'TREE_ROOT'), [
1504
('d', '', 0, False, null_stat),
1505
('d', '', 0, False, revision_id),
1507
'a':(('', 'a', 'a-id'), [
1508
('f', '', 0, False, null_stat),
1509
('f', a_sha, a_len, False, revision_id),
1511
'b':(('', 'b', 'b-id'), [
1512
('d', '', 0, False, null_stat),
1513
('d', '', 0, False, revision_id),
1515
'b/c':(('b', 'c', 'c-id'), [
1516
('f', '', 0, False, null_stat),
1517
('f', c_sha, c_len, False, revision_id),
1519
'b/d':(('b', 'd', 'd-id'), [
1520
('d', '', 0, False, null_stat),
1521
('d', '', 0, False, revision_id),
1523
'b/d/e':(('b/d', 'e', 'e-id'), [
1524
('f', '', 0, False, null_stat),
1525
('f', e_sha, e_len, False, revision_id),
1527
'f':(('', 'f', 'f-id'), [
1528
('f', '', 0, False, null_stat),
1529
('f', f_sha, f_len, False, revision_id),
1532
state = dirstate.DirState.from_tree(tree, 'dirstate')
1537
# Use a different object, to make sure nothing is pre-cached in memory.
1538
state = dirstate.DirState.on_file('dirstate')
1540
self.addCleanup(state.unlock)
1541
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1542
state._dirblock_state)
1543
# This is code is only really tested if we actually have to make more
1544
# than one read, so set the page size to something smaller.
1545
# We want it to contain about 2.2 records, so that we have a couple
1546
# records that we can read per attempt
1547
state._bisect_page_size = 200
1548
return tree, state, expected
1550
def create_duplicated_dirstate(self):
1551
"""Create a dirstate with a deleted and added entries.
1553
This grabs a basic_dirstate, and then removes and re adds every entry
1556
tree, state, expected = self.create_basic_dirstate()
1557
# Now we will just remove and add every file so we get an extra entry
1558
# per entry. Unversion in reverse order so we handle subdirs
1559
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1560
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1561
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1563
# Update the expected dictionary.
1564
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1565
orig = expected[path]
1567
# This record was deleted in the current tree
1568
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1570
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1571
# And didn't exist in the basis tree
1572
expected[path2] = (new_key, [orig[1][0],
1573
dirstate.DirState.NULL_PARENT_DETAILS])
1575
# We will replace the 'dirstate' file underneath 'state', but that is
1576
# okay as lock as we unlock 'state' first.
1579
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1585
# But we need to leave state in a read-lock because we already have
1586
# a cleanup scheduled
1588
return tree, state, expected
1590
def create_renamed_dirstate(self):
1591
"""Create a dirstate with a few internal renames.
1593
This takes the basic dirstate, and moves the paths around.
1595
tree, state, expected = self.create_basic_dirstate()
1597
tree.rename_one('a', 'b/g')
1599
tree.rename_one('b/d', 'h')
1601
old_a = expected['a']
1602
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1603
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1604
('r', 'a', 0, False, '')])
1605
old_d = expected['b/d']
1606
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1607
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1608
('r', 'b/d', 0, False, '')])
1610
old_e = expected['b/d/e']
1611
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1613
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1614
('r', 'b/d/e', 0, False, '')])
1618
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1625
return tree, state, expected
1627
def assertBisect(self, expected_map, map_keys, state, paths):
1628
"""Assert that bisecting for paths returns the right result.
1630
:param expected_map: A map from key => entry value
1631
:param map_keys: The keys to expect for each path
1632
:param state: The DirState object.
1633
:param paths: A list of paths, these will automatically be split into
1634
(dir, name) tuples, and sorted according to how _bisect
1637
dir_names = sorted(osutils.split(p) for p in paths)
1638
result = state._bisect(dir_names)
1639
# For now, results are just returned in whatever order we read them.
1640
# We could sort by (dir, name, file_id) or something like that, but in
1641
# the end it would still be fairly arbitrary, and we don't want the
1642
# extra overhead if we can avoid it. So sort everything to make sure
1644
assert len(map_keys) == len(dir_names)
1646
for dir_name, keys in zip(dir_names, map_keys):
1648
# This should not be present in the output
1650
expected[dir_name] = sorted(expected_map[k] for k in keys)
1652
for dir_name in result:
1653
result[dir_name].sort()
1655
self.assertEqual(expected, result)
1657
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1658
"""Assert that bisecting for dirbblocks returns the right result.
1660
:param expected_map: A map from key => expected values
1661
:param map_keys: A nested list of paths we expect to be returned.
1662
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1663
:param state: The DirState object.
1664
:param paths: A list of directories
1666
result = state._bisect_dirblocks(paths)
1667
assert len(map_keys) == len(paths)
1670
for path, keys in zip(paths, map_keys):
1672
# This should not be present in the output
1674
expected[path] = sorted(expected_map[k] for k in keys)
1678
self.assertEqual(expected, result)
1680
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1681
"""Assert the return value of a recursive bisection.
1683
:param expected_map: A map from key => entry value
1684
:param map_keys: A list of paths we expect to be returned.
1685
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1686
:param state: The DirState object.
1687
:param paths: A list of files and directories. It will be broken up
1688
into (dir, name) pairs and sorted before calling _bisect_recursive.
1691
for key in map_keys:
1692
entry = expected_map[key]
1693
dir_name_id, trees_info = entry
1694
expected[dir_name_id] = trees_info
1696
dir_names = sorted(osutils.split(p) for p in paths)
1697
result = state._bisect_recursive(dir_names)
1699
self.assertEqual(expected, result)
1701
def test_bisect_each(self):
1702
"""Find a single record using bisect."""
1703
tree, state, expected = self.create_basic_dirstate()
1705
# Bisect should return the rows for the specified files.
1706
self.assertBisect(expected, [['']], state, [''])
1707
self.assertBisect(expected, [['a']], state, ['a'])
1708
self.assertBisect(expected, [['b']], state, ['b'])
1709
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1710
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1711
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1712
self.assertBisect(expected, [['f']], state, ['f'])
1714
def test_bisect_multi(self):
1715
"""Bisect can be used to find multiple records at the same time."""
1716
tree, state, expected = self.create_basic_dirstate()
1717
# Bisect should be capable of finding multiple entries at the same time
1718
self.assertBisect(expected, [['a'], ['b'], ['f']],
1719
state, ['a', 'b', 'f'])
1720
# ('', 'f') sorts before the others
1721
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1722
state, ['b/d', 'b/d/e', 'f'])
1724
def test_bisect_one_page(self):
1725
"""Test bisect when there is only 1 page to read"""
1726
tree, state, expected = self.create_basic_dirstate()
1727
state._bisect_page_size = 5000
1728
self.assertBisect(expected,[['']], state, [''])
1729
self.assertBisect(expected,[['a']], state, ['a'])
1730
self.assertBisect(expected,[['b']], state, ['b'])
1731
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1732
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1733
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1734
self.assertBisect(expected,[['f']], state, ['f'])
1735
self.assertBisect(expected,[['a'], ['b'], ['f']],
1736
state, ['a', 'b', 'f'])
1737
# ('', 'f') sorts before the others
1738
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1739
state, ['b/d', 'b/d/e', 'f'])
1741
def test_bisect_duplicate_paths(self):
1742
"""When bisecting for a path, handle multiple entries."""
1743
tree, state, expected = self.create_duplicated_dirstate()
1745
# Now make sure that both records are properly returned.
1746
self.assertBisect(expected, [['']], state, [''])
1747
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1748
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1749
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1750
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1751
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1753
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1755
def test_bisect_page_size_too_small(self):
1756
"""If the page size is too small, we will auto increase it."""
1757
tree, state, expected = self.create_basic_dirstate()
1758
state._bisect_page_size = 50
1759
self.assertBisect(expected, [None], state, ['b/e'])
1760
self.assertBisect(expected, [['a']], state, ['a'])
1761
self.assertBisect(expected, [['b']], state, ['b'])
1762
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1763
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1764
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1765
self.assertBisect(expected, [['f']], state, ['f'])
1767
def test_bisect_missing(self):
1768
"""Test that bisect return None if it cannot find a path."""
1769
tree, state, expected = self.create_basic_dirstate()
1770
self.assertBisect(expected, [None], state, ['foo'])
1771
self.assertBisect(expected, [None], state, ['b/foo'])
1772
self.assertBisect(expected, [None], state, ['bar/foo'])
1774
self.assertBisect(expected, [['a'], None, ['b/d']],
1775
state, ['a', 'foo', 'b/d'])
1777
def test_bisect_rename(self):
1778
"""Check that we find a renamed row."""
1779
tree, state, expected = self.create_renamed_dirstate()
1781
# Search for the pre and post renamed entries
1782
self.assertBisect(expected, [['a']], state, ['a'])
1783
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1784
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1785
self.assertBisect(expected, [['h']], state, ['h'])
1787
# What about b/d/e? shouldn't that also get 2 directory entries?
1788
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1789
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1791
def test_bisect_dirblocks(self):
1792
tree, state, expected = self.create_duplicated_dirstate()
1793
self.assertBisectDirBlocks(expected,
1794
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1795
self.assertBisectDirBlocks(expected,
1796
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1797
self.assertBisectDirBlocks(expected,
1798
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1799
self.assertBisectDirBlocks(expected,
1800
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1801
['b/c', 'b/c2', 'b/d', 'b/d2'],
1802
['b/d/e', 'b/d/e2'],
1803
], state, ['', 'b', 'b/d'])
1805
def test_bisect_dirblocks_missing(self):
1806
tree, state, expected = self.create_basic_dirstate()
1807
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1808
state, ['b/d', 'b/e'])
1809
# Files don't show up in this search
1810
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1811
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1812
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1813
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1814
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1816
def test_bisect_recursive_each(self):
1817
tree, state, expected = self.create_basic_dirstate()
1818
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1819
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1820
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1821
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1823
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1825
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1829
def test_bisect_recursive_multiple(self):
1830
tree, state, expected = self.create_basic_dirstate()
1831
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1832
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1833
state, ['b/d', 'b/d/e'])
1835
def test_bisect_recursive_missing(self):
1836
tree, state, expected = self.create_basic_dirstate()
1837
self.assertBisectRecursive(expected, [], state, ['d'])
1838
self.assertBisectRecursive(expected, [], state, ['b/e'])
1839
self.assertBisectRecursive(expected, [], state, ['g'])
1840
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1842
def test_bisect_recursive_renamed(self):
1843
tree, state, expected = self.create_renamed_dirstate()
1845
# Looking for either renamed item should find the other
1846
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1847
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1848
# Looking in the containing directory should find the rename target,
1849
# and anything in a subdir of the renamed target.
1850
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1851
'b/d/e', 'b/g', 'h', 'h/e'],
1855
class TestBisectDirblock(TestCase):
1856
"""Test that bisect_dirblock() returns the expected values.
1858
bisect_dirblock is intended to work like bisect.bisect_left() except it
1859
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1860
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1863
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1864
"""Assert that bisect_split works like bisect_left on the split paths.
1866
:param dirblocks: A list of (path, [info]) pairs.
1867
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1868
:param path: The path we are indexing.
1870
All other arguments will be passed along.
1872
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1874
split_dirblock = (path.split('/'), [])
1875
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1877
self.assertEqual(bisect_left_idx, bisect_split_idx,
1878
'bisect_split disagreed. %s != %s'
1880
% (bisect_left_idx, bisect_split_idx, path)
1883
def paths_to_dirblocks(self, paths):
1884
"""Convert a list of paths into dirblock form.
1886
Also, ensure that the paths are in proper sorted order.
1888
dirblocks = [(path, []) for path in paths]
1889
split_dirblocks = [(path.split('/'), []) for path in paths]
1890
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1891
return dirblocks, split_dirblocks
1893
def test_simple(self):
1894
"""In the simple case it works just like bisect_left"""
1895
paths = ['', 'a', 'b', 'c', 'd']
1896
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1898
self.assertBisect(dirblocks, split_dirblocks, path)
1899
self.assertBisect(dirblocks, split_dirblocks, '_')
1900
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1901
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1902
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1903
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1904
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1905
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1906
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1907
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1909
def test_involved(self):
1910
"""This is where bisect_left diverges slightly."""
1912
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1913
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1915
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1916
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1919
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1921
self.assertBisect(dirblocks, split_dirblocks, path)
1923
def test_involved_cached(self):
1924
"""This is where bisect_left diverges slightly."""
1926
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1927
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1929
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1930
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1934
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1936
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)