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."""
27
from bzrlib.memorytree import MemoryTree
28
from bzrlib.tests import TestCase, TestCaseWithTransport
32
# test DirStateRevisionTree : test filtering out of deleted files does not
33
# filter out files called RECYCLED.BIN ;)
34
# test 0 parents, 1 parent, 4 parents.
35
# test unicode parents, non unicode parents
36
# test all change permutations in one and two parents.
37
# i.e. file in parent 1, dir in parent 2, symlink in tree.
38
# test that renames in the tree result in correct parent paths
39
# Test get state from a file, then asking for lines.
40
# write a smaller state, and check the file has been truncated.
41
# add a entry when its in state deleted
42
# revision attribute for root entries.
43
# test that utf8 strings are preserved in _row_to_line
44
# test parent manipulation
45
# test parents that are null in save : i.e. no record in the parent tree for this.
46
# todo: _set_data records ghost parents.
48
# general checks for NOT_IN_MEMORY error conditions.
49
# set_path_id on a NOT_IN_MEMORY dirstate
50
# set_path_id unicode support
51
# set_path_id setting id of a path not root
52
# set_path_id setting id when there are parents without the id in the parents
53
# set_path_id setting id when there are parents with the id in the parents
54
# set_path_id setting id when state is not in memory
55
# set_path_id setting id when state is in memory unmodified
56
# set_path_id setting id when state is in memory modified
58
class TestCaseWithDirState(TestCaseWithTransport):
59
"""Helper functions for creating DirState objects with various content."""
61
def create_empty_dirstate(self):
62
"""Return a locked but empty dirstate"""
63
state = dirstate.DirState.initialize('dirstate')
66
def create_dirstate_with_root(self):
67
"""Return a write-locked state with a single root entry."""
68
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
69
root_entry_direntry = ('', '', 'a-root-value'), [
70
('d', '', 0, False, packed_stat),
73
dirblocks.append(('', [root_entry_direntry]))
74
dirblocks.append(('', []))
75
state = self.create_empty_dirstate()
77
state._set_data([], dirblocks)
83
def create_dirstate_with_root_and_subdir(self):
84
"""Return a locked DirState with a root and a subdir"""
85
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
86
subdir_entry = ('', 'subdir', 'subdir-id'), [
87
('d', '', 0, False, packed_stat),
89
state = self.create_dirstate_with_root()
91
dirblocks = list(state._dirblocks)
92
dirblocks[1][1].append(subdir_entry)
93
state._set_data([], dirblocks)
99
def create_complex_dirstate(self):
100
"""This dirstate contains multiple files and directories.
110
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
112
# Notice that a/e is an empty directory.
114
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
115
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
116
root_entry = ('', '', 'a-root-value'), [
117
('d', '', 0, False, packed_stat),
119
a_entry = ('', 'a', 'a-dir'), [
120
('d', '', 0, False, packed_stat),
122
b_entry = ('', 'b', 'b-dir'), [
123
('d', '', 0, False, packed_stat),
125
c_entry = ('', 'c', 'c-file'), [
126
('f', null_sha, 10, False, packed_stat),
128
d_entry = ('', 'd', 'd-file'), [
129
('f', null_sha, 20, False, packed_stat),
131
e_entry = ('a', 'e', 'e-dir'), [
132
('d', '', 0, False, packed_stat),
134
f_entry = ('a', 'f', 'f-file'), [
135
('f', null_sha, 30, False, packed_stat),
137
g_entry = ('b', 'g', 'g-file'), [
138
('f', null_sha, 30, False, packed_stat),
140
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
141
('f', null_sha, 40, False, packed_stat),
144
dirblocks.append(('', [root_entry]))
145
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
146
dirblocks.append(('a', [e_entry, f_entry]))
147
dirblocks.append(('b', [g_entry, h_entry]))
148
state = dirstate.DirState.initialize('dirstate')
150
state._set_data([], dirblocks)
156
def check_state_with_reopen(self, expected_result, state):
157
"""Check that state has current state expected_result.
159
This will check the current state, open the file anew and check it
161
This function expects the current state to be locked for writing, and
162
will unlock it before re-opening.
163
This is required because we can't open a lock_read() while something
164
else has a lock_write().
165
write => mutually exclusive lock
168
# The state should already be write locked, since we just had to do
169
# some operation to get here.
170
assert state._lock_token is not None
172
self.assertEqual(expected_result[0], state.get_parent_ids())
173
# there should be no ghosts in this tree.
174
self.assertEqual([], state.get_ghosts())
175
# there should be one fileid in this tree - the root of the tree.
176
self.assertEqual(expected_result[1], list(state._iter_entries()))
180
del state # Callers should unlock
181
state = dirstate.DirState.on_file('dirstate')
184
self.assertEqual(expected_result[1], list(state._iter_entries()))
189
class TestTreeToDirState(TestCaseWithDirState):
191
def test_empty_to_dirstate(self):
192
"""We should be able to create a dirstate for an empty tree."""
193
# There are no files on disk and no parents
194
tree = self.make_branch_and_tree('tree')
195
expected_result = ([], [
196
(('', '', tree.path2id('')), # common details
197
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
199
state = dirstate.DirState.from_tree(tree, 'dirstate')
200
self.check_state_with_reopen(expected_result, state)
202
def test_1_parents_empty_to_dirstate(self):
203
# create a parent by doing a commit
204
tree = self.make_branch_and_tree('tree')
205
rev_id = tree.commit('first post').encode('utf8')
206
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
207
expected_result = ([rev_id], [
208
(('', '', tree.path2id('')), # common details
209
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
210
('d', '', 0, False, rev_id), # first parent details
212
state = dirstate.DirState.from_tree(tree, 'dirstate')
213
self.check_state_with_reopen(expected_result, state)
215
def test_2_parents_empty_to_dirstate(self):
216
# create a parent by doing a commit
217
tree = self.make_branch_and_tree('tree')
218
rev_id = tree.commit('first post')
219
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
220
rev_id2 = tree2.commit('second post', allow_pointless=True)
221
tree.merge_from_branch(tree2.branch)
222
expected_result = ([rev_id, rev_id2], [
223
(('', '', tree.path2id('')), # common details
224
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
225
('d', '', 0, False, rev_id), # first parent details
226
('d', '', 0, False, rev_id2), # second parent details
228
state = dirstate.DirState.from_tree(tree, 'dirstate')
229
self.check_state_with_reopen(expected_result, state)
231
def test_empty_unknowns_are_ignored_to_dirstate(self):
232
"""We should be able to create a dirstate for an empty tree."""
233
# There are no files on disk and no parents
234
tree = self.make_branch_and_tree('tree')
235
self.build_tree(['tree/unknown'])
236
expected_result = ([], [
237
(('', '', tree.path2id('')), # common details
238
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
240
state = dirstate.DirState.from_tree(tree, 'dirstate')
241
self.check_state_with_reopen(expected_result, state)
243
def get_tree_with_a_file(self):
244
tree = self.make_branch_and_tree('tree')
245
self.build_tree(['tree/a file'])
246
tree.add('a file', 'a file id')
249
def test_non_empty_no_parents_to_dirstate(self):
250
"""We should be able to create a dirstate for an empty tree."""
251
# There are files on disk and no parents
252
tree = self.get_tree_with_a_file()
253
expected_result = ([], [
254
(('', '', tree.path2id('')), # common details
255
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
257
(('', 'a file', 'a file id'), # common
258
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
261
state = dirstate.DirState.from_tree(tree, 'dirstate')
262
self.check_state_with_reopen(expected_result, state)
264
def test_1_parents_not_empty_to_dirstate(self):
265
# create a parent by doing a commit
266
tree = self.get_tree_with_a_file()
267
rev_id = tree.commit('first post').encode('utf8')
268
# change the current content to be different this will alter stat, sha
270
self.build_tree_contents([('tree/a file', 'new content\n')])
271
expected_result = ([rev_id], [
272
(('', '', tree.path2id('')), # common details
273
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
274
('d', '', 0, False, rev_id), # first parent details
276
(('', 'a file', 'a file id'), # common
277
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
278
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
279
rev_id), # first parent
282
state = dirstate.DirState.from_tree(tree, 'dirstate')
283
self.check_state_with_reopen(expected_result, state)
285
def test_2_parents_not_empty_to_dirstate(self):
286
# create a parent by doing a commit
287
tree = self.get_tree_with_a_file()
288
rev_id = tree.commit('first post').encode('utf8')
289
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
290
# change the current content to be different this will alter stat, sha
292
self.build_tree_contents([('tree2/a file', 'merge content\n')])
293
rev_id2 = tree2.commit('second post').encode('utf8')
294
tree.merge_from_branch(tree2.branch)
295
# change the current content to be different this will alter stat, sha
296
# and length again, giving us three distinct values:
297
self.build_tree_contents([('tree/a file', 'new content\n')])
298
expected_result = ([rev_id, rev_id2], [
299
(('', '', tree.path2id('')), # common details
300
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
301
('d', '', 0, False, rev_id), # first parent details
302
('d', '', 0, False, rev_id2), # second parent details
304
(('', 'a file', 'a file id'), # common
305
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
306
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
307
rev_id), # first parent
308
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
309
rev_id2), # second parent
312
state = dirstate.DirState.from_tree(tree, 'dirstate')
313
self.check_state_with_reopen(expected_result, state)
316
class TestDirStateOnFile(TestCaseWithDirState):
318
def test_construct_with_path(self):
319
tree = self.make_branch_and_tree('tree')
320
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
321
# we want to be able to get the lines of the dirstate that we will
323
lines = state.get_lines()
325
self.build_tree_contents([('dirstate', ''.join(lines))])
327
# no parents, default tree content
328
expected_result = ([], [
329
(('', '', tree.path2id('')), # common details
330
# current tree details, but new from_tree skips statting, it
331
# uses set_state_from_inventory, and thus depends on the
333
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
336
state = dirstate.DirState.on_file('dirstate')
337
state.lock_write() # check_state_with_reopen will save() and unlock it
338
self.check_state_with_reopen(expected_result, state)
340
def test_can_save_clean_on_file(self):
341
tree = self.make_branch_and_tree('tree')
342
state = dirstate.DirState.from_tree(tree, 'dirstate')
344
# doing a save should work here as there have been no changes.
346
# TODO: stat it and check it hasn't changed; may require waiting
347
# for the state accuracy window.
352
class TestDirStateInitialize(TestCaseWithDirState):
354
def test_initialize(self):
355
expected_result = ([], [
356
(('', '', 'TREE_ROOT'), # common details
357
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
360
state = dirstate.DirState.initialize('dirstate')
362
self.assertIsInstance(state, dirstate.DirState)
363
lines = state.get_lines()
364
self.assertFileEqual(''.join(state.get_lines()),
366
self.check_state_with_reopen(expected_result, state)
372
class TestDirStateManipulations(TestCaseWithDirState):
374
def test_set_state_from_inventory_no_content_no_parents(self):
375
# setting the current inventory is a slow but important api to support.
376
tree1 = self.make_branch_and_memory_tree('tree1')
380
revid1 = tree1.commit('foo').encode('utf8')
381
root_id = tree1.inventory.root.file_id
382
inv = tree1.inventory
385
expected_result = [], [
386
(('', '', root_id), [
387
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
388
state = dirstate.DirState.initialize('dirstate')
390
state.set_state_from_inventory(inv)
391
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
393
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
394
state._dirblock_state)
399
# This will unlock it
400
self.check_state_with_reopen(expected_result, state)
402
def test_set_path_id_no_parents(self):
403
"""The id of a path can be changed trivally with no parents."""
404
state = dirstate.DirState.initialize('dirstate')
406
# check precondition to be sure the state does change appropriately.
408
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
409
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
410
list(state._iter_entries()))
411
state.set_path_id('', 'foobarbaz')
413
(('', '', 'foobarbaz'), [('d', '', 0, False,
414
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
415
self.assertEqual(expected_rows, list(state._iter_entries()))
416
# should work across save too
420
state = dirstate.DirState.on_file('dirstate')
423
self.assertEqual(expected_rows, list(state._iter_entries()))
427
def test_set_parent_trees_no_content(self):
428
# set_parent_trees is a slow but important api to support.
429
tree1 = self.make_branch_and_memory_tree('tree1')
433
revid1 = tree1.commit('foo')
436
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
437
tree2 = MemoryTree.create_on_branch(branch2)
440
revid2 = tree2.commit('foo')
441
root_id = tree2.inventory.root.file_id
444
state = dirstate.DirState.initialize('dirstate')
446
state.set_path_id('', root_id)
447
state.set_parent_trees(
448
((revid1, tree1.branch.repository.revision_tree(revid1)),
449
(revid2, tree2.branch.repository.revision_tree(revid2)),
450
('ghost-rev', None)),
452
# check we can reopen and use the dirstate after setting parent
457
state = dirstate.DirState.on_file('dirstate')
460
self.assertEqual([revid1, revid2, 'ghost-rev'],
461
state.get_parent_ids())
462
# iterating the entire state ensures that the state is parsable.
463
list(state._iter_entries())
464
# be sure that it sets not appends - change it
465
state.set_parent_trees(
466
((revid1, tree1.branch.repository.revision_tree(revid1)),
467
('ghost-rev', None)),
469
# and now put it back.
470
state.set_parent_trees(
471
((revid1, tree1.branch.repository.revision_tree(revid1)),
472
(revid2, tree2.branch.repository.revision_tree(revid2)),
473
('ghost-rev', tree2.branch.repository.revision_tree(None))),
475
self.assertEqual([revid1, revid2, 'ghost-rev'],
476
state.get_parent_ids())
477
# the ghost should be recorded as such by set_parent_trees.
478
self.assertEqual(['ghost-rev'], state.get_ghosts())
480
[(('', '', root_id), [
481
('d', '', 0, False, dirstate.DirState.NULLSTAT),
482
('d', '', 0, False, revid1),
483
('d', '', 0, False, revid2)
485
list(state._iter_entries()))
489
def test_set_parent_trees_file_missing_from_tree(self):
490
# Adding a parent tree may reference files not in the current state.
491
# they should get listed just once by id, even if they are in two
493
# set_parent_trees is a slow but important api to support.
494
tree1 = self.make_branch_and_memory_tree('tree1')
498
tree1.add(['a file'], ['file-id'], ['file'])
499
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
500
revid1 = tree1.commit('foo')
503
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
504
tree2 = MemoryTree.create_on_branch(branch2)
507
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
508
revid2 = tree2.commit('foo')
509
root_id = tree2.inventory.root.file_id
512
# check the layout in memory
513
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
514
(('', '', root_id), [
515
('d', '', 0, False, dirstate.DirState.NULLSTAT),
516
('d', '', 0, False, revid1.encode('utf8')),
517
('d', '', 0, False, revid2.encode('utf8'))
519
(('', 'a file', 'file-id'), [
520
('a', '', 0, False, ''),
521
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
522
revid1.encode('utf8')),
523
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
524
revid2.encode('utf8'))
527
state = dirstate.DirState.initialize('dirstate')
529
state.set_path_id('', root_id)
530
state.set_parent_trees(
531
((revid1, tree1.branch.repository.revision_tree(revid1)),
532
(revid2, tree2.branch.repository.revision_tree(revid2)),
538
# check_state_with_reopen will unlock
539
self.check_state_with_reopen(expected_result, state)
541
### add a path via _set_data - so we dont need delta work, just
542
# raw data in, and ensure that it comes out via get_lines happily.
544
def test_add_path_to_root_no_parents_all_data(self):
545
# The most trivial addition of a path is when there are no parents and
546
# its in the root and all data about the file is supplied
547
self.build_tree(['a file'])
548
stat = os.lstat('a file')
549
# the 1*20 is the sha1 pretend value.
550
state = dirstate.DirState.initialize('dirstate')
552
(('', '', 'TREE_ROOT'), [
553
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
555
(('', 'a file', 'a file id'), [
556
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
560
state.add('a file', 'a file id', 'file', stat, '1'*20)
561
# having added it, it should be in the output of iter_entries.
562
self.assertEqual(expected_entries, list(state._iter_entries()))
563
# saving and reloading should not affect this.
567
state = dirstate.DirState.on_file('dirstate')
570
self.assertEqual(expected_entries, list(state._iter_entries()))
574
def test_add_path_to_unversioned_directory(self):
575
"""Adding a path to an unversioned directory should error.
577
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
578
once dirstate is stable and if it is merged with WorkingTree3, consider
579
removing this copy of the test.
581
self.build_tree(['unversioned/', 'unversioned/a file'])
582
state = dirstate.DirState.initialize('dirstate')
584
self.assertRaises(errors.NotVersionedError, state.add,
585
'unversioned/a file', 'a file id', 'file', None, None)
589
def test_add_directory_to_root_no_parents_all_data(self):
590
# The most trivial addition of a dir is when there are no parents and
591
# its in the root and all data about the file is supplied
592
self.build_tree(['a dir/'])
593
stat = os.lstat('a dir')
595
(('', '', 'TREE_ROOT'), [
596
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
598
(('', 'a dir', 'a dir id'), [
599
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
602
state = dirstate.DirState.initialize('dirstate')
604
state.add('a dir', 'a dir id', 'directory', stat, None)
605
# having added it, it should be in the output of iter_entries.
606
self.assertEqual(expected_entries, list(state._iter_entries()))
607
# saving and reloading should not affect this.
611
state = dirstate.DirState.on_file('dirstate')
614
self.assertEqual(expected_entries, list(state._iter_entries()))
618
def test_add_symlink_to_root_no_parents_all_data(self):
619
# The most trivial addition of a symlink when there are no parents and
620
# its in the root and all data about the file is supplied
621
## TODO: windows: dont fail this test. Also, how are symlinks meant to
622
# be represented on windows.
623
os.symlink('target', 'a link')
624
stat = os.lstat('a link')
626
(('', '', 'TREE_ROOT'), [
627
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
629
(('', 'a link', 'a link id'), [
630
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
633
state = dirstate.DirState.initialize('dirstate')
635
state.add('a link', 'a link id', 'symlink', stat, 'target')
636
# having added it, it should be in the output of iter_entries.
637
self.assertEqual(expected_entries, list(state._iter_entries()))
638
# saving and reloading should not affect this.
642
state = dirstate.DirState.on_file('dirstate')
645
self.assertEqual(expected_entries, list(state._iter_entries()))
649
def test_add_directory_and_child_no_parents_all_data(self):
650
# after adding a directory, we should be able to add children to it.
651
self.build_tree(['a dir/', 'a dir/a file'])
652
dirstat = os.lstat('a dir')
653
filestat = os.lstat('a dir/a file')
655
(('', '', 'TREE_ROOT'), [
656
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
658
(('', 'a dir', 'a dir id'), [
659
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
661
(('a dir', 'a file', 'a file id'), [
662
('f', '1'*20, 25, False,
663
dirstate.pack_stat(filestat)), # current tree details
666
state = dirstate.DirState.initialize('dirstate')
668
state.add('a dir', 'a dir id', 'directory', dirstat, None)
669
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
670
# added it, it should be in the output of iter_entries.
671
self.assertEqual(expected_entries, list(state._iter_entries()))
672
# saving and reloading should not affect this.
676
state = dirstate.DirState.on_file('dirstate')
679
self.assertEqual(expected_entries, list(state._iter_entries()))
684
class TestGetLines(TestCaseWithDirState):
686
def test_get_line_with_2_rows(self):
687
state = self.create_dirstate_with_root_and_subdir()
689
self.assertEqual(['#bazaar dirstate flat format 3\n',
690
'adler32: -1327947603\n',
694
'\x00\x00a-root-value\x00'
695
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
696
'\x00subdir\x00subdir-id\x00'
697
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
698
], state.get_lines())
702
def test_entry_to_line(self):
703
state = self.create_dirstate_with_root()
706
'\x00\x00a-root-value\x00d\x00\x000\x00n'
707
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
708
state._entry_to_line(state._dirblocks[0][1][0]))
712
def test_entry_to_line_with_parent(self):
713
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
714
root_entry = ('', '', 'a-root-value'), [
715
('d', '', 0, False, packed_stat), # current tree details
716
# first: a pointer to the current location
717
('a', 'dirname/basename', 0, False, ''),
719
state = dirstate.DirState.initialize('dirstate')
722
'\x00\x00a-root-value\x00'
723
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
724
'a\x00dirname/basename\x000\x00n\x00',
725
state._entry_to_line(root_entry))
729
def test_entry_to_line_with_two_parents_at_different_paths(self):
730
# / in the tree, at / in one parent and /dirname/basename in the other.
731
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
732
root_entry = ('', '', 'a-root-value'), [
733
('d', '', 0, False, packed_stat), # current tree details
734
('d', '', 0, False, 'rev_id'), # first parent details
735
# second: a pointer to the current location
736
('a', 'dirname/basename', 0, False, ''),
738
state = dirstate.DirState.initialize('dirstate')
741
'\x00\x00a-root-value\x00'
742
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
743
'd\x00\x000\x00n\x00rev_id\x00'
744
'a\x00dirname/basename\x000\x00n\x00',
745
state._entry_to_line(root_entry))
749
def test_iter_entries(self):
750
# we should be able to iterate the dirstate entries from end to end
751
# this is for get_lines to be easy to read.
752
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
754
root_entries = [(('', '', 'a-root-value'), [
755
('d', '', 0, False, packed_stat), # current tree details
757
dirblocks.append(('', root_entries))
758
# add two files in the root
759
subdir_entry = ('', 'subdir', 'subdir-id'), [
760
('d', '', 0, False, packed_stat), # current tree details
762
afile_entry = ('', 'afile', 'afile-id'), [
763
('f', 'sha1value', 34, False, packed_stat), # current tree details
765
dirblocks.append(('', [subdir_entry, afile_entry]))
767
file_entry2 = ('subdir', '2file', '2file-id'), [
768
('f', 'sha1value', 23, False, packed_stat), # current tree details
770
dirblocks.append(('subdir', [file_entry2]))
771
state = dirstate.DirState.initialize('dirstate')
773
state._set_data([], dirblocks)
774
expected_entries = [root_entries[0], subdir_entry, afile_entry,
776
self.assertEqual(expected_entries, list(state._iter_entries()))
781
class TestGetBlockRowIndex(TestCaseWithDirState):
783
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
784
file_present, state, dirname, basename, tree_index):
785
self.assertEqual((block_index, row_index, dir_present, file_present),
786
state._get_block_entry_index(dirname, basename, tree_index))
788
block = state._dirblocks[block_index]
789
self.assertEqual(dirname, block[0])
790
if dir_present and file_present:
791
row = state._dirblocks[block_index][1][row_index]
792
self.assertEqual(dirname, row[0][0])
793
self.assertEqual(basename, row[0][1])
795
def test_simple_structure(self):
796
state = self.create_dirstate_with_root_and_subdir()
797
self.addCleanup(state.unlock)
798
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
799
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
800
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
801
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
802
self.assertBlockRowIndexEqual(2, 0, False, False, state,
805
def test_complex_structure_exists(self):
806
state = self.create_complex_dirstate()
807
self.addCleanup(state.unlock)
808
# Make sure we can find everything that exists
809
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
810
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
811
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
812
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
813
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
814
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
815
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
816
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
817
self.assertBlockRowIndexEqual(3, 1, True, True, state,
820
def test_complex_structure_missing(self):
821
state = self.create_complex_dirstate()
822
self.addCleanup(state.unlock)
823
# Make sure things would be inserted in the right locations
824
# '_' comes before 'a'
825
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
826
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
827
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
828
self.assertBlockRowIndexEqual(1, 4, True, False, state,
830
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
831
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
832
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
833
# This would be inserted between a/ and b/
834
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
836
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
839
class TestGetEntry(TestCaseWithDirState):
841
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
842
"""Check that the right entry is returned for a request to getEntry."""
843
entry = state._get_entry(index, path_utf8=path)
845
self.assertEqual((None, None), entry)
848
self.assertEqual((dirname, basename, file_id), cur[:3])
850
def test_simple_structure(self):
851
state = self.create_dirstate_with_root_and_subdir()
852
self.addCleanup(state.unlock)
853
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
854
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
855
self.assertEntryEqual(None, None, None, state, 'missing', 0)
856
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
857
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
859
def test_complex_structure_exists(self):
860
state = self.create_complex_dirstate()
861
self.addCleanup(state.unlock)
862
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
863
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
864
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
865
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
866
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
867
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
868
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
869
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
870
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
873
def test_complex_structure_missing(self):
874
state = self.create_complex_dirstate()
875
self.addCleanup(state.unlock)
876
self.assertEntryEqual(None, None, None, state, '_', 0)
877
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
878
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
879
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
881
def test_get_entry_uninitialized(self):
882
"""Calling get_entry will load data if it needs to"""
883
state = self.create_dirstate_with_root()
889
state = dirstate.DirState.on_file('dirstate')
892
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
894
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
895
state._dirblock_state)
896
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
901
class TestDirstateSortOrder(TestCaseWithTransport):
902
"""Test that DirState adds entries in the right order."""
904
def test_add_sorting(self):
905
"""Add entries in lexicographical order, we get path sorted order.
907
This tests it to a depth of 4, to make sure we don't just get it right
908
at a single depth. 'a/a' should come before 'a-a', even though it
909
doesn't lexicographically.
911
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
912
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
915
state = dirstate.DirState.initialize('dirstate')
916
self.addCleanup(state.unlock)
918
fake_stat = os.stat('dirstate')
920
d_id = d.replace('/', '_')+'-id'
922
file_id = file_path.replace('/', '_')+'-id'
923
state.add(d, d_id, 'directory', fake_stat, null_sha)
924
state.add(file_path, file_id, 'file', fake_stat, null_sha)
926
expected = ['', '', 'a',
927
'a/a', 'a/a/a', 'a/a/a/a',
928
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
930
split = lambda p:p.split('/')
931
self.assertEqual(sorted(expected, key=split), expected)
932
dirblock_names = [d[0] for d in state._dirblocks]
933
self.assertEqual(expected, dirblock_names)
935
def test_set_parent_trees_correct_order(self):
936
"""After calling set_parent_trees() we should maintain the order."""
937
dirs = ['a', 'a-a', 'a/a']
939
state = dirstate.DirState.initialize('dirstate')
940
self.addCleanup(state.unlock)
942
fake_stat = os.stat('dirstate')
944
d_id = d.replace('/', '_')+'-id'
946
file_id = file_path.replace('/', '_')+'-id'
947
state.add(d, d_id, 'directory', fake_stat, null_sha)
948
state.add(file_path, file_id, 'file', fake_stat, null_sha)
950
expected = ['', '', 'a', 'a/a', 'a-a']
951
dirblock_names = [d[0] for d in state._dirblocks]
952
self.assertEqual(expected, dirblock_names)
954
# *really* cheesy way to just get an empty tree
955
repo = self.make_repository('repo')
956
empty_tree = repo.revision_tree(None)
957
state.set_parent_trees([('null:', empty_tree)], [])
959
dirblock_names = [d[0] for d in state._dirblocks]
960
self.assertEqual(expected, dirblock_names)
963
class TestBisect(TestCaseWithTransport):
964
"""Test the ability to bisect into the disk format."""
966
def create_basic_dirstate(self):
967
"""Create a dirstate with a few files and directories.
976
tree = self.make_branch_and_tree('tree')
977
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
978
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
979
self.build_tree(['tree/' + p for p in paths])
980
tree.set_root_id('TREE_ROOT')
981
tree.add([p.rstrip('/') for p in paths], file_ids)
982
tree.commit('initial', rev_id='rev-1')
983
revision_id = 'rev-1'
984
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
985
t = self.get_transport().clone('tree')
986
a_text = t.get_bytes('a')
987
a_sha = osutils.sha_string(a_text)
989
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
990
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
991
c_text = t.get_bytes('b/c')
992
c_sha = osutils.sha_string(c_text)
994
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
995
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
996
e_text = t.get_bytes('b/d/e')
997
e_sha = osutils.sha_string(e_text)
999
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1000
f_text = t.get_bytes('f')
1001
f_sha = osutils.sha_string(f_text)
1003
null_stat = dirstate.DirState.NULLSTAT
1005
'':(('', '', 'TREE_ROOT'), [
1006
('d', '', 0, False, null_stat),
1007
('d', '', 0, False, revision_id),
1009
'a':(('', 'a', 'a-id'), [
1010
('f', '', 0, False, null_stat),
1011
('f', a_sha, a_len, False, revision_id),
1013
'b':(('', 'b', 'b-id'), [
1014
('d', '', 0, False, null_stat),
1015
('d', '', 0, False, revision_id),
1017
'b/c':(('b', 'c', 'c-id'), [
1018
('f', '', 0, False, null_stat),
1019
('f', c_sha, c_len, False, revision_id),
1021
'b/d':(('b', 'd', 'd-id'), [
1022
('d', '', 0, False, null_stat),
1023
('d', '', 0, False, revision_id),
1025
'b/d/e':(('b/d', 'e', 'e-id'), [
1026
('f', '', 0, False, null_stat),
1027
('f', e_sha, e_len, False, revision_id),
1029
'f':(('', 'f', 'f-id'), [
1030
('f', '', 0, False, null_stat),
1031
('f', f_sha, f_len, False, revision_id),
1034
state = dirstate.DirState.from_tree(tree, 'dirstate')
1039
# Use a different object, to make sure nothing is pre-cached in memory.
1040
state = dirstate.DirState.on_file('dirstate')
1042
self.addCleanup(state.unlock)
1043
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1044
state._dirblock_state)
1045
# This is code is only really tested if we actually have to make more
1046
# than one read, so set the page size to something smaller.
1047
# We want it to contain about 2.2 records, so that we have a couple
1048
# records that we can read per attempt
1049
state._bisect_page_size = 200
1050
return tree, state, expected
1052
def create_duplicated_dirstate(self):
1053
"""Create a dirstate with a deleted and added entries.
1055
This grabs a basic_dirstate, and then removes and re adds every entry
1058
tree, state, expected = self.create_basic_dirstate()
1059
# Now we will just remove and add every file so we get an extra entry
1060
# per entry. Unversion in reverse order so we handle subdirs
1061
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1062
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1063
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1065
# Update the expected dictionary.
1066
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1067
orig = expected[path]
1069
# This record was deleted in the current tree
1070
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1072
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1073
# And didn't exist in the basis tree
1074
expected[path2] = (new_key, [orig[1][0],
1075
dirstate.DirState.NULL_PARENT_DETAILS])
1077
# We will replace the 'dirstate' file underneath 'state', but that is
1078
# okay as lock as we unlock 'state' first.
1081
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1087
# But we need to leave state in a read-lock because we already have
1088
# a cleanup scheduled
1090
return tree, state, expected
1092
def create_renamed_dirstate(self):
1093
"""Create a dirstate with a few internal renames.
1095
This takes the basic dirstate, and moves the paths around.
1097
tree, state, expected = self.create_basic_dirstate()
1099
tree.rename_one('a', 'b/g')
1101
tree.rename_one('b/d', 'h')
1103
old_a = expected['a']
1104
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1105
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1106
('r', 'a', 0, False, '')])
1107
old_d = expected['b/d']
1108
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1109
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1110
('r', 'b/d', 0, False, '')])
1112
old_e = expected['b/d/e']
1113
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1115
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1116
('r', 'b/d/e', 0, False, '')])
1120
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1127
return tree, state, expected
1129
def assertBisect(self, expected_map, map_keys, state, paths):
1130
"""Assert that bisecting for paths returns the right result.
1132
:param expected_map: A map from key => entry value
1133
:param map_keys: The keys to expect for each path
1134
:param state: The DirState object.
1135
:param paths: A list of paths, these will automatically be split into
1136
(dir, name) tuples, and sorted according to how _bisect
1139
dir_names = sorted(osutils.split(p) for p in paths)
1140
result = state._bisect(dir_names)
1141
# For now, results are just returned in whatever order we read them.
1142
# We could sort by (dir, name, file_id) or something like that, but in
1143
# the end it would still be fairly arbitrary, and we don't want the
1144
# extra overhead if we can avoid it. So sort everything to make sure
1146
assert len(map_keys) == len(dir_names)
1148
for dir_name, keys in zip(dir_names, map_keys):
1150
# This should not be present in the output
1152
expected[dir_name] = sorted(expected_map[k] for k in keys)
1154
for dir_name in result:
1155
result[dir_name].sort()
1157
self.assertEqual(expected, result)
1159
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1160
"""Assert that bisecting for dirbblocks returns the right result.
1162
:param expected_map: A map from key => expected values
1163
:param map_keys: A nested list of paths we expect to be returned.
1164
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1165
:param state: The DirState object.
1166
:param paths: A list of directories
1168
result = state._bisect_dirblocks(paths)
1169
assert len(map_keys) == len(paths)
1172
for path, keys in zip(paths, map_keys):
1174
# This should not be present in the output
1176
expected[path] = sorted(expected_map[k] for k in keys)
1180
self.assertEqual(expected, result)
1182
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1183
"""Assert the return value of a recursive bisection.
1185
:param expected_map: A map from key => entry value
1186
:param map_keys: A list of paths we expect to be returned.
1187
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1188
:param state: The DirState object.
1189
:param paths: A list of files and directories. It will be broken up
1190
into (dir, name) pairs and sorted before calling _bisect_recursive.
1193
for key in map_keys:
1194
entry = expected_map[key]
1195
dir_name_id, trees_info = entry
1196
expected[dir_name_id] = trees_info
1198
dir_names = sorted(osutils.split(p) for p in paths)
1199
result = state._bisect_recursive(dir_names)
1201
self.assertEqual(expected, result)
1203
def test_bisect_each(self):
1204
"""Find a single record using bisect."""
1205
tree, state, expected = self.create_basic_dirstate()
1207
# Bisect should return the rows for the specified files.
1208
self.assertBisect(expected, [['']], state, [''])
1209
self.assertBisect(expected, [['a']], state, ['a'])
1210
self.assertBisect(expected, [['b']], state, ['b'])
1211
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1212
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1213
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1214
self.assertBisect(expected, [['f']], state, ['f'])
1216
def test_bisect_multi(self):
1217
"""Bisect can be used to find multiple records at the same time."""
1218
tree, state, expected = self.create_basic_dirstate()
1219
# Bisect should be capable of finding multiple entries at the same time
1220
self.assertBisect(expected, [['a'], ['b'], ['f']],
1221
state, ['a', 'b', 'f'])
1222
# ('', 'f') sorts before the others
1223
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1224
state, ['b/d', 'b/d/e', 'f'])
1226
def test_bisect_one_page(self):
1227
"""Test bisect when there is only 1 page to read"""
1228
tree, state, expected = self.create_basic_dirstate()
1229
state._bisect_page_size = 5000
1230
self.assertBisect(expected,[['']], state, [''])
1231
self.assertBisect(expected,[['a']], state, ['a'])
1232
self.assertBisect(expected,[['b']], state, ['b'])
1233
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1234
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1235
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1236
self.assertBisect(expected,[['f']], state, ['f'])
1237
self.assertBisect(expected,[['a'], ['b'], ['f']],
1238
state, ['a', 'b', 'f'])
1239
# ('', 'f') sorts before the others
1240
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1241
state, ['b/d', 'b/d/e', 'f'])
1243
def test_bisect_duplicate_paths(self):
1244
"""When bisecting for a path, handle multiple entries."""
1245
tree, state, expected = self.create_duplicated_dirstate()
1247
# Now make sure that both records are properly returned.
1248
self.assertBisect(expected, [['']], state, [''])
1249
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1250
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1251
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1252
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1253
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1255
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1257
def test_bisect_page_size_too_small(self):
1258
"""If the page size is too small, we will auto increase it."""
1259
tree, state, expected = self.create_basic_dirstate()
1260
state._bisect_page_size = 50
1261
self.assertBisect(expected, [None], state, ['b/e'])
1262
self.assertBisect(expected, [['a']], state, ['a'])
1263
self.assertBisect(expected, [['b']], state, ['b'])
1264
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1265
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1266
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1267
self.assertBisect(expected, [['f']], state, ['f'])
1269
def test_bisect_missing(self):
1270
"""Test that bisect return None if it cannot find a path."""
1271
tree, state, expected = self.create_basic_dirstate()
1272
self.assertBisect(expected, [None], state, ['foo'])
1273
self.assertBisect(expected, [None], state, ['b/foo'])
1274
self.assertBisect(expected, [None], state, ['bar/foo'])
1276
self.assertBisect(expected, [['a'], None, ['b/d']],
1277
state, ['a', 'foo', 'b/d'])
1279
def test_bisect_rename(self):
1280
"""Check that we find a renamed row."""
1281
tree, state, expected = self.create_renamed_dirstate()
1283
# Search for the pre and post renamed entries
1284
self.assertBisect(expected, [['a']], state, ['a'])
1285
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1286
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1287
self.assertBisect(expected, [['h']], state, ['h'])
1289
# What about b/d/e? shouldn't that also get 2 directory entries?
1290
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1291
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1293
def test_bisect_dirblocks(self):
1294
tree, state, expected = self.create_duplicated_dirstate()
1295
self.assertBisectDirBlocks(expected,
1296
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1297
self.assertBisectDirBlocks(expected,
1298
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1299
self.assertBisectDirBlocks(expected,
1300
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1301
self.assertBisectDirBlocks(expected,
1302
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1303
['b/c', 'b/c2', 'b/d', 'b/d2'],
1304
['b/d/e', 'b/d/e2'],
1305
], state, ['', 'b', 'b/d'])
1307
def test_bisect_dirblocks_missing(self):
1308
tree, state, expected = self.create_basic_dirstate()
1309
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1310
state, ['b/d', 'b/e'])
1311
# Files don't show up in this search
1312
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1313
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1314
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1315
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1316
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1318
def test_bisect_recursive_each(self):
1319
tree, state, expected = self.create_basic_dirstate()
1320
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1321
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1322
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1323
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1325
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1327
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1331
def test_bisect_recursive_multiple(self):
1332
tree, state, expected = self.create_basic_dirstate()
1333
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1334
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1335
state, ['b/d', 'b/d/e'])
1337
def test_bisect_recursive_missing(self):
1338
tree, state, expected = self.create_basic_dirstate()
1339
self.assertBisectRecursive(expected, [], state, ['d'])
1340
self.assertBisectRecursive(expected, [], state, ['b/e'])
1341
self.assertBisectRecursive(expected, [], state, ['g'])
1342
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1344
def test_bisect_recursive_renamed(self):
1345
tree, state, expected = self.create_renamed_dirstate()
1347
# Looking for either renamed item should find the other
1348
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1349
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1350
# Looking in the containing directory should find the rename target,
1351
# and anything in a subdir of the renamed target.
1352
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1353
'b/d/e', 'b/g', 'h', 'h/e'],
1357
class TestBisectDirblock(TestCase):
1358
"""Test that bisect_dirblock() returns the expected values.
1360
bisect_dirblock is intended to work like bisect.bisect_left() except it
1361
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1362
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1365
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1366
"""Assert that bisect_split works like bisect_left on the split paths.
1368
:param dirblocks: A list of (path, [info]) pairs.
1369
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1370
:param path: The path we are indexing.
1372
All other arguments will be passed along.
1374
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1376
split_dirblock = (path.split('/'), [])
1377
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1379
self.assertEqual(bisect_left_idx, bisect_split_idx,
1380
'bisect_split disagreed. %s != %s'
1382
% (bisect_left_idx, bisect_split_idx, path)
1385
def paths_to_dirblocks(self, paths):
1386
"""Convert a list of paths into dirblock form.
1388
Also, ensure that the paths are in proper sorted order.
1390
dirblocks = [(path, []) for path in paths]
1391
split_dirblocks = [(path.split('/'), []) for path in paths]
1392
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1393
return dirblocks, split_dirblocks
1395
def test_simple(self):
1396
"""In the simple case it works just like bisect_left"""
1397
paths = ['', 'a', 'b', 'c', 'd']
1398
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1400
self.assertBisect(dirblocks, split_dirblocks, path)
1401
self.assertBisect(dirblocks, split_dirblocks, '_')
1402
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1403
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1404
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1405
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1406
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1407
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1408
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1409
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1411
def test_involved(self):
1412
"""This is where bisect_left diverges slightly."""
1414
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1415
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1417
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1418
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1421
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1423
self.assertBisect(dirblocks, split_dirblocks, path)
1425
def test_involved_cached(self):
1426
"""This is where bisect_left diverges slightly."""
1428
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1429
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1431
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1432
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1436
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1438
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)