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
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
562
# We should have gotten a real sha1
563
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
566
# The dirblock has been updated
567
self.assertEqual(sha1sum, entry[1][0][1])
568
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
569
state._dirblock_state)
572
# Now, since we are the only one holding a lock, we should be able
573
# to save and have it written to disk
578
# Re-open the file, and ensure that the state has been updated.
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
self.assertEqual(sha1sum, entry[1][0][1])
587
def test_save_fails_quietly_if_locked(self):
588
"""If dirstate is locked, save will fail without complaining."""
589
self.build_tree(['a-file'])
590
state = dirstate.DirState.initialize('dirstate')
592
# No stat and no sha1 sum.
593
state.add('a-file', 'a-file-id', 'file', None, '')
598
state = dirstate.DirState.on_file('dirstate')
601
entry = state._get_entry(0, path_utf8='a-file')
602
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
603
# We should have gotten a real sha1
604
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
606
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
607
state._dirblock_state)
609
# Now, before we try to save, grab another dirstate, and take out a
611
# TODO: jam 20070315 Ideally this would be locked by another
612
# process. To make sure the file is really OS locked.
613
state2 = dirstate.DirState.on_file('dirstate')
616
# This won't actually write anything, because it couldn't grab
617
# a write lock. But it shouldn't raise an error, either.
618
# TODO: jam 20070315 We should probably distinguish between
619
# being dirty because of 'update_entry'. And dirty
620
# because of real modification. So that save() *does*
621
# raise a real error if it fails when we have real
629
# The file on disk should not be modified.
630
state = dirstate.DirState.on_file('dirstate')
633
entry = state._get_entry(0, path_utf8='a-file')
634
self.assertEqual('', entry[1][0][1])
639
class TestDirStateInitialize(TestCaseWithDirState):
641
def test_initialize(self):
642
expected_result = ([], [
643
(('', '', 'TREE_ROOT'), # common details
644
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
647
state = dirstate.DirState.initialize('dirstate')
649
self.assertIsInstance(state, dirstate.DirState)
650
lines = state.get_lines()
653
# On win32 you can't read from a locked file, even within the same
654
# process. So we have to unlock and release before we check the file
656
self.assertFileEqual(''.join(lines), 'dirstate')
657
state.lock_read() # check_state_with_reopen will unlock
658
self.check_state_with_reopen(expected_result, state)
661
class TestDirStateManipulations(TestCaseWithDirState):
663
def test_set_state_from_inventory_no_content_no_parents(self):
664
# setting the current inventory is a slow but important api to support.
665
tree1 = self.make_branch_and_memory_tree('tree1')
669
revid1 = tree1.commit('foo').encode('utf8')
670
root_id = tree1.inventory.root.file_id
671
inv = tree1.inventory
674
expected_result = [], [
675
(('', '', root_id), [
676
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
677
state = dirstate.DirState.initialize('dirstate')
679
state.set_state_from_inventory(inv)
680
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
682
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
683
state._dirblock_state)
688
# This will unlock it
689
self.check_state_with_reopen(expected_result, state)
691
def test_set_path_id_no_parents(self):
692
"""The id of a path can be changed trivally with no parents."""
693
state = dirstate.DirState.initialize('dirstate')
695
# check precondition to be sure the state does change appropriately.
697
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
698
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
699
list(state._iter_entries()))
700
state.set_path_id('', 'foobarbaz')
702
(('', '', 'foobarbaz'), [('d', '', 0, False,
703
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
704
self.assertEqual(expected_rows, list(state._iter_entries()))
705
# should work across save too
709
state = dirstate.DirState.on_file('dirstate')
713
self.assertEqual(expected_rows, list(state._iter_entries()))
717
def test_set_path_id_with_parents(self):
718
"""Set the root file id in a dirstate with parents"""
719
mt = self.make_branch_and_tree('mt')
720
# in case the default tree format uses a different root id
721
mt.set_root_id('TREE_ROOT')
722
mt.commit('foo', rev_id='parent-revid')
723
rt = mt.branch.repository.revision_tree('parent-revid')
724
state = dirstate.DirState.initialize('dirstate')
727
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
728
state.set_path_id('', 'foobarbaz')
730
# now see that it is what we expected
732
(('', '', 'TREE_ROOT'),
733
[('a', '', 0, False, ''),
734
('d', '', 0, False, 'parent-revid'),
736
(('', '', 'foobarbaz'),
737
[('d', '', 0, False, ''),
738
('a', '', 0, False, ''),
742
self.assertEqual(expected_rows, list(state._iter_entries()))
743
# should work across save too
747
# now flush & check we get the same
748
state = dirstate.DirState.on_file('dirstate')
752
self.assertEqual(expected_rows, list(state._iter_entries()))
755
# now change within an existing file-backed state
759
state.set_path_id('', 'tree-root-2')
765
def test_set_parent_trees_no_content(self):
766
# set_parent_trees is a slow but important api to support.
767
tree1 = self.make_branch_and_memory_tree('tree1')
771
revid1 = tree1.commit('foo')
774
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
775
tree2 = MemoryTree.create_on_branch(branch2)
778
revid2 = tree2.commit('foo')
779
root_id = tree2.inventory.root.file_id
782
state = dirstate.DirState.initialize('dirstate')
784
state.set_path_id('', root_id)
785
state.set_parent_trees(
786
((revid1, tree1.branch.repository.revision_tree(revid1)),
787
(revid2, tree2.branch.repository.revision_tree(revid2)),
788
('ghost-rev', None)),
790
# check we can reopen and use the dirstate after setting parent
797
state = dirstate.DirState.on_file('dirstate')
800
self.assertEqual([revid1, revid2, 'ghost-rev'],
801
state.get_parent_ids())
802
# iterating the entire state ensures that the state is parsable.
803
list(state._iter_entries())
804
# be sure that it sets not appends - change it
805
state.set_parent_trees(
806
((revid1, tree1.branch.repository.revision_tree(revid1)),
807
('ghost-rev', None)),
809
# and now put it back.
810
state.set_parent_trees(
811
((revid1, tree1.branch.repository.revision_tree(revid1)),
812
(revid2, tree2.branch.repository.revision_tree(revid2)),
813
('ghost-rev', tree2.branch.repository.revision_tree(None))),
815
self.assertEqual([revid1, revid2, 'ghost-rev'],
816
state.get_parent_ids())
817
# the ghost should be recorded as such by set_parent_trees.
818
self.assertEqual(['ghost-rev'], state.get_ghosts())
820
[(('', '', root_id), [
821
('d', '', 0, False, dirstate.DirState.NULLSTAT),
822
('d', '', 0, False, revid1),
823
('d', '', 0, False, revid2)
825
list(state._iter_entries()))
829
def test_set_parent_trees_file_missing_from_tree(self):
830
# Adding a parent tree may reference files not in the current state.
831
# they should get listed just once by id, even if they are in two
833
# set_parent_trees is a slow but important api to support.
834
tree1 = self.make_branch_and_memory_tree('tree1')
838
tree1.add(['a file'], ['file-id'], ['file'])
839
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
840
revid1 = tree1.commit('foo')
843
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
844
tree2 = MemoryTree.create_on_branch(branch2)
847
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
848
revid2 = tree2.commit('foo')
849
root_id = tree2.inventory.root.file_id
852
# check the layout in memory
853
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
854
(('', '', root_id), [
855
('d', '', 0, False, dirstate.DirState.NULLSTAT),
856
('d', '', 0, False, revid1.encode('utf8')),
857
('d', '', 0, False, revid2.encode('utf8'))
859
(('', 'a file', 'file-id'), [
860
('a', '', 0, False, ''),
861
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
862
revid1.encode('utf8')),
863
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
864
revid2.encode('utf8'))
867
state = dirstate.DirState.initialize('dirstate')
869
state.set_path_id('', root_id)
870
state.set_parent_trees(
871
((revid1, tree1.branch.repository.revision_tree(revid1)),
872
(revid2, tree2.branch.repository.revision_tree(revid2)),
878
# check_state_with_reopen will unlock
879
self.check_state_with_reopen(expected_result, state)
881
### add a path via _set_data - so we dont need delta work, just
882
# raw data in, and ensure that it comes out via get_lines happily.
884
def test_add_path_to_root_no_parents_all_data(self):
885
# The most trivial addition of a path is when there are no parents and
886
# its in the root and all data about the file is supplied
887
self.build_tree(['a file'])
888
stat = os.lstat('a file')
889
# the 1*20 is the sha1 pretend value.
890
state = dirstate.DirState.initialize('dirstate')
892
(('', '', 'TREE_ROOT'), [
893
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
895
(('', 'a file', 'a file id'), [
896
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
900
state.add('a file', 'a file id', 'file', stat, '1'*20)
901
# having added it, it should be in the output of iter_entries.
902
self.assertEqual(expected_entries, list(state._iter_entries()))
903
# saving and reloading should not affect this.
907
state = dirstate.DirState.on_file('dirstate')
910
self.assertEqual(expected_entries, list(state._iter_entries()))
914
def test_add_path_to_unversioned_directory(self):
915
"""Adding a path to an unversioned directory should error.
917
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
918
once dirstate is stable and if it is merged with WorkingTree3, consider
919
removing this copy of the test.
921
self.build_tree(['unversioned/', 'unversioned/a file'])
922
state = dirstate.DirState.initialize('dirstate')
924
self.assertRaises(errors.NotVersionedError, state.add,
925
'unversioned/a file', 'a file id', 'file', None, None)
929
def test_add_directory_to_root_no_parents_all_data(self):
930
# The most trivial addition of a dir is when there are no parents and
931
# its in the root and all data about the file is supplied
932
self.build_tree(['a dir/'])
933
stat = os.lstat('a dir')
935
(('', '', 'TREE_ROOT'), [
936
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
938
(('', 'a dir', 'a dir id'), [
939
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
942
state = dirstate.DirState.initialize('dirstate')
944
state.add('a dir', 'a dir id', 'directory', stat, None)
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')
955
self.assertEqual(expected_entries, list(state._iter_entries()))
959
def test_add_symlink_to_root_no_parents_all_data(self):
960
# The most trivial addition of a symlink when there are no parents and
961
# its in the root and all data about the file is supplied
962
# bzr doesn't support fake symlinks on windows, yet.
963
if not has_symlinks():
964
raise TestSkipped("No symlink support")
965
os.symlink('target', 'a link')
966
stat = os.lstat('a link')
968
(('', '', 'TREE_ROOT'), [
969
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
971
(('', 'a link', 'a link id'), [
972
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
975
state = dirstate.DirState.initialize('dirstate')
977
state.add('a link', 'a link id', 'symlink', stat, 'target')
978
# having added it, it should be in the output of iter_entries.
979
self.assertEqual(expected_entries, list(state._iter_entries()))
980
# saving and reloading should not affect this.
984
state = dirstate.DirState.on_file('dirstate')
987
self.assertEqual(expected_entries, list(state._iter_entries()))
991
def test_add_directory_and_child_no_parents_all_data(self):
992
# after adding a directory, we should be able to add children to it.
993
self.build_tree(['a dir/', 'a dir/a file'])
994
dirstat = os.lstat('a dir')
995
filestat = os.lstat('a dir/a file')
997
(('', '', 'TREE_ROOT'), [
998
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1000
(('', 'a dir', 'a dir id'), [
1001
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1003
(('a dir', 'a file', 'a file id'), [
1004
('f', '1'*20, 25, False,
1005
dirstate.pack_stat(filestat)), # current tree details
1008
state = dirstate.DirState.initialize('dirstate')
1010
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1011
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1012
# added it, it should be in the output of iter_entries.
1013
self.assertEqual(expected_entries, list(state._iter_entries()))
1014
# saving and reloading should not affect this.
1018
state = dirstate.DirState.on_file('dirstate')
1021
self.assertEqual(expected_entries, list(state._iter_entries()))
1025
def test_add_tree_reference(self):
1026
# make a dirstate and add a tree reference
1027
state = dirstate.DirState.initialize('dirstate')
1029
('', 'subdir', 'subdir-id'),
1030
[('t', 'subtree-123123', 0, False,
1031
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1034
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1035
entry = state._get_entry(0, 'subdir-id', 'subdir')
1036
self.assertEqual(entry, expected_entry)
1041
# now check we can read it back
1045
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1046
self.assertEqual(entry, entry2)
1047
self.assertEqual(entry, expected_entry)
1048
# and lookup by id should work too
1049
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1050
self.assertEqual(entry, expected_entry)
1054
def test_add_forbidden_names(self):
1055
state = dirstate.DirState.initialize('dirstate')
1056
self.addCleanup(state.unlock)
1057
self.assertRaises(errors.BzrError,
1058
state.add, '.', 'ass-id', 'directory', None, None)
1059
self.assertRaises(errors.BzrError,
1060
state.add, '..', 'ass-id', 'directory', None, None)
1063
class TestGetLines(TestCaseWithDirState):
1065
def test_get_line_with_2_rows(self):
1066
state = self.create_dirstate_with_root_and_subdir()
1068
self.assertEqual(['#bazaar dirstate flat format 3\n',
1069
'crc32: 41262208\n',
1073
'\x00\x00a-root-value\x00'
1074
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1075
'\x00subdir\x00subdir-id\x00'
1076
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1077
], state.get_lines())
1081
def test_entry_to_line(self):
1082
state = self.create_dirstate_with_root()
1085
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1086
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1087
state._entry_to_line(state._dirblocks[0][1][0]))
1091
def test_entry_to_line_with_parent(self):
1092
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1093
root_entry = ('', '', 'a-root-value'), [
1094
('d', '', 0, False, packed_stat), # current tree details
1095
# first: a pointer to the current location
1096
('a', 'dirname/basename', 0, False, ''),
1098
state = dirstate.DirState.initialize('dirstate')
1101
'\x00\x00a-root-value\x00'
1102
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1103
'a\x00dirname/basename\x000\x00n\x00',
1104
state._entry_to_line(root_entry))
1108
def test_entry_to_line_with_two_parents_at_different_paths(self):
1109
# / in the tree, at / in one parent and /dirname/basename in the other.
1110
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1111
root_entry = ('', '', 'a-root-value'), [
1112
('d', '', 0, False, packed_stat), # current tree details
1113
('d', '', 0, False, 'rev_id'), # first parent details
1114
# second: a pointer to the current location
1115
('a', 'dirname/basename', 0, False, ''),
1117
state = dirstate.DirState.initialize('dirstate')
1120
'\x00\x00a-root-value\x00'
1121
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1122
'd\x00\x000\x00n\x00rev_id\x00'
1123
'a\x00dirname/basename\x000\x00n\x00',
1124
state._entry_to_line(root_entry))
1128
def test_iter_entries(self):
1129
# we should be able to iterate the dirstate entries from end to end
1130
# this is for get_lines to be easy to read.
1131
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1133
root_entries = [(('', '', 'a-root-value'), [
1134
('d', '', 0, False, packed_stat), # current tree details
1136
dirblocks.append(('', root_entries))
1137
# add two files in the root
1138
subdir_entry = ('', 'subdir', 'subdir-id'), [
1139
('d', '', 0, False, packed_stat), # current tree details
1141
afile_entry = ('', 'afile', 'afile-id'), [
1142
('f', 'sha1value', 34, False, packed_stat), # current tree details
1144
dirblocks.append(('', [subdir_entry, afile_entry]))
1146
file_entry2 = ('subdir', '2file', '2file-id'), [
1147
('f', 'sha1value', 23, False, packed_stat), # current tree details
1149
dirblocks.append(('subdir', [file_entry2]))
1150
state = dirstate.DirState.initialize('dirstate')
1152
state._set_data([], dirblocks)
1153
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1155
self.assertEqual(expected_entries, list(state._iter_entries()))
1160
class TestGetBlockRowIndex(TestCaseWithDirState):
1162
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1163
file_present, state, dirname, basename, tree_index):
1164
self.assertEqual((block_index, row_index, dir_present, file_present),
1165
state._get_block_entry_index(dirname, basename, tree_index))
1167
block = state._dirblocks[block_index]
1168
self.assertEqual(dirname, block[0])
1169
if dir_present and file_present:
1170
row = state._dirblocks[block_index][1][row_index]
1171
self.assertEqual(dirname, row[0][0])
1172
self.assertEqual(basename, row[0][1])
1174
def test_simple_structure(self):
1175
state = self.create_dirstate_with_root_and_subdir()
1176
self.addCleanup(state.unlock)
1177
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1178
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1179
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1180
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1181
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1184
def test_complex_structure_exists(self):
1185
state = self.create_complex_dirstate()
1186
self.addCleanup(state.unlock)
1187
# Make sure we can find everything that exists
1188
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1189
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1190
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1191
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1192
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1193
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1194
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1195
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1196
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1197
'b', 'h\xc3\xa5', 0)
1199
def test_complex_structure_missing(self):
1200
state = self.create_complex_dirstate()
1201
self.addCleanup(state.unlock)
1202
# Make sure things would be inserted in the right locations
1203
# '_' comes before 'a'
1204
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1205
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1206
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1207
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1209
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1210
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1211
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1212
# This would be inserted between a/ and b/
1213
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1215
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1218
class TestGetEntry(TestCaseWithDirState):
1220
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1221
"""Check that the right entry is returned for a request to getEntry."""
1222
entry = state._get_entry(index, path_utf8=path)
1224
self.assertEqual((None, None), entry)
1227
self.assertEqual((dirname, basename, file_id), cur[:3])
1229
def test_simple_structure(self):
1230
state = self.create_dirstate_with_root_and_subdir()
1231
self.addCleanup(state.unlock)
1232
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1233
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1234
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1235
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1236
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1238
def test_complex_structure_exists(self):
1239
state = self.create_complex_dirstate()
1240
self.addCleanup(state.unlock)
1241
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1242
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1243
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1244
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1245
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1246
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1247
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1248
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1249
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1252
def test_complex_structure_missing(self):
1253
state = self.create_complex_dirstate()
1254
self.addCleanup(state.unlock)
1255
self.assertEntryEqual(None, None, None, state, '_', 0)
1256
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1257
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1258
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1260
def test_get_entry_uninitialized(self):
1261
"""Calling get_entry will load data if it needs to"""
1262
state = self.create_dirstate_with_root()
1268
state = dirstate.DirState.on_file('dirstate')
1271
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1272
state._header_state)
1273
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1274
state._dirblock_state)
1275
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1280
class TestDirstateSortOrder(TestCaseWithTransport):
1281
"""Test that DirState adds entries in the right order."""
1283
def test_add_sorting(self):
1284
"""Add entries in lexicographical order, we get path sorted order.
1286
This tests it to a depth of 4, to make sure we don't just get it right
1287
at a single depth. 'a/a' should come before 'a-a', even though it
1288
doesn't lexicographically.
1290
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1291
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1294
state = dirstate.DirState.initialize('dirstate')
1295
self.addCleanup(state.unlock)
1297
fake_stat = os.stat('dirstate')
1299
d_id = d.replace('/', '_')+'-id'
1300
file_path = d + '/f'
1301
file_id = file_path.replace('/', '_')+'-id'
1302
state.add(d, d_id, 'directory', fake_stat, null_sha)
1303
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1305
expected = ['', '', 'a',
1306
'a/a', 'a/a/a', 'a/a/a/a',
1307
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1309
split = lambda p:p.split('/')
1310
self.assertEqual(sorted(expected, key=split), expected)
1311
dirblock_names = [d[0] for d in state._dirblocks]
1312
self.assertEqual(expected, dirblock_names)
1314
def test_set_parent_trees_correct_order(self):
1315
"""After calling set_parent_trees() we should maintain the order."""
1316
dirs = ['a', 'a-a', 'a/a']
1318
state = dirstate.DirState.initialize('dirstate')
1319
self.addCleanup(state.unlock)
1321
fake_stat = os.stat('dirstate')
1323
d_id = d.replace('/', '_')+'-id'
1324
file_path = d + '/f'
1325
file_id = file_path.replace('/', '_')+'-id'
1326
state.add(d, d_id, 'directory', fake_stat, null_sha)
1327
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1329
expected = ['', '', 'a', 'a/a', 'a-a']
1330
dirblock_names = [d[0] for d in state._dirblocks]
1331
self.assertEqual(expected, dirblock_names)
1333
# *really* cheesy way to just get an empty tree
1334
repo = self.make_repository('repo')
1335
empty_tree = repo.revision_tree(None)
1336
state.set_parent_trees([('null:', empty_tree)], [])
1338
dirblock_names = [d[0] for d in state._dirblocks]
1339
self.assertEqual(expected, dirblock_names)
1342
class InstrumentedDirState(dirstate.DirState):
1343
"""An DirState with instrumented sha1 functionality."""
1345
def __init__(self, path):
1346
super(InstrumentedDirState, self).__init__(path)
1347
self._time_offset = 0
1350
def _sha_cutoff_time(self):
1351
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1352
self._cutoff_time = timestamp + self._time_offset
1354
def _sha1_file(self, abspath, entry):
1355
self._log.append(('sha1', abspath))
1356
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1358
def _read_link(self, abspath, old_link):
1359
self._log.append(('read_link', abspath, old_link))
1360
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1362
def _lstat(self, abspath, entry):
1363
self._log.append(('lstat', abspath))
1364
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1366
def _is_executable(self, mode, old_executable):
1367
self._log.append(('is_exec', mode, old_executable))
1368
return super(InstrumentedDirState, self)._is_executable(mode,
1371
def adjust_time(self, secs):
1372
"""Move the clock forward or back.
1374
:param secs: The amount to adjust the clock by. Positive values make it
1375
seem as if we are in the future, negative values make it seem like we
1378
self._time_offset += secs
1379
self._cutoff_time = None
1382
class _FakeStat(object):
1383
"""A class with the same attributes as a real stat result."""
1385
def __init__(self, size, mtime, ctime, dev, ino, mode):
1387
self.st_mtime = mtime
1388
self.st_ctime = ctime
1394
class TestUpdateEntry(TestCaseWithDirState):
1395
"""Test the DirState.update_entry functions"""
1397
def get_state_with_a(self):
1398
"""Create a DirState tracking a single object named 'a'"""
1399
state = InstrumentedDirState.initialize('dirstate')
1400
self.addCleanup(state.unlock)
1401
state.add('a', 'a-id', 'file', None, '')
1402
entry = state._get_entry(0, path_utf8='a')
1405
def test_update_entry(self):
1406
state, entry = self.get_state_with_a()
1407
self.build_tree(['a'])
1408
# Add one where we don't provide the stat or sha already
1409
self.assertEqual(('', 'a', 'a-id'), entry[0])
1410
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1412
# Flush the buffers to disk
1414
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1415
state._dirblock_state)
1417
stat_value = os.lstat('a')
1418
packed_stat = dirstate.pack_stat(stat_value)
1419
link_or_sha1 = state.update_entry(entry, abspath='a',
1420
stat_value=stat_value)
1421
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1424
# The dirblock entry should be updated with the new info
1425
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1427
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1428
state._dirblock_state)
1429
mode = stat_value.st_mode
1430
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1433
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1434
state._dirblock_state)
1436
# If we do it again right away, we don't know if the file has changed
1437
# so we will re-read the file. Roll the clock back so the file is
1438
# guaranteed to look too new.
1439
state.adjust_time(-10)
1441
link_or_sha1 = state.update_entry(entry, abspath='a',
1442
stat_value=stat_value)
1443
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1444
('sha1', 'a'), ('is_exec', mode, False),
1446
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1448
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1449
state._dirblock_state)
1452
# However, if we move the clock forward so the file is considered
1453
# "stable", it should just returned the cached value.
1454
state.adjust_time(20)
1455
link_or_sha1 = state.update_entry(entry, abspath='a',
1456
stat_value=stat_value)
1457
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1459
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1460
('sha1', 'a'), ('is_exec', mode, False),
1463
def test_update_entry_no_stat_value(self):
1464
"""Passing the stat_value is optional."""
1465
state, entry = self.get_state_with_a()
1466
state.adjust_time(-10) # Make sure the file looks new
1467
self.build_tree(['a'])
1468
# Add one where we don't provide the stat or sha already
1469
link_or_sha1 = state.update_entry(entry, abspath='a')
1470
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1472
stat_value = os.lstat('a')
1473
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1474
('is_exec', stat_value.st_mode, False),
1477
def test_update_entry_symlink(self):
1478
"""Update entry should read symlinks."""
1479
if not osutils.has_symlinks():
1480
# PlatformDeficiency / TestSkipped
1481
raise TestSkipped("No symlink support")
1482
state, entry = self.get_state_with_a()
1484
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1485
state._dirblock_state)
1486
os.symlink('target', 'a')
1488
state.adjust_time(-10) # Make the symlink look new
1489
stat_value = os.lstat('a')
1490
packed_stat = dirstate.pack_stat(stat_value)
1491
link_or_sha1 = state.update_entry(entry, abspath='a',
1492
stat_value=stat_value)
1493
self.assertEqual('target', link_or_sha1)
1494
self.assertEqual([('read_link', 'a', '')], state._log)
1495
# Dirblock is updated
1496
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1498
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1499
state._dirblock_state)
1501
# Because the stat_value looks new, we should re-read the target
1502
link_or_sha1 = state.update_entry(entry, abspath='a',
1503
stat_value=stat_value)
1504
self.assertEqual('target', link_or_sha1)
1505
self.assertEqual([('read_link', 'a', ''),
1506
('read_link', 'a', 'target'),
1508
state.adjust_time(+20) # Skip into the future, all files look old
1509
link_or_sha1 = state.update_entry(entry, abspath='a',
1510
stat_value=stat_value)
1511
self.assertEqual('target', link_or_sha1)
1512
# There should not be a new read_link call.
1513
# (this is a weak assertion, because read_link is fairly inexpensive,
1514
# versus the number of symlinks that we would have)
1515
self.assertEqual([('read_link', 'a', ''),
1516
('read_link', 'a', 'target'),
1519
def test_update_entry_dir(self):
1520
state, entry = self.get_state_with_a()
1521
self.build_tree(['a/'])
1522
self.assertIs(None, state.update_entry(entry, 'a'))
1524
def create_and_test_file(self, state, entry):
1525
"""Create a file at 'a' and verify the state finds it.
1527
The state should already be versioning *something* at 'a'. This makes
1528
sure that state.update_entry recognizes it as a file.
1530
self.build_tree(['a'])
1531
stat_value = os.lstat('a')
1532
packed_stat = dirstate.pack_stat(stat_value)
1534
link_or_sha1 = state.update_entry(entry, abspath='a')
1535
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1537
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1541
def create_and_test_dir(self, state, entry):
1542
"""Create a directory at 'a' and verify the state finds it.
1544
The state should already be versioning *something* at 'a'. This makes
1545
sure that state.update_entry recognizes it as a directory.
1547
self.build_tree(['a/'])
1548
stat_value = os.lstat('a')
1549
packed_stat = dirstate.pack_stat(stat_value)
1551
link_or_sha1 = state.update_entry(entry, abspath='a')
1552
self.assertIs(None, link_or_sha1)
1553
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1557
def create_and_test_symlink(self, state, entry):
1558
"""Create a symlink at 'a' and verify the state finds it.
1560
The state should already be versioning *something* at 'a'. This makes
1561
sure that state.update_entry recognizes it as a symlink.
1563
This should not be called if this platform does not have symlink
1566
# caller should care about skipping test on platforms without symlinks
1567
os.symlink('path/to/foo', 'a')
1569
stat_value = os.lstat('a')
1570
packed_stat = dirstate.pack_stat(stat_value)
1572
link_or_sha1 = state.update_entry(entry, abspath='a')
1573
self.assertEqual('path/to/foo', link_or_sha1)
1574
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1578
def test_update_missing_file(self):
1579
state, entry = self.get_state_with_a()
1580
packed_stat = self.create_and_test_file(state, entry)
1581
# Now if we delete the file, update_entry should recover and
1584
self.assertIs(None, state.update_entry(entry, abspath='a'))
1585
# And the record shouldn't be changed.
1586
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1587
self.assertEqual([('f', digest, 14, False, packed_stat)],
1590
def test_update_missing_dir(self):
1591
state, entry = self.get_state_with_a()
1592
packed_stat = self.create_and_test_dir(state, entry)
1593
# Now if we delete the directory, update_entry should recover and
1596
self.assertIs(None, state.update_entry(entry, abspath='a'))
1597
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1599
def test_update_missing_symlink(self):
1600
if not osutils.has_symlinks():
1601
# PlatformDeficiency / TestSkipped
1602
raise TestSkipped("No symlink support")
1603
state, entry = self.get_state_with_a()
1604
packed_stat = self.create_and_test_symlink(state, entry)
1606
self.assertIs(None, state.update_entry(entry, abspath='a'))
1607
# And the record shouldn't be changed.
1608
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1611
def test_update_file_to_dir(self):
1612
"""If a file changes to a directory we return None for the sha.
1613
We also update the inventory record.
1615
state, entry = self.get_state_with_a()
1616
self.create_and_test_file(state, entry)
1618
self.create_and_test_dir(state, entry)
1620
def test_update_file_to_symlink(self):
1621
"""File becomes a symlink"""
1622
if not osutils.has_symlinks():
1623
# PlatformDeficiency / TestSkipped
1624
raise TestSkipped("No symlink support")
1625
state, entry = self.get_state_with_a()
1626
self.create_and_test_file(state, entry)
1628
self.create_and_test_symlink(state, entry)
1630
def test_update_dir_to_file(self):
1631
"""Directory becoming a file updates the entry."""
1632
state, entry = self.get_state_with_a()
1633
self.create_and_test_dir(state, entry)
1635
self.create_and_test_file(state, entry)
1637
def test_update_dir_to_symlink(self):
1638
"""Directory becomes a symlink"""
1639
if not osutils.has_symlinks():
1640
# PlatformDeficiency / TestSkipped
1641
raise TestSkipped("No symlink support")
1642
state, entry = self.get_state_with_a()
1643
self.create_and_test_dir(state, entry)
1645
self.create_and_test_symlink(state, entry)
1647
def test_update_symlink_to_file(self):
1648
"""Symlink becomes a file"""
1649
if not has_symlinks():
1650
raise TestSkipped("No symlink support")
1651
state, entry = self.get_state_with_a()
1652
self.create_and_test_symlink(state, entry)
1654
self.create_and_test_file(state, entry)
1656
def test_update_symlink_to_dir(self):
1657
"""Symlink becomes a directory"""
1658
if not has_symlinks():
1659
raise TestSkipped("No symlink support")
1660
state, entry = self.get_state_with_a()
1661
self.create_and_test_symlink(state, entry)
1663
self.create_and_test_dir(state, entry)
1665
def test__is_executable_win32(self):
1666
state, entry = self.get_state_with_a()
1667
self.build_tree(['a'])
1669
# Make sure we are using the win32 implementation of _is_executable
1670
state._is_executable = state._is_executable_win32
1672
# The file on disk is not executable, but we are marking it as though
1673
# it is. With _is_executable_win32 we ignore what is on disk.
1674
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1676
stat_value = os.lstat('a')
1677
packed_stat = dirstate.pack_stat(stat_value)
1679
state.adjust_time(-10) # Make sure everything is new
1680
# Make sure it wants to kkkkkkkk
1681
state.update_entry(entry, abspath='a', stat_value=stat_value)
1683
# The row is updated, but the executable bit stays set.
1684
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1685
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1688
class TestPackStat(TestCaseWithTransport):
1690
def assertPackStat(self, expected, stat_value):
1691
"""Check the packed and serialized form of a stat value."""
1692
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1694
def test_pack_stat_int(self):
1695
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1696
# Make sure that all parameters have an impact on the packed stat.
1697
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1700
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1701
st.st_mtime = 1172758620
1703
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1704
st.st_ctime = 1172758630
1706
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1709
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1710
st.st_ino = 6499540L
1712
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1713
st.st_mode = 0100744
1715
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1717
def test_pack_stat_float(self):
1718
"""On some platforms mtime and ctime are floats.
1720
Make sure we don't get warnings or errors, and that we ignore changes <
1723
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1724
777L, 6499538L, 0100644)
1725
# These should all be the same as the integer counterparts
1726
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1727
st.st_mtime = 1172758620.0
1729
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1730
st.st_ctime = 1172758630.0
1732
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1733
# fractional seconds are discarded, so no change from above
1734
st.st_mtime = 1172758620.453
1735
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1736
st.st_ctime = 1172758630.228
1737
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1740
class TestBisect(TestCaseWithDirState):
1741
"""Test the ability to bisect into the disk format."""
1744
def assertBisect(self, expected_map, map_keys, state, paths):
1745
"""Assert that bisecting for paths returns the right result.
1747
:param expected_map: A map from key => entry value
1748
:param map_keys: The keys to expect for each path
1749
:param state: The DirState object.
1750
:param paths: A list of paths, these will automatically be split into
1751
(dir, name) tuples, and sorted according to how _bisect
1754
dir_names = sorted(osutils.split(p) for p in paths)
1755
result = state._bisect(dir_names)
1756
# For now, results are just returned in whatever order we read them.
1757
# We could sort by (dir, name, file_id) or something like that, but in
1758
# the end it would still be fairly arbitrary, and we don't want the
1759
# extra overhead if we can avoid it. So sort everything to make sure
1761
assert len(map_keys) == len(dir_names)
1763
for dir_name, keys in zip(dir_names, map_keys):
1765
# This should not be present in the output
1767
expected[dir_name] = sorted(expected_map[k] for k in keys)
1769
for dir_name in result:
1770
result[dir_name].sort()
1772
self.assertEqual(expected, result)
1774
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1775
"""Assert that bisecting for dirbblocks returns the right result.
1777
:param expected_map: A map from key => expected values
1778
:param map_keys: A nested list of paths we expect to be returned.
1779
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1780
:param state: The DirState object.
1781
:param paths: A list of directories
1783
result = state._bisect_dirblocks(paths)
1784
assert len(map_keys) == len(paths)
1787
for path, keys in zip(paths, map_keys):
1789
# This should not be present in the output
1791
expected[path] = sorted(expected_map[k] for k in keys)
1795
self.assertEqual(expected, result)
1797
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1798
"""Assert the return value of a recursive bisection.
1800
:param expected_map: A map from key => entry value
1801
:param map_keys: A list of paths we expect to be returned.
1802
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1803
:param state: The DirState object.
1804
:param paths: A list of files and directories. It will be broken up
1805
into (dir, name) pairs and sorted before calling _bisect_recursive.
1808
for key in map_keys:
1809
entry = expected_map[key]
1810
dir_name_id, trees_info = entry
1811
expected[dir_name_id] = trees_info
1813
dir_names = sorted(osutils.split(p) for p in paths)
1814
result = state._bisect_recursive(dir_names)
1816
self.assertEqual(expected, result)
1818
def test_bisect_each(self):
1819
"""Find a single record using bisect."""
1820
tree, state, expected = self.create_basic_dirstate()
1822
# Bisect should return the rows for the specified files.
1823
self.assertBisect(expected, [['']], state, [''])
1824
self.assertBisect(expected, [['a']], state, ['a'])
1825
self.assertBisect(expected, [['b']], state, ['b'])
1826
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1827
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1828
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1829
self.assertBisect(expected, [['f']], state, ['f'])
1831
def test_bisect_multi(self):
1832
"""Bisect can be used to find multiple records at the same time."""
1833
tree, state, expected = self.create_basic_dirstate()
1834
# Bisect should be capable of finding multiple entries at the same time
1835
self.assertBisect(expected, [['a'], ['b'], ['f']],
1836
state, ['a', 'b', 'f'])
1837
# ('', 'f') sorts before the others
1838
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1839
state, ['b/d', 'b/d/e', 'f'])
1841
def test_bisect_one_page(self):
1842
"""Test bisect when there is only 1 page to read"""
1843
tree, state, expected = self.create_basic_dirstate()
1844
state._bisect_page_size = 5000
1845
self.assertBisect(expected,[['']], state, [''])
1846
self.assertBisect(expected,[['a']], state, ['a'])
1847
self.assertBisect(expected,[['b']], state, ['b'])
1848
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1849
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1850
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1851
self.assertBisect(expected,[['f']], state, ['f'])
1852
self.assertBisect(expected,[['a'], ['b'], ['f']],
1853
state, ['a', 'b', 'f'])
1854
# ('', 'f') sorts before the others
1855
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1856
state, ['b/d', 'b/d/e', 'f'])
1858
def test_bisect_duplicate_paths(self):
1859
"""When bisecting for a path, handle multiple entries."""
1860
tree, state, expected = self.create_duplicated_dirstate()
1862
# Now make sure that both records are properly returned.
1863
self.assertBisect(expected, [['']], state, [''])
1864
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1865
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1866
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1867
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1868
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1870
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1872
def test_bisect_page_size_too_small(self):
1873
"""If the page size is too small, we will auto increase it."""
1874
tree, state, expected = self.create_basic_dirstate()
1875
state._bisect_page_size = 50
1876
self.assertBisect(expected, [None], state, ['b/e'])
1877
self.assertBisect(expected, [['a']], state, ['a'])
1878
self.assertBisect(expected, [['b']], state, ['b'])
1879
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1880
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1881
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1882
self.assertBisect(expected, [['f']], state, ['f'])
1884
def test_bisect_missing(self):
1885
"""Test that bisect return None if it cannot find a path."""
1886
tree, state, expected = self.create_basic_dirstate()
1887
self.assertBisect(expected, [None], state, ['foo'])
1888
self.assertBisect(expected, [None], state, ['b/foo'])
1889
self.assertBisect(expected, [None], state, ['bar/foo'])
1891
self.assertBisect(expected, [['a'], None, ['b/d']],
1892
state, ['a', 'foo', 'b/d'])
1894
def test_bisect_rename(self):
1895
"""Check that we find a renamed row."""
1896
tree, state, expected = self.create_renamed_dirstate()
1898
# Search for the pre and post renamed entries
1899
self.assertBisect(expected, [['a']], state, ['a'])
1900
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1901
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1902
self.assertBisect(expected, [['h']], state, ['h'])
1904
# What about b/d/e? shouldn't that also get 2 directory entries?
1905
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1906
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1908
def test_bisect_dirblocks(self):
1909
tree, state, expected = self.create_duplicated_dirstate()
1910
self.assertBisectDirBlocks(expected,
1911
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1912
self.assertBisectDirBlocks(expected,
1913
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1914
self.assertBisectDirBlocks(expected,
1915
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1916
self.assertBisectDirBlocks(expected,
1917
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1918
['b/c', 'b/c2', 'b/d', 'b/d2'],
1919
['b/d/e', 'b/d/e2'],
1920
], state, ['', 'b', 'b/d'])
1922
def test_bisect_dirblocks_missing(self):
1923
tree, state, expected = self.create_basic_dirstate()
1924
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1925
state, ['b/d', 'b/e'])
1926
# Files don't show up in this search
1927
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1928
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1929
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1930
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1931
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1933
def test_bisect_recursive_each(self):
1934
tree, state, expected = self.create_basic_dirstate()
1935
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1936
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1937
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1938
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1940
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1942
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1946
def test_bisect_recursive_multiple(self):
1947
tree, state, expected = self.create_basic_dirstate()
1948
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1949
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1950
state, ['b/d', 'b/d/e'])
1952
def test_bisect_recursive_missing(self):
1953
tree, state, expected = self.create_basic_dirstate()
1954
self.assertBisectRecursive(expected, [], state, ['d'])
1955
self.assertBisectRecursive(expected, [], state, ['b/e'])
1956
self.assertBisectRecursive(expected, [], state, ['g'])
1957
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1959
def test_bisect_recursive_renamed(self):
1960
tree, state, expected = self.create_renamed_dirstate()
1962
# Looking for either renamed item should find the other
1963
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1964
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1965
# Looking in the containing directory should find the rename target,
1966
# and anything in a subdir of the renamed target.
1967
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1968
'b/d/e', 'b/g', 'h', 'h/e'],
1972
class TestBisectDirblock(TestCase):
1973
"""Test that bisect_dirblock() returns the expected values.
1975
bisect_dirblock is intended to work like bisect.bisect_left() except it
1976
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1977
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1980
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1981
"""Assert that bisect_split works like bisect_left on the split paths.
1983
:param dirblocks: A list of (path, [info]) pairs.
1984
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1985
:param path: The path we are indexing.
1987
All other arguments will be passed along.
1989
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1991
split_dirblock = (path.split('/'), [])
1992
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1994
self.assertEqual(bisect_left_idx, bisect_split_idx,
1995
'bisect_split disagreed. %s != %s'
1997
% (bisect_left_idx, bisect_split_idx, path)
2000
def paths_to_dirblocks(self, paths):
2001
"""Convert a list of paths into dirblock form.
2003
Also, ensure that the paths are in proper sorted order.
2005
dirblocks = [(path, []) for path in paths]
2006
split_dirblocks = [(path.split('/'), []) for path in paths]
2007
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
2008
return dirblocks, split_dirblocks
2010
def test_simple(self):
2011
"""In the simple case it works just like bisect_left"""
2012
paths = ['', 'a', 'b', 'c', 'd']
2013
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2015
self.assertBisect(dirblocks, split_dirblocks, path)
2016
self.assertBisect(dirblocks, split_dirblocks, '_')
2017
self.assertBisect(dirblocks, split_dirblocks, 'aa')
2018
self.assertBisect(dirblocks, split_dirblocks, 'bb')
2019
self.assertBisect(dirblocks, split_dirblocks, 'cc')
2020
self.assertBisect(dirblocks, split_dirblocks, 'dd')
2021
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
2022
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
2023
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
2024
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
2026
def test_involved(self):
2027
"""This is where bisect_left diverges slightly."""
2029
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2030
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2032
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2033
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2036
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2038
self.assertBisect(dirblocks, split_dirblocks, path)
2040
def test_involved_cached(self):
2041
"""This is where bisect_left diverges slightly."""
2043
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2044
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2046
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2047
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2051
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2053
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2056
class TestDirstateValidation(TestCaseWithDirState):
2058
def test_validate_correct_dirstate(self):
2059
state = self.create_complex_dirstate()
2062
# and make sure we can also validate with a read lock
2069
def test_dirblock_not_sorted(self):
2070
tree, state, expected = self.create_renamed_dirstate()
2071
state._read_dirblocks_if_needed()
2072
last_dirblock = state._dirblocks[-1]
2073
# we're appending to the dirblock, but this name comes before some of
2074
# the existing names; that's wrong
2075
last_dirblock[1].append(
2076
(('h', 'aaaa', 'a-id'),
2077
[('a', '', 0, False, ''),
2078
('a', '', 0, False, '')]))
2079
e = self.assertRaises(AssertionError,
2081
self.assertContainsRe(str(e), 'not sorted')
2083
def test_dirblock_name_mismatch(self):
2084
tree, state, expected = self.create_renamed_dirstate()
2085
state._read_dirblocks_if_needed()
2086
last_dirblock = state._dirblocks[-1]
2087
# add an entry with the wrong directory name
2088
last_dirblock[1].append(
2090
[('a', '', 0, False, ''),
2091
('a', '', 0, False, '')]))
2092
e = self.assertRaises(AssertionError,
2094
self.assertContainsRe(str(e),
2095
"doesn't match directory name")
2097
def test_dirblock_missing_rename(self):
2098
tree, state, expected = self.create_renamed_dirstate()
2099
state._read_dirblocks_if_needed()
2100
last_dirblock = state._dirblocks[-1]
2101
# make another entry for a-id, without a correct 'r' pointer to
2102
# the real occurrence in the working tree
2103
last_dirblock[1].append(
2104
(('h', 'z', 'a-id'),
2105
[('a', '', 0, False, ''),
2106
('a', '', 0, False, '')]))
2107
e = self.assertRaises(AssertionError,
2109
self.assertContainsRe(str(e),
2110
'file a-id is absent in row')