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.osutils import has_symlinks
30
from bzrlib.tests import (
32
TestCaseWithTransport,
39
# general checks for NOT_IN_MEMORY error conditions.
40
# set_path_id on a NOT_IN_MEMORY dirstate
41
# set_path_id unicode support
42
# set_path_id setting id of a path not root
43
# set_path_id setting id when there are parents without the id in the parents
44
# set_path_id setting id when there are parents with the id in the parents
45
# set_path_id setting id when state is not in memory
46
# set_path_id setting id when state is in memory unmodified
47
# set_path_id setting id when state is in memory modified
50
class TestCaseWithDirState(TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
def create_empty_dirstate(self):
54
"""Return a locked but empty dirstate"""
55
state = dirstate.DirState.initialize('dirstate')
58
def create_dirstate_with_root(self):
59
"""Return a write-locked state with a single root entry."""
60
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
61
root_entry_direntry = ('', '', 'a-root-value'), [
62
('d', '', 0, False, packed_stat),
65
dirblocks.append(('', [root_entry_direntry]))
66
dirblocks.append(('', []))
67
state = self.create_empty_dirstate()
69
state._set_data([], dirblocks)
76
def create_dirstate_with_root_and_subdir(self):
77
"""Return a locked DirState with a root and a subdir"""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
subdir_entry = ('', 'subdir', 'subdir-id'), [
80
('d', '', 0, False, packed_stat),
82
state = self.create_dirstate_with_root()
84
dirblocks = list(state._dirblocks)
85
dirblocks[1][1].append(subdir_entry)
86
state._set_data([], dirblocks)
92
def create_complex_dirstate(self):
93
"""This dirstate contains multiple files and directories.
103
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
105
Notice that a/e is an empty directory.
107
:return: The dirstate, still write-locked.
109
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
111
root_entry = ('', '', 'a-root-value'), [
112
('d', '', 0, False, packed_stat),
114
a_entry = ('', 'a', 'a-dir'), [
115
('d', '', 0, False, packed_stat),
117
b_entry = ('', 'b', 'b-dir'), [
118
('d', '', 0, False, packed_stat),
120
c_entry = ('', 'c', 'c-file'), [
121
('f', null_sha, 10, False, packed_stat),
123
d_entry = ('', 'd', 'd-file'), [
124
('f', null_sha, 20, False, packed_stat),
126
e_entry = ('a', 'e', 'e-dir'), [
127
('d', '', 0, False, packed_stat),
129
f_entry = ('a', 'f', 'f-file'), [
130
('f', null_sha, 30, False, packed_stat),
132
g_entry = ('b', 'g', 'g-file'), [
133
('f', null_sha, 30, False, packed_stat),
135
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
136
('f', null_sha, 40, False, packed_stat),
139
dirblocks.append(('', [root_entry]))
140
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
141
dirblocks.append(('a', [e_entry, f_entry]))
142
dirblocks.append(('b', [g_entry, h_entry]))
143
state = dirstate.DirState.initialize('dirstate')
146
state._set_data([], dirblocks)
152
def check_state_with_reopen(self, expected_result, state):
153
"""Check that state has current state expected_result.
155
This will check the current state, open the file anew and check it
157
This function expects the current state to be locked for writing, and
158
will unlock it before re-opening.
159
This is required because we can't open a lock_read() while something
160
else has a lock_write().
161
write => mutually exclusive lock
164
# The state should already be write locked, since we just had to do
165
# some operation to get here.
166
assert state._lock_token is not None
168
self.assertEqual(expected_result[0], state.get_parent_ids())
169
# there should be no ghosts in this tree.
170
self.assertEqual([], state.get_ghosts())
171
# there should be one fileid in this tree - the root of the tree.
172
self.assertEqual(expected_result[1], list(state._iter_entries()))
177
state = dirstate.DirState.on_file('dirstate')
180
self.assertEqual(expected_result[1], list(state._iter_entries()))
184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
194
tree = self.make_branch_and_tree('tree')
195
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
196
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
197
self.build_tree(['tree/' + p for p in paths])
198
tree.set_root_id('TREE_ROOT')
199
tree.add([p.rstrip('/') for p in paths], file_ids)
200
tree.commit('initial', rev_id='rev-1')
201
revision_id = 'rev-1'
202
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
203
t = self.get_transport().clone('tree')
204
a_text = t.get_bytes('a')
205
a_sha = osutils.sha_string(a_text)
207
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
208
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
209
c_text = t.get_bytes('b/c')
210
c_sha = osutils.sha_string(c_text)
212
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
213
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
214
e_text = t.get_bytes('b/d/e')
215
e_sha = osutils.sha_string(e_text)
217
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
218
f_text = t.get_bytes('f')
219
f_sha = osutils.sha_string(f_text)
221
null_stat = dirstate.DirState.NULLSTAT
223
'':(('', '', 'TREE_ROOT'), [
224
('d', '', 0, False, null_stat),
225
('d', '', 0, False, revision_id),
227
'a':(('', 'a', 'a-id'), [
228
('f', '', 0, False, null_stat),
229
('f', a_sha, a_len, False, revision_id),
231
'b':(('', 'b', 'b-id'), [
232
('d', '', 0, False, null_stat),
233
('d', '', 0, False, revision_id),
235
'b/c':(('b', 'c', 'c-id'), [
236
('f', '', 0, False, null_stat),
237
('f', c_sha, c_len, False, revision_id),
239
'b/d':(('b', 'd', 'd-id'), [
240
('d', '', 0, False, null_stat),
241
('d', '', 0, False, revision_id),
243
'b/d/e':(('b/d', 'e', 'e-id'), [
244
('f', '', 0, False, null_stat),
245
('f', e_sha, e_len, False, revision_id),
247
'f':(('', 'f', 'f-id'), [
248
('f', '', 0, False, null_stat),
249
('f', f_sha, f_len, False, revision_id),
252
state = dirstate.DirState.from_tree(tree, 'dirstate')
257
# Use a different object, to make sure nothing is pre-cached in memory.
258
state = dirstate.DirState.on_file('dirstate')
260
self.addCleanup(state.unlock)
261
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
262
state._dirblock_state)
263
# This is code is only really tested if we actually have to make more
264
# than one read, so set the page size to something smaller.
265
# We want it to contain about 2.2 records, so that we have a couple
266
# records that we can read per attempt
267
state._bisect_page_size = 200
268
return tree, state, expected
270
def create_duplicated_dirstate(self):
271
"""Create a dirstate with a deleted and added entries.
273
This grabs a basic_dirstate, and then removes and re adds every entry
276
tree, state, expected = self.create_basic_dirstate()
277
# Now we will just remove and add every file so we get an extra entry
278
# per entry. Unversion in reverse order so we handle subdirs
279
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
280
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
281
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
283
# Update the expected dictionary.
284
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
285
orig = expected[path]
287
# This record was deleted in the current tree
288
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
290
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
291
# And didn't exist in the basis tree
292
expected[path2] = (new_key, [orig[1][0],
293
dirstate.DirState.NULL_PARENT_DETAILS])
295
# We will replace the 'dirstate' file underneath 'state', but that is
296
# okay as lock as we unlock 'state' first.
299
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
305
# But we need to leave state in a read-lock because we already have
306
# a cleanup scheduled
308
return tree, state, expected
310
def create_renamed_dirstate(self):
311
"""Create a dirstate with a few internal renames.
313
This takes the basic dirstate, and moves the paths around.
315
tree, state, expected = self.create_basic_dirstate()
317
tree.rename_one('a', 'b/g')
319
tree.rename_one('b/d', 'h')
321
old_a = expected['a']
322
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
323
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
324
('r', 'a', 0, False, '')])
325
old_d = expected['b/d']
326
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
327
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
328
('r', 'b/d', 0, False, '')])
330
old_e = expected['b/d/e']
331
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
333
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
334
('r', 'b/d/e', 0, False, '')])
338
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
345
return tree, state, expected
347
class TestTreeToDirState(TestCaseWithDirState):
349
def test_empty_to_dirstate(self):
350
"""We should be able to create a dirstate for an empty tree."""
351
# There are no files on disk and no parents
352
tree = self.make_branch_and_tree('tree')
353
expected_result = ([], [
354
(('', '', tree.path2id('')), # common details
355
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
357
state = dirstate.DirState.from_tree(tree, 'dirstate')
359
self.check_state_with_reopen(expected_result, state)
361
def test_1_parents_empty_to_dirstate(self):
362
# create a parent by doing a commit
363
tree = self.make_branch_and_tree('tree')
364
rev_id = tree.commit('first post').encode('utf8')
365
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
366
expected_result = ([rev_id], [
367
(('', '', tree.path2id('')), # common details
368
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
369
('d', '', 0, False, rev_id), # first parent details
371
state = dirstate.DirState.from_tree(tree, 'dirstate')
372
self.check_state_with_reopen(expected_result, state)
379
def test_2_parents_empty_to_dirstate(self):
380
# create a parent by doing a commit
381
tree = self.make_branch_and_tree('tree')
382
rev_id = tree.commit('first post')
383
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
384
rev_id2 = tree2.commit('second post', allow_pointless=True)
385
tree.merge_from_branch(tree2.branch)
386
expected_result = ([rev_id, rev_id2], [
387
(('', '', tree.path2id('')), # common details
388
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
389
('d', '', 0, False, rev_id), # first parent details
390
('d', '', 0, False, rev_id2), # second parent details
392
state = dirstate.DirState.from_tree(tree, 'dirstate')
393
self.check_state_with_reopen(expected_result, state)
400
def test_empty_unknowns_are_ignored_to_dirstate(self):
401
"""We should be able to create a dirstate for an empty tree."""
402
# There are no files on disk and no parents
403
tree = self.make_branch_and_tree('tree')
404
self.build_tree(['tree/unknown'])
405
expected_result = ([], [
406
(('', '', tree.path2id('')), # common details
407
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
409
state = dirstate.DirState.from_tree(tree, 'dirstate')
410
self.check_state_with_reopen(expected_result, state)
412
def get_tree_with_a_file(self):
413
tree = self.make_branch_and_tree('tree')
414
self.build_tree(['tree/a file'])
415
tree.add('a file', 'a file id')
418
def test_non_empty_no_parents_to_dirstate(self):
419
"""We should be able to create a dirstate for an empty tree."""
420
# There are files on disk and no parents
421
tree = self.get_tree_with_a_file()
422
expected_result = ([], [
423
(('', '', tree.path2id('')), # common details
424
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
426
(('', 'a file', 'a file id'), # common
427
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
430
state = dirstate.DirState.from_tree(tree, 'dirstate')
431
self.check_state_with_reopen(expected_result, state)
433
def test_1_parents_not_empty_to_dirstate(self):
434
# create a parent by doing a commit
435
tree = self.get_tree_with_a_file()
436
rev_id = tree.commit('first post').encode('utf8')
437
# change the current content to be different this will alter stat, sha
439
self.build_tree_contents([('tree/a file', 'new content\n')])
440
expected_result = ([rev_id], [
441
(('', '', tree.path2id('')), # common details
442
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
443
('d', '', 0, False, rev_id), # first parent details
445
(('', 'a file', 'a file id'), # common
446
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
447
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
448
rev_id), # first parent
451
state = dirstate.DirState.from_tree(tree, 'dirstate')
452
self.check_state_with_reopen(expected_result, state)
454
def test_2_parents_not_empty_to_dirstate(self):
455
# create a parent by doing a commit
456
tree = self.get_tree_with_a_file()
457
rev_id = tree.commit('first post').encode('utf8')
458
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
459
# change the current content to be different this will alter stat, sha
461
self.build_tree_contents([('tree2/a file', 'merge content\n')])
462
rev_id2 = tree2.commit('second post').encode('utf8')
463
tree.merge_from_branch(tree2.branch)
464
# change the current content to be different this will alter stat, sha
465
# and length again, giving us three distinct values:
466
self.build_tree_contents([('tree/a file', 'new content\n')])
467
expected_result = ([rev_id, rev_id2], [
468
(('', '', tree.path2id('')), # common details
469
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
470
('d', '', 0, False, rev_id), # first parent details
471
('d', '', 0, False, rev_id2), # second parent details
473
(('', 'a file', 'a file id'), # common
474
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
475
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
476
rev_id), # first parent
477
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
478
rev_id2), # second parent
481
state = dirstate.DirState.from_tree(tree, 'dirstate')
482
self.check_state_with_reopen(expected_result, state)
484
def test_colliding_fileids(self):
485
# test insertion of parents creating several entries at the same path.
486
# we used to have a bug where they could cause the dirstate to break
487
# its ordering invariants.
488
# create some trees to test from
491
tree = self.make_branch_and_tree('tree%d' % i)
492
self.build_tree(['tree%d/name' % i,])
493
tree.add(['name'], ['file-id%d' % i])
494
revision_id = 'revid-%d' % i
495
tree.commit('message', rev_id=revision_id)
496
parents.append((revision_id,
497
tree.branch.repository.revision_tree(revision_id)))
498
# now fold these trees into a dirstate
499
state = dirstate.DirState.initialize('dirstate')
501
state.set_parent_trees(parents, [])
507
class TestDirStateOnFile(TestCaseWithDirState):
509
def test_construct_with_path(self):
510
tree = self.make_branch_and_tree('tree')
511
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
512
# we want to be able to get the lines of the dirstate that we will
514
lines = state.get_lines()
516
self.build_tree_contents([('dirstate', ''.join(lines))])
518
# no parents, default tree content
519
expected_result = ([], [
520
(('', '', tree.path2id('')), # common details
521
# current tree details, but new from_tree skips statting, it
522
# uses set_state_from_inventory, and thus depends on the
524
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
527
state = dirstate.DirState.on_file('dirstate')
528
state.lock_write() # check_state_with_reopen will save() and unlock it
529
self.check_state_with_reopen(expected_result, state)
531
def test_can_save_clean_on_file(self):
532
tree = self.make_branch_and_tree('tree')
533
state = dirstate.DirState.from_tree(tree, 'dirstate')
535
# doing a save should work here as there have been no changes.
537
# TODO: stat it and check it hasn't changed; may require waiting
538
# for the state accuracy window.
542
def test_can_save_in_read_lock(self):
543
self.build_tree(['a-file'])
544
state = dirstate.DirState.initialize('dirstate')
546
# No stat and no sha1 sum.
547
state.add('a-file', 'a-file-id', 'file', None, '')
552
# Now open in readonly mode
553
state = dirstate.DirState.on_file('dirstate')
556
entry = state._get_entry(0, path_utf8='a-file')
557
# The current sha1 sum should be empty
558
self.assertEqual('', entry[1][0][1])
559
# We should have a real entry.
560
self.assertNotEqual((None, None), entry)
561
# Make sure everything is old enough
562
state._sha_cutoff_time()
563
state._cutoff_time += 10
564
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
565
# We should have gotten a real sha1
566
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
569
# The dirblock has been updated
570
self.assertEqual(sha1sum, entry[1][0][1])
571
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
572
state._dirblock_state)
575
# Now, since we are the only one holding a lock, we should be able
576
# to save and have it written to disk
581
# Re-open the file, and ensure that the state has been updated.
582
state = dirstate.DirState.on_file('dirstate')
585
entry = state._get_entry(0, path_utf8='a-file')
586
self.assertEqual(sha1sum, entry[1][0][1])
590
def test_save_fails_quietly_if_locked(self):
591
"""If dirstate is locked, save will fail without complaining."""
592
self.build_tree(['a-file'])
593
state = dirstate.DirState.initialize('dirstate')
595
# No stat and no sha1 sum.
596
state.add('a-file', 'a-file-id', 'file', None, '')
601
state = dirstate.DirState.on_file('dirstate')
604
entry = state._get_entry(0, path_utf8='a-file')
605
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
606
# We should have gotten a real sha1
607
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
609
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
610
state._dirblock_state)
612
# Now, before we try to save, grab another dirstate, and take out a
614
# TODO: jam 20070315 Ideally this would be locked by another
615
# process. To make sure the file is really OS locked.
616
state2 = dirstate.DirState.on_file('dirstate')
619
# This won't actually write anything, because it couldn't grab
620
# a write lock. But it shouldn't raise an error, either.
621
# TODO: jam 20070315 We should probably distinguish between
622
# being dirty because of 'update_entry'. And dirty
623
# because of real modification. So that save() *does*
624
# raise a real error if it fails when we have real
632
# The file on disk should not be modified.
633
state = dirstate.DirState.on_file('dirstate')
636
entry = state._get_entry(0, path_utf8='a-file')
637
self.assertEqual('', entry[1][0][1])
642
class TestDirStateInitialize(TestCaseWithDirState):
644
def test_initialize(self):
645
expected_result = ([], [
646
(('', '', 'TREE_ROOT'), # common details
647
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
650
state = dirstate.DirState.initialize('dirstate')
652
self.assertIsInstance(state, dirstate.DirState)
653
lines = state.get_lines()
656
# On win32 you can't read from a locked file, even within the same
657
# process. So we have to unlock and release before we check the file
659
self.assertFileEqual(''.join(lines), 'dirstate')
660
state.lock_read() # check_state_with_reopen will unlock
661
self.check_state_with_reopen(expected_result, state)
664
class TestDirStateManipulations(TestCaseWithDirState):
666
def test_set_state_from_inventory_no_content_no_parents(self):
667
# setting the current inventory is a slow but important api to support.
668
tree1 = self.make_branch_and_memory_tree('tree1')
672
revid1 = tree1.commit('foo').encode('utf8')
673
root_id = tree1.inventory.root.file_id
674
inv = tree1.inventory
677
expected_result = [], [
678
(('', '', root_id), [
679
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
680
state = dirstate.DirState.initialize('dirstate')
682
state.set_state_from_inventory(inv)
683
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
685
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
686
state._dirblock_state)
691
# This will unlock it
692
self.check_state_with_reopen(expected_result, state)
694
def test_set_path_id_no_parents(self):
695
"""The id of a path can be changed trivally with no parents."""
696
state = dirstate.DirState.initialize('dirstate')
698
# check precondition to be sure the state does change appropriately.
700
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
701
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
702
list(state._iter_entries()))
703
state.set_path_id('', 'foobarbaz')
705
(('', '', 'foobarbaz'), [('d', '', 0, False,
706
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
707
self.assertEqual(expected_rows, list(state._iter_entries()))
708
# should work across save too
712
state = dirstate.DirState.on_file('dirstate')
716
self.assertEqual(expected_rows, list(state._iter_entries()))
720
def test_set_path_id_with_parents(self):
721
"""Set the root file id in a dirstate with parents"""
722
mt = self.make_branch_and_tree('mt')
723
# in case the default tree format uses a different root id
724
mt.set_root_id('TREE_ROOT')
725
mt.commit('foo', rev_id='parent-revid')
726
rt = mt.branch.repository.revision_tree('parent-revid')
727
state = dirstate.DirState.initialize('dirstate')
730
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
731
state.set_path_id('', 'foobarbaz')
733
# now see that it is what we expected
735
(('', '', 'TREE_ROOT'),
736
[('a', '', 0, False, ''),
737
('d', '', 0, False, 'parent-revid'),
739
(('', '', 'foobarbaz'),
740
[('d', '', 0, False, ''),
741
('a', '', 0, False, ''),
745
self.assertEqual(expected_rows, list(state._iter_entries()))
746
# should work across save too
750
# now flush & check we get the same
751
state = dirstate.DirState.on_file('dirstate')
755
self.assertEqual(expected_rows, list(state._iter_entries()))
758
# now change within an existing file-backed state
762
state.set_path_id('', 'tree-root-2')
768
def test_set_parent_trees_no_content(self):
769
# set_parent_trees is a slow but important api to support.
770
tree1 = self.make_branch_and_memory_tree('tree1')
774
revid1 = tree1.commit('foo')
777
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
778
tree2 = MemoryTree.create_on_branch(branch2)
781
revid2 = tree2.commit('foo')
782
root_id = tree2.inventory.root.file_id
785
state = dirstate.DirState.initialize('dirstate')
787
state.set_path_id('', root_id)
788
state.set_parent_trees(
789
((revid1, tree1.branch.repository.revision_tree(revid1)),
790
(revid2, tree2.branch.repository.revision_tree(revid2)),
791
('ghost-rev', None)),
793
# check we can reopen and use the dirstate after setting parent
800
state = dirstate.DirState.on_file('dirstate')
803
self.assertEqual([revid1, revid2, 'ghost-rev'],
804
state.get_parent_ids())
805
# iterating the entire state ensures that the state is parsable.
806
list(state._iter_entries())
807
# be sure that it sets not appends - change it
808
state.set_parent_trees(
809
((revid1, tree1.branch.repository.revision_tree(revid1)),
810
('ghost-rev', None)),
812
# and now put it back.
813
state.set_parent_trees(
814
((revid1, tree1.branch.repository.revision_tree(revid1)),
815
(revid2, tree2.branch.repository.revision_tree(revid2)),
816
('ghost-rev', tree2.branch.repository.revision_tree(None))),
818
self.assertEqual([revid1, revid2, 'ghost-rev'],
819
state.get_parent_ids())
820
# the ghost should be recorded as such by set_parent_trees.
821
self.assertEqual(['ghost-rev'], state.get_ghosts())
823
[(('', '', root_id), [
824
('d', '', 0, False, dirstate.DirState.NULLSTAT),
825
('d', '', 0, False, revid1),
826
('d', '', 0, False, revid2)
828
list(state._iter_entries()))
832
def test_set_parent_trees_file_missing_from_tree(self):
833
# Adding a parent tree may reference files not in the current state.
834
# they should get listed just once by id, even if they are in two
836
# set_parent_trees is a slow but important api to support.
837
tree1 = self.make_branch_and_memory_tree('tree1')
841
tree1.add(['a file'], ['file-id'], ['file'])
842
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
843
revid1 = tree1.commit('foo')
846
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
847
tree2 = MemoryTree.create_on_branch(branch2)
850
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
851
revid2 = tree2.commit('foo')
852
root_id = tree2.inventory.root.file_id
855
# check the layout in memory
856
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
857
(('', '', root_id), [
858
('d', '', 0, False, dirstate.DirState.NULLSTAT),
859
('d', '', 0, False, revid1.encode('utf8')),
860
('d', '', 0, False, revid2.encode('utf8'))
862
(('', 'a file', 'file-id'), [
863
('a', '', 0, False, ''),
864
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
865
revid1.encode('utf8')),
866
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
867
revid2.encode('utf8'))
870
state = dirstate.DirState.initialize('dirstate')
872
state.set_path_id('', root_id)
873
state.set_parent_trees(
874
((revid1, tree1.branch.repository.revision_tree(revid1)),
875
(revid2, tree2.branch.repository.revision_tree(revid2)),
881
# check_state_with_reopen will unlock
882
self.check_state_with_reopen(expected_result, state)
884
### add a path via _set_data - so we dont need delta work, just
885
# raw data in, and ensure that it comes out via get_lines happily.
887
def test_add_path_to_root_no_parents_all_data(self):
888
# The most trivial addition of a path is when there are no parents and
889
# its in the root and all data about the file is supplied
890
self.build_tree(['a file'])
891
stat = os.lstat('a file')
892
# the 1*20 is the sha1 pretend value.
893
state = dirstate.DirState.initialize('dirstate')
895
(('', '', 'TREE_ROOT'), [
896
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
898
(('', 'a file', 'a file id'), [
899
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
903
state.add('a file', 'a file id', 'file', stat, '1'*20)
904
# having added it, it should be in the output of iter_entries.
905
self.assertEqual(expected_entries, list(state._iter_entries()))
906
# saving and reloading should not affect this.
910
state = dirstate.DirState.on_file('dirstate')
913
self.assertEqual(expected_entries, list(state._iter_entries()))
917
def test_add_path_to_unversioned_directory(self):
918
"""Adding a path to an unversioned directory should error.
920
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
921
once dirstate is stable and if it is merged with WorkingTree3, consider
922
removing this copy of the test.
924
self.build_tree(['unversioned/', 'unversioned/a file'])
925
state = dirstate.DirState.initialize('dirstate')
927
self.assertRaises(errors.NotVersionedError, state.add,
928
'unversioned/a file', 'a file id', 'file', None, None)
932
def test_add_directory_to_root_no_parents_all_data(self):
933
# The most trivial addition of a dir is when there are no parents and
934
# its in the root and all data about the file is supplied
935
self.build_tree(['a dir/'])
936
stat = os.lstat('a dir')
938
(('', '', 'TREE_ROOT'), [
939
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
941
(('', 'a dir', 'a dir id'), [
942
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
945
state = dirstate.DirState.initialize('dirstate')
947
state.add('a dir', 'a dir id', 'directory', stat, None)
948
# having added it, it should be in the output of iter_entries.
949
self.assertEqual(expected_entries, list(state._iter_entries()))
950
# saving and reloading should not affect this.
954
state = dirstate.DirState.on_file('dirstate')
958
self.assertEqual(expected_entries, list(state._iter_entries()))
962
def test_add_symlink_to_root_no_parents_all_data(self):
963
# The most trivial addition of a symlink when there are no parents and
964
# its in the root and all data about the file is supplied
965
# bzr doesn't support fake symlinks on windows, yet.
966
if not has_symlinks():
967
raise TestSkipped("No symlink support")
968
os.symlink('target', 'a link')
969
stat = os.lstat('a link')
971
(('', '', 'TREE_ROOT'), [
972
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
974
(('', 'a link', 'a link id'), [
975
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
978
state = dirstate.DirState.initialize('dirstate')
980
state.add('a link', 'a link id', 'symlink', stat, 'target')
981
# having added it, it should be in the output of iter_entries.
982
self.assertEqual(expected_entries, list(state._iter_entries()))
983
# saving and reloading should not affect this.
987
state = dirstate.DirState.on_file('dirstate')
990
self.assertEqual(expected_entries, list(state._iter_entries()))
994
def test_add_directory_and_child_no_parents_all_data(self):
995
# after adding a directory, we should be able to add children to it.
996
self.build_tree(['a dir/', 'a dir/a file'])
997
dirstat = os.lstat('a dir')
998
filestat = os.lstat('a dir/a file')
1000
(('', '', 'TREE_ROOT'), [
1001
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1003
(('', 'a dir', 'a dir id'), [
1004
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1006
(('a dir', 'a file', 'a file id'), [
1007
('f', '1'*20, 25, False,
1008
dirstate.pack_stat(filestat)), # current tree details
1011
state = dirstate.DirState.initialize('dirstate')
1013
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1014
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1015
# added it, it should be in the output of iter_entries.
1016
self.assertEqual(expected_entries, list(state._iter_entries()))
1017
# saving and reloading should not affect this.
1021
state = dirstate.DirState.on_file('dirstate')
1024
self.assertEqual(expected_entries, list(state._iter_entries()))
1028
def test_add_tree_reference(self):
1029
# make a dirstate and add a tree reference
1030
state = dirstate.DirState.initialize('dirstate')
1032
('', 'subdir', 'subdir-id'),
1033
[('t', 'subtree-123123', 0, False,
1034
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1037
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1038
entry = state._get_entry(0, 'subdir-id', 'subdir')
1039
self.assertEqual(entry, expected_entry)
1044
# now check we can read it back
1048
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1049
self.assertEqual(entry, entry2)
1050
self.assertEqual(entry, expected_entry)
1051
# and lookup by id should work too
1052
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1053
self.assertEqual(entry, expected_entry)
1057
def test_add_forbidden_names(self):
1058
state = dirstate.DirState.initialize('dirstate')
1059
self.addCleanup(state.unlock)
1060
self.assertRaises(errors.BzrError,
1061
state.add, '.', 'ass-id', 'directory', None, None)
1062
self.assertRaises(errors.BzrError,
1063
state.add, '..', 'ass-id', 'directory', None, None)
1066
class TestGetLines(TestCaseWithDirState):
1068
def test_get_line_with_2_rows(self):
1069
state = self.create_dirstate_with_root_and_subdir()
1071
self.assertEqual(['#bazaar dirstate flat format 3\n',
1072
'crc32: 41262208\n',
1076
'\x00\x00a-root-value\x00'
1077
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1078
'\x00subdir\x00subdir-id\x00'
1079
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1080
], state.get_lines())
1084
def test_entry_to_line(self):
1085
state = self.create_dirstate_with_root()
1088
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1089
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1090
state._entry_to_line(state._dirblocks[0][1][0]))
1094
def test_entry_to_line_with_parent(self):
1095
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1096
root_entry = ('', '', 'a-root-value'), [
1097
('d', '', 0, False, packed_stat), # current tree details
1098
# first: a pointer to the current location
1099
('a', 'dirname/basename', 0, False, ''),
1101
state = dirstate.DirState.initialize('dirstate')
1104
'\x00\x00a-root-value\x00'
1105
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1106
'a\x00dirname/basename\x000\x00n\x00',
1107
state._entry_to_line(root_entry))
1111
def test_entry_to_line_with_two_parents_at_different_paths(self):
1112
# / in the tree, at / in one parent and /dirname/basename in the other.
1113
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1114
root_entry = ('', '', 'a-root-value'), [
1115
('d', '', 0, False, packed_stat), # current tree details
1116
('d', '', 0, False, 'rev_id'), # first parent details
1117
# second: a pointer to the current location
1118
('a', 'dirname/basename', 0, False, ''),
1120
state = dirstate.DirState.initialize('dirstate')
1123
'\x00\x00a-root-value\x00'
1124
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1125
'd\x00\x000\x00n\x00rev_id\x00'
1126
'a\x00dirname/basename\x000\x00n\x00',
1127
state._entry_to_line(root_entry))
1131
def test_iter_entries(self):
1132
# we should be able to iterate the dirstate entries from end to end
1133
# this is for get_lines to be easy to read.
1134
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1136
root_entries = [(('', '', 'a-root-value'), [
1137
('d', '', 0, False, packed_stat), # current tree details
1139
dirblocks.append(('', root_entries))
1140
# add two files in the root
1141
subdir_entry = ('', 'subdir', 'subdir-id'), [
1142
('d', '', 0, False, packed_stat), # current tree details
1144
afile_entry = ('', 'afile', 'afile-id'), [
1145
('f', 'sha1value', 34, False, packed_stat), # current tree details
1147
dirblocks.append(('', [subdir_entry, afile_entry]))
1149
file_entry2 = ('subdir', '2file', '2file-id'), [
1150
('f', 'sha1value', 23, False, packed_stat), # current tree details
1152
dirblocks.append(('subdir', [file_entry2]))
1153
state = dirstate.DirState.initialize('dirstate')
1155
state._set_data([], dirblocks)
1156
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1158
self.assertEqual(expected_entries, list(state._iter_entries()))
1163
class TestGetBlockRowIndex(TestCaseWithDirState):
1165
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1166
file_present, state, dirname, basename, tree_index):
1167
self.assertEqual((block_index, row_index, dir_present, file_present),
1168
state._get_block_entry_index(dirname, basename, tree_index))
1170
block = state._dirblocks[block_index]
1171
self.assertEqual(dirname, block[0])
1172
if dir_present and file_present:
1173
row = state._dirblocks[block_index][1][row_index]
1174
self.assertEqual(dirname, row[0][0])
1175
self.assertEqual(basename, row[0][1])
1177
def test_simple_structure(self):
1178
state = self.create_dirstate_with_root_and_subdir()
1179
self.addCleanup(state.unlock)
1180
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1181
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1182
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1183
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1184
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1187
def test_complex_structure_exists(self):
1188
state = self.create_complex_dirstate()
1189
self.addCleanup(state.unlock)
1190
# Make sure we can find everything that exists
1191
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1192
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1193
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1194
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1195
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1196
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1197
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1198
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1199
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1200
'b', 'h\xc3\xa5', 0)
1202
def test_complex_structure_missing(self):
1203
state = self.create_complex_dirstate()
1204
self.addCleanup(state.unlock)
1205
# Make sure things would be inserted in the right locations
1206
# '_' comes before 'a'
1207
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1208
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1209
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1210
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1212
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1213
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1214
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1215
# This would be inserted between a/ and b/
1216
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1218
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1221
class TestGetEntry(TestCaseWithDirState):
1223
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1224
"""Check that the right entry is returned for a request to getEntry."""
1225
entry = state._get_entry(index, path_utf8=path)
1227
self.assertEqual((None, None), entry)
1230
self.assertEqual((dirname, basename, file_id), cur[:3])
1232
def test_simple_structure(self):
1233
state = self.create_dirstate_with_root_and_subdir()
1234
self.addCleanup(state.unlock)
1235
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1236
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1237
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1238
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1239
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1241
def test_complex_structure_exists(self):
1242
state = self.create_complex_dirstate()
1243
self.addCleanup(state.unlock)
1244
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1245
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1246
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1247
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1248
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1249
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1250
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1251
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1252
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1255
def test_complex_structure_missing(self):
1256
state = self.create_complex_dirstate()
1257
self.addCleanup(state.unlock)
1258
self.assertEntryEqual(None, None, None, state, '_', 0)
1259
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1260
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1261
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1263
def test_get_entry_uninitialized(self):
1264
"""Calling get_entry will load data if it needs to"""
1265
state = self.create_dirstate_with_root()
1271
state = dirstate.DirState.on_file('dirstate')
1274
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1275
state._header_state)
1276
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1277
state._dirblock_state)
1278
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1283
class TestDirstateSortOrder(TestCaseWithTransport):
1284
"""Test that DirState adds entries in the right order."""
1286
def test_add_sorting(self):
1287
"""Add entries in lexicographical order, we get path sorted order.
1289
This tests it to a depth of 4, to make sure we don't just get it right
1290
at a single depth. 'a/a' should come before 'a-a', even though it
1291
doesn't lexicographically.
1293
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1294
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1297
state = dirstate.DirState.initialize('dirstate')
1298
self.addCleanup(state.unlock)
1300
fake_stat = os.stat('dirstate')
1302
d_id = d.replace('/', '_')+'-id'
1303
file_path = d + '/f'
1304
file_id = file_path.replace('/', '_')+'-id'
1305
state.add(d, d_id, 'directory', fake_stat, null_sha)
1306
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1308
expected = ['', '', 'a',
1309
'a/a', 'a/a/a', 'a/a/a/a',
1310
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1312
split = lambda p:p.split('/')
1313
self.assertEqual(sorted(expected, key=split), expected)
1314
dirblock_names = [d[0] for d in state._dirblocks]
1315
self.assertEqual(expected, dirblock_names)
1317
def test_set_parent_trees_correct_order(self):
1318
"""After calling set_parent_trees() we should maintain the order."""
1319
dirs = ['a', 'a-a', 'a/a']
1321
state = dirstate.DirState.initialize('dirstate')
1322
self.addCleanup(state.unlock)
1324
fake_stat = os.stat('dirstate')
1326
d_id = d.replace('/', '_')+'-id'
1327
file_path = d + '/f'
1328
file_id = file_path.replace('/', '_')+'-id'
1329
state.add(d, d_id, 'directory', fake_stat, null_sha)
1330
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1332
expected = ['', '', 'a', 'a/a', 'a-a']
1333
dirblock_names = [d[0] for d in state._dirblocks]
1334
self.assertEqual(expected, dirblock_names)
1336
# *really* cheesy way to just get an empty tree
1337
repo = self.make_repository('repo')
1338
empty_tree = repo.revision_tree(None)
1339
state.set_parent_trees([('null:', empty_tree)], [])
1341
dirblock_names = [d[0] for d in state._dirblocks]
1342
self.assertEqual(expected, dirblock_names)
1345
class InstrumentedDirState(dirstate.DirState):
1346
"""An DirState with instrumented sha1 functionality."""
1348
def __init__(self, path):
1349
super(InstrumentedDirState, self).__init__(path)
1350
self._time_offset = 0
1353
def _sha_cutoff_time(self):
1354
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1355
self._cutoff_time = timestamp + self._time_offset
1357
def _sha1_file(self, abspath, entry):
1358
self._log.append(('sha1', abspath))
1359
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1361
def _read_link(self, abspath, old_link):
1362
self._log.append(('read_link', abspath, old_link))
1363
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1365
def _lstat(self, abspath, entry):
1366
self._log.append(('lstat', abspath))
1367
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1369
def _is_executable(self, mode, old_executable):
1370
self._log.append(('is_exec', mode, old_executable))
1371
return super(InstrumentedDirState, self)._is_executable(mode,
1374
def adjust_time(self, secs):
1375
"""Move the clock forward or back.
1377
:param secs: The amount to adjust the clock by. Positive values make it
1378
seem as if we are in the future, negative values make it seem like we
1381
self._time_offset += secs
1382
self._cutoff_time = None
1385
class _FakeStat(object):
1386
"""A class with the same attributes as a real stat result."""
1388
def __init__(self, size, mtime, ctime, dev, ino, mode):
1390
self.st_mtime = mtime
1391
self.st_ctime = ctime
1397
class TestUpdateEntry(TestCaseWithDirState):
1398
"""Test the DirState.update_entry functions"""
1400
def get_state_with_a(self):
1401
"""Create a DirState tracking a single object named 'a'"""
1402
state = InstrumentedDirState.initialize('dirstate')
1403
self.addCleanup(state.unlock)
1404
state.add('a', 'a-id', 'file', None, '')
1405
entry = state._get_entry(0, path_utf8='a')
1408
def test_update_entry(self):
1409
state, entry = self.get_state_with_a()
1410
self.build_tree(['a'])
1411
# Add one where we don't provide the stat or sha already
1412
self.assertEqual(('', 'a', 'a-id'), entry[0])
1413
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1415
# Flush the buffers to disk
1417
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1418
state._dirblock_state)
1420
stat_value = os.lstat('a')
1421
packed_stat = dirstate.pack_stat(stat_value)
1422
link_or_sha1 = state.update_entry(entry, abspath='a',
1423
stat_value=stat_value)
1424
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1427
# The dirblock entry should not cache the file's sha1
1428
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1430
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1431
state._dirblock_state)
1432
mode = stat_value.st_mode
1433
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1436
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1437
state._dirblock_state)
1439
# If we do it again right away, we don't know if the file has changed
1440
# so we will re-read the file. Roll the clock back so the file is
1441
# guaranteed to look too new.
1442
state.adjust_time(-10)
1444
link_or_sha1 = state.update_entry(entry, abspath='a',
1445
stat_value=stat_value)
1446
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1447
('sha1', 'a'), ('is_exec', mode, False),
1449
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1451
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1452
state._dirblock_state)
1453
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1457
# However, if we move the clock forward so the file is considered
1458
# "stable", it should just cache the value.
1459
state.adjust_time(+20)
1460
link_or_sha1 = state.update_entry(entry, abspath='a',
1461
stat_value=stat_value)
1462
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1464
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1465
('sha1', 'a'), ('is_exec', mode, False),
1466
('sha1', 'a'), ('is_exec', mode, False),
1468
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1471
# Subsequent calls will just return the cached value
1472
link_or_sha1 = state.update_entry(entry, abspath='a',
1473
stat_value=stat_value)
1474
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1476
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1477
('sha1', 'a'), ('is_exec', mode, False),
1478
('sha1', 'a'), ('is_exec', mode, False),
1480
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1483
def test_update_entry_symlink(self):
1484
"""Update entry should read symlinks."""
1485
if not osutils.has_symlinks():
1486
# PlatformDeficiency / TestSkipped
1487
raise TestSkipped("No symlink support")
1488
state, entry = self.get_state_with_a()
1490
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1491
state._dirblock_state)
1492
os.symlink('target', 'a')
1494
state.adjust_time(-10) # Make the symlink look new
1495
stat_value = os.lstat('a')
1496
packed_stat = dirstate.pack_stat(stat_value)
1497
link_or_sha1 = state.update_entry(entry, abspath='a',
1498
stat_value=stat_value)
1499
self.assertEqual('target', link_or_sha1)
1500
self.assertEqual([('read_link', 'a', '')], state._log)
1501
# Dirblock is not updated (the link is too new)
1502
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1504
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1505
state._dirblock_state)
1507
# Because the stat_value looks new, we should re-read the target
1508
link_or_sha1 = state.update_entry(entry, abspath='a',
1509
stat_value=stat_value)
1510
self.assertEqual('target', link_or_sha1)
1511
self.assertEqual([('read_link', 'a', ''),
1512
('read_link', 'a', ''),
1514
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1516
state.adjust_time(+20) # Skip into the future, all files look old
1517
link_or_sha1 = state.update_entry(entry, abspath='a',
1518
stat_value=stat_value)
1519
self.assertEqual('target', link_or_sha1)
1520
# We need to re-read the link because only now can we cache it
1521
self.assertEqual([('read_link', 'a', ''),
1522
('read_link', 'a', ''),
1523
('read_link', 'a', ''),
1525
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1528
# Another call won't re-read the link
1529
self.assertEqual([('read_link', 'a', ''),
1530
('read_link', 'a', ''),
1531
('read_link', 'a', ''),
1533
link_or_sha1 = state.update_entry(entry, abspath='a',
1534
stat_value=stat_value)
1535
self.assertEqual('target', link_or_sha1)
1536
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1539
def do_update_entry(self, state, entry, abspath):
1540
stat_value = os.lstat(abspath)
1541
return state.update_entry(entry, abspath, stat_value)
1543
def test_update_entry_dir(self):
1544
state, entry = self.get_state_with_a()
1545
self.build_tree(['a/'])
1546
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1548
def test_update_entry_dir_unchanged(self):
1549
state, entry = self.get_state_with_a()
1550
self.build_tree(['a/'])
1551
state.adjust_time(+20)
1552
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1553
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1554
state._dirblock_state)
1556
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1557
state._dirblock_state)
1558
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1559
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1560
state._dirblock_state)
1562
def test_update_entry_file_unchanged(self):
1563
state, entry = self.get_state_with_a()
1564
self.build_tree(['a'])
1565
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1566
state.adjust_time(+20)
1567
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1568
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1569
state._dirblock_state)
1571
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1572
state._dirblock_state)
1573
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1574
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1575
state._dirblock_state)
1577
def create_and_test_file(self, state, entry):
1578
"""Create a file at 'a' and verify the state finds it.
1580
The state should already be versioning *something* at 'a'. This makes
1581
sure that state.update_entry recognizes it as a file.
1583
self.build_tree(['a'])
1584
stat_value = os.lstat('a')
1585
packed_stat = dirstate.pack_stat(stat_value)
1587
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1588
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1590
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1594
def create_and_test_dir(self, state, entry):
1595
"""Create a directory at 'a' and verify the state finds it.
1597
The state should already be versioning *something* at 'a'. This makes
1598
sure that state.update_entry recognizes it as a directory.
1600
self.build_tree(['a/'])
1601
stat_value = os.lstat('a')
1602
packed_stat = dirstate.pack_stat(stat_value)
1604
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1605
self.assertIs(None, link_or_sha1)
1606
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1610
def create_and_test_symlink(self, state, entry):
1611
"""Create a symlink at 'a' and verify the state finds it.
1613
The state should already be versioning *something* at 'a'. This makes
1614
sure that state.update_entry recognizes it as a symlink.
1616
This should not be called if this platform does not have symlink
1619
# caller should care about skipping test on platforms without symlinks
1620
os.symlink('path/to/foo', 'a')
1622
stat_value = os.lstat('a')
1623
packed_stat = dirstate.pack_stat(stat_value)
1625
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1626
self.assertEqual('path/to/foo', link_or_sha1)
1627
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1631
def test_update_file_to_dir(self):
1632
"""If a file changes to a directory we return None for the sha.
1633
We also update the inventory record.
1635
state, entry = self.get_state_with_a()
1636
# The file sha1 won't be cached unless the file is old
1637
state.adjust_time(+10)
1638
self.create_and_test_file(state, entry)
1640
self.create_and_test_dir(state, entry)
1642
def test_update_file_to_symlink(self):
1643
"""File becomes a symlink"""
1644
if not osutils.has_symlinks():
1645
# PlatformDeficiency / TestSkipped
1646
raise TestSkipped("No symlink support")
1647
state, entry = self.get_state_with_a()
1648
# The file sha1 won't be cached unless the file is old
1649
state.adjust_time(+10)
1650
self.create_and_test_file(state, entry)
1652
self.create_and_test_symlink(state, entry)
1654
def test_update_dir_to_file(self):
1655
"""Directory becoming a file updates the entry."""
1656
state, entry = self.get_state_with_a()
1657
# The file sha1 won't be cached unless the file is old
1658
state.adjust_time(+10)
1659
self.create_and_test_dir(state, entry)
1661
self.create_and_test_file(state, entry)
1663
def test_update_dir_to_symlink(self):
1664
"""Directory becomes a symlink"""
1665
if not osutils.has_symlinks():
1666
# PlatformDeficiency / TestSkipped
1667
raise TestSkipped("No symlink support")
1668
state, entry = self.get_state_with_a()
1669
# The symlink target won't be cached if it isn't old
1670
state.adjust_time(+10)
1671
self.create_and_test_dir(state, entry)
1673
self.create_and_test_symlink(state, entry)
1675
def test_update_symlink_to_file(self):
1676
"""Symlink becomes a file"""
1677
if not has_symlinks():
1678
raise TestSkipped("No symlink support")
1679
state, entry = self.get_state_with_a()
1680
# The symlink and file info won't be cached unless old
1681
state.adjust_time(+10)
1682
self.create_and_test_symlink(state, entry)
1684
self.create_and_test_file(state, entry)
1686
def test_update_symlink_to_dir(self):
1687
"""Symlink becomes a directory"""
1688
if not has_symlinks():
1689
raise TestSkipped("No symlink support")
1690
state, entry = self.get_state_with_a()
1691
# The symlink target won't be cached if it isn't old
1692
state.adjust_time(+10)
1693
self.create_and_test_symlink(state, entry)
1695
self.create_and_test_dir(state, entry)
1697
def test__is_executable_win32(self):
1698
state, entry = self.get_state_with_a()
1699
self.build_tree(['a'])
1701
# Make sure we are using the win32 implementation of _is_executable
1702
state._is_executable = state._is_executable_win32
1704
# The file on disk is not executable, but we are marking it as though
1705
# it is. With _is_executable_win32 we ignore what is on disk.
1706
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1708
stat_value = os.lstat('a')
1709
packed_stat = dirstate.pack_stat(stat_value)
1711
state.adjust_time(-10) # Make sure everything is new
1712
state.update_entry(entry, abspath='a', stat_value=stat_value)
1714
# The row is updated, but the executable bit stays set.
1715
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1718
# Make the disk object look old enough to cache
1719
state.adjust_time(+20)
1720
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1721
state.update_entry(entry, abspath='a', stat_value=stat_value)
1722
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1725
class TestPackStat(TestCaseWithTransport):
1727
def assertPackStat(self, expected, stat_value):
1728
"""Check the packed and serialized form of a stat value."""
1729
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1731
def test_pack_stat_int(self):
1732
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1733
# Make sure that all parameters have an impact on the packed stat.
1734
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1737
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1738
st.st_mtime = 1172758620
1740
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1741
st.st_ctime = 1172758630
1743
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1746
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1747
st.st_ino = 6499540L
1749
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1750
st.st_mode = 0100744
1752
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1754
def test_pack_stat_float(self):
1755
"""On some platforms mtime and ctime are floats.
1757
Make sure we don't get warnings or errors, and that we ignore changes <
1760
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1761
777L, 6499538L, 0100644)
1762
# These should all be the same as the integer counterparts
1763
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1764
st.st_mtime = 1172758620.0
1766
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1767
st.st_ctime = 1172758630.0
1769
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1770
# fractional seconds are discarded, so no change from above
1771
st.st_mtime = 1172758620.453
1772
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1773
st.st_ctime = 1172758630.228
1774
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1777
class TestBisect(TestCaseWithDirState):
1778
"""Test the ability to bisect into the disk format."""
1781
def assertBisect(self, expected_map, map_keys, state, paths):
1782
"""Assert that bisecting for paths returns the right result.
1784
:param expected_map: A map from key => entry value
1785
:param map_keys: The keys to expect for each path
1786
:param state: The DirState object.
1787
:param paths: A list of paths, these will automatically be split into
1788
(dir, name) tuples, and sorted according to how _bisect
1791
dir_names = sorted(osutils.split(p) for p in paths)
1792
result = state._bisect(dir_names)
1793
# For now, results are just returned in whatever order we read them.
1794
# We could sort by (dir, name, file_id) or something like that, but in
1795
# the end it would still be fairly arbitrary, and we don't want the
1796
# extra overhead if we can avoid it. So sort everything to make sure
1798
assert len(map_keys) == len(dir_names)
1800
for dir_name, keys in zip(dir_names, map_keys):
1802
# This should not be present in the output
1804
expected[dir_name] = sorted(expected_map[k] for k in keys)
1806
for dir_name in result:
1807
result[dir_name].sort()
1809
self.assertEqual(expected, result)
1811
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1812
"""Assert that bisecting for dirbblocks returns the right result.
1814
:param expected_map: A map from key => expected values
1815
:param map_keys: A nested list of paths we expect to be returned.
1816
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1817
:param state: The DirState object.
1818
:param paths: A list of directories
1820
result = state._bisect_dirblocks(paths)
1821
assert len(map_keys) == len(paths)
1824
for path, keys in zip(paths, map_keys):
1826
# This should not be present in the output
1828
expected[path] = sorted(expected_map[k] for k in keys)
1832
self.assertEqual(expected, result)
1834
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1835
"""Assert the return value of a recursive bisection.
1837
:param expected_map: A map from key => entry value
1838
:param map_keys: A list of paths we expect to be returned.
1839
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1840
:param state: The DirState object.
1841
:param paths: A list of files and directories. It will be broken up
1842
into (dir, name) pairs and sorted before calling _bisect_recursive.
1845
for key in map_keys:
1846
entry = expected_map[key]
1847
dir_name_id, trees_info = entry
1848
expected[dir_name_id] = trees_info
1850
dir_names = sorted(osutils.split(p) for p in paths)
1851
result = state._bisect_recursive(dir_names)
1853
self.assertEqual(expected, result)
1855
def test_bisect_each(self):
1856
"""Find a single record using bisect."""
1857
tree, state, expected = self.create_basic_dirstate()
1859
# Bisect should return the rows for the specified files.
1860
self.assertBisect(expected, [['']], state, [''])
1861
self.assertBisect(expected, [['a']], state, ['a'])
1862
self.assertBisect(expected, [['b']], state, ['b'])
1863
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1864
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1865
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1866
self.assertBisect(expected, [['f']], state, ['f'])
1868
def test_bisect_multi(self):
1869
"""Bisect can be used to find multiple records at the same time."""
1870
tree, state, expected = self.create_basic_dirstate()
1871
# Bisect should be capable of finding multiple entries at the same time
1872
self.assertBisect(expected, [['a'], ['b'], ['f']],
1873
state, ['a', 'b', 'f'])
1874
# ('', 'f') sorts before the others
1875
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1876
state, ['b/d', 'b/d/e', 'f'])
1878
def test_bisect_one_page(self):
1879
"""Test bisect when there is only 1 page to read"""
1880
tree, state, expected = self.create_basic_dirstate()
1881
state._bisect_page_size = 5000
1882
self.assertBisect(expected,[['']], state, [''])
1883
self.assertBisect(expected,[['a']], state, ['a'])
1884
self.assertBisect(expected,[['b']], state, ['b'])
1885
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1886
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1887
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1888
self.assertBisect(expected,[['f']], state, ['f'])
1889
self.assertBisect(expected,[['a'], ['b'], ['f']],
1890
state, ['a', 'b', 'f'])
1891
# ('', 'f') sorts before the others
1892
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1893
state, ['b/d', 'b/d/e', 'f'])
1895
def test_bisect_duplicate_paths(self):
1896
"""When bisecting for a path, handle multiple entries."""
1897
tree, state, expected = self.create_duplicated_dirstate()
1899
# Now make sure that both records are properly returned.
1900
self.assertBisect(expected, [['']], state, [''])
1901
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1902
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1903
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1904
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1905
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1907
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1909
def test_bisect_page_size_too_small(self):
1910
"""If the page size is too small, we will auto increase it."""
1911
tree, state, expected = self.create_basic_dirstate()
1912
state._bisect_page_size = 50
1913
self.assertBisect(expected, [None], state, ['b/e'])
1914
self.assertBisect(expected, [['a']], state, ['a'])
1915
self.assertBisect(expected, [['b']], state, ['b'])
1916
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1917
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1918
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1919
self.assertBisect(expected, [['f']], state, ['f'])
1921
def test_bisect_missing(self):
1922
"""Test that bisect return None if it cannot find a path."""
1923
tree, state, expected = self.create_basic_dirstate()
1924
self.assertBisect(expected, [None], state, ['foo'])
1925
self.assertBisect(expected, [None], state, ['b/foo'])
1926
self.assertBisect(expected, [None], state, ['bar/foo'])
1928
self.assertBisect(expected, [['a'], None, ['b/d']],
1929
state, ['a', 'foo', 'b/d'])
1931
def test_bisect_rename(self):
1932
"""Check that we find a renamed row."""
1933
tree, state, expected = self.create_renamed_dirstate()
1935
# Search for the pre and post renamed entries
1936
self.assertBisect(expected, [['a']], state, ['a'])
1937
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1938
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1939
self.assertBisect(expected, [['h']], state, ['h'])
1941
# What about b/d/e? shouldn't that also get 2 directory entries?
1942
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1943
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1945
def test_bisect_dirblocks(self):
1946
tree, state, expected = self.create_duplicated_dirstate()
1947
self.assertBisectDirBlocks(expected,
1948
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1949
self.assertBisectDirBlocks(expected,
1950
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1951
self.assertBisectDirBlocks(expected,
1952
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1953
self.assertBisectDirBlocks(expected,
1954
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1955
['b/c', 'b/c2', 'b/d', 'b/d2'],
1956
['b/d/e', 'b/d/e2'],
1957
], state, ['', 'b', 'b/d'])
1959
def test_bisect_dirblocks_missing(self):
1960
tree, state, expected = self.create_basic_dirstate()
1961
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1962
state, ['b/d', 'b/e'])
1963
# Files don't show up in this search
1964
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1965
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1966
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1967
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1968
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1970
def test_bisect_recursive_each(self):
1971
tree, state, expected = self.create_basic_dirstate()
1972
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1973
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1974
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1975
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1977
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1979
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1983
def test_bisect_recursive_multiple(self):
1984
tree, state, expected = self.create_basic_dirstate()
1985
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1986
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1987
state, ['b/d', 'b/d/e'])
1989
def test_bisect_recursive_missing(self):
1990
tree, state, expected = self.create_basic_dirstate()
1991
self.assertBisectRecursive(expected, [], state, ['d'])
1992
self.assertBisectRecursive(expected, [], state, ['b/e'])
1993
self.assertBisectRecursive(expected, [], state, ['g'])
1994
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1996
def test_bisect_recursive_renamed(self):
1997
tree, state, expected = self.create_renamed_dirstate()
1999
# Looking for either renamed item should find the other
2000
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2001
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2002
# Looking in the containing directory should find the rename target,
2003
# and anything in a subdir of the renamed target.
2004
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2005
'b/d/e', 'b/g', 'h', 'h/e'],
2009
class TestBisectDirblock(TestCase):
2010
"""Test that bisect_dirblock() returns the expected values.
2012
bisect_dirblock is intended to work like bisect.bisect_left() except it
2013
knows it is working on dirblocks and that dirblocks are sorted by ('path',
2014
'to', 'foo') chunks rather than by raw 'path/to/foo'.
2017
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
2018
"""Assert that bisect_split works like bisect_left on the split paths.
2020
:param dirblocks: A list of (path, [info]) pairs.
2021
:param split_dirblocks: A list of ((split, path), [info]) pairs.
2022
:param path: The path we are indexing.
2024
All other arguments will be passed along.
2026
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
2028
split_dirblock = (path.split('/'), [])
2029
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
2031
self.assertEqual(bisect_left_idx, bisect_split_idx,
2032
'bisect_split disagreed. %s != %s'
2034
% (bisect_left_idx, bisect_split_idx, path)
2037
def paths_to_dirblocks(self, paths):
2038
"""Convert a list of paths into dirblock form.
2040
Also, ensure that the paths are in proper sorted order.
2042
dirblocks = [(path, []) for path in paths]
2043
split_dirblocks = [(path.split('/'), []) for path in paths]
2044
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
2045
return dirblocks, split_dirblocks
2047
def test_simple(self):
2048
"""In the simple case it works just like bisect_left"""
2049
paths = ['', 'a', 'b', 'c', 'd']
2050
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2052
self.assertBisect(dirblocks, split_dirblocks, path)
2053
self.assertBisect(dirblocks, split_dirblocks, '_')
2054
self.assertBisect(dirblocks, split_dirblocks, 'aa')
2055
self.assertBisect(dirblocks, split_dirblocks, 'bb')
2056
self.assertBisect(dirblocks, split_dirblocks, 'cc')
2057
self.assertBisect(dirblocks, split_dirblocks, 'dd')
2058
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
2059
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
2060
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
2061
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
2063
def test_involved(self):
2064
"""This is where bisect_left diverges slightly."""
2066
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2067
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2069
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2070
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2073
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2075
self.assertBisect(dirblocks, split_dirblocks, path)
2077
def test_involved_cached(self):
2078
"""This is where bisect_left diverges slightly."""
2080
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2081
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2083
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2084
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2088
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2090
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2093
class TestDirstateValidation(TestCaseWithDirState):
2095
def test_validate_correct_dirstate(self):
2096
state = self.create_complex_dirstate()
2099
# and make sure we can also validate with a read lock
2106
def test_dirblock_not_sorted(self):
2107
tree, state, expected = self.create_renamed_dirstate()
2108
state._read_dirblocks_if_needed()
2109
last_dirblock = state._dirblocks[-1]
2110
# we're appending to the dirblock, but this name comes before some of
2111
# the existing names; that's wrong
2112
last_dirblock[1].append(
2113
(('h', 'aaaa', 'a-id'),
2114
[('a', '', 0, False, ''),
2115
('a', '', 0, False, '')]))
2116
e = self.assertRaises(AssertionError,
2118
self.assertContainsRe(str(e), 'not sorted')
2120
def test_dirblock_name_mismatch(self):
2121
tree, state, expected = self.create_renamed_dirstate()
2122
state._read_dirblocks_if_needed()
2123
last_dirblock = state._dirblocks[-1]
2124
# add an entry with the wrong directory name
2125
last_dirblock[1].append(
2127
[('a', '', 0, False, ''),
2128
('a', '', 0, False, '')]))
2129
e = self.assertRaises(AssertionError,
2131
self.assertContainsRe(str(e),
2132
"doesn't match directory name")
2134
def test_dirblock_missing_rename(self):
2135
tree, state, expected = self.create_renamed_dirstate()
2136
state._read_dirblocks_if_needed()
2137
last_dirblock = state._dirblocks[-1]
2138
# make another entry for a-id, without a correct 'r' pointer to
2139
# the real occurrence in the working tree
2140
last_dirblock[1].append(
2141
(('h', 'z', 'a-id'),
2142
[('a', '', 0, False, ''),
2143
('a', '', 0, False, '')]))
2144
e = self.assertRaises(AssertionError,
2146
self.assertContainsRe(str(e),
2147
'file a-id is absent in row')