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_state_from_inventory_mixed_paths(self):
695
tree1 = self.make_branch_and_tree('tree1')
696
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
697
'tree1/a/b/foo', 'tree1/a-b/bar'])
700
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
701
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
702
tree1.commit('rev1', rev_id='rev1')
703
root_id = tree1.get_root_id()
704
inv = tree1.inventory
707
expected_result1 = [('', '', root_id, 'd'),
708
('', 'a', 'a-id', 'd'),
709
('', 'a-b', 'a-b-id', 'd'),
710
('a', 'b', 'b-id', 'd'),
711
('a/b', 'foo', 'foo-id', 'f'),
712
('a-b', 'bar', 'bar-id', 'f'),
714
expected_result2 = [('', '', root_id, 'd'),
715
('', 'a', 'a-id', 'd'),
716
('', 'a-b', 'a-b-id', 'd'),
717
('a-b', 'bar', 'bar-id', 'f'),
719
state = dirstate.DirState.initialize('dirstate')
721
state.set_state_from_inventory(inv)
723
for entry in state._iter_entries():
724
values.append(entry[0] + entry[1][0][:1])
725
self.assertEqual(expected_result1, values)
727
state.set_state_from_inventory(inv)
729
for entry in state._iter_entries():
730
values.append(entry[0] + entry[1][0][:1])
731
self.assertEqual(expected_result2, values)
735
def test_set_path_id_no_parents(self):
736
"""The id of a path can be changed trivally with no parents."""
737
state = dirstate.DirState.initialize('dirstate')
739
# check precondition to be sure the state does change appropriately.
741
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
742
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
743
list(state._iter_entries()))
744
state.set_path_id('', 'foobarbaz')
746
(('', '', 'foobarbaz'), [('d', '', 0, False,
747
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
748
self.assertEqual(expected_rows, list(state._iter_entries()))
749
# should work across save too
753
state = dirstate.DirState.on_file('dirstate')
757
self.assertEqual(expected_rows, list(state._iter_entries()))
761
def test_set_path_id_with_parents(self):
762
"""Set the root file id in a dirstate with parents"""
763
mt = self.make_branch_and_tree('mt')
764
# in case the default tree format uses a different root id
765
mt.set_root_id('TREE_ROOT')
766
mt.commit('foo', rev_id='parent-revid')
767
rt = mt.branch.repository.revision_tree('parent-revid')
768
state = dirstate.DirState.initialize('dirstate')
771
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
772
state.set_path_id('', 'foobarbaz')
774
# now see that it is what we expected
776
(('', '', 'TREE_ROOT'),
777
[('a', '', 0, False, ''),
778
('d', '', 0, False, 'parent-revid'),
780
(('', '', 'foobarbaz'),
781
[('d', '', 0, False, ''),
782
('a', '', 0, False, ''),
786
self.assertEqual(expected_rows, list(state._iter_entries()))
787
# should work across save too
791
# now flush & check we get the same
792
state = dirstate.DirState.on_file('dirstate')
796
self.assertEqual(expected_rows, list(state._iter_entries()))
799
# now change within an existing file-backed state
803
state.set_path_id('', 'tree-root-2')
809
def test_set_parent_trees_no_content(self):
810
# set_parent_trees is a slow but important api to support.
811
tree1 = self.make_branch_and_memory_tree('tree1')
815
revid1 = tree1.commit('foo')
818
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
819
tree2 = MemoryTree.create_on_branch(branch2)
822
revid2 = tree2.commit('foo')
823
root_id = tree2.inventory.root.file_id
826
state = dirstate.DirState.initialize('dirstate')
828
state.set_path_id('', root_id)
829
state.set_parent_trees(
830
((revid1, tree1.branch.repository.revision_tree(revid1)),
831
(revid2, tree2.branch.repository.revision_tree(revid2)),
832
('ghost-rev', None)),
834
# check we can reopen and use the dirstate after setting parent
841
state = dirstate.DirState.on_file('dirstate')
844
self.assertEqual([revid1, revid2, 'ghost-rev'],
845
state.get_parent_ids())
846
# iterating the entire state ensures that the state is parsable.
847
list(state._iter_entries())
848
# be sure that it sets not appends - change it
849
state.set_parent_trees(
850
((revid1, tree1.branch.repository.revision_tree(revid1)),
851
('ghost-rev', None)),
853
# and now put it back.
854
state.set_parent_trees(
855
((revid1, tree1.branch.repository.revision_tree(revid1)),
856
(revid2, tree2.branch.repository.revision_tree(revid2)),
857
('ghost-rev', tree2.branch.repository.revision_tree(None))),
859
self.assertEqual([revid1, revid2, 'ghost-rev'],
860
state.get_parent_ids())
861
# the ghost should be recorded as such by set_parent_trees.
862
self.assertEqual(['ghost-rev'], state.get_ghosts())
864
[(('', '', root_id), [
865
('d', '', 0, False, dirstate.DirState.NULLSTAT),
866
('d', '', 0, False, revid1),
867
('d', '', 0, False, revid2)
869
list(state._iter_entries()))
873
def test_set_parent_trees_file_missing_from_tree(self):
874
# Adding a parent tree may reference files not in the current state.
875
# they should get listed just once by id, even if they are in two
877
# set_parent_trees is a slow but important api to support.
878
tree1 = self.make_branch_and_memory_tree('tree1')
882
tree1.add(['a file'], ['file-id'], ['file'])
883
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
884
revid1 = tree1.commit('foo')
887
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
888
tree2 = MemoryTree.create_on_branch(branch2)
891
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
892
revid2 = tree2.commit('foo')
893
root_id = tree2.inventory.root.file_id
896
# check the layout in memory
897
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
898
(('', '', root_id), [
899
('d', '', 0, False, dirstate.DirState.NULLSTAT),
900
('d', '', 0, False, revid1.encode('utf8')),
901
('d', '', 0, False, revid2.encode('utf8'))
903
(('', 'a file', 'file-id'), [
904
('a', '', 0, False, ''),
905
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
906
revid1.encode('utf8')),
907
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
908
revid2.encode('utf8'))
911
state = dirstate.DirState.initialize('dirstate')
913
state.set_path_id('', root_id)
914
state.set_parent_trees(
915
((revid1, tree1.branch.repository.revision_tree(revid1)),
916
(revid2, tree2.branch.repository.revision_tree(revid2)),
922
# check_state_with_reopen will unlock
923
self.check_state_with_reopen(expected_result, state)
925
### add a path via _set_data - so we dont need delta work, just
926
# raw data in, and ensure that it comes out via get_lines happily.
928
def test_add_path_to_root_no_parents_all_data(self):
929
# The most trivial addition of a path is when there are no parents and
930
# its in the root and all data about the file is supplied
931
self.build_tree(['a file'])
932
stat = os.lstat('a file')
933
# the 1*20 is the sha1 pretend value.
934
state = dirstate.DirState.initialize('dirstate')
936
(('', '', 'TREE_ROOT'), [
937
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
939
(('', 'a file', 'a file id'), [
940
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
944
state.add('a file', 'a file id', 'file', stat, '1'*20)
945
# having added it, it should be in the output of iter_entries.
946
self.assertEqual(expected_entries, list(state._iter_entries()))
947
# saving and reloading should not affect this.
951
state = dirstate.DirState.on_file('dirstate')
954
self.assertEqual(expected_entries, list(state._iter_entries()))
958
def test_add_path_to_unversioned_directory(self):
959
"""Adding a path to an unversioned directory should error.
961
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
962
once dirstate is stable and if it is merged with WorkingTree3, consider
963
removing this copy of the test.
965
self.build_tree(['unversioned/', 'unversioned/a file'])
966
state = dirstate.DirState.initialize('dirstate')
968
self.assertRaises(errors.NotVersionedError, state.add,
969
'unversioned/a file', 'a file id', 'file', None, None)
973
def test_add_directory_to_root_no_parents_all_data(self):
974
# The most trivial addition of a dir is when there are no parents and
975
# its in the root and all data about the file is supplied
976
self.build_tree(['a dir/'])
977
stat = os.lstat('a dir')
979
(('', '', 'TREE_ROOT'), [
980
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
982
(('', 'a dir', 'a dir id'), [
983
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
986
state = dirstate.DirState.initialize('dirstate')
988
state.add('a dir', 'a dir id', 'directory', stat, None)
989
# having added it, it should be in the output of iter_entries.
990
self.assertEqual(expected_entries, list(state._iter_entries()))
991
# saving and reloading should not affect this.
995
state = dirstate.DirState.on_file('dirstate')
999
self.assertEqual(expected_entries, list(state._iter_entries()))
1003
def test_add_symlink_to_root_no_parents_all_data(self):
1004
# The most trivial addition of a symlink when there are no parents and
1005
# its in the root and all data about the file is supplied
1006
# bzr doesn't support fake symlinks on windows, yet.
1007
if not has_symlinks():
1008
raise TestSkipped("No symlink support")
1009
os.symlink('target', 'a link')
1010
stat = os.lstat('a link')
1011
expected_entries = [
1012
(('', '', 'TREE_ROOT'), [
1013
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1015
(('', 'a link', 'a link id'), [
1016
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1019
state = dirstate.DirState.initialize('dirstate')
1021
state.add('a link', 'a link id', 'symlink', stat, 'target')
1022
# having added it, it should be in the output of iter_entries.
1023
self.assertEqual(expected_entries, list(state._iter_entries()))
1024
# saving and reloading should not affect this.
1028
state = dirstate.DirState.on_file('dirstate')
1031
self.assertEqual(expected_entries, list(state._iter_entries()))
1035
def test_add_directory_and_child_no_parents_all_data(self):
1036
# after adding a directory, we should be able to add children to it.
1037
self.build_tree(['a dir/', 'a dir/a file'])
1038
dirstat = os.lstat('a dir')
1039
filestat = os.lstat('a dir/a file')
1040
expected_entries = [
1041
(('', '', 'TREE_ROOT'), [
1042
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1044
(('', 'a dir', 'a dir id'), [
1045
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1047
(('a dir', 'a file', 'a file id'), [
1048
('f', '1'*20, 25, False,
1049
dirstate.pack_stat(filestat)), # current tree details
1052
state = dirstate.DirState.initialize('dirstate')
1054
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1055
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1056
# added it, it should be in the output of iter_entries.
1057
self.assertEqual(expected_entries, list(state._iter_entries()))
1058
# saving and reloading should not affect this.
1062
state = dirstate.DirState.on_file('dirstate')
1065
self.assertEqual(expected_entries, list(state._iter_entries()))
1069
def test_add_tree_reference(self):
1070
# make a dirstate and add a tree reference
1071
state = dirstate.DirState.initialize('dirstate')
1073
('', 'subdir', 'subdir-id'),
1074
[('t', 'subtree-123123', 0, False,
1075
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1078
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1079
entry = state._get_entry(0, 'subdir-id', 'subdir')
1080
self.assertEqual(entry, expected_entry)
1085
# now check we can read it back
1089
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1090
self.assertEqual(entry, entry2)
1091
self.assertEqual(entry, expected_entry)
1092
# and lookup by id should work too
1093
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1094
self.assertEqual(entry, expected_entry)
1098
def test_add_forbidden_names(self):
1099
state = dirstate.DirState.initialize('dirstate')
1100
self.addCleanup(state.unlock)
1101
self.assertRaises(errors.BzrError,
1102
state.add, '.', 'ass-id', 'directory', None, None)
1103
self.assertRaises(errors.BzrError,
1104
state.add, '..', 'ass-id', 'directory', None, None)
1107
class TestGetLines(TestCaseWithDirState):
1109
def test_get_line_with_2_rows(self):
1110
state = self.create_dirstate_with_root_and_subdir()
1112
self.assertEqual(['#bazaar dirstate flat format 3\n',
1113
'crc32: 41262208\n',
1117
'\x00\x00a-root-value\x00'
1118
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1119
'\x00subdir\x00subdir-id\x00'
1120
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1121
], state.get_lines())
1125
def test_entry_to_line(self):
1126
state = self.create_dirstate_with_root()
1129
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1130
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1131
state._entry_to_line(state._dirblocks[0][1][0]))
1135
def test_entry_to_line_with_parent(self):
1136
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1137
root_entry = ('', '', 'a-root-value'), [
1138
('d', '', 0, False, packed_stat), # current tree details
1139
# first: a pointer to the current location
1140
('a', 'dirname/basename', 0, False, ''),
1142
state = dirstate.DirState.initialize('dirstate')
1145
'\x00\x00a-root-value\x00'
1146
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1147
'a\x00dirname/basename\x000\x00n\x00',
1148
state._entry_to_line(root_entry))
1152
def test_entry_to_line_with_two_parents_at_different_paths(self):
1153
# / in the tree, at / in one parent and /dirname/basename in the other.
1154
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1155
root_entry = ('', '', 'a-root-value'), [
1156
('d', '', 0, False, packed_stat), # current tree details
1157
('d', '', 0, False, 'rev_id'), # first parent details
1158
# second: a pointer to the current location
1159
('a', 'dirname/basename', 0, False, ''),
1161
state = dirstate.DirState.initialize('dirstate')
1164
'\x00\x00a-root-value\x00'
1165
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1166
'd\x00\x000\x00n\x00rev_id\x00'
1167
'a\x00dirname/basename\x000\x00n\x00',
1168
state._entry_to_line(root_entry))
1172
def test_iter_entries(self):
1173
# we should be able to iterate the dirstate entries from end to end
1174
# this is for get_lines to be easy to read.
1175
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1177
root_entries = [(('', '', 'a-root-value'), [
1178
('d', '', 0, False, packed_stat), # current tree details
1180
dirblocks.append(('', root_entries))
1181
# add two files in the root
1182
subdir_entry = ('', 'subdir', 'subdir-id'), [
1183
('d', '', 0, False, packed_stat), # current tree details
1185
afile_entry = ('', 'afile', 'afile-id'), [
1186
('f', 'sha1value', 34, False, packed_stat), # current tree details
1188
dirblocks.append(('', [subdir_entry, afile_entry]))
1190
file_entry2 = ('subdir', '2file', '2file-id'), [
1191
('f', 'sha1value', 23, False, packed_stat), # current tree details
1193
dirblocks.append(('subdir', [file_entry2]))
1194
state = dirstate.DirState.initialize('dirstate')
1196
state._set_data([], dirblocks)
1197
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1199
self.assertEqual(expected_entries, list(state._iter_entries()))
1204
class TestGetBlockRowIndex(TestCaseWithDirState):
1206
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1207
file_present, state, dirname, basename, tree_index):
1208
self.assertEqual((block_index, row_index, dir_present, file_present),
1209
state._get_block_entry_index(dirname, basename, tree_index))
1211
block = state._dirblocks[block_index]
1212
self.assertEqual(dirname, block[0])
1213
if dir_present and file_present:
1214
row = state._dirblocks[block_index][1][row_index]
1215
self.assertEqual(dirname, row[0][0])
1216
self.assertEqual(basename, row[0][1])
1218
def test_simple_structure(self):
1219
state = self.create_dirstate_with_root_and_subdir()
1220
self.addCleanup(state.unlock)
1221
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1222
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1223
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1224
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1225
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1228
def test_complex_structure_exists(self):
1229
state = self.create_complex_dirstate()
1230
self.addCleanup(state.unlock)
1231
# Make sure we can find everything that exists
1232
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1233
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1234
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1235
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1236
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1237
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1238
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1239
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1240
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1241
'b', 'h\xc3\xa5', 0)
1243
def test_complex_structure_missing(self):
1244
state = self.create_complex_dirstate()
1245
self.addCleanup(state.unlock)
1246
# Make sure things would be inserted in the right locations
1247
# '_' comes before 'a'
1248
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1249
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1250
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1251
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1253
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1254
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1255
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1256
# This would be inserted between a/ and b/
1257
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1259
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1262
class TestGetEntry(TestCaseWithDirState):
1264
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1265
"""Check that the right entry is returned for a request to getEntry."""
1266
entry = state._get_entry(index, path_utf8=path)
1268
self.assertEqual((None, None), entry)
1271
self.assertEqual((dirname, basename, file_id), cur[:3])
1273
def test_simple_structure(self):
1274
state = self.create_dirstate_with_root_and_subdir()
1275
self.addCleanup(state.unlock)
1276
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1277
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1278
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1279
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1280
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1282
def test_complex_structure_exists(self):
1283
state = self.create_complex_dirstate()
1284
self.addCleanup(state.unlock)
1285
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1286
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1287
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1288
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1289
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1290
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1291
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1292
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1293
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1296
def test_complex_structure_missing(self):
1297
state = self.create_complex_dirstate()
1298
self.addCleanup(state.unlock)
1299
self.assertEntryEqual(None, None, None, state, '_', 0)
1300
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1301
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1302
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1304
def test_get_entry_uninitialized(self):
1305
"""Calling get_entry will load data if it needs to"""
1306
state = self.create_dirstate_with_root()
1312
state = dirstate.DirState.on_file('dirstate')
1315
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1316
state._header_state)
1317
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1318
state._dirblock_state)
1319
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1324
class TestDirstateSortOrder(TestCaseWithTransport):
1325
"""Test that DirState adds entries in the right order."""
1327
def test_add_sorting(self):
1328
"""Add entries in lexicographical order, we get path sorted order.
1330
This tests it to a depth of 4, to make sure we don't just get it right
1331
at a single depth. 'a/a' should come before 'a-a', even though it
1332
doesn't lexicographically.
1334
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1335
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1338
state = dirstate.DirState.initialize('dirstate')
1339
self.addCleanup(state.unlock)
1341
fake_stat = os.stat('dirstate')
1343
d_id = d.replace('/', '_')+'-id'
1344
file_path = d + '/f'
1345
file_id = file_path.replace('/', '_')+'-id'
1346
state.add(d, d_id, 'directory', fake_stat, null_sha)
1347
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1349
expected = ['', '', 'a',
1350
'a/a', 'a/a/a', 'a/a/a/a',
1351
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1353
split = lambda p:p.split('/')
1354
self.assertEqual(sorted(expected, key=split), expected)
1355
dirblock_names = [d[0] for d in state._dirblocks]
1356
self.assertEqual(expected, dirblock_names)
1358
def test_set_parent_trees_correct_order(self):
1359
"""After calling set_parent_trees() we should maintain the order."""
1360
dirs = ['a', 'a-a', 'a/a']
1362
state = dirstate.DirState.initialize('dirstate')
1363
self.addCleanup(state.unlock)
1365
fake_stat = os.stat('dirstate')
1367
d_id = d.replace('/', '_')+'-id'
1368
file_path = d + '/f'
1369
file_id = file_path.replace('/', '_')+'-id'
1370
state.add(d, d_id, 'directory', fake_stat, null_sha)
1371
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1373
expected = ['', '', 'a', 'a/a', 'a-a']
1374
dirblock_names = [d[0] for d in state._dirblocks]
1375
self.assertEqual(expected, dirblock_names)
1377
# *really* cheesy way to just get an empty tree
1378
repo = self.make_repository('repo')
1379
empty_tree = repo.revision_tree(None)
1380
state.set_parent_trees([('null:', empty_tree)], [])
1382
dirblock_names = [d[0] for d in state._dirblocks]
1383
self.assertEqual(expected, dirblock_names)
1386
class InstrumentedDirState(dirstate.DirState):
1387
"""An DirState with instrumented sha1 functionality."""
1389
def __init__(self, path):
1390
super(InstrumentedDirState, self).__init__(path)
1391
self._time_offset = 0
1394
def _sha_cutoff_time(self):
1395
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1396
self._cutoff_time = timestamp + self._time_offset
1398
def _sha1_file(self, abspath, entry):
1399
self._log.append(('sha1', abspath))
1400
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1402
def _read_link(self, abspath, old_link):
1403
self._log.append(('read_link', abspath, old_link))
1404
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1406
def _lstat(self, abspath, entry):
1407
self._log.append(('lstat', abspath))
1408
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1410
def _is_executable(self, mode, old_executable):
1411
self._log.append(('is_exec', mode, old_executable))
1412
return super(InstrumentedDirState, self)._is_executable(mode,
1415
def adjust_time(self, secs):
1416
"""Move the clock forward or back.
1418
:param secs: The amount to adjust the clock by. Positive values make it
1419
seem as if we are in the future, negative values make it seem like we
1422
self._time_offset += secs
1423
self._cutoff_time = None
1426
class _FakeStat(object):
1427
"""A class with the same attributes as a real stat result."""
1429
def __init__(self, size, mtime, ctime, dev, ino, mode):
1431
self.st_mtime = mtime
1432
self.st_ctime = ctime
1438
class TestUpdateEntry(TestCaseWithDirState):
1439
"""Test the DirState.update_entry functions"""
1441
def get_state_with_a(self):
1442
"""Create a DirState tracking a single object named 'a'"""
1443
state = InstrumentedDirState.initialize('dirstate')
1444
self.addCleanup(state.unlock)
1445
state.add('a', 'a-id', 'file', None, '')
1446
entry = state._get_entry(0, path_utf8='a')
1449
def test_update_entry(self):
1450
state, entry = self.get_state_with_a()
1451
self.build_tree(['a'])
1452
# Add one where we don't provide the stat or sha already
1453
self.assertEqual(('', 'a', 'a-id'), entry[0])
1454
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1456
# Flush the buffers to disk
1458
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1459
state._dirblock_state)
1461
stat_value = os.lstat('a')
1462
packed_stat = dirstate.pack_stat(stat_value)
1463
link_or_sha1 = state.update_entry(entry, abspath='a',
1464
stat_value=stat_value)
1465
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1468
# The dirblock entry should not cache the file's sha1
1469
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1471
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1472
state._dirblock_state)
1473
mode = stat_value.st_mode
1474
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1477
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1478
state._dirblock_state)
1480
# If we do it again right away, we don't know if the file has changed
1481
# so we will re-read the file. Roll the clock back so the file is
1482
# guaranteed to look too new.
1483
state.adjust_time(-10)
1485
link_or_sha1 = state.update_entry(entry, abspath='a',
1486
stat_value=stat_value)
1487
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1488
('sha1', 'a'), ('is_exec', mode, False),
1490
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1492
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1493
state._dirblock_state)
1494
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1498
# However, if we move the clock forward so the file is considered
1499
# "stable", it should just cache the value.
1500
state.adjust_time(+20)
1501
link_or_sha1 = state.update_entry(entry, abspath='a',
1502
stat_value=stat_value)
1503
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1505
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1506
('sha1', 'a'), ('is_exec', mode, False),
1507
('sha1', 'a'), ('is_exec', mode, False),
1509
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1512
# Subsequent calls will just return the cached value
1513
link_or_sha1 = state.update_entry(entry, abspath='a',
1514
stat_value=stat_value)
1515
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1517
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1518
('sha1', 'a'), ('is_exec', mode, False),
1519
('sha1', 'a'), ('is_exec', mode, False),
1521
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1524
def test_update_entry_symlink(self):
1525
"""Update entry should read symlinks."""
1526
if not osutils.has_symlinks():
1527
# PlatformDeficiency / TestSkipped
1528
raise TestSkipped("No symlink support")
1529
state, entry = self.get_state_with_a()
1531
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1532
state._dirblock_state)
1533
os.symlink('target', 'a')
1535
state.adjust_time(-10) # Make the symlink look new
1536
stat_value = os.lstat('a')
1537
packed_stat = dirstate.pack_stat(stat_value)
1538
link_or_sha1 = state.update_entry(entry, abspath='a',
1539
stat_value=stat_value)
1540
self.assertEqual('target', link_or_sha1)
1541
self.assertEqual([('read_link', 'a', '')], state._log)
1542
# Dirblock is not updated (the link is too new)
1543
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1545
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1546
state._dirblock_state)
1548
# Because the stat_value looks new, we should re-read the target
1549
link_or_sha1 = state.update_entry(entry, abspath='a',
1550
stat_value=stat_value)
1551
self.assertEqual('target', link_or_sha1)
1552
self.assertEqual([('read_link', 'a', ''),
1553
('read_link', 'a', ''),
1555
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1557
state.adjust_time(+20) # Skip into the future, all files look old
1558
link_or_sha1 = state.update_entry(entry, abspath='a',
1559
stat_value=stat_value)
1560
self.assertEqual('target', link_or_sha1)
1561
# We need to re-read the link because only now can we cache it
1562
self.assertEqual([('read_link', 'a', ''),
1563
('read_link', 'a', ''),
1564
('read_link', 'a', ''),
1566
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1569
# Another call won't re-read the link
1570
self.assertEqual([('read_link', 'a', ''),
1571
('read_link', 'a', ''),
1572
('read_link', 'a', ''),
1574
link_or_sha1 = state.update_entry(entry, abspath='a',
1575
stat_value=stat_value)
1576
self.assertEqual('target', link_or_sha1)
1577
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1580
def do_update_entry(self, state, entry, abspath):
1581
stat_value = os.lstat(abspath)
1582
return state.update_entry(entry, abspath, stat_value)
1584
def test_update_entry_dir(self):
1585
state, entry = self.get_state_with_a()
1586
self.build_tree(['a/'])
1587
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1589
def test_update_entry_dir_unchanged(self):
1590
state, entry = self.get_state_with_a()
1591
self.build_tree(['a/'])
1592
state.adjust_time(+20)
1593
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1594
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1595
state._dirblock_state)
1597
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1598
state._dirblock_state)
1599
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1600
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1601
state._dirblock_state)
1603
def test_update_entry_file_unchanged(self):
1604
state, entry = self.get_state_with_a()
1605
self.build_tree(['a'])
1606
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1607
state.adjust_time(+20)
1608
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1609
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1610
state._dirblock_state)
1612
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1613
state._dirblock_state)
1614
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1615
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1616
state._dirblock_state)
1618
def create_and_test_file(self, state, entry):
1619
"""Create a file at 'a' and verify the state finds it.
1621
The state should already be versioning *something* at 'a'. This makes
1622
sure that state.update_entry recognizes it as a file.
1624
self.build_tree(['a'])
1625
stat_value = os.lstat('a')
1626
packed_stat = dirstate.pack_stat(stat_value)
1628
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1629
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1631
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1635
def create_and_test_dir(self, state, entry):
1636
"""Create a directory at 'a' and verify the state finds it.
1638
The state should already be versioning *something* at 'a'. This makes
1639
sure that state.update_entry recognizes it as a directory.
1641
self.build_tree(['a/'])
1642
stat_value = os.lstat('a')
1643
packed_stat = dirstate.pack_stat(stat_value)
1645
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1646
self.assertIs(None, link_or_sha1)
1647
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1651
def create_and_test_symlink(self, state, entry):
1652
"""Create a symlink at 'a' and verify the state finds it.
1654
The state should already be versioning *something* at 'a'. This makes
1655
sure that state.update_entry recognizes it as a symlink.
1657
This should not be called if this platform does not have symlink
1660
# caller should care about skipping test on platforms without symlinks
1661
os.symlink('path/to/foo', 'a')
1663
stat_value = os.lstat('a')
1664
packed_stat = dirstate.pack_stat(stat_value)
1666
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1667
self.assertEqual('path/to/foo', link_or_sha1)
1668
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1672
def test_update_file_to_dir(self):
1673
"""If a file changes to a directory we return None for the sha.
1674
We also update the inventory record.
1676
state, entry = self.get_state_with_a()
1677
# The file sha1 won't be cached unless the file is old
1678
state.adjust_time(+10)
1679
self.create_and_test_file(state, entry)
1681
self.create_and_test_dir(state, entry)
1683
def test_update_file_to_symlink(self):
1684
"""File becomes a symlink"""
1685
if not osutils.has_symlinks():
1686
# PlatformDeficiency / TestSkipped
1687
raise TestSkipped("No symlink support")
1688
state, entry = self.get_state_with_a()
1689
# The file sha1 won't be cached unless the file is old
1690
state.adjust_time(+10)
1691
self.create_and_test_file(state, entry)
1693
self.create_and_test_symlink(state, entry)
1695
def test_update_dir_to_file(self):
1696
"""Directory becoming a file updates the entry."""
1697
state, entry = self.get_state_with_a()
1698
# The file sha1 won't be cached unless the file is old
1699
state.adjust_time(+10)
1700
self.create_and_test_dir(state, entry)
1702
self.create_and_test_file(state, entry)
1704
def test_update_dir_to_symlink(self):
1705
"""Directory becomes a symlink"""
1706
if not osutils.has_symlinks():
1707
# PlatformDeficiency / TestSkipped
1708
raise TestSkipped("No symlink support")
1709
state, entry = self.get_state_with_a()
1710
# The symlink target won't be cached if it isn't old
1711
state.adjust_time(+10)
1712
self.create_and_test_dir(state, entry)
1714
self.create_and_test_symlink(state, entry)
1716
def test_update_symlink_to_file(self):
1717
"""Symlink becomes a file"""
1718
if not has_symlinks():
1719
raise TestSkipped("No symlink support")
1720
state, entry = self.get_state_with_a()
1721
# The symlink and file info won't be cached unless old
1722
state.adjust_time(+10)
1723
self.create_and_test_symlink(state, entry)
1725
self.create_and_test_file(state, entry)
1727
def test_update_symlink_to_dir(self):
1728
"""Symlink becomes a directory"""
1729
if not has_symlinks():
1730
raise TestSkipped("No symlink support")
1731
state, entry = self.get_state_with_a()
1732
# The symlink target won't be cached if it isn't old
1733
state.adjust_time(+10)
1734
self.create_and_test_symlink(state, entry)
1736
self.create_and_test_dir(state, entry)
1738
def test__is_executable_win32(self):
1739
state, entry = self.get_state_with_a()
1740
self.build_tree(['a'])
1742
# Make sure we are using the win32 implementation of _is_executable
1743
state._is_executable = state._is_executable_win32
1745
# The file on disk is not executable, but we are marking it as though
1746
# it is. With _is_executable_win32 we ignore what is on disk.
1747
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1749
stat_value = os.lstat('a')
1750
packed_stat = dirstate.pack_stat(stat_value)
1752
state.adjust_time(-10) # Make sure everything is new
1753
state.update_entry(entry, abspath='a', stat_value=stat_value)
1755
# The row is updated, but the executable bit stays set.
1756
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1759
# Make the disk object look old enough to cache
1760
state.adjust_time(+20)
1761
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1762
state.update_entry(entry, abspath='a', stat_value=stat_value)
1763
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1766
class TestPackStat(TestCaseWithTransport):
1768
def assertPackStat(self, expected, stat_value):
1769
"""Check the packed and serialized form of a stat value."""
1770
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1772
def test_pack_stat_int(self):
1773
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1774
# Make sure that all parameters have an impact on the packed stat.
1775
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1778
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1779
st.st_mtime = 1172758620
1781
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1782
st.st_ctime = 1172758630
1784
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1787
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1788
st.st_ino = 6499540L
1790
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1791
st.st_mode = 0100744
1793
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1795
def test_pack_stat_float(self):
1796
"""On some platforms mtime and ctime are floats.
1798
Make sure we don't get warnings or errors, and that we ignore changes <
1801
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1802
777L, 6499538L, 0100644)
1803
# These should all be the same as the integer counterparts
1804
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1805
st.st_mtime = 1172758620.0
1807
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1808
st.st_ctime = 1172758630.0
1810
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1811
# fractional seconds are discarded, so no change from above
1812
st.st_mtime = 1172758620.453
1813
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1814
st.st_ctime = 1172758630.228
1815
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1818
class TestBisect(TestCaseWithDirState):
1819
"""Test the ability to bisect into the disk format."""
1822
def assertBisect(self, expected_map, map_keys, state, paths):
1823
"""Assert that bisecting for paths returns the right result.
1825
:param expected_map: A map from key => entry value
1826
:param map_keys: The keys to expect for each path
1827
:param state: The DirState object.
1828
:param paths: A list of paths, these will automatically be split into
1829
(dir, name) tuples, and sorted according to how _bisect
1832
dir_names = sorted(osutils.split(p) for p in paths)
1833
result = state._bisect(dir_names)
1834
# For now, results are just returned in whatever order we read them.
1835
# We could sort by (dir, name, file_id) or something like that, but in
1836
# the end it would still be fairly arbitrary, and we don't want the
1837
# extra overhead if we can avoid it. So sort everything to make sure
1839
assert len(map_keys) == len(dir_names)
1841
for dir_name, keys in zip(dir_names, map_keys):
1843
# This should not be present in the output
1845
expected[dir_name] = sorted(expected_map[k] for k in keys)
1847
for dir_name in result:
1848
result[dir_name].sort()
1850
self.assertEqual(expected, result)
1852
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1853
"""Assert that bisecting for dirbblocks returns the right result.
1855
:param expected_map: A map from key => expected values
1856
:param map_keys: A nested list of paths we expect to be returned.
1857
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1858
:param state: The DirState object.
1859
:param paths: A list of directories
1861
result = state._bisect_dirblocks(paths)
1862
assert len(map_keys) == len(paths)
1865
for path, keys in zip(paths, map_keys):
1867
# This should not be present in the output
1869
expected[path] = sorted(expected_map[k] for k in keys)
1873
self.assertEqual(expected, result)
1875
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1876
"""Assert the return value of a recursive bisection.
1878
:param expected_map: A map from key => entry value
1879
:param map_keys: A list of paths we expect to be returned.
1880
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1881
:param state: The DirState object.
1882
:param paths: A list of files and directories. It will be broken up
1883
into (dir, name) pairs and sorted before calling _bisect_recursive.
1886
for key in map_keys:
1887
entry = expected_map[key]
1888
dir_name_id, trees_info = entry
1889
expected[dir_name_id] = trees_info
1891
dir_names = sorted(osutils.split(p) for p in paths)
1892
result = state._bisect_recursive(dir_names)
1894
self.assertEqual(expected, result)
1896
def test_bisect_each(self):
1897
"""Find a single record using bisect."""
1898
tree, state, expected = self.create_basic_dirstate()
1900
# Bisect should return the rows for the specified files.
1901
self.assertBisect(expected, [['']], state, [''])
1902
self.assertBisect(expected, [['a']], state, ['a'])
1903
self.assertBisect(expected, [['b']], state, ['b'])
1904
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1905
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1906
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1907
self.assertBisect(expected, [['f']], state, ['f'])
1909
def test_bisect_multi(self):
1910
"""Bisect can be used to find multiple records at the same time."""
1911
tree, state, expected = self.create_basic_dirstate()
1912
# Bisect should be capable of finding multiple entries at the same time
1913
self.assertBisect(expected, [['a'], ['b'], ['f']],
1914
state, ['a', 'b', 'f'])
1915
# ('', 'f') sorts before the others
1916
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1917
state, ['b/d', 'b/d/e', 'f'])
1919
def test_bisect_one_page(self):
1920
"""Test bisect when there is only 1 page to read"""
1921
tree, state, expected = self.create_basic_dirstate()
1922
state._bisect_page_size = 5000
1923
self.assertBisect(expected,[['']], state, [''])
1924
self.assertBisect(expected,[['a']], state, ['a'])
1925
self.assertBisect(expected,[['b']], state, ['b'])
1926
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1927
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1928
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1929
self.assertBisect(expected,[['f']], state, ['f'])
1930
self.assertBisect(expected,[['a'], ['b'], ['f']],
1931
state, ['a', 'b', 'f'])
1932
# ('', 'f') sorts before the others
1933
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1934
state, ['b/d', 'b/d/e', 'f'])
1936
def test_bisect_duplicate_paths(self):
1937
"""When bisecting for a path, handle multiple entries."""
1938
tree, state, expected = self.create_duplicated_dirstate()
1940
# Now make sure that both records are properly returned.
1941
self.assertBisect(expected, [['']], state, [''])
1942
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1943
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1944
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1945
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1946
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1948
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1950
def test_bisect_page_size_too_small(self):
1951
"""If the page size is too small, we will auto increase it."""
1952
tree, state, expected = self.create_basic_dirstate()
1953
state._bisect_page_size = 50
1954
self.assertBisect(expected, [None], state, ['b/e'])
1955
self.assertBisect(expected, [['a']], state, ['a'])
1956
self.assertBisect(expected, [['b']], state, ['b'])
1957
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1958
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1959
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1960
self.assertBisect(expected, [['f']], state, ['f'])
1962
def test_bisect_missing(self):
1963
"""Test that bisect return None if it cannot find a path."""
1964
tree, state, expected = self.create_basic_dirstate()
1965
self.assertBisect(expected, [None], state, ['foo'])
1966
self.assertBisect(expected, [None], state, ['b/foo'])
1967
self.assertBisect(expected, [None], state, ['bar/foo'])
1969
self.assertBisect(expected, [['a'], None, ['b/d']],
1970
state, ['a', 'foo', 'b/d'])
1972
def test_bisect_rename(self):
1973
"""Check that we find a renamed row."""
1974
tree, state, expected = self.create_renamed_dirstate()
1976
# Search for the pre and post renamed entries
1977
self.assertBisect(expected, [['a']], state, ['a'])
1978
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1979
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1980
self.assertBisect(expected, [['h']], state, ['h'])
1982
# What about b/d/e? shouldn't that also get 2 directory entries?
1983
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1984
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1986
def test_bisect_dirblocks(self):
1987
tree, state, expected = self.create_duplicated_dirstate()
1988
self.assertBisectDirBlocks(expected,
1989
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1990
self.assertBisectDirBlocks(expected,
1991
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1992
self.assertBisectDirBlocks(expected,
1993
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1994
self.assertBisectDirBlocks(expected,
1995
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1996
['b/c', 'b/c2', 'b/d', 'b/d2'],
1997
['b/d/e', 'b/d/e2'],
1998
], state, ['', 'b', 'b/d'])
2000
def test_bisect_dirblocks_missing(self):
2001
tree, state, expected = self.create_basic_dirstate()
2002
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2003
state, ['b/d', 'b/e'])
2004
# Files don't show up in this search
2005
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2006
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2007
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2008
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2009
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2011
def test_bisect_recursive_each(self):
2012
tree, state, expected = self.create_basic_dirstate()
2013
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2014
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2015
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2016
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2018
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2020
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
2024
def test_bisect_recursive_multiple(self):
2025
tree, state, expected = self.create_basic_dirstate()
2026
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2027
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2028
state, ['b/d', 'b/d/e'])
2030
def test_bisect_recursive_missing(self):
2031
tree, state, expected = self.create_basic_dirstate()
2032
self.assertBisectRecursive(expected, [], state, ['d'])
2033
self.assertBisectRecursive(expected, [], state, ['b/e'])
2034
self.assertBisectRecursive(expected, [], state, ['g'])
2035
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2037
def test_bisect_recursive_renamed(self):
2038
tree, state, expected = self.create_renamed_dirstate()
2040
# Looking for either renamed item should find the other
2041
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2042
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2043
# Looking in the containing directory should find the rename target,
2044
# and anything in a subdir of the renamed target.
2045
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2046
'b/d/e', 'b/g', 'h', 'h/e'],
2050
class TestBisectDirblock(TestCase):
2051
"""Test that bisect_dirblock() returns the expected values.
2053
bisect_dirblock is intended to work like bisect.bisect_left() except it
2054
knows it is working on dirblocks and that dirblocks are sorted by ('path',
2055
'to', 'foo') chunks rather than by raw 'path/to/foo'.
2058
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
2059
"""Assert that bisect_split works like bisect_left on the split paths.
2061
:param dirblocks: A list of (path, [info]) pairs.
2062
:param split_dirblocks: A list of ((split, path), [info]) pairs.
2063
:param path: The path we are indexing.
2065
All other arguments will be passed along.
2067
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
2069
split_dirblock = (path.split('/'), [])
2070
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
2072
self.assertEqual(bisect_left_idx, bisect_split_idx,
2073
'bisect_split disagreed. %s != %s'
2075
% (bisect_left_idx, bisect_split_idx, path)
2078
def paths_to_dirblocks(self, paths):
2079
"""Convert a list of paths into dirblock form.
2081
Also, ensure that the paths are in proper sorted order.
2083
dirblocks = [(path, []) for path in paths]
2084
split_dirblocks = [(path.split('/'), []) for path in paths]
2085
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
2086
return dirblocks, split_dirblocks
2088
def test_simple(self):
2089
"""In the simple case it works just like bisect_left"""
2090
paths = ['', 'a', 'b', 'c', 'd']
2091
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2093
self.assertBisect(dirblocks, split_dirblocks, path)
2094
self.assertBisect(dirblocks, split_dirblocks, '_')
2095
self.assertBisect(dirblocks, split_dirblocks, 'aa')
2096
self.assertBisect(dirblocks, split_dirblocks, 'bb')
2097
self.assertBisect(dirblocks, split_dirblocks, 'cc')
2098
self.assertBisect(dirblocks, split_dirblocks, 'dd')
2099
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
2100
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
2101
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
2102
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
2104
def test_involved(self):
2105
"""This is where bisect_left diverges slightly."""
2107
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2108
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2110
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2111
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2114
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2116
self.assertBisect(dirblocks, split_dirblocks, path)
2118
def test_involved_cached(self):
2119
"""This is where bisect_left diverges slightly."""
2121
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2122
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2124
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2125
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2129
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2131
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2134
class TestDirstateValidation(TestCaseWithDirState):
2136
def test_validate_correct_dirstate(self):
2137
state = self.create_complex_dirstate()
2140
# and make sure we can also validate with a read lock
2147
def test_dirblock_not_sorted(self):
2148
tree, state, expected = self.create_renamed_dirstate()
2149
state._read_dirblocks_if_needed()
2150
last_dirblock = state._dirblocks[-1]
2151
# we're appending to the dirblock, but this name comes before some of
2152
# the existing names; that's wrong
2153
last_dirblock[1].append(
2154
(('h', 'aaaa', 'a-id'),
2155
[('a', '', 0, False, ''),
2156
('a', '', 0, False, '')]))
2157
e = self.assertRaises(AssertionError,
2159
self.assertContainsRe(str(e), 'not sorted')
2161
def test_dirblock_name_mismatch(self):
2162
tree, state, expected = self.create_renamed_dirstate()
2163
state._read_dirblocks_if_needed()
2164
last_dirblock = state._dirblocks[-1]
2165
# add an entry with the wrong directory name
2166
last_dirblock[1].append(
2168
[('a', '', 0, False, ''),
2169
('a', '', 0, False, '')]))
2170
e = self.assertRaises(AssertionError,
2172
self.assertContainsRe(str(e),
2173
"doesn't match directory name")
2175
def test_dirblock_missing_rename(self):
2176
tree, state, expected = self.create_renamed_dirstate()
2177
state._read_dirblocks_if_needed()
2178
last_dirblock = state._dirblocks[-1]
2179
# make another entry for a-id, without a correct 'r' pointer to
2180
# the real occurrence in the working tree
2181
last_dirblock[1].append(
2182
(('h', 'z', 'a-id'),
2183
[('a', '', 0, False, ''),
2184
('a', '', 0, False, '')]))
2185
e = self.assertRaises(AssertionError,
2187
self.assertContainsRe(str(e),
2188
'file a-id is absent in row')