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."""
26
from bzrlib.memorytree import MemoryTree
27
from bzrlib.tests import TestCaseWithTransport
31
# test DirStateRevisionTree : test filtering out of deleted files does not
32
# filter out files called RECYCLED.BIN ;)
33
# test 0 parents, 1 parent, 4 parents.
34
# test unicode parents, non unicode parents
35
# test all change permutations in one and two parents.
36
# i.e. file in parent 1, dir in parent 2, symlink in tree.
37
# test that renames in the tree result in correct parent paths
38
# Test get state from a file, then asking for lines.
39
# write a smaller state, and check the file has been truncated.
40
# add a entry when its in state deleted
41
# revision attribute for root entries.
42
# test that utf8 strings are preserved in _row_to_line
43
# test parent manipulation
44
# test parents that are null in save : i.e. no record in the parent tree for this.
45
# todo: _set_data records ghost parents.
47
# general checks for NOT_IN_MEMORY error conditions.
48
# set_path_id on a NOT_IN_MEMORY dirstate
49
# set_path_id unicode support
50
# set_path_id setting id of a path not root
51
# set_path_id setting id when there are parents without the id in the parents
52
# set_path_id setting id when there are parents with the id in the parents
53
# set_path_id setting id when state is not in memory
54
# set_path_id setting id when state is in memory unmodified
55
# set_path_id setting id when state is in memory modified
57
class TestCaseWithDirState(TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
60
def create_empty_dirstate(self):
61
"""Return a locked but empty dirstate"""
62
state = dirstate.DirState.initialize('dirstate')
65
def create_dirstate_with_root(self):
66
"""Return a write-locked state with a single root entry."""
67
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
68
root_entry_direntry = ('', '', 'a-root-value'), [
69
('d', '', 0, False, packed_stat),
72
dirblocks.append(('', [root_entry_direntry]))
73
dirblocks.append(('', []))
74
state = self.create_empty_dirstate()
76
state._set_data([], dirblocks)
82
def create_dirstate_with_root_and_subdir(self):
83
"""Return a locked DirState with a root and a subdir"""
84
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
85
subdir_entry = ('', 'subdir', 'subdir-id'), [
86
('d', '', 0, False, packed_stat),
88
state = self.create_dirstate_with_root()
90
dirblocks = list(state._dirblocks)
91
dirblocks[1][1].append(subdir_entry)
92
state._set_data([], dirblocks)
98
def create_complex_dirstate(self):
99
"""This dirstate contains multiple files and directories.
109
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
111
# Notice that a/e is an empty directory.
113
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
114
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
115
root_entry = ('', '', 'a-root-value'), [
116
('d', '', 0, False, packed_stat),
118
a_entry = ('', 'a', 'a-dir'), [
119
('d', '', 0, False, packed_stat),
121
b_entry = ('', 'b', 'b-dir'), [
122
('d', '', 0, False, packed_stat),
124
c_entry = ('', 'c', 'c-file'), [
125
('f', null_sha, 10, False, packed_stat),
127
d_entry = ('', 'd', 'd-file'), [
128
('f', null_sha, 20, False, packed_stat),
130
e_entry = ('a', 'e', 'e-dir'), [
131
('d', '', 0, False, packed_stat),
133
f_entry = ('a', 'f', 'f-file'), [
134
('f', null_sha, 30, False, packed_stat),
136
g_entry = ('b', 'g', 'g-file'), [
137
('f', null_sha, 30, False, packed_stat),
139
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
140
('f', null_sha, 40, False, packed_stat),
143
dirblocks.append(('', [root_entry]))
144
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
145
dirblocks.append(('a', [e_entry, f_entry]))
146
dirblocks.append(('b', [g_entry, h_entry]))
147
state = dirstate.DirState.initialize('dirstate')
149
state._set_data([], dirblocks)
155
def check_state_with_reopen(self, expected_result, state):
156
"""Check that state has current state expected_result.
158
This will check the current state, open the file anew and check it
160
This function expects the current state to be locked for writing, and
161
will unlock it before re-opening.
162
This is required because we can't open a lock_read() while something
163
else has a lock_write().
164
write => mutually exclusive lock
167
# The state should already be write locked, since we just had to do
168
# some operation to get here.
169
assert state._lock_token is not None
171
self.assertEqual(expected_result[0], state.get_parent_ids())
172
# there should be no ghosts in this tree.
173
self.assertEqual([], state.get_ghosts())
174
# there should be one fileid in this tree - the root of the tree.
175
self.assertEqual(expected_result[1], list(state._iter_entries()))
179
del state # Callers should unlock
180
state = dirstate.DirState.on_file('dirstate')
183
self.assertEqual(expected_result[1], list(state._iter_entries()))
188
class TestTreeToDirState(TestCaseWithDirState):
190
def test_empty_to_dirstate(self):
191
"""We should be able to create a dirstate for an empty tree."""
192
# There are no files on disk and no parents
193
tree = self.make_branch_and_tree('tree')
194
expected_result = ([], [
195
(('', '', tree.path2id('')), # common details
196
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
198
state = dirstate.DirState.from_tree(tree, 'dirstate')
199
self.check_state_with_reopen(expected_result, state)
201
def test_1_parents_empty_to_dirstate(self):
202
# create a parent by doing a commit
203
tree = self.make_branch_and_tree('tree')
204
rev_id = tree.commit('first post').encode('utf8')
205
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
206
expected_result = ([rev_id], [
207
(('', '', tree.path2id('')), # common details
208
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
209
('d', '', 0, False, rev_id), # first parent details
211
state = dirstate.DirState.from_tree(tree, 'dirstate')
212
self.check_state_with_reopen(expected_result, state)
214
def test_2_parents_empty_to_dirstate(self):
215
# create a parent by doing a commit
216
tree = self.make_branch_and_tree('tree')
217
rev_id = tree.commit('first post')
218
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
219
rev_id2 = tree2.commit('second post', allow_pointless=True)
220
tree.merge_from_branch(tree2.branch)
221
expected_result = ([rev_id, rev_id2], [
222
(('', '', tree.path2id('')), # common details
223
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
224
('d', '', 0, False, rev_id), # first parent details
225
('d', '', 0, False, rev_id2), # second parent details
227
state = dirstate.DirState.from_tree(tree, 'dirstate')
228
self.check_state_with_reopen(expected_result, state)
230
def test_empty_unknowns_are_ignored_to_dirstate(self):
231
"""We should be able to create a dirstate for an empty tree."""
232
# There are no files on disk and no parents
233
tree = self.make_branch_and_tree('tree')
234
self.build_tree(['tree/unknown'])
235
expected_result = ([], [
236
(('', '', tree.path2id('')), # common details
237
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
239
state = dirstate.DirState.from_tree(tree, 'dirstate')
240
self.check_state_with_reopen(expected_result, state)
242
def get_tree_with_a_file(self):
243
tree = self.make_branch_and_tree('tree')
244
self.build_tree(['tree/a file'])
245
tree.add('a file', 'a file id')
248
def test_non_empty_no_parents_to_dirstate(self):
249
"""We should be able to create a dirstate for an empty tree."""
250
# There are files on disk and no parents
251
tree = self.get_tree_with_a_file()
252
expected_result = ([], [
253
(('', '', tree.path2id('')), # common details
254
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
256
(('', 'a file', 'a file id'), # common
257
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
261
self.check_state_with_reopen(expected_result, state)
263
def test_1_parents_not_empty_to_dirstate(self):
264
# create a parent by doing a commit
265
tree = self.get_tree_with_a_file()
266
rev_id = tree.commit('first post').encode('utf8')
267
# change the current content to be different this will alter stat, sha
269
self.build_tree_contents([('tree/a file', 'new content\n')])
270
expected_result = ([rev_id], [
271
(('', '', tree.path2id('')), # common details
272
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
273
('d', '', 0, False, rev_id), # first parent details
275
(('', 'a file', 'a file id'), # common
276
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
277
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
278
rev_id), # first parent
281
state = dirstate.DirState.from_tree(tree, 'dirstate')
282
self.check_state_with_reopen(expected_result, state)
284
def test_2_parents_not_empty_to_dirstate(self):
285
# create a parent by doing a commit
286
tree = self.get_tree_with_a_file()
287
rev_id = tree.commit('first post').encode('utf8')
288
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
289
# change the current content to be different this will alter stat, sha
291
self.build_tree_contents([('tree2/a file', 'merge content\n')])
292
rev_id2 = tree2.commit('second post').encode('utf8')
293
tree.merge_from_branch(tree2.branch)
294
# change the current content to be different this will alter stat, sha
295
# and length again, giving us three distinct values:
296
self.build_tree_contents([('tree/a file', 'new content\n')])
297
expected_result = ([rev_id, rev_id2], [
298
(('', '', tree.path2id('')), # common details
299
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
300
('d', '', 0, False, rev_id), # first parent details
301
('d', '', 0, False, rev_id2), # second parent details
303
(('', 'a file', 'a file id'), # common
304
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
305
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
306
rev_id), # first parent
307
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
308
rev_id2), # second parent
311
state = dirstate.DirState.from_tree(tree, 'dirstate')
312
self.check_state_with_reopen(expected_result, state)
315
class TestDirStateOnFile(TestCaseWithDirState):
317
def test_construct_with_path(self):
318
tree = self.make_branch_and_tree('tree')
319
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
320
# we want to be able to get the lines of the dirstate that we will
322
lines = state.get_lines()
324
self.build_tree_contents([('dirstate', ''.join(lines))])
326
# no parents, default tree content
327
expected_result = ([], [
328
(('', '', tree.path2id('')), # common details
329
# current tree details, but new from_tree skips statting, it
330
# uses set_state_from_inventory, and thus depends on the
332
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
335
state = dirstate.DirState.on_file('dirstate')
336
state.lock_write() # check_state_with_reopen will save() and unlock it
337
self.check_state_with_reopen(expected_result, state)
339
def test_can_save_clean_on_file(self):
340
tree = self.make_branch_and_tree('tree')
341
state = dirstate.DirState.from_tree(tree, 'dirstate')
343
# doing a save should work here as there have been no changes.
345
# TODO: stat it and check it hasn't changed; may require waiting
346
# for the state accuracy window.
351
class TestDirStateInitialize(TestCaseWithDirState):
353
def test_initialize(self):
354
expected_result = ([], [
355
(('', '', 'TREE_ROOT'), # common details
356
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
359
state = dirstate.DirState.initialize('dirstate')
361
self.assertIsInstance(state, dirstate.DirState)
362
lines = state.get_lines()
363
self.assertFileEqual(''.join(state.get_lines()),
365
self.check_state_with_reopen(expected_result, state)
371
class TestDirStateManipulations(TestCaseWithDirState):
373
def test_set_state_from_inventory_no_content_no_parents(self):
374
# setting the current inventory is a slow but important api to support.
375
tree1 = self.make_branch_and_memory_tree('tree1')
379
revid1 = tree1.commit('foo').encode('utf8')
380
root_id = tree1.inventory.root.file_id
381
inv = tree1.inventory
384
expected_result = [], [
385
(('', '', root_id), [
386
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
387
state = dirstate.DirState.initialize('dirstate')
389
state.set_state_from_inventory(inv)
390
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
392
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
393
state._dirblock_state)
398
# This will unlock it
399
self.check_state_with_reopen(expected_result, state)
401
def test_set_path_id_no_parents(self):
402
"""The id of a path can be changed trivally with no parents."""
403
state = dirstate.DirState.initialize('dirstate')
405
# check precondition to be sure the state does change appropriately.
407
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
408
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
409
list(state._iter_entries()))
410
state.set_path_id('', 'foobarbaz')
412
(('', '', 'foobarbaz'), [('d', '', 0, False,
413
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
414
self.assertEqual(expected_rows, list(state._iter_entries()))
415
# should work across save too
419
state = dirstate.DirState.on_file('dirstate')
422
self.assertEqual(expected_rows, list(state._iter_entries()))
426
def test_set_parent_trees_no_content(self):
427
# set_parent_trees is a slow but important api to support.
428
tree1 = self.make_branch_and_memory_tree('tree1')
432
revid1 = tree1.commit('foo')
435
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
436
tree2 = MemoryTree.create_on_branch(branch2)
439
revid2 = tree2.commit('foo')
440
root_id = tree2.inventory.root.file_id
443
state = dirstate.DirState.initialize('dirstate')
445
state.set_path_id('', root_id)
446
state.set_parent_trees(
447
((revid1, tree1.branch.repository.revision_tree(revid1)),
448
(revid2, tree2.branch.repository.revision_tree(revid2)),
449
('ghost-rev', None)),
451
# check we can reopen and use the dirstate after setting parent
456
state = dirstate.DirState.on_file('dirstate')
459
self.assertEqual([revid1, revid2, 'ghost-rev'],
460
state.get_parent_ids())
461
# iterating the entire state ensures that the state is parsable.
462
list(state._iter_entries())
463
# be sure that it sets not appends - change it
464
state.set_parent_trees(
465
((revid1, tree1.branch.repository.revision_tree(revid1)),
466
('ghost-rev', None)),
468
# and now put it back.
469
state.set_parent_trees(
470
((revid1, tree1.branch.repository.revision_tree(revid1)),
471
(revid2, tree2.branch.repository.revision_tree(revid2)),
472
('ghost-rev', tree2.branch.repository.revision_tree(None))),
474
self.assertEqual([revid1, revid2, 'ghost-rev'],
475
state.get_parent_ids())
476
# the ghost should be recorded as such by set_parent_trees.
477
self.assertEqual(['ghost-rev'], state.get_ghosts())
479
[(('', '', root_id), [
480
('d', '', 0, False, dirstate.DirState.NULLSTAT),
481
('d', '', 0, False, revid1),
482
('d', '', 0, False, revid2)
484
list(state._iter_entries()))
488
def test_set_parent_trees_file_missing_from_tree(self):
489
# Adding a parent tree may reference files not in the current state.
490
# they should get listed just once by id, even if they are in two
492
# set_parent_trees is a slow but important api to support.
493
tree1 = self.make_branch_and_memory_tree('tree1')
497
tree1.add(['a file'], ['file-id'], ['file'])
498
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
499
revid1 = tree1.commit('foo')
502
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
503
tree2 = MemoryTree.create_on_branch(branch2)
506
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
507
revid2 = tree2.commit('foo')
508
root_id = tree2.inventory.root.file_id
511
# check the layout in memory
512
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
513
(('', '', root_id), [
514
('d', '', 0, False, dirstate.DirState.NULLSTAT),
515
('d', '', 0, False, revid1.encode('utf8')),
516
('d', '', 0, False, revid2.encode('utf8'))
518
(('', 'a file', 'file-id'), [
519
('a', '', 0, False, ''),
520
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
521
revid1.encode('utf8')),
522
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
523
revid2.encode('utf8'))
526
state = dirstate.DirState.initialize('dirstate')
528
state.set_path_id('', root_id)
529
state.set_parent_trees(
530
((revid1, tree1.branch.repository.revision_tree(revid1)),
531
(revid2, tree2.branch.repository.revision_tree(revid2)),
537
# check_state_with_reopen will unlock
538
self.check_state_with_reopen(expected_result, state)
540
### add a path via _set_data - so we dont need delta work, just
541
# raw data in, and ensure that it comes out via get_lines happily.
543
def test_add_path_to_root_no_parents_all_data(self):
544
# The most trivial addition of a path is when there are no parents and
545
# its in the root and all data about the file is supplied
546
self.build_tree(['a file'])
547
stat = os.lstat('a file')
548
# the 1*20 is the sha1 pretend value.
549
state = dirstate.DirState.initialize('dirstate')
551
(('', '', 'TREE_ROOT'), [
552
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
554
(('', 'a file', 'a file id'), [
555
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
559
state.add('a file', 'a file id', 'file', stat, '1'*20)
560
# having added it, it should be in the output of iter_entries.
561
self.assertEqual(expected_entries, list(state._iter_entries()))
562
# saving and reloading should not affect this.
566
state = dirstate.DirState.on_file('dirstate')
569
self.assertEqual(expected_entries, list(state._iter_entries()))
573
def test_add_path_to_unversioned_directory(self):
574
"""Adding a path to an unversioned directory should error.
576
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
577
once dirstate is stable and if it is merged with WorkingTree3, consider
578
removing this copy of the test.
580
self.build_tree(['unversioned/', 'unversioned/a file'])
581
state = dirstate.DirState.initialize('dirstate')
583
self.assertRaises(errors.NotVersionedError, state.add,
584
'unversioned/a file', 'a file id', 'file', None, None)
588
def test_add_directory_to_root_no_parents_all_data(self):
589
# The most trivial addition of a dir is when there are no parents and
590
# its in the root and all data about the file is supplied
591
self.build_tree(['a dir/'])
592
stat = os.lstat('a dir')
594
(('', '', 'TREE_ROOT'), [
595
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
597
(('', 'a dir', 'a dir id'), [
598
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
601
state = dirstate.DirState.initialize('dirstate')
603
state.add('a dir', 'a dir id', 'directory', stat, None)
604
# having added it, it should be in the output of iter_entries.
605
self.assertEqual(expected_entries, list(state._iter_entries()))
606
# saving and reloading should not affect this.
610
state = dirstate.DirState.on_file('dirstate')
613
self.assertEqual(expected_entries, list(state._iter_entries()))
617
def test_add_symlink_to_root_no_parents_all_data(self):
618
# The most trivial addition of a symlink when there are no parents and
619
# its in the root and all data about the file is supplied
620
## TODO: windows: dont fail this test. Also, how are symlinks meant to
621
# be represented on windows.
622
os.symlink('target', 'a link')
623
stat = os.lstat('a link')
625
(('', '', 'TREE_ROOT'), [
626
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
628
(('', 'a link', 'a link id'), [
629
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
632
state = dirstate.DirState.initialize('dirstate')
634
state.add('a link', 'a link id', 'symlink', stat, 'target')
635
# having added it, it should be in the output of iter_entries.
636
self.assertEqual(expected_entries, list(state._iter_entries()))
637
# saving and reloading should not affect this.
641
state = dirstate.DirState.on_file('dirstate')
644
self.assertEqual(expected_entries, list(state._iter_entries()))
648
def test_add_directory_and_child_no_parents_all_data(self):
649
# after adding a directory, we should be able to add children to it.
650
self.build_tree(['a dir/', 'a dir/a file'])
651
dirstat = os.lstat('a dir')
652
filestat = os.lstat('a dir/a file')
654
(('', '', 'TREE_ROOT'), [
655
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
657
(('', 'a dir', 'a dir id'), [
658
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
660
(('a dir', 'a file', 'a file id'), [
661
('f', '1'*20, 25, False,
662
dirstate.pack_stat(filestat)), # current tree details
665
state = dirstate.DirState.initialize('dirstate')
667
state.add('a dir', 'a dir id', 'directory', dirstat, None)
668
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
669
# added it, it should be in the output of iter_entries.
670
self.assertEqual(expected_entries, list(state._iter_entries()))
671
# saving and reloading should not affect this.
675
state = dirstate.DirState.on_file('dirstate')
678
self.assertEqual(expected_entries, list(state._iter_entries()))
683
class TestGetLines(TestCaseWithDirState):
685
def test_get_line_with_2_rows(self):
686
state = self.create_dirstate_with_root_and_subdir()
688
self.assertEqual(['#bazaar dirstate flat format 2\n',
689
'adler32: -1327947603\n',
693
'\x00\x00a-root-value\x00'
694
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
695
'\x00subdir\x00subdir-id\x00'
696
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
697
], state.get_lines())
701
def test_entry_to_line(self):
702
state = self.create_dirstate_with_root()
705
'\x00\x00a-root-value\x00d\x00\x000\x00n'
706
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
707
state._entry_to_line(state._dirblocks[0][1][0]))
711
def test_entry_to_line_with_parent(self):
712
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
713
root_entry = ('', '', 'a-root-value'), [
714
('d', '', 0, False, packed_stat), # current tree details
715
# first: a pointer to the current location
716
('a', 'dirname/basename', 0, False, ''),
718
state = dirstate.DirState.initialize('dirstate')
721
'\x00\x00a-root-value\x00'
722
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
723
'a\x00dirname/basename\x000\x00n\x00',
724
state._entry_to_line(root_entry))
728
def test_entry_to_line_with_two_parents_at_different_paths(self):
729
# / in the tree, at / in one parent and /dirname/basename in the other.
730
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
731
root_entry = ('', '', 'a-root-value'), [
732
('d', '', 0, False, packed_stat), # current tree details
733
('d', '', 0, False, 'rev_id'), # first parent details
734
# second: a pointer to the current location
735
('a', 'dirname/basename', 0, False, ''),
737
state = dirstate.DirState.initialize('dirstate')
740
'\x00\x00a-root-value\x00'
741
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
742
'd\x00\x000\x00n\x00rev_id\x00'
743
'a\x00dirname/basename\x000\x00n\x00',
744
state._entry_to_line(root_entry))
748
def test_iter_entries(self):
749
# we should be able to iterate the dirstate entries from end to end
750
# this is for get_lines to be easy to read.
751
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
753
root_entries = [(('', '', 'a-root-value'), [
754
('d', '', 0, False, packed_stat), # current tree details
756
dirblocks.append(('', root_entries))
757
# add two files in the root
758
subdir_entry = ('', 'subdir', 'subdir-id'), [
759
('d', '', 0, False, packed_stat), # current tree details
761
afile_entry = ('', 'afile', 'afile-id'), [
762
('f', 'sha1value', 34, False, packed_stat), # current tree details
764
dirblocks.append(('', [subdir_entry, afile_entry]))
766
file_entry2 = ('subdir', '2file', '2file-id'), [
767
('f', 'sha1value', 23, False, packed_stat), # current tree details
769
dirblocks.append(('subdir', [file_entry2]))
770
state = dirstate.DirState.initialize('dirstate')
772
state._set_data([], dirblocks)
773
expected_entries = [root_entries[0], subdir_entry, afile_entry,
775
self.assertEqual(expected_entries, list(state._iter_entries()))
780
class TestGetBlockRowIndex(TestCaseWithDirState):
782
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
783
file_present, state, dirname, basename, tree_index):
784
self.assertEqual((block_index, row_index, dir_present, file_present),
785
state._get_block_entry_index(dirname, basename, tree_index))
787
block = state._dirblocks[block_index]
788
self.assertEqual(dirname, block[0])
789
if dir_present and file_present:
790
row = state._dirblocks[block_index][1][row_index]
791
self.assertEqual(dirname, row[0][0])
792
self.assertEqual(basename, row[0][1])
794
def test_simple_structure(self):
795
state = self.create_dirstate_with_root_and_subdir()
796
self.addCleanup(state.unlock)
797
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
798
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
799
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
800
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
801
self.assertBlockRowIndexEqual(2, 0, False, False, state,
804
def test_complex_structure_exists(self):
805
state = self.create_complex_dirstate()
806
self.addCleanup(state.unlock)
807
# Make sure we can find everything that exists
808
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
809
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
810
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
811
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
812
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
813
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
814
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
815
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
816
self.assertBlockRowIndexEqual(3, 1, True, True, state,
819
def test_complex_structure_missing(self):
820
state = self.create_complex_dirstate()
821
self.addCleanup(state.unlock)
822
# Make sure things would be inserted in the right locations
823
# '_' comes before 'a'
824
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
825
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
826
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
827
self.assertBlockRowIndexEqual(1, 4, True, False, state,
829
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
830
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
831
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
832
# This would be inserted between a/ and b/
833
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
835
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
838
class TestGetEntry(TestCaseWithDirState):
840
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
841
"""Check that the right entry is returned for a request to getEntry."""
842
entry = state._get_entry(index, path_utf8=path)
844
self.assertEqual((None, None), entry)
847
self.assertEqual((dirname, basename, file_id), cur[:3])
849
def test_simple_structure(self):
850
state = self.create_dirstate_with_root_and_subdir()
851
self.addCleanup(state.unlock)
852
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
853
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
854
self.assertEntryEqual(None, None, None, state, 'missing', 0)
855
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
856
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
858
def test_complex_structure_exists(self):
859
state = self.create_complex_dirstate()
860
self.addCleanup(state.unlock)
861
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
862
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
863
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
864
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
865
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
866
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
867
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
868
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
869
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
872
def test_complex_structure_missing(self):
873
state = self.create_complex_dirstate()
874
self.addCleanup(state.unlock)
875
self.assertEntryEqual(None, None, None, state, '_', 0)
876
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
877
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
878
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
880
def test_get_entry_uninitialized(self):
881
"""Calling get_entry will load data if it needs to"""
882
state = self.create_dirstate_with_root()
888
state = dirstate.DirState.on_file('dirstate')
891
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
893
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
894
state._dirblock_state)
895
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
900
class TestBisect(TestCaseWithTransport):
901
"""Test the ability to bisect into the disk format."""
903
def create_basic_dirstate(self):
904
"""Create a dirstate with a few files and directories.
913
tree = self.make_branch_and_tree('tree')
914
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
915
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
916
self.build_tree(['tree/' + p for p in paths])
917
tree.set_root_id('TREE_ROOT')
918
tree.add([p.rstrip('/') for p in paths], file_ids)
919
tree.commit('initial', rev_id='rev-1')
920
revision_id = 'rev-1'
921
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
922
t = self.get_transport().clone('tree')
923
a_text = t.get_bytes('a')
924
a_sha = osutils.sha_string(a_text)
926
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
927
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
928
c_text = t.get_bytes('b/c')
929
c_sha = osutils.sha_string(c_text)
931
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
932
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
933
e_text = t.get_bytes('b/d/e')
934
e_sha = osutils.sha_string(e_text)
936
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
937
f_text = t.get_bytes('f')
938
f_sha = osutils.sha_string(f_text)
940
null_stat = dirstate.DirState.NULLSTAT
942
'':(('', '', 'TREE_ROOT'), [
943
('d', '', 0, False, null_stat),
944
('d', '', 0, False, revision_id),
946
'a':(('', 'a', 'a-id'), [
947
('f', '', 0, False, null_stat),
948
('f', a_sha, a_len, False, revision_id),
950
'b':(('', 'b', 'b-id'), [
951
('d', '', 0, False, null_stat),
952
('d', '', 0, False, revision_id),
954
'b/c':(('b', 'c', 'c-id'), [
955
('f', '', 0, False, null_stat),
956
('f', c_sha, c_len, False, revision_id),
958
'b/d':(('b', 'd', 'd-id'), [
959
('d', '', 0, False, null_stat),
960
('d', '', 0, False, revision_id),
962
'b/d/e':(('b/d', 'e', 'e-id'), [
963
('f', '', 0, False, null_stat),
964
('f', e_sha, e_len, False, revision_id),
966
'f':(('', 'f', 'f-id'), [
967
('f', '', 0, False, null_stat),
968
('f', f_sha, f_len, False, revision_id),
971
state = dirstate.DirState.from_tree(tree, 'dirstate')
976
# Use a different object, to make sure nothing is pre-cached in memory.
977
state = dirstate.DirState.on_file('dirstate')
979
self.addCleanup(state.unlock)
980
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
981
state._dirblock_state)
982
# This is code is only really tested if we actually have to make more
983
# than one read, so set the page size to something smaller.
984
# We want it to contain about 2.2 records, so that we have a couple
985
# records that we can read per attempt
986
state._bisect_page_size = 200
987
return tree, state, expected
989
def create_duplicated_dirstate(self):
990
"""Create a dirstate with a deleted and added entries.
992
This grabs a basic_dirstate, and then removes and re adds every entry
995
tree, state, expected = self.create_basic_dirstate()
996
# Now we will just remove and add every file so we get an extra entry
997
# per entry. Unversion in reverse order so we handle subdirs
998
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
999
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1000
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1002
# Update the expected dictionary.
1003
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1004
orig = expected[path]
1006
# This record was deleted in the current tree
1007
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1009
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1010
# And didn't exist in the basis tree
1011
expected[path2] = (new_key, [orig[1][0],
1012
dirstate.DirState.NULL_PARENT_DETAILS])
1014
# We will replace the 'dirstate' file underneath 'state', but that is
1015
# okay as lock as we unlock 'state' first.
1018
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1024
# But we need to leave state in a read-lock because we already have
1025
# a cleanup scheduled
1027
return tree, state, expected
1029
def create_renamed_dirstate(self):
1030
"""Create a dirstate with a few internal renames.
1032
This takes the basic dirstate, and moves the paths around.
1034
tree, state, expected = self.create_basic_dirstate()
1036
tree.rename_one('a', 'b/g')
1038
tree.rename_one('b/d', 'h')
1040
old_a = expected['a']
1041
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1042
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1043
('r', 'a', 0, False, '')])
1044
old_d = expected['b/d']
1045
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1046
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1047
('r', 'b/d', 0, False, '')])
1049
old_e = expected['b/d/e']
1050
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1052
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1053
('r', 'b/d/e', 0, False, '')])
1057
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1064
return tree, state, expected
1066
def assertBisect(self, expected_map, map_keys, state, paths):
1067
"""Assert that bisecting for paths returns the right result.
1069
:param expected_map: A map from key => entry value
1070
:param map_keys: The keys to expect for each path
1071
:param state: The DirState object.
1072
:param paths: A list of paths, these will automatically be split into
1073
(dir, name) tuples, and sorted according to how _bisect
1076
dir_names = sorted(osutils.split(p) for p in paths)
1077
result = state._bisect(dir_names)
1078
# For now, results are just returned in whatever order we read them.
1079
# We could sort by (dir, name, file_id) or something like that, but in
1080
# the end it would still be fairly arbitrary, and we don't want the
1081
# extra overhead if we can avoid it. So sort everything to make sure
1083
assert len(map_keys) == len(dir_names)
1085
for dir_name, keys in zip(dir_names, map_keys):
1087
# This should not be present in the output
1089
expected[dir_name] = sorted(expected_map[k] for k in keys)
1091
for dir_name in result:
1092
result[dir_name].sort()
1094
self.assertEqual(expected, result)
1096
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1097
"""Assert that bisecting for dirbblocks returns the right result.
1099
:param expected_map: A map from key => expected values
1100
:param map_keys: A nested list of paths we expect to be returned.
1101
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1102
:param state: The DirState object.
1103
:param paths: A list of directories
1105
result = state._bisect_dirblocks(paths)
1106
assert len(map_keys) == len(paths)
1109
for path, keys in zip(paths, map_keys):
1111
# This should not be present in the output
1113
expected[path] = sorted(expected_map[k] for k in keys)
1117
self.assertEqual(expected, result)
1119
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1120
"""Assert the return value of a recursive bisection.
1122
:param expected_map: A map from key => entry value
1123
:param map_keys: A list of paths we expect to be returned.
1124
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1125
:param state: The DirState object.
1126
:param paths: A list of files and directories. It will be broken up
1127
into (dir, name) pairs and sorted before calling _bisect_recursive.
1130
for key in map_keys:
1131
entry = expected_map[key]
1132
dir_name_id, trees_info = entry
1133
expected[dir_name_id] = trees_info
1135
dir_names = sorted(osutils.split(p) for p in paths)
1136
result = state._bisect_recursive(dir_names)
1138
self.assertEqual(expected, result)
1140
def test_bisect_each(self):
1141
"""Find a single record using bisect."""
1142
tree, state, expected = self.create_basic_dirstate()
1144
# Bisect should return the rows for the specified files.
1145
self.assertBisect(expected, [['']], state, [''])
1146
self.assertBisect(expected, [['a']], state, ['a'])
1147
self.assertBisect(expected, [['b']], state, ['b'])
1148
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1149
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1150
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1151
self.assertBisect(expected, [['f']], state, ['f'])
1153
def test_bisect_multi(self):
1154
"""Bisect can be used to find multiple records at the same time."""
1155
tree, state, expected = self.create_basic_dirstate()
1156
# Bisect should be capable of finding multiple entries at the same time
1157
self.assertBisect(expected, [['a'], ['b'], ['f']],
1158
state, ['a', 'b', 'f'])
1159
# ('', 'f') sorts before the others
1160
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1161
state, ['b/d', 'b/d/e', 'f'])
1163
def test_bisect_one_page(self):
1164
"""Test bisect when there is only 1 page to read"""
1165
tree, state, expected = self.create_basic_dirstate()
1166
state._bisect_page_size = 5000
1167
self.assertBisect(expected,[['']], state, [''])
1168
self.assertBisect(expected,[['a']], state, ['a'])
1169
self.assertBisect(expected,[['b']], state, ['b'])
1170
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1171
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1172
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1173
self.assertBisect(expected,[['f']], state, ['f'])
1174
self.assertBisect(expected,[['a'], ['b'], ['f']],
1175
state, ['a', 'b', 'f'])
1176
# ('', 'f') sorts before the others
1177
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1178
state, ['b/d', 'b/d/e', 'f'])
1180
def test_bisect_duplicate_paths(self):
1181
"""When bisecting for a path, handle multiple entries."""
1182
tree, state, expected = self.create_duplicated_dirstate()
1184
# Now make sure that both records are properly returned.
1185
self.assertBisect(expected, [['']], state, [''])
1186
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1187
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1188
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1189
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1190
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1192
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1194
def test_bisect_page_size_too_small(self):
1195
"""If the page size is too small, we will auto increase it."""
1196
tree, state, expected = self.create_basic_dirstate()
1197
state._bisect_page_size = 50
1198
self.assertBisect(expected, [None], state, ['b/e'])
1199
self.assertBisect(expected, [['a']], state, ['a'])
1200
self.assertBisect(expected, [['b']], state, ['b'])
1201
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1202
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1203
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1204
self.assertBisect(expected, [['f']], state, ['f'])
1206
def test_bisect_missing(self):
1207
"""Test that bisect return None if it cannot find a path."""
1208
tree, state, expected = self.create_basic_dirstate()
1209
self.assertBisect(expected, [None], state, ['foo'])
1210
self.assertBisect(expected, [None], state, ['b/foo'])
1211
self.assertBisect(expected, [None], state, ['bar/foo'])
1213
self.assertBisect(expected, [['a'], None, ['b/d']],
1214
state, ['a', 'foo', 'b/d'])
1216
def test_bisect_rename(self):
1217
"""Check that we find a renamed row."""
1218
tree, state, expected = self.create_renamed_dirstate()
1220
# Search for the pre and post renamed entries
1221
self.assertBisect(expected, [['a']], state, ['a'])
1222
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1223
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1224
self.assertBisect(expected, [['h']], state, ['h'])
1226
# What about b/d/e? shouldn't that also get 2 directory entries?
1227
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1228
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1230
def test_bisect_dirblocks(self):
1231
tree, state, expected = self.create_duplicated_dirstate()
1232
self.assertBisectDirBlocks(expected,
1233
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1234
self.assertBisectDirBlocks(expected,
1235
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1236
self.assertBisectDirBlocks(expected,
1237
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1238
self.assertBisectDirBlocks(expected,
1239
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1240
['b/c', 'b/c2', 'b/d', 'b/d2'],
1241
['b/d/e', 'b/d/e2'],
1242
], state, ['', 'b', 'b/d'])
1244
def test_bisect_dirblocks_missing(self):
1245
tree, state, expected = self.create_basic_dirstate()
1246
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1247
state, ['b/d', 'b/e'])
1248
# Files don't show up in this search
1249
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1250
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1251
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1252
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1253
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1255
def test_bisect_recursive_each(self):
1256
tree, state, expected = self.create_basic_dirstate()
1257
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1258
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1259
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1260
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1262
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1264
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1268
def test_bisect_recursive_multiple(self):
1269
tree, state, expected = self.create_basic_dirstate()
1270
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1271
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1272
state, ['b/d', 'b/d/e'])
1274
def test_bisect_recursive_missing(self):
1275
tree, state, expected = self.create_basic_dirstate()
1276
self.assertBisectRecursive(expected, [], state, ['d'])
1277
self.assertBisectRecursive(expected, [], state, ['b/e'])
1278
self.assertBisectRecursive(expected, [], state, ['g'])
1279
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1281
def test_bisect_recursive_renamed(self):
1282
tree, state, expected = self.create_renamed_dirstate()
1284
# Looking for either renamed item should find the other
1285
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1286
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1287
# Looking in the containing directory should find the rename target,
1288
# and anything in a subdir of the renamed target.
1289
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1290
'b/d/e', 'b/g', 'h', 'h/e'],