1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
from bzrlib.memorytree import MemoryTree
29
from bzrlib.tests import TestCase, TestCaseWithTransport
33
# test DirStateRevisionTree : test filtering out of deleted files does not
34
# filter out files called RECYCLED.BIN ;)
35
# test 0 parents, 1 parent, 4 parents.
36
# test unicode parents, non unicode parents
37
# test all change permutations in one and two parents.
38
# i.e. file in parent 1, dir in parent 2, symlink in tree.
39
# test that renames in the tree result in correct parent paths
40
# Test get state from a file, then asking for lines.
41
# write a smaller state, and check the file has been truncated.
42
# add a entry when its in state deleted
43
# revision attribute for root entries.
44
# test that utf8 strings are preserved in _row_to_line
45
# test parent manipulation
46
# test parents that are null in save : i.e. no record in the parent tree for this.
47
# todo: _set_data records ghost parents.
49
# general checks for NOT_IN_MEMORY error conditions.
50
# set_path_id on a NOT_IN_MEMORY dirstate
51
# set_path_id unicode support
52
# set_path_id setting id of a path not root
53
# set_path_id setting id when there are parents without the id in the parents
54
# set_path_id setting id when there are parents with the id in the parents
55
# set_path_id setting id when state is not in memory
56
# set_path_id setting id when state is in memory unmodified
57
# set_path_id setting id when state is in memory modified
59
class TestCaseWithDirState(TestCaseWithTransport):
60
"""Helper functions for creating DirState objects with various content."""
62
def create_empty_dirstate(self):
63
"""Return a locked but empty dirstate"""
64
state = dirstate.DirState.initialize('dirstate')
67
def create_dirstate_with_root(self):
68
"""Return a write-locked state with a single root entry."""
69
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
70
root_entry_direntry = ('', '', 'a-root-value'), [
71
('d', '', 0, False, packed_stat),
74
dirblocks.append(('', [root_entry_direntry]))
75
dirblocks.append(('', []))
76
state = self.create_empty_dirstate()
78
state._set_data([], dirblocks)
85
def create_dirstate_with_root_and_subdir(self):
86
"""Return a locked DirState with a root and a subdir"""
87
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
88
subdir_entry = ('', 'subdir', 'subdir-id'), [
89
('d', '', 0, False, packed_stat),
91
state = self.create_dirstate_with_root()
93
dirblocks = list(state._dirblocks)
94
dirblocks[1][1].append(subdir_entry)
95
state._set_data([], dirblocks)
101
def create_complex_dirstate(self):
102
"""This dirstate contains multiple files and directories.
112
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
114
# Notice that a/e is an empty directory.
116
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
117
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
118
root_entry = ('', '', 'a-root-value'), [
119
('d', '', 0, False, packed_stat),
121
a_entry = ('', 'a', 'a-dir'), [
122
('d', '', 0, False, packed_stat),
124
b_entry = ('', 'b', 'b-dir'), [
125
('d', '', 0, False, packed_stat),
127
c_entry = ('', 'c', 'c-file'), [
128
('f', null_sha, 10, False, packed_stat),
130
d_entry = ('', 'd', 'd-file'), [
131
('f', null_sha, 20, False, packed_stat),
133
e_entry = ('a', 'e', 'e-dir'), [
134
('d', '', 0, False, packed_stat),
136
f_entry = ('a', 'f', 'f-file'), [
137
('f', null_sha, 30, False, packed_stat),
139
g_entry = ('b', 'g', 'g-file'), [
140
('f', null_sha, 30, False, packed_stat),
142
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
143
('f', null_sha, 40, False, packed_stat),
146
dirblocks.append(('', [root_entry]))
147
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
148
dirblocks.append(('a', [e_entry, f_entry]))
149
dirblocks.append(('b', [g_entry, h_entry]))
150
state = dirstate.DirState.initialize('dirstate')
153
state._set_data([], dirblocks)
159
def check_state_with_reopen(self, expected_result, state):
160
"""Check that state has current state expected_result.
162
This will check the current state, open the file anew and check it
164
This function expects the current state to be locked for writing, and
165
will unlock it before re-opening.
166
This is required because we can't open a lock_read() while something
167
else has a lock_write().
168
write => mutually exclusive lock
171
# The state should already be write locked, since we just had to do
172
# some operation to get here.
173
assert state._lock_token is not None
175
self.assertEqual(expected_result[0], state.get_parent_ids())
176
# there should be no ghosts in this tree.
177
self.assertEqual([], state.get_ghosts())
178
# there should be one fileid in this tree - the root of the tree.
179
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
del state # Callers should unlock
184
state = dirstate.DirState.on_file('dirstate')
187
self.assertEqual(expected_result[1], list(state._iter_entries()))
192
class TestTreeToDirState(TestCaseWithDirState):
194
def test_empty_to_dirstate(self):
195
"""We should be able to create a dirstate for an empty tree."""
196
# There are no files on disk and no parents
197
tree = self.make_branch_and_tree('tree')
198
expected_result = ([], [
199
(('', '', tree.path2id('')), # common details
200
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
202
state = dirstate.DirState.from_tree(tree, 'dirstate')
204
self.check_state_with_reopen(expected_result, state)
206
def test_1_parents_empty_to_dirstate(self):
207
# create a parent by doing a commit
208
tree = self.make_branch_and_tree('tree')
209
rev_id = tree.commit('first post').encode('utf8')
210
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
211
expected_result = ([rev_id], [
212
(('', '', tree.path2id('')), # common details
213
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
214
('d', '', 0, False, rev_id), # first parent details
216
state = dirstate.DirState.from_tree(tree, 'dirstate')
217
self.check_state_with_reopen(expected_result, state)
220
def test_2_parents_empty_to_dirstate(self):
221
# create a parent by doing a commit
222
tree = self.make_branch_and_tree('tree')
223
rev_id = tree.commit('first post')
224
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
225
rev_id2 = tree2.commit('second post', allow_pointless=True)
226
tree.merge_from_branch(tree2.branch)
227
expected_result = ([rev_id, rev_id2], [
228
(('', '', tree.path2id('')), # common details
229
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
230
('d', '', 0, False, rev_id), # first parent details
231
('d', '', 0, False, rev_id2), # second parent details
233
state = dirstate.DirState.from_tree(tree, 'dirstate')
234
self.check_state_with_reopen(expected_result, state)
237
def test_empty_unknowns_are_ignored_to_dirstate(self):
238
"""We should be able to create a dirstate for an empty tree."""
239
# There are no files on disk and no parents
240
tree = self.make_branch_and_tree('tree')
241
self.build_tree(['tree/unknown'])
242
expected_result = ([], [
243
(('', '', tree.path2id('')), # common details
244
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
246
state = dirstate.DirState.from_tree(tree, 'dirstate')
247
self.check_state_with_reopen(expected_result, state)
249
def get_tree_with_a_file(self):
250
tree = self.make_branch_and_tree('tree')
251
self.build_tree(['tree/a file'])
252
tree.add('a file', 'a file id')
255
def test_non_empty_no_parents_to_dirstate(self):
256
"""We should be able to create a dirstate for an empty tree."""
257
# There are files on disk and no parents
258
tree = self.get_tree_with_a_file()
259
expected_result = ([], [
260
(('', '', tree.path2id('')), # common details
261
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
263
(('', 'a file', 'a file id'), # common
264
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
267
state = dirstate.DirState.from_tree(tree, 'dirstate')
268
self.check_state_with_reopen(expected_result, state)
270
def test_1_parents_not_empty_to_dirstate(self):
271
# create a parent by doing a commit
272
tree = self.get_tree_with_a_file()
273
rev_id = tree.commit('first post').encode('utf8')
274
# change the current content to be different this will alter stat, sha
276
self.build_tree_contents([('tree/a file', 'new content\n')])
277
expected_result = ([rev_id], [
278
(('', '', tree.path2id('')), # common details
279
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
280
('d', '', 0, False, rev_id), # first parent details
282
(('', 'a file', 'a file id'), # common
283
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
284
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
285
rev_id), # first parent
288
state = dirstate.DirState.from_tree(tree, 'dirstate')
289
self.check_state_with_reopen(expected_result, state)
291
def test_2_parents_not_empty_to_dirstate(self):
292
# create a parent by doing a commit
293
tree = self.get_tree_with_a_file()
294
rev_id = tree.commit('first post').encode('utf8')
295
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
296
# change the current content to be different this will alter stat, sha
298
self.build_tree_contents([('tree2/a file', 'merge content\n')])
299
rev_id2 = tree2.commit('second post').encode('utf8')
300
tree.merge_from_branch(tree2.branch)
301
# change the current content to be different this will alter stat, sha
302
# and length again, giving us three distinct values:
303
self.build_tree_contents([('tree/a file', 'new content\n')])
304
expected_result = ([rev_id, rev_id2], [
305
(('', '', tree.path2id('')), # common details
306
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
307
('d', '', 0, False, rev_id), # first parent details
308
('d', '', 0, False, rev_id2), # second parent details
310
(('', 'a file', 'a file id'), # common
311
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
312
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
313
rev_id), # first parent
314
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
315
rev_id2), # second parent
318
state = dirstate.DirState.from_tree(tree, 'dirstate')
319
self.check_state_with_reopen(expected_result, state)
322
class TestDirStateOnFile(TestCaseWithDirState):
324
def test_construct_with_path(self):
325
tree = self.make_branch_and_tree('tree')
326
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
327
# we want to be able to get the lines of the dirstate that we will
329
lines = state.get_lines()
331
self.build_tree_contents([('dirstate', ''.join(lines))])
333
# no parents, default tree content
334
expected_result = ([], [
335
(('', '', tree.path2id('')), # common details
336
# current tree details, but new from_tree skips statting, it
337
# uses set_state_from_inventory, and thus depends on the
339
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
342
state = dirstate.DirState.on_file('dirstate')
343
state.lock_write() # check_state_with_reopen will save() and unlock it
344
self.check_state_with_reopen(expected_result, state)
346
def test_can_save_clean_on_file(self):
347
tree = self.make_branch_and_tree('tree')
348
state = dirstate.DirState.from_tree(tree, 'dirstate')
350
# doing a save should work here as there have been no changes.
352
# TODO: stat it and check it hasn't changed; may require waiting
353
# for the state accuracy window.
358
class TestDirStateInitialize(TestCaseWithDirState):
360
def test_initialize(self):
361
expected_result = ([], [
362
(('', '', 'TREE_ROOT'), # common details
363
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.initialize('dirstate')
368
self.assertIsInstance(state, dirstate.DirState)
369
lines = state.get_lines()
370
self.assertFileEqual(''.join(state.get_lines()),
372
self.check_state_with_reopen(expected_result, state)
378
class TestDirStateManipulations(TestCaseWithDirState):
380
def test_set_state_from_inventory_no_content_no_parents(self):
381
# setting the current inventory is a slow but important api to support.
382
tree1 = self.make_branch_and_memory_tree('tree1')
386
revid1 = tree1.commit('foo').encode('utf8')
387
root_id = tree1.inventory.root.file_id
388
inv = tree1.inventory
391
expected_result = [], [
392
(('', '', root_id), [
393
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
394
state = dirstate.DirState.initialize('dirstate')
396
state.set_state_from_inventory(inv)
397
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
399
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
400
state._dirblock_state)
405
# This will unlock it
406
self.check_state_with_reopen(expected_result, state)
408
def test_set_path_id_no_parents(self):
409
"""The id of a path can be changed trivally with no parents."""
410
state = dirstate.DirState.initialize('dirstate')
412
# check precondition to be sure the state does change appropriately.
414
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
415
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
416
list(state._iter_entries()))
417
state.set_path_id('', 'foobarbaz')
419
(('', '', 'foobarbaz'), [('d', '', 0, False,
420
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
421
self.assertEqual(expected_rows, list(state._iter_entries()))
422
# should work across save too
426
state = dirstate.DirState.on_file('dirstate')
430
self.assertEqual(expected_rows, list(state._iter_entries()))
434
def test_set_path_id_with_parents(self):
435
"""Set the root file id in a dirstate with parents"""
436
mt = self.make_branch_and_tree('mt')
437
# may need to set the root when the default format is one where it's
439
mt.commit('foo', rev_id='parent-revid')
440
rt = mt.branch.repository.revision_tree('parent-revid')
441
state = dirstate.DirState.initialize('dirstate')
444
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
445
state.set_path_id('', 'foobarbaz')
446
# now see that it is what we expected
448
(('', '', 'TREE_ROOT'),
449
[('a', '', 0, False, ''),
450
('d', '', 0, False, 'parent-revid'),
452
(('', '', 'foobarbaz'),
453
[('d', '', 0, False, ''),
454
('a', '', 0, False, ''),
457
self.assertEqual(expected_rows, list(state._iter_entries()))
458
# should work across save too
462
# now flush & check we get the same
463
state = dirstate.DirState.on_file('dirstate')
466
self.assertEqual(expected_rows, list(state._iter_entries()))
470
def test_set_parent_trees_no_content(self):
471
# set_parent_trees is a slow but important api to support.
472
tree1 = self.make_branch_and_memory_tree('tree1')
476
revid1 = tree1.commit('foo')
479
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
480
tree2 = MemoryTree.create_on_branch(branch2)
483
revid2 = tree2.commit('foo')
484
root_id = tree2.inventory.root.file_id
487
state = dirstate.DirState.initialize('dirstate')
489
state.set_path_id('', root_id)
490
state.set_parent_trees(
491
((revid1, tree1.branch.repository.revision_tree(revid1)),
492
(revid2, tree2.branch.repository.revision_tree(revid2)),
493
('ghost-rev', None)),
495
# check we can reopen and use the dirstate after setting parent
502
state = dirstate.DirState.on_file('dirstate')
505
self.assertEqual([revid1, revid2, 'ghost-rev'],
506
state.get_parent_ids())
507
# iterating the entire state ensures that the state is parsable.
508
list(state._iter_entries())
509
# be sure that it sets not appends - change it
510
state.set_parent_trees(
511
((revid1, tree1.branch.repository.revision_tree(revid1)),
512
('ghost-rev', None)),
514
# and now put it back.
515
state.set_parent_trees(
516
((revid1, tree1.branch.repository.revision_tree(revid1)),
517
(revid2, tree2.branch.repository.revision_tree(revid2)),
518
('ghost-rev', tree2.branch.repository.revision_tree(None))),
520
self.assertEqual([revid1, revid2, 'ghost-rev'],
521
state.get_parent_ids())
522
# the ghost should be recorded as such by set_parent_trees.
523
self.assertEqual(['ghost-rev'], state.get_ghosts())
525
[(('', '', root_id), [
526
('d', '', 0, False, dirstate.DirState.NULLSTAT),
527
('d', '', 0, False, revid1),
528
('d', '', 0, False, revid2)
530
list(state._iter_entries()))
534
def test_set_parent_trees_file_missing_from_tree(self):
535
# Adding a parent tree may reference files not in the current state.
536
# they should get listed just once by id, even if they are in two
538
# set_parent_trees is a slow but important api to support.
539
tree1 = self.make_branch_and_memory_tree('tree1')
543
tree1.add(['a file'], ['file-id'], ['file'])
544
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
545
revid1 = tree1.commit('foo')
548
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
549
tree2 = MemoryTree.create_on_branch(branch2)
552
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
553
revid2 = tree2.commit('foo')
554
root_id = tree2.inventory.root.file_id
557
# check the layout in memory
558
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
559
(('', '', root_id), [
560
('d', '', 0, False, dirstate.DirState.NULLSTAT),
561
('d', '', 0, False, revid1.encode('utf8')),
562
('d', '', 0, False, revid2.encode('utf8'))
564
(('', 'a file', 'file-id'), [
565
('a', '', 0, False, ''),
566
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
567
revid1.encode('utf8')),
568
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
569
revid2.encode('utf8'))
572
state = dirstate.DirState.initialize('dirstate')
574
state.set_path_id('', root_id)
575
state.set_parent_trees(
576
((revid1, tree1.branch.repository.revision_tree(revid1)),
577
(revid2, tree2.branch.repository.revision_tree(revid2)),
583
# check_state_with_reopen will unlock
584
self.check_state_with_reopen(expected_result, state)
586
### add a path via _set_data - so we dont need delta work, just
587
# raw data in, and ensure that it comes out via get_lines happily.
589
def test_add_path_to_root_no_parents_all_data(self):
590
# The most trivial addition of a path 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 file'])
593
stat = os.lstat('a file')
594
# the 1*20 is the sha1 pretend value.
595
state = dirstate.DirState.initialize('dirstate')
597
(('', '', 'TREE_ROOT'), [
598
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
600
(('', 'a file', 'a file id'), [
601
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
605
state.add('a file', 'a file id', 'file', stat, '1'*20)
606
# having added it, it should be in the output of iter_entries.
607
self.assertEqual(expected_entries, list(state._iter_entries()))
608
# saving and reloading should not affect this.
612
state = dirstate.DirState.on_file('dirstate')
615
self.assertEqual(expected_entries, list(state._iter_entries()))
619
def test_add_path_to_unversioned_directory(self):
620
"""Adding a path to an unversioned directory should error.
622
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
623
once dirstate is stable and if it is merged with WorkingTree3, consider
624
removing this copy of the test.
626
self.build_tree(['unversioned/', 'unversioned/a file'])
627
state = dirstate.DirState.initialize('dirstate')
629
self.assertRaises(errors.NotVersionedError, state.add,
630
'unversioned/a file', 'a file id', 'file', None, None)
634
def test_add_directory_to_root_no_parents_all_data(self):
635
# The most trivial addition of a dir is when there are no parents and
636
# its in the root and all data about the file is supplied
637
self.build_tree(['a dir/'])
638
stat = os.lstat('a dir')
640
(('', '', 'TREE_ROOT'), [
641
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
643
(('', 'a dir', 'a dir id'), [
644
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
647
state = dirstate.DirState.initialize('dirstate')
649
state.add('a dir', 'a dir id', 'directory', stat, None)
650
# having added it, it should be in the output of iter_entries.
651
self.assertEqual(expected_entries, list(state._iter_entries()))
652
# saving and reloading should not affect this.
656
state = dirstate.DirState.on_file('dirstate')
660
self.assertEqual(expected_entries, list(state._iter_entries()))
664
def test_add_symlink_to_root_no_parents_all_data(self):
665
# The most trivial addition of a symlink when there are no parents and
666
# its in the root and all data about the file is supplied
667
## TODO: windows: dont fail this test. Also, how are symlinks meant to
668
# be represented on windows.
669
os.symlink('target', 'a link')
670
stat = os.lstat('a link')
672
(('', '', 'TREE_ROOT'), [
673
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
675
(('', 'a link', 'a link id'), [
676
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
679
state = dirstate.DirState.initialize('dirstate')
681
state.add('a link', 'a link id', 'symlink', stat, 'target')
682
# having added it, it should be in the output of iter_entries.
683
self.assertEqual(expected_entries, list(state._iter_entries()))
684
# saving and reloading should not affect this.
688
state = dirstate.DirState.on_file('dirstate')
691
self.assertEqual(expected_entries, list(state._iter_entries()))
695
def test_add_directory_and_child_no_parents_all_data(self):
696
# after adding a directory, we should be able to add children to it.
697
self.build_tree(['a dir/', 'a dir/a file'])
698
dirstat = os.lstat('a dir')
699
filestat = os.lstat('a dir/a file')
701
(('', '', 'TREE_ROOT'), [
702
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
704
(('', 'a dir', 'a dir id'), [
705
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
707
(('a dir', 'a file', 'a file id'), [
708
('f', '1'*20, 25, False,
709
dirstate.pack_stat(filestat)), # current tree details
712
state = dirstate.DirState.initialize('dirstate')
714
state.add('a dir', 'a dir id', 'directory', dirstat, None)
715
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
716
# added it, it should be in the output of iter_entries.
717
self.assertEqual(expected_entries, list(state._iter_entries()))
718
# saving and reloading should not affect this.
722
state = dirstate.DirState.on_file('dirstate')
725
self.assertEqual(expected_entries, list(state._iter_entries()))
730
class TestGetLines(TestCaseWithDirState):
732
def test_get_line_with_2_rows(self):
733
state = self.create_dirstate_with_root_and_subdir()
735
self.assertEqual(['#bazaar dirstate flat format 3\n',
736
'adler32: -1327947603\n',
740
'\x00\x00a-root-value\x00'
741
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
742
'\x00subdir\x00subdir-id\x00'
743
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
744
], state.get_lines())
748
def test_entry_to_line(self):
749
state = self.create_dirstate_with_root()
752
'\x00\x00a-root-value\x00d\x00\x000\x00n'
753
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
754
state._entry_to_line(state._dirblocks[0][1][0]))
758
def test_entry_to_line_with_parent(self):
759
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
760
root_entry = ('', '', 'a-root-value'), [
761
('d', '', 0, False, packed_stat), # current tree details
762
# first: a pointer to the current location
763
('a', 'dirname/basename', 0, False, ''),
765
state = dirstate.DirState.initialize('dirstate')
768
'\x00\x00a-root-value\x00'
769
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
770
'a\x00dirname/basename\x000\x00n\x00',
771
state._entry_to_line(root_entry))
775
def test_entry_to_line_with_two_parents_at_different_paths(self):
776
# / in the tree, at / in one parent and /dirname/basename in the other.
777
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
778
root_entry = ('', '', 'a-root-value'), [
779
('d', '', 0, False, packed_stat), # current tree details
780
('d', '', 0, False, 'rev_id'), # first parent details
781
# second: a pointer to the current location
782
('a', 'dirname/basename', 0, False, ''),
784
state = dirstate.DirState.initialize('dirstate')
787
'\x00\x00a-root-value\x00'
788
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
789
'd\x00\x000\x00n\x00rev_id\x00'
790
'a\x00dirname/basename\x000\x00n\x00',
791
state._entry_to_line(root_entry))
795
def test_iter_entries(self):
796
# we should be able to iterate the dirstate entries from end to end
797
# this is for get_lines to be easy to read.
798
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
800
root_entries = [(('', '', 'a-root-value'), [
801
('d', '', 0, False, packed_stat), # current tree details
803
dirblocks.append(('', root_entries))
804
# add two files in the root
805
subdir_entry = ('', 'subdir', 'subdir-id'), [
806
('d', '', 0, False, packed_stat), # current tree details
808
afile_entry = ('', 'afile', 'afile-id'), [
809
('f', 'sha1value', 34, False, packed_stat), # current tree details
811
dirblocks.append(('', [subdir_entry, afile_entry]))
813
file_entry2 = ('subdir', '2file', '2file-id'), [
814
('f', 'sha1value', 23, False, packed_stat), # current tree details
816
dirblocks.append(('subdir', [file_entry2]))
817
state = dirstate.DirState.initialize('dirstate')
819
state._set_data([], dirblocks)
820
expected_entries = [root_entries[0], subdir_entry, afile_entry,
822
self.assertEqual(expected_entries, list(state._iter_entries()))
827
class TestGetBlockRowIndex(TestCaseWithDirState):
829
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
830
file_present, state, dirname, basename, tree_index):
831
self.assertEqual((block_index, row_index, dir_present, file_present),
832
state._get_block_entry_index(dirname, basename, tree_index))
834
block = state._dirblocks[block_index]
835
self.assertEqual(dirname, block[0])
836
if dir_present and file_present:
837
row = state._dirblocks[block_index][1][row_index]
838
self.assertEqual(dirname, row[0][0])
839
self.assertEqual(basename, row[0][1])
841
def test_simple_structure(self):
842
state = self.create_dirstate_with_root_and_subdir()
843
self.addCleanup(state.unlock)
844
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
845
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
846
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
847
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
848
self.assertBlockRowIndexEqual(2, 0, False, False, state,
851
def test_complex_structure_exists(self):
852
state = self.create_complex_dirstate()
853
self.addCleanup(state.unlock)
854
# Make sure we can find everything that exists
855
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
856
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
857
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
858
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
859
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
860
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
861
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
862
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
863
self.assertBlockRowIndexEqual(3, 1, True, True, state,
866
def test_complex_structure_missing(self):
867
state = self.create_complex_dirstate()
868
self.addCleanup(state.unlock)
869
# Make sure things would be inserted in the right locations
870
# '_' comes before 'a'
871
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
872
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
873
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
874
self.assertBlockRowIndexEqual(1, 4, True, False, state,
876
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
877
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
878
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
879
# This would be inserted between a/ and b/
880
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
882
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
885
class TestGetEntry(TestCaseWithDirState):
887
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
888
"""Check that the right entry is returned for a request to getEntry."""
889
entry = state._get_entry(index, path_utf8=path)
891
self.assertEqual((None, None), entry)
894
self.assertEqual((dirname, basename, file_id), cur[:3])
896
def test_simple_structure(self):
897
state = self.create_dirstate_with_root_and_subdir()
898
self.addCleanup(state.unlock)
899
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
900
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
901
self.assertEntryEqual(None, None, None, state, 'missing', 0)
902
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
903
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
905
def test_complex_structure_exists(self):
906
state = self.create_complex_dirstate()
907
self.addCleanup(state.unlock)
908
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
909
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
910
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
911
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
912
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
913
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
914
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
915
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
916
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
919
def test_complex_structure_missing(self):
920
state = self.create_complex_dirstate()
921
self.addCleanup(state.unlock)
922
self.assertEntryEqual(None, None, None, state, '_', 0)
923
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
924
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
925
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
927
def test_get_entry_uninitialized(self):
928
"""Calling get_entry will load data if it needs to"""
929
state = self.create_dirstate_with_root()
935
state = dirstate.DirState.on_file('dirstate')
938
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
940
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
941
state._dirblock_state)
942
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
947
class TestDirstateSortOrder(TestCaseWithTransport):
948
"""Test that DirState adds entries in the right order."""
950
def test_add_sorting(self):
951
"""Add entries in lexicographical order, we get path sorted order.
953
This tests it to a depth of 4, to make sure we don't just get it right
954
at a single depth. 'a/a' should come before 'a-a', even though it
955
doesn't lexicographically.
957
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
958
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
961
state = dirstate.DirState.initialize('dirstate')
962
self.addCleanup(state.unlock)
964
fake_stat = os.stat('dirstate')
966
d_id = d.replace('/', '_')+'-id'
968
file_id = file_path.replace('/', '_')+'-id'
969
state.add(d, d_id, 'directory', fake_stat, null_sha)
970
state.add(file_path, file_id, 'file', fake_stat, null_sha)
972
expected = ['', '', 'a',
973
'a/a', 'a/a/a', 'a/a/a/a',
974
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
976
split = lambda p:p.split('/')
977
self.assertEqual(sorted(expected, key=split), expected)
978
dirblock_names = [d[0] for d in state._dirblocks]
979
self.assertEqual(expected, dirblock_names)
981
def test_set_parent_trees_correct_order(self):
982
"""After calling set_parent_trees() we should maintain the order."""
983
dirs = ['a', 'a-a', 'a/a']
985
state = dirstate.DirState.initialize('dirstate')
986
self.addCleanup(state.unlock)
988
fake_stat = os.stat('dirstate')
990
d_id = d.replace('/', '_')+'-id'
992
file_id = file_path.replace('/', '_')+'-id'
993
state.add(d, d_id, 'directory', fake_stat, null_sha)
994
state.add(file_path, file_id, 'file', fake_stat, null_sha)
996
expected = ['', '', 'a', 'a/a', 'a-a']
997
dirblock_names = [d[0] for d in state._dirblocks]
998
self.assertEqual(expected, dirblock_names)
1000
# *really* cheesy way to just get an empty tree
1001
repo = self.make_repository('repo')
1002
empty_tree = repo.revision_tree(None)
1003
state.set_parent_trees([('null:', empty_tree)], [])
1005
dirblock_names = [d[0] for d in state._dirblocks]
1006
self.assertEqual(expected, dirblock_names)
1009
class InstrumentedDirState(dirstate.DirState):
1010
"""An DirState with instrumented sha1 functionality."""
1012
def __init__(self, path):
1013
super(InstrumentedDirState, self).__init__(path)
1014
self._time_offset = 0
1017
def _sha_cutoff_time(self):
1018
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1019
self._cutoff_time = timestamp + self._time_offset
1021
def _sha1_file(self, abspath, entry):
1022
self._log.append(('sha1', abspath))
1023
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1025
def _read_link(self, abspath, old_link):
1026
self._log.append(('read_link', abspath, old_link))
1027
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1029
def _lstat(self, abspath, entry):
1030
self._log.append(('lstat', abspath))
1031
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1033
def _is_executable(self, mode, old_executable):
1034
self._log.append(('is_exec', mode, old_executable))
1035
return super(InstrumentedDirState, self)._is_executable(mode,
1038
def adjust_time(self, secs):
1039
"""Move the clock forward or back.
1041
:param secs: The amount to adjust the clock by. Positive values make it
1042
seem as if we are in the future, negative values make it seem like we
1045
self._time_offset += secs
1046
self._cutoff_time = None
1049
class _FakeStat(object):
1050
"""A class with the same attributes as a real stat result."""
1052
def __init__(self, size, mtime, ctime, dev, ino, mode):
1054
self.st_mtime = mtime
1055
self.st_ctime = ctime
1061
class TestUpdateEntry(TestCaseWithDirState):
1062
"""Test the DirState.update_entry functions"""
1064
def get_state_with_a(self):
1065
"""Create a DirState tracking a single object named 'a'"""
1066
state = InstrumentedDirState.initialize('dirstate')
1067
self.addCleanup(state.unlock)
1068
state.add('a', 'a-id', 'file', None, '')
1069
entry = state._get_entry(0, path_utf8='a')
1072
def test_update_entry(self):
1073
state, entry = self.get_state_with_a()
1074
self.build_tree(['a'])
1075
# Add one where we don't provide the stat or sha already
1076
self.assertEqual(('', 'a', 'a-id'), entry[0])
1077
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1079
# Flush the buffers to disk
1081
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1082
state._dirblock_state)
1084
stat_value = os.lstat('a')
1085
packed_stat = dirstate.pack_stat(stat_value)
1086
link_or_sha1 = state.update_entry(entry, abspath='a',
1087
stat_value=stat_value)
1088
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1091
# The dirblock entry should be updated with the new info
1092
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1094
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1095
state._dirblock_state)
1096
mode = stat_value.st_mode
1097
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1100
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1101
state._dirblock_state)
1103
# If we do it again right away, we don't know if the file has changed
1104
# so we will re-read the file. Roll the clock back so the file is
1105
# guaranteed to look too new.
1106
state.adjust_time(-10)
1108
link_or_sha1 = state.update_entry(entry, abspath='a',
1109
stat_value=stat_value)
1110
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1111
('sha1', 'a'), ('is_exec', mode, False),
1113
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1115
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1116
state._dirblock_state)
1119
# However, if we move the clock forward so the file is considered
1120
# "stable", it should just returned the cached value.
1121
state.adjust_time(20)
1122
link_or_sha1 = state.update_entry(entry, abspath='a',
1123
stat_value=stat_value)
1124
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1126
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1127
('sha1', 'a'), ('is_exec', mode, False),
1130
def test_update_entry_no_stat_value(self):
1131
"""Passing the stat_value is optional."""
1132
state, entry = self.get_state_with_a()
1133
state.adjust_time(-10) # Make sure the file looks new
1134
self.build_tree(['a'])
1135
# Add one where we don't provide the stat or sha already
1136
link_or_sha1 = state.update_entry(entry, abspath='a')
1137
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1139
stat_value = os.lstat('a')
1140
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1141
('is_exec', stat_value.st_mode, False),
1144
def test_update_entry_symlink(self):
1145
"""Update entry should read symlinks."""
1146
if not osutils.has_symlinks():
1147
return # PlatformDeficiency / TestSkipped
1148
state, entry = self.get_state_with_a()
1150
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1151
state._dirblock_state)
1152
os.symlink('target', 'a')
1154
state.adjust_time(-10) # Make the symlink look new
1155
stat_value = os.lstat('a')
1156
packed_stat = dirstate.pack_stat(stat_value)
1157
link_or_sha1 = state.update_entry(entry, abspath='a',
1158
stat_value=stat_value)
1159
self.assertEqual('target', link_or_sha1)
1160
self.assertEqual([('read_link', 'a', '')], state._log)
1161
# Dirblock is updated
1162
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1164
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1165
state._dirblock_state)
1167
# Because the stat_value looks new, we should re-read the target
1168
link_or_sha1 = state.update_entry(entry, abspath='a',
1169
stat_value=stat_value)
1170
self.assertEqual('target', link_or_sha1)
1171
self.assertEqual([('read_link', 'a', ''),
1172
('read_link', 'a', 'target'),
1174
state.adjust_time(+20) # Skip into the future, all files look old
1175
link_or_sha1 = state.update_entry(entry, abspath='a',
1176
stat_value=stat_value)
1177
self.assertEqual('target', link_or_sha1)
1178
# There should not be a new read_link call.
1179
# (this is a weak assertion, because read_link is fairly inexpensive,
1180
# versus the number of symlinks that we would have)
1181
self.assertEqual([('read_link', 'a', ''),
1182
('read_link', 'a', 'target'),
1185
def test_update_entry_dir(self):
1186
state, entry = self.get_state_with_a()
1187
self.build_tree(['a/'])
1188
self.assertIs(None, state.update_entry(entry, 'a'))
1190
def create_and_test_file(self, state, entry):
1191
"""Create a file at 'a' and verify the state finds it.
1193
The state should already be versioning *something* at 'a'. This makes
1194
sure that state.update_entry recognizes it as a file.
1196
self.build_tree(['a'])
1197
stat_value = os.lstat('a')
1198
packed_stat = dirstate.pack_stat(stat_value)
1200
link_or_sha1 = state.update_entry(entry, abspath='a')
1201
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1203
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1207
def create_and_test_dir(self, state, entry):
1208
"""Create a directory at 'a' and verify the state finds it.
1210
The state should already be versioning *something* at 'a'. This makes
1211
sure that state.update_entry recognizes it as a directory.
1213
self.build_tree(['a/'])
1214
stat_value = os.lstat('a')
1215
packed_stat = dirstate.pack_stat(stat_value)
1217
link_or_sha1 = state.update_entry(entry, abspath='a')
1218
self.assertIs(None, link_or_sha1)
1219
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1223
def create_and_test_symlink(self, state, entry):
1224
"""Create a symlink at 'a' and verify the state finds it.
1226
The state should already be versioning *something* at 'a'. This makes
1227
sure that state.update_entry recognizes it as a symlink.
1229
This should not be called if this platform does not have symlink
1232
os.symlink('path/to/foo', 'a')
1234
stat_value = os.lstat('a')
1235
packed_stat = dirstate.pack_stat(stat_value)
1237
link_or_sha1 = state.update_entry(entry, abspath='a')
1238
self.assertEqual('path/to/foo', link_or_sha1)
1239
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1243
def test_update_missing_file(self):
1244
state, entry = self.get_state_with_a()
1245
packed_stat = self.create_and_test_file(state, entry)
1246
# Now if we delete the file, update_entry should recover and
1249
self.assertIs(None, state.update_entry(entry, abspath='a'))
1250
# And the record shouldn't be changed.
1251
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1252
self.assertEqual([('f', digest, 14, False, packed_stat)],
1255
def test_update_missing_dir(self):
1256
state, entry = self.get_state_with_a()
1257
packed_stat = self.create_and_test_dir(state, entry)
1258
# Now if we delete the directory, update_entry should recover and
1261
self.assertIs(None, state.update_entry(entry, abspath='a'))
1262
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1264
def test_update_missing_symlink(self):
1265
if not osutils.has_symlinks():
1266
return # PlatformDeficiency / TestSkipped
1267
state, entry = self.get_state_with_a()
1268
packed_stat = self.create_and_test_symlink(state, entry)
1270
self.assertIs(None, state.update_entry(entry, abspath='a'))
1271
# And the record shouldn't be changed.
1272
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1275
def test_update_file_to_dir(self):
1276
"""If a file changes to a directory we return None for the sha.
1277
We also update the inventory record.
1279
state, entry = self.get_state_with_a()
1280
self.create_and_test_file(state, entry)
1282
self.create_and_test_dir(state, entry)
1284
def test_update_file_to_symlink(self):
1285
"""File becomes a symlink"""
1286
if not osutils.has_symlinks():
1287
return # PlatformDeficiency / TestSkipped
1288
state, entry = self.get_state_with_a()
1289
self.create_and_test_file(state, entry)
1291
self.create_and_test_symlink(state, entry)
1293
def test_update_dir_to_file(self):
1294
"""Directory becoming a file updates the entry."""
1295
state, entry = self.get_state_with_a()
1296
self.create_and_test_dir(state, entry)
1298
self.create_and_test_file(state, entry)
1300
def test_update_dir_to_symlink(self):
1301
"""Directory becomes a symlink"""
1302
if not osutils.has_symlinks():
1303
return # PlatformDeficiency / TestSkipped
1304
state, entry = self.get_state_with_a()
1305
self.create_and_test_dir(state, entry)
1307
self.create_and_test_symlink(state, entry)
1309
def test_update_symlink_to_file(self):
1310
"""Symlink becomes a file"""
1311
state, entry = self.get_state_with_a()
1312
self.create_and_test_symlink(state, entry)
1314
self.create_and_test_file(state, entry)
1316
def test_update_symlink_to_dir(self):
1317
"""Symlink becomes a directory"""
1318
state, entry = self.get_state_with_a()
1319
self.create_and_test_symlink(state, entry)
1321
self.create_and_test_dir(state, entry)
1323
def test__is_executable_win32(self):
1324
state, entry = self.get_state_with_a()
1325
self.build_tree(['a'])
1327
# Make sure we are using the win32 implementation of _is_executable
1328
state._is_executable = state._is_executable_win32
1330
# The file on disk is not executable, but we are marking it as though
1331
# it is. With _is_executable_win32 we ignore what is on disk.
1332
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1334
stat_value = os.lstat('a')
1335
packed_stat = dirstate.pack_stat(stat_value)
1337
state.adjust_time(-10) # Make sure everything is new
1338
# Make sure it wants to kkkkkkkk
1339
state.update_entry(entry, abspath='a', stat_value=stat_value)
1341
# The row is updated, but the executable bit stays set.
1342
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1343
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1346
class TestPackStat(TestCaseWithTransport):
1348
def assertPackStat(self, expected, stat_value):
1349
"""Check the packed and serialized form of a stat value."""
1350
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1352
def test_pack_stat_int(self):
1353
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1354
# Make sure that all parameters have an impact on the packed stat.
1355
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1358
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1359
st.st_mtime = 1172758620
1361
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1362
st.st_ctime = 1172758630
1364
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1367
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1368
st.st_ino = 6499540L
1370
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1371
st.st_mode = 0100744
1373
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1375
def test_pack_stat_float(self):
1376
"""On some platforms mtime and ctime are floats.
1378
Make sure we don't get warnings or errors, and that we ignore changes <
1381
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1382
777L, 6499538L, 0100644)
1383
# These should all be the same as the integer counterparts
1384
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1385
st.st_mtime = 1172758620.0
1387
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1388
st.st_ctime = 1172758630.0
1390
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1391
# fractional seconds are discarded, so no change from above
1392
st.st_mtime = 1172758620.453
1393
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1394
st.st_ctime = 1172758630.228
1395
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1398
class TestBisect(TestCaseWithTransport):
1399
"""Test the ability to bisect into the disk format."""
1401
def create_basic_dirstate(self):
1402
"""Create a dirstate with a few files and directories.
1411
tree = self.make_branch_and_tree('tree')
1412
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1413
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1414
self.build_tree(['tree/' + p for p in paths])
1415
tree.set_root_id('TREE_ROOT')
1416
tree.add([p.rstrip('/') for p in paths], file_ids)
1417
tree.commit('initial', rev_id='rev-1')
1418
revision_id = 'rev-1'
1419
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1420
t = self.get_transport().clone('tree')
1421
a_text = t.get_bytes('a')
1422
a_sha = osutils.sha_string(a_text)
1424
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1425
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1426
c_text = t.get_bytes('b/c')
1427
c_sha = osutils.sha_string(c_text)
1429
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1430
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1431
e_text = t.get_bytes('b/d/e')
1432
e_sha = osutils.sha_string(e_text)
1434
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1435
f_text = t.get_bytes('f')
1436
f_sha = osutils.sha_string(f_text)
1438
null_stat = dirstate.DirState.NULLSTAT
1440
'':(('', '', 'TREE_ROOT'), [
1441
('d', '', 0, False, null_stat),
1442
('d', '', 0, False, revision_id),
1444
'a':(('', 'a', 'a-id'), [
1445
('f', '', 0, False, null_stat),
1446
('f', a_sha, a_len, False, revision_id),
1448
'b':(('', 'b', 'b-id'), [
1449
('d', '', 0, False, null_stat),
1450
('d', '', 0, False, revision_id),
1452
'b/c':(('b', 'c', 'c-id'), [
1453
('f', '', 0, False, null_stat),
1454
('f', c_sha, c_len, False, revision_id),
1456
'b/d':(('b', 'd', 'd-id'), [
1457
('d', '', 0, False, null_stat),
1458
('d', '', 0, False, revision_id),
1460
'b/d/e':(('b/d', 'e', 'e-id'), [
1461
('f', '', 0, False, null_stat),
1462
('f', e_sha, e_len, False, revision_id),
1464
'f':(('', 'f', 'f-id'), [
1465
('f', '', 0, False, null_stat),
1466
('f', f_sha, f_len, False, revision_id),
1469
state = dirstate.DirState.from_tree(tree, 'dirstate')
1474
# Use a different object, to make sure nothing is pre-cached in memory.
1475
state = dirstate.DirState.on_file('dirstate')
1477
self.addCleanup(state.unlock)
1478
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1479
state._dirblock_state)
1480
# This is code is only really tested if we actually have to make more
1481
# than one read, so set the page size to something smaller.
1482
# We want it to contain about 2.2 records, so that we have a couple
1483
# records that we can read per attempt
1484
state._bisect_page_size = 200
1485
return tree, state, expected
1487
def create_duplicated_dirstate(self):
1488
"""Create a dirstate with a deleted and added entries.
1490
This grabs a basic_dirstate, and then removes and re adds every entry
1493
tree, state, expected = self.create_basic_dirstate()
1494
# Now we will just remove and add every file so we get an extra entry
1495
# per entry. Unversion in reverse order so we handle subdirs
1496
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1497
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1498
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1500
# Update the expected dictionary.
1501
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1502
orig = expected[path]
1504
# This record was deleted in the current tree
1505
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1507
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1508
# And didn't exist in the basis tree
1509
expected[path2] = (new_key, [orig[1][0],
1510
dirstate.DirState.NULL_PARENT_DETAILS])
1512
# We will replace the 'dirstate' file underneath 'state', but that is
1513
# okay as lock as we unlock 'state' first.
1516
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1522
# But we need to leave state in a read-lock because we already have
1523
# a cleanup scheduled
1525
return tree, state, expected
1527
def create_renamed_dirstate(self):
1528
"""Create a dirstate with a few internal renames.
1530
This takes the basic dirstate, and moves the paths around.
1532
tree, state, expected = self.create_basic_dirstate()
1534
tree.rename_one('a', 'b/g')
1536
tree.rename_one('b/d', 'h')
1538
old_a = expected['a']
1539
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1540
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1541
('r', 'a', 0, False, '')])
1542
old_d = expected['b/d']
1543
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1544
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1545
('r', 'b/d', 0, False, '')])
1547
old_e = expected['b/d/e']
1548
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1550
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1551
('r', 'b/d/e', 0, False, '')])
1555
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1562
return tree, state, expected
1564
def assertBisect(self, expected_map, map_keys, state, paths):
1565
"""Assert that bisecting for paths returns the right result.
1567
:param expected_map: A map from key => entry value
1568
:param map_keys: The keys to expect for each path
1569
:param state: The DirState object.
1570
:param paths: A list of paths, these will automatically be split into
1571
(dir, name) tuples, and sorted according to how _bisect
1574
dir_names = sorted(osutils.split(p) for p in paths)
1575
result = state._bisect(dir_names)
1576
# For now, results are just returned in whatever order we read them.
1577
# We could sort by (dir, name, file_id) or something like that, but in
1578
# the end it would still be fairly arbitrary, and we don't want the
1579
# extra overhead if we can avoid it. So sort everything to make sure
1581
assert len(map_keys) == len(dir_names)
1583
for dir_name, keys in zip(dir_names, map_keys):
1585
# This should not be present in the output
1587
expected[dir_name] = sorted(expected_map[k] for k in keys)
1589
for dir_name in result:
1590
result[dir_name].sort()
1592
self.assertEqual(expected, result)
1594
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1595
"""Assert that bisecting for dirbblocks returns the right result.
1597
:param expected_map: A map from key => expected values
1598
:param map_keys: A nested list of paths we expect to be returned.
1599
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1600
:param state: The DirState object.
1601
:param paths: A list of directories
1603
result = state._bisect_dirblocks(paths)
1604
assert len(map_keys) == len(paths)
1607
for path, keys in zip(paths, map_keys):
1609
# This should not be present in the output
1611
expected[path] = sorted(expected_map[k] for k in keys)
1615
self.assertEqual(expected, result)
1617
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1618
"""Assert the return value of a recursive bisection.
1620
:param expected_map: A map from key => entry value
1621
:param map_keys: A list of paths we expect to be returned.
1622
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1623
:param state: The DirState object.
1624
:param paths: A list of files and directories. It will be broken up
1625
into (dir, name) pairs and sorted before calling _bisect_recursive.
1628
for key in map_keys:
1629
entry = expected_map[key]
1630
dir_name_id, trees_info = entry
1631
expected[dir_name_id] = trees_info
1633
dir_names = sorted(osutils.split(p) for p in paths)
1634
result = state._bisect_recursive(dir_names)
1636
self.assertEqual(expected, result)
1638
def test_bisect_each(self):
1639
"""Find a single record using bisect."""
1640
tree, state, expected = self.create_basic_dirstate()
1642
# Bisect should return the rows for the specified files.
1643
self.assertBisect(expected, [['']], state, [''])
1644
self.assertBisect(expected, [['a']], state, ['a'])
1645
self.assertBisect(expected, [['b']], state, ['b'])
1646
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1647
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1648
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1649
self.assertBisect(expected, [['f']], state, ['f'])
1651
def test_bisect_multi(self):
1652
"""Bisect can be used to find multiple records at the same time."""
1653
tree, state, expected = self.create_basic_dirstate()
1654
# Bisect should be capable of finding multiple entries at the same time
1655
self.assertBisect(expected, [['a'], ['b'], ['f']],
1656
state, ['a', 'b', 'f'])
1657
# ('', 'f') sorts before the others
1658
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1659
state, ['b/d', 'b/d/e', 'f'])
1661
def test_bisect_one_page(self):
1662
"""Test bisect when there is only 1 page to read"""
1663
tree, state, expected = self.create_basic_dirstate()
1664
state._bisect_page_size = 5000
1665
self.assertBisect(expected,[['']], state, [''])
1666
self.assertBisect(expected,[['a']], state, ['a'])
1667
self.assertBisect(expected,[['b']], state, ['b'])
1668
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1669
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1670
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1671
self.assertBisect(expected,[['f']], state, ['f'])
1672
self.assertBisect(expected,[['a'], ['b'], ['f']],
1673
state, ['a', 'b', 'f'])
1674
# ('', 'f') sorts before the others
1675
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1676
state, ['b/d', 'b/d/e', 'f'])
1678
def test_bisect_duplicate_paths(self):
1679
"""When bisecting for a path, handle multiple entries."""
1680
tree, state, expected = self.create_duplicated_dirstate()
1682
# Now make sure that both records are properly returned.
1683
self.assertBisect(expected, [['']], state, [''])
1684
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1685
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1686
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1687
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1688
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1690
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1692
def test_bisect_page_size_too_small(self):
1693
"""If the page size is too small, we will auto increase it."""
1694
tree, state, expected = self.create_basic_dirstate()
1695
state._bisect_page_size = 50
1696
self.assertBisect(expected, [None], state, ['b/e'])
1697
self.assertBisect(expected, [['a']], state, ['a'])
1698
self.assertBisect(expected, [['b']], state, ['b'])
1699
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1700
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1701
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1702
self.assertBisect(expected, [['f']], state, ['f'])
1704
def test_bisect_missing(self):
1705
"""Test that bisect return None if it cannot find a path."""
1706
tree, state, expected = self.create_basic_dirstate()
1707
self.assertBisect(expected, [None], state, ['foo'])
1708
self.assertBisect(expected, [None], state, ['b/foo'])
1709
self.assertBisect(expected, [None], state, ['bar/foo'])
1711
self.assertBisect(expected, [['a'], None, ['b/d']],
1712
state, ['a', 'foo', 'b/d'])
1714
def test_bisect_rename(self):
1715
"""Check that we find a renamed row."""
1716
tree, state, expected = self.create_renamed_dirstate()
1718
# Search for the pre and post renamed entries
1719
self.assertBisect(expected, [['a']], state, ['a'])
1720
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1721
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1722
self.assertBisect(expected, [['h']], state, ['h'])
1724
# What about b/d/e? shouldn't that also get 2 directory entries?
1725
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1726
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1728
def test_bisect_dirblocks(self):
1729
tree, state, expected = self.create_duplicated_dirstate()
1730
self.assertBisectDirBlocks(expected,
1731
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1732
self.assertBisectDirBlocks(expected,
1733
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1734
self.assertBisectDirBlocks(expected,
1735
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1736
self.assertBisectDirBlocks(expected,
1737
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1738
['b/c', 'b/c2', 'b/d', 'b/d2'],
1739
['b/d/e', 'b/d/e2'],
1740
], state, ['', 'b', 'b/d'])
1742
def test_bisect_dirblocks_missing(self):
1743
tree, state, expected = self.create_basic_dirstate()
1744
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1745
state, ['b/d', 'b/e'])
1746
# Files don't show up in this search
1747
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1748
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1749
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1750
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1751
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1753
def test_bisect_recursive_each(self):
1754
tree, state, expected = self.create_basic_dirstate()
1755
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1756
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1757
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1758
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1760
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1762
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1766
def test_bisect_recursive_multiple(self):
1767
tree, state, expected = self.create_basic_dirstate()
1768
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1769
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1770
state, ['b/d', 'b/d/e'])
1772
def test_bisect_recursive_missing(self):
1773
tree, state, expected = self.create_basic_dirstate()
1774
self.assertBisectRecursive(expected, [], state, ['d'])
1775
self.assertBisectRecursive(expected, [], state, ['b/e'])
1776
self.assertBisectRecursive(expected, [], state, ['g'])
1777
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1779
def test_bisect_recursive_renamed(self):
1780
tree, state, expected = self.create_renamed_dirstate()
1782
# Looking for either renamed item should find the other
1783
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1784
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1785
# Looking in the containing directory should find the rename target,
1786
# and anything in a subdir of the renamed target.
1787
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1788
'b/d/e', 'b/g', 'h', 'h/e'],
1792
class TestBisectDirblock(TestCase):
1793
"""Test that bisect_dirblock() returns the expected values.
1795
bisect_dirblock is intended to work like bisect.bisect_left() except it
1796
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1797
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1800
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1801
"""Assert that bisect_split works like bisect_left on the split paths.
1803
:param dirblocks: A list of (path, [info]) pairs.
1804
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1805
:param path: The path we are indexing.
1807
All other arguments will be passed along.
1809
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1811
split_dirblock = (path.split('/'), [])
1812
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1814
self.assertEqual(bisect_left_idx, bisect_split_idx,
1815
'bisect_split disagreed. %s != %s'
1817
% (bisect_left_idx, bisect_split_idx, path)
1820
def paths_to_dirblocks(self, paths):
1821
"""Convert a list of paths into dirblock form.
1823
Also, ensure that the paths are in proper sorted order.
1825
dirblocks = [(path, []) for path in paths]
1826
split_dirblocks = [(path.split('/'), []) for path in paths]
1827
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1828
return dirblocks, split_dirblocks
1830
def test_simple(self):
1831
"""In the simple case it works just like bisect_left"""
1832
paths = ['', 'a', 'b', 'c', 'd']
1833
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1835
self.assertBisect(dirblocks, split_dirblocks, path)
1836
self.assertBisect(dirblocks, split_dirblocks, '_')
1837
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1838
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1839
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1840
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1841
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1842
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1843
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1844
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1846
def test_involved(self):
1847
"""This is where bisect_left diverges slightly."""
1849
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1850
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1852
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1853
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1856
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1858
self.assertBisect(dirblocks, split_dirblocks, path)
1860
def test_involved_cached(self):
1861
"""This is where bisect_left diverges slightly."""
1863
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1864
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1866
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1867
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1871
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1873
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)