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)
84
def create_dirstate_with_root_and_subdir(self):
85
"""Return a locked DirState with a root and a subdir"""
86
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
87
subdir_entry = ('', 'subdir', 'subdir-id'), [
88
('d', '', 0, False, packed_stat),
90
state = self.create_dirstate_with_root()
92
dirblocks = list(state._dirblocks)
93
dirblocks[1][1].append(subdir_entry)
94
state._set_data([], dirblocks)
100
def create_complex_dirstate(self):
101
"""This dirstate contains multiple files and directories.
111
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
113
# Notice that a/e is an empty directory.
115
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
116
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
117
root_entry = ('', '', 'a-root-value'), [
118
('d', '', 0, False, packed_stat),
120
a_entry = ('', 'a', 'a-dir'), [
121
('d', '', 0, False, packed_stat),
123
b_entry = ('', 'b', 'b-dir'), [
124
('d', '', 0, False, packed_stat),
126
c_entry = ('', 'c', 'c-file'), [
127
('f', null_sha, 10, False, packed_stat),
129
d_entry = ('', 'd', 'd-file'), [
130
('f', null_sha, 20, False, packed_stat),
132
e_entry = ('a', 'e', 'e-dir'), [
133
('d', '', 0, False, packed_stat),
135
f_entry = ('a', 'f', 'f-file'), [
136
('f', null_sha, 30, False, packed_stat),
138
g_entry = ('b', 'g', 'g-file'), [
139
('f', null_sha, 30, False, packed_stat),
141
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
142
('f', null_sha, 40, False, packed_stat),
145
dirblocks.append(('', [root_entry]))
146
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
147
dirblocks.append(('a', [e_entry, f_entry]))
148
dirblocks.append(('b', [g_entry, h_entry]))
149
state = dirstate.DirState.initialize('dirstate')
152
state._set_data([], dirblocks)
158
def check_state_with_reopen(self, expected_result, state):
159
"""Check that state has current state expected_result.
161
This will check the current state, open the file anew and check it
163
This function expects the current state to be locked for writing, and
164
will unlock it before re-opening.
165
This is required because we can't open a lock_read() while something
166
else has a lock_write().
167
write => mutually exclusive lock
170
# The state should already be write locked, since we just had to do
171
# some operation to get here.
172
assert state._lock_token is not None
174
self.assertEqual(expected_result[0], state.get_parent_ids())
175
# there should be no ghosts in this tree.
176
self.assertEqual([], state.get_ghosts())
177
# there should be one fileid in this tree - the root of the tree.
178
self.assertEqual(expected_result[1], list(state._iter_entries()))
182
del state # Callers should unlock
183
state = dirstate.DirState.on_file('dirstate')
186
self.assertEqual(expected_result[1], list(state._iter_entries()))
191
class TestTreeToDirState(TestCaseWithDirState):
193
def test_empty_to_dirstate(self):
194
"""We should be able to create a dirstate for an empty tree."""
195
# There are no files on disk and no parents
196
tree = self.make_branch_and_tree('tree')
197
expected_result = ([], [
198
(('', '', tree.path2id('')), # common details
199
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
201
state = dirstate.DirState.from_tree(tree, 'dirstate')
203
self.check_state_with_reopen(expected_result, state)
205
def test_1_parents_empty_to_dirstate(self):
206
# create a parent by doing a commit
207
tree = self.make_branch_and_tree('tree')
208
rev_id = tree.commit('first post').encode('utf8')
209
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
210
expected_result = ([rev_id], [
211
(('', '', tree.path2id('')), # common details
212
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
213
('d', '', 0, False, rev_id), # first parent details
215
state = dirstate.DirState.from_tree(tree, 'dirstate')
216
self.check_state_with_reopen(expected_result, state)
219
def test_2_parents_empty_to_dirstate(self):
220
# create a parent by doing a commit
221
tree = self.make_branch_and_tree('tree')
222
rev_id = tree.commit('first post')
223
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
224
rev_id2 = tree2.commit('second post', allow_pointless=True)
225
tree.merge_from_branch(tree2.branch)
226
expected_result = ([rev_id, rev_id2], [
227
(('', '', tree.path2id('')), # common details
228
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
229
('d', '', 0, False, rev_id), # first parent details
230
('d', '', 0, False, rev_id2), # second parent details
232
state = dirstate.DirState.from_tree(tree, 'dirstate')
233
self.check_state_with_reopen(expected_result, state)
236
def test_empty_unknowns_are_ignored_to_dirstate(self):
237
"""We should be able to create a dirstate for an empty tree."""
238
# There are no files on disk and no parents
239
tree = self.make_branch_and_tree('tree')
240
self.build_tree(['tree/unknown'])
241
expected_result = ([], [
242
(('', '', tree.path2id('')), # common details
243
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
245
state = dirstate.DirState.from_tree(tree, 'dirstate')
246
self.check_state_with_reopen(expected_result, state)
248
def get_tree_with_a_file(self):
249
tree = self.make_branch_and_tree('tree')
250
self.build_tree(['tree/a file'])
251
tree.add('a file', 'a file id')
254
def test_non_empty_no_parents_to_dirstate(self):
255
"""We should be able to create a dirstate for an empty tree."""
256
# There are files on disk and no parents
257
tree = self.get_tree_with_a_file()
258
expected_result = ([], [
259
(('', '', tree.path2id('')), # common details
260
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
262
(('', 'a file', 'a file id'), # common
263
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
266
state = dirstate.DirState.from_tree(tree, 'dirstate')
267
self.check_state_with_reopen(expected_result, state)
269
def test_1_parents_not_empty_to_dirstate(self):
270
# create a parent by doing a commit
271
tree = self.get_tree_with_a_file()
272
rev_id = tree.commit('first post').encode('utf8')
273
# change the current content to be different this will alter stat, sha
275
self.build_tree_contents([('tree/a file', 'new content\n')])
276
expected_result = ([rev_id], [
277
(('', '', tree.path2id('')), # common details
278
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
279
('d', '', 0, False, rev_id), # first parent details
281
(('', 'a file', 'a file id'), # common
282
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
283
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
284
rev_id), # first parent
287
state = dirstate.DirState.from_tree(tree, 'dirstate')
288
self.check_state_with_reopen(expected_result, state)
290
def test_2_parents_not_empty_to_dirstate(self):
291
# create a parent by doing a commit
292
tree = self.get_tree_with_a_file()
293
rev_id = tree.commit('first post').encode('utf8')
294
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
295
# change the current content to be different this will alter stat, sha
297
self.build_tree_contents([('tree2/a file', 'merge content\n')])
298
rev_id2 = tree2.commit('second post').encode('utf8')
299
tree.merge_from_branch(tree2.branch)
300
# change the current content to be different this will alter stat, sha
301
# and length again, giving us three distinct values:
302
self.build_tree_contents([('tree/a file', 'new content\n')])
303
expected_result = ([rev_id, rev_id2], [
304
(('', '', tree.path2id('')), # common details
305
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
306
('d', '', 0, False, rev_id), # first parent details
307
('d', '', 0, False, rev_id2), # second parent details
309
(('', 'a file', 'a file id'), # common
310
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
311
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
312
rev_id), # first parent
313
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
314
rev_id2), # second parent
317
state = dirstate.DirState.from_tree(tree, 'dirstate')
318
self.check_state_with_reopen(expected_result, state)
321
class TestDirStateOnFile(TestCaseWithDirState):
323
def test_construct_with_path(self):
324
tree = self.make_branch_and_tree('tree')
325
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
326
# we want to be able to get the lines of the dirstate that we will
328
lines = state.get_lines()
330
self.build_tree_contents([('dirstate', ''.join(lines))])
332
# no parents, default tree content
333
expected_result = ([], [
334
(('', '', tree.path2id('')), # common details
335
# current tree details, but new from_tree skips statting, it
336
# uses set_state_from_inventory, and thus depends on the
338
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
341
state = dirstate.DirState.on_file('dirstate')
342
state.lock_write() # check_state_with_reopen will save() and unlock it
343
self.check_state_with_reopen(expected_result, state)
345
def test_can_save_clean_on_file(self):
346
tree = self.make_branch_and_tree('tree')
347
state = dirstate.DirState.from_tree(tree, 'dirstate')
349
# doing a save should work here as there have been no changes.
351
# TODO: stat it and check it hasn't changed; may require waiting
352
# for the state accuracy window.
357
class TestDirStateInitialize(TestCaseWithDirState):
359
def test_initialize(self):
360
expected_result = ([], [
361
(('', '', 'TREE_ROOT'), # common details
362
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
365
state = dirstate.DirState.initialize('dirstate')
367
self.assertIsInstance(state, dirstate.DirState)
368
lines = state.get_lines()
369
self.assertFileEqual(''.join(state.get_lines()),
371
self.check_state_with_reopen(expected_result, state)
377
class TestDirStateManipulations(TestCaseWithDirState):
379
def test_set_state_from_inventory_no_content_no_parents(self):
380
# setting the current inventory is a slow but important api to support.
381
tree1 = self.make_branch_and_memory_tree('tree1')
385
revid1 = tree1.commit('foo').encode('utf8')
386
root_id = tree1.inventory.root.file_id
387
inv = tree1.inventory
390
expected_result = [], [
391
(('', '', root_id), [
392
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
393
state = dirstate.DirState.initialize('dirstate')
395
state.set_state_from_inventory(inv)
396
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
398
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
399
state._dirblock_state)
404
# This will unlock it
405
self.check_state_with_reopen(expected_result, state)
407
def test_set_path_id_no_parents(self):
408
"""The id of a path can be changed trivally with no parents."""
409
state = dirstate.DirState.initialize('dirstate')
411
# check precondition to be sure the state does change appropriately.
413
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
414
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
415
list(state._iter_entries()))
416
state.set_path_id('', 'foobarbaz')
418
(('', '', 'foobarbaz'), [('d', '', 0, False,
419
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
420
self.assertEqual(expected_rows, list(state._iter_entries()))
421
# should work across save too
425
state = dirstate.DirState.on_file('dirstate')
429
self.assertEqual(expected_rows, list(state._iter_entries()))
433
def test_set_path_id_with_parents(self):
434
"""Set the root file id in a dirstate with parents"""
435
mt = self.make_branch_and_tree('mt')
436
# may need to set the root when the default format is one where it's
438
mt.commit('foo', rev_id='parent-revid')
439
rt = mt.branch.repository.revision_tree('parent-revid')
440
state = dirstate.DirState.initialize('dirstate')
443
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
444
state.set_path_id('', 'foobarbaz')
445
# now see that it is what we expected
447
(('', '', 'TREE_ROOT'),
448
[('a', '', 0, False, ''),
449
('d', '', 0, False, 'parent-revid'),
451
(('', '', 'foobarbaz'),
452
[('d', '', 0, False, ''),
453
('a', '', 0, False, ''),
456
self.assertEqual(expected_rows, list(state._iter_entries()))
457
# should work across save too
461
# now flush & check we get the same
462
state = dirstate.DirState.on_file('dirstate')
465
self.assertEqual(expected_rows, list(state._iter_entries()))
469
def test_set_parent_trees_no_content(self):
470
# set_parent_trees is a slow but important api to support.
471
tree1 = self.make_branch_and_memory_tree('tree1')
475
revid1 = tree1.commit('foo')
478
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
479
tree2 = MemoryTree.create_on_branch(branch2)
482
revid2 = tree2.commit('foo')
483
root_id = tree2.inventory.root.file_id
486
state = dirstate.DirState.initialize('dirstate')
488
state.set_path_id('', root_id)
489
state.set_parent_trees(
490
((revid1, tree1.branch.repository.revision_tree(revid1)),
491
(revid2, tree2.branch.repository.revision_tree(revid2)),
492
('ghost-rev', None)),
494
# check we can reopen and use the dirstate after setting parent
501
state = dirstate.DirState.on_file('dirstate')
504
self.assertEqual([revid1, revid2, 'ghost-rev'],
505
state.get_parent_ids())
506
# iterating the entire state ensures that the state is parsable.
507
list(state._iter_entries())
508
# be sure that it sets not appends - change it
509
state.set_parent_trees(
510
((revid1, tree1.branch.repository.revision_tree(revid1)),
511
('ghost-rev', None)),
513
# and now put it back.
514
state.set_parent_trees(
515
((revid1, tree1.branch.repository.revision_tree(revid1)),
516
(revid2, tree2.branch.repository.revision_tree(revid2)),
517
('ghost-rev', tree2.branch.repository.revision_tree(None))),
519
self.assertEqual([revid1, revid2, 'ghost-rev'],
520
state.get_parent_ids())
521
# the ghost should be recorded as such by set_parent_trees.
522
self.assertEqual(['ghost-rev'], state.get_ghosts())
524
[(('', '', root_id), [
525
('d', '', 0, False, dirstate.DirState.NULLSTAT),
526
('d', '', 0, False, revid1),
527
('d', '', 0, False, revid2)
529
list(state._iter_entries()))
533
def test_set_parent_trees_file_missing_from_tree(self):
534
# Adding a parent tree may reference files not in the current state.
535
# they should get listed just once by id, even if they are in two
537
# set_parent_trees is a slow but important api to support.
538
tree1 = self.make_branch_and_memory_tree('tree1')
542
tree1.add(['a file'], ['file-id'], ['file'])
543
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
544
revid1 = tree1.commit('foo')
547
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
548
tree2 = MemoryTree.create_on_branch(branch2)
551
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
552
revid2 = tree2.commit('foo')
553
root_id = tree2.inventory.root.file_id
556
# check the layout in memory
557
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
558
(('', '', root_id), [
559
('d', '', 0, False, dirstate.DirState.NULLSTAT),
560
('d', '', 0, False, revid1.encode('utf8')),
561
('d', '', 0, False, revid2.encode('utf8'))
563
(('', 'a file', 'file-id'), [
564
('a', '', 0, False, ''),
565
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
566
revid1.encode('utf8')),
567
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
568
revid2.encode('utf8'))
571
state = dirstate.DirState.initialize('dirstate')
573
state.set_path_id('', root_id)
574
state.set_parent_trees(
575
((revid1, tree1.branch.repository.revision_tree(revid1)),
576
(revid2, tree2.branch.repository.revision_tree(revid2)),
582
# check_state_with_reopen will unlock
583
self.check_state_with_reopen(expected_result, state)
585
### add a path via _set_data - so we dont need delta work, just
586
# raw data in, and ensure that it comes out via get_lines happily.
588
def test_add_path_to_root_no_parents_all_data(self):
589
# The most trivial addition of a path 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 file'])
592
stat = os.lstat('a file')
593
# the 1*20 is the sha1 pretend value.
594
state = dirstate.DirState.initialize('dirstate')
596
(('', '', 'TREE_ROOT'), [
597
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
599
(('', 'a file', 'a file id'), [
600
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
604
state.add('a file', 'a file id', 'file', stat, '1'*20)
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_path_to_unversioned_directory(self):
619
"""Adding a path to an unversioned directory should error.
621
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
622
once dirstate is stable and if it is merged with WorkingTree3, consider
623
removing this copy of the test.
625
self.build_tree(['unversioned/', 'unversioned/a file'])
626
state = dirstate.DirState.initialize('dirstate')
628
self.assertRaises(errors.NotVersionedError, state.add,
629
'unversioned/a file', 'a file id', 'file', None, None)
633
def test_add_directory_to_root_no_parents_all_data(self):
634
# The most trivial addition of a dir is when there are no parents and
635
# its in the root and all data about the file is supplied
636
self.build_tree(['a dir/'])
637
stat = os.lstat('a dir')
639
(('', '', 'TREE_ROOT'), [
640
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
642
(('', 'a dir', 'a dir id'), [
643
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
646
state = dirstate.DirState.initialize('dirstate')
648
state.add('a dir', 'a dir id', 'directory', stat, None)
649
# having added it, it should be in the output of iter_entries.
650
self.assertEqual(expected_entries, list(state._iter_entries()))
651
# saving and reloading should not affect this.
655
state = dirstate.DirState.on_file('dirstate')
659
self.assertEqual(expected_entries, list(state._iter_entries()))
663
def test_add_symlink_to_root_no_parents_all_data(self):
664
# The most trivial addition of a symlink when there are no parents and
665
# its in the root and all data about the file is supplied
666
## TODO: windows: dont fail this test. Also, how are symlinks meant to
667
# be represented on windows.
668
os.symlink('target', 'a link')
669
stat = os.lstat('a link')
671
(('', '', 'TREE_ROOT'), [
672
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
674
(('', 'a link', 'a link id'), [
675
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
678
state = dirstate.DirState.initialize('dirstate')
680
state.add('a link', 'a link id', 'symlink', stat, 'target')
681
# having added it, it should be in the output of iter_entries.
682
self.assertEqual(expected_entries, list(state._iter_entries()))
683
# saving and reloading should not affect this.
687
state = dirstate.DirState.on_file('dirstate')
690
self.assertEqual(expected_entries, list(state._iter_entries()))
694
def test_add_directory_and_child_no_parents_all_data(self):
695
# after adding a directory, we should be able to add children to it.
696
self.build_tree(['a dir/', 'a dir/a file'])
697
dirstat = os.lstat('a dir')
698
filestat = os.lstat('a dir/a file')
700
(('', '', 'TREE_ROOT'), [
701
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
703
(('', 'a dir', 'a dir id'), [
704
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
706
(('a dir', 'a file', 'a file id'), [
707
('f', '1'*20, 25, False,
708
dirstate.pack_stat(filestat)), # current tree details
711
state = dirstate.DirState.initialize('dirstate')
713
state.add('a dir', 'a dir id', 'directory', dirstat, None)
714
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
715
# added it, it should be in the output of iter_entries.
716
self.assertEqual(expected_entries, list(state._iter_entries()))
717
# saving and reloading should not affect this.
721
state = dirstate.DirState.on_file('dirstate')
724
self.assertEqual(expected_entries, list(state._iter_entries()))
729
class TestGetLines(TestCaseWithDirState):
731
def test_get_line_with_2_rows(self):
732
state = self.create_dirstate_with_root_and_subdir()
734
self.assertEqual(['#bazaar dirstate flat format 3\n',
735
'adler32: -1327947603\n',
739
'\x00\x00a-root-value\x00'
740
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
741
'\x00subdir\x00subdir-id\x00'
742
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
743
], state.get_lines())
747
def test_entry_to_line(self):
748
state = self.create_dirstate_with_root()
751
'\x00\x00a-root-value\x00d\x00\x000\x00n'
752
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
753
state._entry_to_line(state._dirblocks[0][1][0]))
757
def test_entry_to_line_with_parent(self):
758
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
759
root_entry = ('', '', 'a-root-value'), [
760
('d', '', 0, False, packed_stat), # current tree details
761
# first: a pointer to the current location
762
('a', 'dirname/basename', 0, False, ''),
764
state = dirstate.DirState.initialize('dirstate')
767
'\x00\x00a-root-value\x00'
768
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
769
'a\x00dirname/basename\x000\x00n\x00',
770
state._entry_to_line(root_entry))
774
def test_entry_to_line_with_two_parents_at_different_paths(self):
775
# / in the tree, at / in one parent and /dirname/basename in the other.
776
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
777
root_entry = ('', '', 'a-root-value'), [
778
('d', '', 0, False, packed_stat), # current tree details
779
('d', '', 0, False, 'rev_id'), # first parent details
780
# second: a pointer to the current location
781
('a', 'dirname/basename', 0, False, ''),
783
state = dirstate.DirState.initialize('dirstate')
786
'\x00\x00a-root-value\x00'
787
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
788
'd\x00\x000\x00n\x00rev_id\x00'
789
'a\x00dirname/basename\x000\x00n\x00',
790
state._entry_to_line(root_entry))
794
def test_iter_entries(self):
795
# we should be able to iterate the dirstate entries from end to end
796
# this is for get_lines to be easy to read.
797
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
799
root_entries = [(('', '', 'a-root-value'), [
800
('d', '', 0, False, packed_stat), # current tree details
802
dirblocks.append(('', root_entries))
803
# add two files in the root
804
subdir_entry = ('', 'subdir', 'subdir-id'), [
805
('d', '', 0, False, packed_stat), # current tree details
807
afile_entry = ('', 'afile', 'afile-id'), [
808
('f', 'sha1value', 34, False, packed_stat), # current tree details
810
dirblocks.append(('', [subdir_entry, afile_entry]))
812
file_entry2 = ('subdir', '2file', '2file-id'), [
813
('f', 'sha1value', 23, False, packed_stat), # current tree details
815
dirblocks.append(('subdir', [file_entry2]))
816
state = dirstate.DirState.initialize('dirstate')
818
state._set_data([], dirblocks)
819
expected_entries = [root_entries[0], subdir_entry, afile_entry,
821
self.assertEqual(expected_entries, list(state._iter_entries()))
826
class TestGetBlockRowIndex(TestCaseWithDirState):
828
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
829
file_present, state, dirname, basename, tree_index):
830
self.assertEqual((block_index, row_index, dir_present, file_present),
831
state._get_block_entry_index(dirname, basename, tree_index))
833
block = state._dirblocks[block_index]
834
self.assertEqual(dirname, block[0])
835
if dir_present and file_present:
836
row = state._dirblocks[block_index][1][row_index]
837
self.assertEqual(dirname, row[0][0])
838
self.assertEqual(basename, row[0][1])
840
def test_simple_structure(self):
841
state = self.create_dirstate_with_root_and_subdir()
842
self.addCleanup(state.unlock)
843
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
844
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
845
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
846
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
847
self.assertBlockRowIndexEqual(2, 0, False, False, state,
850
def test_complex_structure_exists(self):
851
state = self.create_complex_dirstate()
852
self.addCleanup(state.unlock)
853
# Make sure we can find everything that exists
854
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
855
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
856
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
857
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
858
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
859
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
860
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
861
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
862
self.assertBlockRowIndexEqual(3, 1, True, True, state,
865
def test_complex_structure_missing(self):
866
state = self.create_complex_dirstate()
867
self.addCleanup(state.unlock)
868
# Make sure things would be inserted in the right locations
869
# '_' comes before 'a'
870
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
871
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
872
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
873
self.assertBlockRowIndexEqual(1, 4, True, False, state,
875
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
876
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
877
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
878
# This would be inserted between a/ and b/
879
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
881
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
884
class TestGetEntry(TestCaseWithDirState):
886
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
887
"""Check that the right entry is returned for a request to getEntry."""
888
entry = state._get_entry(index, path_utf8=path)
890
self.assertEqual((None, None), entry)
893
self.assertEqual((dirname, basename, file_id), cur[:3])
895
def test_simple_structure(self):
896
state = self.create_dirstate_with_root_and_subdir()
897
self.addCleanup(state.unlock)
898
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
899
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
900
self.assertEntryEqual(None, None, None, state, 'missing', 0)
901
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
902
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
904
def test_complex_structure_exists(self):
905
state = self.create_complex_dirstate()
906
self.addCleanup(state.unlock)
907
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
908
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
909
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
910
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
911
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
912
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
913
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
914
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
915
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
918
def test_complex_structure_missing(self):
919
state = self.create_complex_dirstate()
920
self.addCleanup(state.unlock)
921
self.assertEntryEqual(None, None, None, state, '_', 0)
922
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
923
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
924
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
926
def test_get_entry_uninitialized(self):
927
"""Calling get_entry will load data if it needs to"""
928
state = self.create_dirstate_with_root()
934
state = dirstate.DirState.on_file('dirstate')
937
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
939
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
940
state._dirblock_state)
941
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
946
class TestDirstateSortOrder(TestCaseWithTransport):
947
"""Test that DirState adds entries in the right order."""
949
def test_add_sorting(self):
950
"""Add entries in lexicographical order, we get path sorted order.
952
This tests it to a depth of 4, to make sure we don't just get it right
953
at a single depth. 'a/a' should come before 'a-a', even though it
954
doesn't lexicographically.
956
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
957
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
960
state = dirstate.DirState.initialize('dirstate')
961
self.addCleanup(state.unlock)
963
fake_stat = os.stat('dirstate')
965
d_id = d.replace('/', '_')+'-id'
967
file_id = file_path.replace('/', '_')+'-id'
968
state.add(d, d_id, 'directory', fake_stat, null_sha)
969
state.add(file_path, file_id, 'file', fake_stat, null_sha)
971
expected = ['', '', 'a',
972
'a/a', 'a/a/a', 'a/a/a/a',
973
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
975
split = lambda p:p.split('/')
976
self.assertEqual(sorted(expected, key=split), expected)
977
dirblock_names = [d[0] for d in state._dirblocks]
978
self.assertEqual(expected, dirblock_names)
980
def test_set_parent_trees_correct_order(self):
981
"""After calling set_parent_trees() we should maintain the order."""
982
dirs = ['a', 'a-a', 'a/a']
984
state = dirstate.DirState.initialize('dirstate')
985
self.addCleanup(state.unlock)
987
fake_stat = os.stat('dirstate')
989
d_id = d.replace('/', '_')+'-id'
991
file_id = file_path.replace('/', '_')+'-id'
992
state.add(d, d_id, 'directory', fake_stat, null_sha)
993
state.add(file_path, file_id, 'file', fake_stat, null_sha)
995
expected = ['', '', 'a', 'a/a', 'a-a']
996
dirblock_names = [d[0] for d in state._dirblocks]
997
self.assertEqual(expected, dirblock_names)
999
# *really* cheesy way to just get an empty tree
1000
repo = self.make_repository('repo')
1001
empty_tree = repo.revision_tree(None)
1002
state.set_parent_trees([('null:', empty_tree)], [])
1004
dirblock_names = [d[0] for d in state._dirblocks]
1005
self.assertEqual(expected, dirblock_names)
1008
class InstrumentedDirState(dirstate.DirState):
1009
"""An DirState with instrumented sha1 functionality."""
1011
def __init__(self, path):
1012
super(InstrumentedDirState, self).__init__(path)
1013
self._time_offset = 0
1016
def _sha_cutoff_time(self):
1017
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1018
return timestamp + self._time_offset
1020
def _sha1_file(self, abspath, entry):
1021
self._log.append(('sha1', abspath))
1022
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1024
def _read_link(self, abspath, old_link):
1025
self._log.append(('read_link', abspath, old_link))
1026
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1028
def _lstat(self, abspath, entry):
1029
self._log.append(('lstat', abspath))
1030
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1032
def _is_executable(self, mode, old_executable):
1033
self._log.append(('is_exec', mode, old_executable))
1034
return super(InstrumentedDirState, self)._is_executable(mode,
1037
def adjust_time(self, secs):
1038
"""Move the clock forward or back.
1040
:param secs: The amount to adjust the clock by. Positive values make it
1041
seem as if we are in the future, negative values make it seem like we
1044
self._time_offset += secs
1047
class _FakeStat(object):
1048
"""A class with the same attributes as a real stat result."""
1050
def __init__(self, size, mtime, ctime, dev, ino, mode):
1052
self.st_mtime = mtime
1053
self.st_ctime = ctime
1059
class TestUpdateEntry(TestCaseWithDirState):
1060
"""Test the DirState.update_entry functions"""
1062
def get_state_with_a(self):
1063
"""Create a DirState tracking a single object named 'a'"""
1064
state = InstrumentedDirState.initialize('dirstate')
1065
self.addCleanup(state.unlock)
1066
state.add('a', 'a-id', 'file', None, '')
1067
entry = state._get_entry(0, path_utf8='a')
1070
def test_update_entry(self):
1071
state, entry = self.get_state_with_a()
1072
self.build_tree(['a'])
1073
# Add one where we don't provide the stat or sha already
1074
self.assertEqual(('', 'a', 'a-id'), entry[0])
1075
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1077
# Flush the buffers to disk
1079
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1080
state._dirblock_state)
1082
stat_value = os.lstat('a')
1083
packed_stat = dirstate.pack_stat(stat_value)
1084
link_or_sha1 = state.update_entry(entry, abspath='a',
1085
stat_value=stat_value)
1086
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1089
# The dirblock entry should be updated with the new info
1090
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1092
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1093
state._dirblock_state)
1094
mode = stat_value.st_mode
1095
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1098
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1099
state._dirblock_state)
1101
# If we do it again right away, we don't know if the file has changed
1102
# so we will re-read the file. Roll the clock back so the file is
1103
# guaranteed to look too new.
1104
state.adjust_time(-10)
1106
link_or_sha1 = state.update_entry(entry, abspath='a',
1107
stat_value=stat_value)
1108
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1109
('sha1', 'a'), ('is_exec', mode, False),
1111
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1113
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1114
state._dirblock_state)
1117
# However, if we move the clock forward so the file is considered
1118
# "stable", it should just returned the cached value.
1119
state.adjust_time(20)
1120
link_or_sha1 = state.update_entry(entry, abspath='a',
1121
stat_value=stat_value)
1122
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1124
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1125
('sha1', 'a'), ('is_exec', mode, False),
1128
def test_update_entry_no_stat_value(self):
1129
"""Passing the stat_value is optional."""
1130
state, entry = self.get_state_with_a()
1131
state.adjust_time(-10) # Make sure the file looks new
1132
self.build_tree(['a'])
1133
# Add one where we don't provide the stat or sha already
1134
link_or_sha1 = state.update_entry(entry, abspath='a')
1135
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1137
stat_value = os.lstat('a')
1138
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1139
('is_exec', stat_value.st_mode, False),
1142
def test_update_entry_symlink(self):
1143
"""Update entry should read symlinks."""
1144
if not osutils.has_symlinks():
1145
return # PlatformDeficiency / TestSkipped
1146
state, entry = self.get_state_with_a()
1148
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1149
state._dirblock_state)
1150
os.symlink('target', 'a')
1152
state.adjust_time(-10) # Make the symlink look new
1153
stat_value = os.lstat('a')
1154
packed_stat = dirstate.pack_stat(stat_value)
1155
link_or_sha1 = state.update_entry(entry, abspath='a',
1156
stat_value=stat_value)
1157
self.assertEqual('target', link_or_sha1)
1158
self.assertEqual([('read_link', 'a', '')], state._log)
1159
# Dirblock is updated
1160
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1162
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1163
state._dirblock_state)
1165
# Because the stat_value looks new, we should re-read the target
1166
link_or_sha1 = state.update_entry(entry, abspath='a',
1167
stat_value=stat_value)
1168
self.assertEqual('target', link_or_sha1)
1169
self.assertEqual([('read_link', 'a', ''),
1170
('read_link', 'a', 'target'),
1172
state.adjust_time(+20) # Skip into the future, all files look old
1173
link_or_sha1 = state.update_entry(entry, abspath='a',
1174
stat_value=stat_value)
1175
self.assertEqual('target', link_or_sha1)
1176
# There should not be a new read_link call.
1177
# (this is a weak assertion, because read_link is fairly inexpensive,
1178
# versus the number of symlinks that we would have)
1179
self.assertEqual([('read_link', 'a', ''),
1180
('read_link', 'a', 'target'),
1183
def test_update_entry_dir(self):
1184
state, entry = self.get_state_with_a()
1185
self.build_tree(['a/'])
1186
self.assertIs(None, state.update_entry(entry, 'a'))
1188
def create_and_test_file(self, state, entry):
1189
"""Create a file at 'a' and verify the state finds it.
1191
The state should already be versioning *something* at 'a'. This makes
1192
sure that state.update_entry recognizes it as a file.
1194
self.build_tree(['a'])
1195
stat_value = os.lstat('a')
1196
packed_stat = dirstate.pack_stat(stat_value)
1198
link_or_sha1 = state.update_entry(entry, abspath='a')
1199
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1201
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1205
def create_and_test_dir(self, state, entry):
1206
"""Create a directory at 'a' and verify the state finds it.
1208
The state should already be versioning *something* at 'a'. This makes
1209
sure that state.update_entry recognizes it as a directory.
1211
self.build_tree(['a/'])
1212
stat_value = os.lstat('a')
1213
packed_stat = dirstate.pack_stat(stat_value)
1215
link_or_sha1 = state.update_entry(entry, abspath='a')
1216
self.assertIs(None, link_or_sha1)
1217
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1221
def create_and_test_symlink(self, state, entry):
1222
"""Create a symlink at 'a' and verify the state finds it.
1224
The state should already be versioning *something* at 'a'. This makes
1225
sure that state.update_entry recognizes it as a symlink.
1227
This should not be called if this platform does not have symlink
1230
os.symlink('path/to/foo', 'a')
1232
stat_value = os.lstat('a')
1233
packed_stat = dirstate.pack_stat(stat_value)
1235
link_or_sha1 = state.update_entry(entry, abspath='a')
1236
self.assertEqual('path/to/foo', link_or_sha1)
1237
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1241
def test_update_missing_file(self):
1242
state, entry = self.get_state_with_a()
1243
packed_stat = self.create_and_test_file(state, entry)
1244
# Now if we delete the file, update_entry should recover and
1247
self.assertIs(None, state.update_entry(entry, abspath='a'))
1248
# And the record shouldn't be changed.
1249
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1250
self.assertEqual([('f', digest, 14, False, packed_stat)],
1253
def test_update_missing_dir(self):
1254
state, entry = self.get_state_with_a()
1255
packed_stat = self.create_and_test_dir(state, entry)
1256
# Now if we delete the directory, update_entry should recover and
1259
self.assertIs(None, state.update_entry(entry, abspath='a'))
1260
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1262
def test_update_missing_symlink(self):
1263
if not osutils.has_symlinks():
1264
return # PlatformDeficiency / TestSkipped
1265
state, entry = self.get_state_with_a()
1266
packed_stat = self.create_and_test_symlink(state, entry)
1268
self.assertIs(None, state.update_entry(entry, abspath='a'))
1269
# And the record shouldn't be changed.
1270
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1273
def test_update_file_to_dir(self):
1274
"""If a file changes to a directory we return None for the sha.
1275
We also update the inventory record.
1277
state, entry = self.get_state_with_a()
1278
self.create_and_test_file(state, entry)
1280
self.create_and_test_dir(state, entry)
1282
def test_update_file_to_symlink(self):
1283
"""File becomes a symlink"""
1284
if not osutils.has_symlinks():
1285
return # PlatformDeficiency / TestSkipped
1286
state, entry = self.get_state_with_a()
1287
self.create_and_test_file(state, entry)
1289
self.create_and_test_symlink(state, entry)
1291
def test_update_dir_to_file(self):
1292
"""Directory becoming a file updates the entry."""
1293
state, entry = self.get_state_with_a()
1294
self.create_and_test_dir(state, entry)
1296
self.create_and_test_file(state, entry)
1298
def test_update_dir_to_symlink(self):
1299
"""Directory becomes a symlink"""
1300
if not osutils.has_symlinks():
1301
return # PlatformDeficiency / TestSkipped
1302
state, entry = self.get_state_with_a()
1303
self.create_and_test_dir(state, entry)
1305
self.create_and_test_symlink(state, entry)
1307
def test_update_symlink_to_file(self):
1308
"""Symlink becomes a file"""
1309
state, entry = self.get_state_with_a()
1310
self.create_and_test_symlink(state, entry)
1312
self.create_and_test_file(state, entry)
1314
def test_update_symlink_to_dir(self):
1315
"""Symlink becomes a directory"""
1316
state, entry = self.get_state_with_a()
1317
self.create_and_test_symlink(state, entry)
1319
self.create_and_test_dir(state, entry)
1322
class TestPackStat(TestCaseWithTransport):
1324
def assertPackStat(self, expected, stat_value):
1325
"""Check the packed and serialized form of a stat value."""
1326
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1328
def test_pack_stat_int(self):
1329
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1330
# Make sure that all parameters have an impact on the packed stat.
1331
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1334
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1335
st.st_mtime = 1172758620
1337
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1338
st.st_ctime = 1172758630
1340
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1343
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1344
st.st_ino = 6499540L
1346
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1347
st.st_mode = 0100744
1349
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1351
def test_pack_stat_float(self):
1352
"""On some platforms mtime and ctime are floats.
1354
Make sure we don't get warnings or errors, and that we ignore changes <
1357
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1358
777L, 6499538L, 0100644)
1359
# These should all be the same as the integer counterparts
1360
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1361
st.st_mtime = 1172758620.0
1363
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1364
st.st_ctime = 1172758630.0
1366
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1367
# fractional seconds are discarded, so no change from above
1368
st.st_mtime = 1172758620.453
1369
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1370
st.st_ctime = 1172758630.228
1371
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1374
class TestBisect(TestCaseWithTransport):
1375
"""Test the ability to bisect into the disk format."""
1377
def create_basic_dirstate(self):
1378
"""Create a dirstate with a few files and directories.
1387
tree = self.make_branch_and_tree('tree')
1388
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1389
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1390
self.build_tree(['tree/' + p for p in paths])
1391
tree.set_root_id('TREE_ROOT')
1392
tree.add([p.rstrip('/') for p in paths], file_ids)
1393
tree.commit('initial', rev_id='rev-1')
1394
revision_id = 'rev-1'
1395
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1396
t = self.get_transport().clone('tree')
1397
a_text = t.get_bytes('a')
1398
a_sha = osutils.sha_string(a_text)
1400
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1401
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1402
c_text = t.get_bytes('b/c')
1403
c_sha = osutils.sha_string(c_text)
1405
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1406
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1407
e_text = t.get_bytes('b/d/e')
1408
e_sha = osutils.sha_string(e_text)
1410
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1411
f_text = t.get_bytes('f')
1412
f_sha = osutils.sha_string(f_text)
1414
null_stat = dirstate.DirState.NULLSTAT
1416
'':(('', '', 'TREE_ROOT'), [
1417
('d', '', 0, False, null_stat),
1418
('d', '', 0, False, revision_id),
1420
'a':(('', 'a', 'a-id'), [
1421
('f', '', 0, False, null_stat),
1422
('f', a_sha, a_len, False, revision_id),
1424
'b':(('', 'b', 'b-id'), [
1425
('d', '', 0, False, null_stat),
1426
('d', '', 0, False, revision_id),
1428
'b/c':(('b', 'c', 'c-id'), [
1429
('f', '', 0, False, null_stat),
1430
('f', c_sha, c_len, False, revision_id),
1432
'b/d':(('b', 'd', 'd-id'), [
1433
('d', '', 0, False, null_stat),
1434
('d', '', 0, False, revision_id),
1436
'b/d/e':(('b/d', 'e', 'e-id'), [
1437
('f', '', 0, False, null_stat),
1438
('f', e_sha, e_len, False, revision_id),
1440
'f':(('', 'f', 'f-id'), [
1441
('f', '', 0, False, null_stat),
1442
('f', f_sha, f_len, False, revision_id),
1445
state = dirstate.DirState.from_tree(tree, 'dirstate')
1450
# Use a different object, to make sure nothing is pre-cached in memory.
1451
state = dirstate.DirState.on_file('dirstate')
1453
self.addCleanup(state.unlock)
1454
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1455
state._dirblock_state)
1456
# This is code is only really tested if we actually have to make more
1457
# than one read, so set the page size to something smaller.
1458
# We want it to contain about 2.2 records, so that we have a couple
1459
# records that we can read per attempt
1460
state._bisect_page_size = 200
1461
return tree, state, expected
1463
def create_duplicated_dirstate(self):
1464
"""Create a dirstate with a deleted and added entries.
1466
This grabs a basic_dirstate, and then removes and re adds every entry
1469
tree, state, expected = self.create_basic_dirstate()
1470
# Now we will just remove and add every file so we get an extra entry
1471
# per entry. Unversion in reverse order so we handle subdirs
1472
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1473
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1474
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1476
# Update the expected dictionary.
1477
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1478
orig = expected[path]
1480
# This record was deleted in the current tree
1481
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1483
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1484
# And didn't exist in the basis tree
1485
expected[path2] = (new_key, [orig[1][0],
1486
dirstate.DirState.NULL_PARENT_DETAILS])
1488
# We will replace the 'dirstate' file underneath 'state', but that is
1489
# okay as lock as we unlock 'state' first.
1492
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1498
# But we need to leave state in a read-lock because we already have
1499
# a cleanup scheduled
1501
return tree, state, expected
1503
def create_renamed_dirstate(self):
1504
"""Create a dirstate with a few internal renames.
1506
This takes the basic dirstate, and moves the paths around.
1508
tree, state, expected = self.create_basic_dirstate()
1510
tree.rename_one('a', 'b/g')
1512
tree.rename_one('b/d', 'h')
1514
old_a = expected['a']
1515
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1516
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1517
('r', 'a', 0, False, '')])
1518
old_d = expected['b/d']
1519
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1520
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1521
('r', 'b/d', 0, False, '')])
1523
old_e = expected['b/d/e']
1524
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1526
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1527
('r', 'b/d/e', 0, False, '')])
1531
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1538
return tree, state, expected
1540
def assertBisect(self, expected_map, map_keys, state, paths):
1541
"""Assert that bisecting for paths returns the right result.
1543
:param expected_map: A map from key => entry value
1544
:param map_keys: The keys to expect for each path
1545
:param state: The DirState object.
1546
:param paths: A list of paths, these will automatically be split into
1547
(dir, name) tuples, and sorted according to how _bisect
1550
dir_names = sorted(osutils.split(p) for p in paths)
1551
result = state._bisect(dir_names)
1552
# For now, results are just returned in whatever order we read them.
1553
# We could sort by (dir, name, file_id) or something like that, but in
1554
# the end it would still be fairly arbitrary, and we don't want the
1555
# extra overhead if we can avoid it. So sort everything to make sure
1557
assert len(map_keys) == len(dir_names)
1559
for dir_name, keys in zip(dir_names, map_keys):
1561
# This should not be present in the output
1563
expected[dir_name] = sorted(expected_map[k] for k in keys)
1565
for dir_name in result:
1566
result[dir_name].sort()
1568
self.assertEqual(expected, result)
1570
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1571
"""Assert that bisecting for dirbblocks returns the right result.
1573
:param expected_map: A map from key => expected values
1574
:param map_keys: A nested list of paths we expect to be returned.
1575
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1576
:param state: The DirState object.
1577
:param paths: A list of directories
1579
result = state._bisect_dirblocks(paths)
1580
assert len(map_keys) == len(paths)
1583
for path, keys in zip(paths, map_keys):
1585
# This should not be present in the output
1587
expected[path] = sorted(expected_map[k] for k in keys)
1591
self.assertEqual(expected, result)
1593
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1594
"""Assert the return value of a recursive bisection.
1596
:param expected_map: A map from key => entry value
1597
:param map_keys: A list of paths we expect to be returned.
1598
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1599
:param state: The DirState object.
1600
:param paths: A list of files and directories. It will be broken up
1601
into (dir, name) pairs and sorted before calling _bisect_recursive.
1604
for key in map_keys:
1605
entry = expected_map[key]
1606
dir_name_id, trees_info = entry
1607
expected[dir_name_id] = trees_info
1609
dir_names = sorted(osutils.split(p) for p in paths)
1610
result = state._bisect_recursive(dir_names)
1612
self.assertEqual(expected, result)
1614
def test_bisect_each(self):
1615
"""Find a single record using bisect."""
1616
tree, state, expected = self.create_basic_dirstate()
1618
# Bisect should return the rows for the specified files.
1619
self.assertBisect(expected, [['']], state, [''])
1620
self.assertBisect(expected, [['a']], state, ['a'])
1621
self.assertBisect(expected, [['b']], state, ['b'])
1622
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1623
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1624
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1625
self.assertBisect(expected, [['f']], state, ['f'])
1627
def test_bisect_multi(self):
1628
"""Bisect can be used to find multiple records at the same time."""
1629
tree, state, expected = self.create_basic_dirstate()
1630
# Bisect should be capable of finding multiple entries at the same time
1631
self.assertBisect(expected, [['a'], ['b'], ['f']],
1632
state, ['a', 'b', 'f'])
1633
# ('', 'f') sorts before the others
1634
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1635
state, ['b/d', 'b/d/e', 'f'])
1637
def test_bisect_one_page(self):
1638
"""Test bisect when there is only 1 page to read"""
1639
tree, state, expected = self.create_basic_dirstate()
1640
state._bisect_page_size = 5000
1641
self.assertBisect(expected,[['']], state, [''])
1642
self.assertBisect(expected,[['a']], state, ['a'])
1643
self.assertBisect(expected,[['b']], state, ['b'])
1644
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1645
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1646
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1647
self.assertBisect(expected,[['f']], state, ['f'])
1648
self.assertBisect(expected,[['a'], ['b'], ['f']],
1649
state, ['a', 'b', 'f'])
1650
# ('', 'f') sorts before the others
1651
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1652
state, ['b/d', 'b/d/e', 'f'])
1654
def test_bisect_duplicate_paths(self):
1655
"""When bisecting for a path, handle multiple entries."""
1656
tree, state, expected = self.create_duplicated_dirstate()
1658
# Now make sure that both records are properly returned.
1659
self.assertBisect(expected, [['']], state, [''])
1660
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1661
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1662
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1663
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1664
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1666
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1668
def test_bisect_page_size_too_small(self):
1669
"""If the page size is too small, we will auto increase it."""
1670
tree, state, expected = self.create_basic_dirstate()
1671
state._bisect_page_size = 50
1672
self.assertBisect(expected, [None], state, ['b/e'])
1673
self.assertBisect(expected, [['a']], state, ['a'])
1674
self.assertBisect(expected, [['b']], state, ['b'])
1675
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1676
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1677
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1678
self.assertBisect(expected, [['f']], state, ['f'])
1680
def test_bisect_missing(self):
1681
"""Test that bisect return None if it cannot find a path."""
1682
tree, state, expected = self.create_basic_dirstate()
1683
self.assertBisect(expected, [None], state, ['foo'])
1684
self.assertBisect(expected, [None], state, ['b/foo'])
1685
self.assertBisect(expected, [None], state, ['bar/foo'])
1687
self.assertBisect(expected, [['a'], None, ['b/d']],
1688
state, ['a', 'foo', 'b/d'])
1690
def test_bisect_rename(self):
1691
"""Check that we find a renamed row."""
1692
tree, state, expected = self.create_renamed_dirstate()
1694
# Search for the pre and post renamed entries
1695
self.assertBisect(expected, [['a']], state, ['a'])
1696
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1697
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1698
self.assertBisect(expected, [['h']], state, ['h'])
1700
# What about b/d/e? shouldn't that also get 2 directory entries?
1701
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1702
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1704
def test_bisect_dirblocks(self):
1705
tree, state, expected = self.create_duplicated_dirstate()
1706
self.assertBisectDirBlocks(expected,
1707
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1708
self.assertBisectDirBlocks(expected,
1709
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1710
self.assertBisectDirBlocks(expected,
1711
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1712
self.assertBisectDirBlocks(expected,
1713
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1714
['b/c', 'b/c2', 'b/d', 'b/d2'],
1715
['b/d/e', 'b/d/e2'],
1716
], state, ['', 'b', 'b/d'])
1718
def test_bisect_dirblocks_missing(self):
1719
tree, state, expected = self.create_basic_dirstate()
1720
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1721
state, ['b/d', 'b/e'])
1722
# Files don't show up in this search
1723
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1724
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1725
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1726
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1727
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1729
def test_bisect_recursive_each(self):
1730
tree, state, expected = self.create_basic_dirstate()
1731
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1732
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1733
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1734
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1736
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1738
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1742
def test_bisect_recursive_multiple(self):
1743
tree, state, expected = self.create_basic_dirstate()
1744
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1745
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1746
state, ['b/d', 'b/d/e'])
1748
def test_bisect_recursive_missing(self):
1749
tree, state, expected = self.create_basic_dirstate()
1750
self.assertBisectRecursive(expected, [], state, ['d'])
1751
self.assertBisectRecursive(expected, [], state, ['b/e'])
1752
self.assertBisectRecursive(expected, [], state, ['g'])
1753
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1755
def test_bisect_recursive_renamed(self):
1756
tree, state, expected = self.create_renamed_dirstate()
1758
# Looking for either renamed item should find the other
1759
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1760
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1761
# Looking in the containing directory should find the rename target,
1762
# and anything in a subdir of the renamed target.
1763
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1764
'b/d/e', 'b/g', 'h', 'h/e'],
1768
class TestBisectDirblock(TestCase):
1769
"""Test that bisect_dirblock() returns the expected values.
1771
bisect_dirblock is intended to work like bisect.bisect_left() except it
1772
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1773
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1776
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1777
"""Assert that bisect_split works like bisect_left on the split paths.
1779
:param dirblocks: A list of (path, [info]) pairs.
1780
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1781
:param path: The path we are indexing.
1783
All other arguments will be passed along.
1785
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1787
split_dirblock = (path.split('/'), [])
1788
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1790
self.assertEqual(bisect_left_idx, bisect_split_idx,
1791
'bisect_split disagreed. %s != %s'
1793
% (bisect_left_idx, bisect_split_idx, path)
1796
def paths_to_dirblocks(self, paths):
1797
"""Convert a list of paths into dirblock form.
1799
Also, ensure that the paths are in proper sorted order.
1801
dirblocks = [(path, []) for path in paths]
1802
split_dirblocks = [(path.split('/'), []) for path in paths]
1803
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1804
return dirblocks, split_dirblocks
1806
def test_simple(self):
1807
"""In the simple case it works just like bisect_left"""
1808
paths = ['', 'a', 'b', 'c', 'd']
1809
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1811
self.assertBisect(dirblocks, split_dirblocks, path)
1812
self.assertBisect(dirblocks, split_dirblocks, '_')
1813
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1814
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1815
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1816
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1817
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1818
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1819
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1820
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1822
def test_involved(self):
1823
"""This is where bisect_left diverges slightly."""
1825
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1826
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1828
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1829
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1832
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1834
self.assertBisect(dirblocks, split_dirblocks, path)
1836
def test_involved_cached(self):
1837
"""This is where bisect_left diverges slightly."""
1839
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1840
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1842
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1843
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1847
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1849
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)