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.
195
tree = self.make_branch_and_tree('tree')
196
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
self.build_tree(['tree/' + p for p in paths])
199
tree.set_root_id('TREE_ROOT')
200
tree.add([p.rstrip('/') for p in paths], file_ids)
201
tree.commit('initial', rev_id='rev-1')
202
revision_id = 'rev-1'
203
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
t = self.get_transport('tree')
205
a_text = t.get_bytes('a')
206
a_sha = osutils.sha_string(a_text)
208
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
c_text = t.get_bytes('b/c')
211
c_sha = osutils.sha_string(c_text)
213
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
e_text = t.get_bytes('b/d/e')
216
e_sha = osutils.sha_string(e_text)
218
b_c_text = t.get_bytes('b-c')
219
b_c_sha = osutils.sha_string(b_c_text)
220
b_c_len = len(b_c_text)
221
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
f_text = t.get_bytes('f')
223
f_sha = osutils.sha_string(f_text)
225
null_stat = dirstate.DirState.NULLSTAT
227
'':(('', '', 'TREE_ROOT'), [
228
('d', '', 0, False, null_stat),
229
('d', '', 0, False, revision_id),
231
'a':(('', 'a', 'a-id'), [
232
('f', '', 0, False, null_stat),
233
('f', a_sha, a_len, False, revision_id),
235
'b':(('', 'b', 'b-id'), [
236
('d', '', 0, False, null_stat),
237
('d', '', 0, False, revision_id),
239
'b/c':(('b', 'c', 'c-id'), [
240
('f', '', 0, False, null_stat),
241
('f', c_sha, c_len, False, revision_id),
243
'b/d':(('b', 'd', 'd-id'), [
244
('d', '', 0, False, null_stat),
245
('d', '', 0, False, revision_id),
247
'b/d/e':(('b/d', 'e', 'e-id'), [
248
('f', '', 0, False, null_stat),
249
('f', e_sha, e_len, False, revision_id),
251
'b-c':(('', 'b-c', 'b-c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', b_c_sha, b_c_len, False, revision_id),
255
'f':(('', 'f', 'f-id'), [
256
('f', '', 0, False, null_stat),
257
('f', f_sha, f_len, False, revision_id),
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
265
# Use a different object, to make sure nothing is pre-cached in memory.
266
state = dirstate.DirState.on_file('dirstate')
268
self.addCleanup(state.unlock)
269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
state._dirblock_state)
271
# This is code is only really tested if we actually have to make more
272
# than one read, so set the page size to something smaller.
273
# We want it to contain about 2.2 records, so that we have a couple
274
# records that we can read per attempt
275
state._bisect_page_size = 200
276
return tree, state, expected
278
def create_duplicated_dirstate(self):
279
"""Create a dirstate with a deleted and added entries.
281
This grabs a basic_dirstate, and then removes and re adds every entry
284
tree, state, expected = self.create_basic_dirstate()
285
# Now we will just remove and add every file so we get an extra entry
286
# per entry. Unversion in reverse order so we handle subdirs
287
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
291
# Update the expected dictionary.
292
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
orig = expected[path]
295
# This record was deleted in the current tree
296
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
298
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
# And didn't exist in the basis tree
300
expected[path2] = (new_key, [orig[1][0],
301
dirstate.DirState.NULL_PARENT_DETAILS])
303
# We will replace the 'dirstate' file underneath 'state', but that is
304
# okay as lock as we unlock 'state' first.
307
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
313
# But we need to leave state in a read-lock because we already have
314
# a cleanup scheduled
316
return tree, state, expected
318
def create_renamed_dirstate(self):
319
"""Create a dirstate with a few internal renames.
321
This takes the basic dirstate, and moves the paths around.
323
tree, state, expected = self.create_basic_dirstate()
325
tree.rename_one('a', 'b/g')
327
tree.rename_one('b/d', 'h')
329
old_a = expected['a']
330
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
('r', 'a', 0, False, '')])
333
old_d = expected['b/d']
334
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
('r', 'b/d', 0, False, '')])
338
old_e = expected['b/d/e']
339
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
341
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
('r', 'b/d/e', 0, False, '')])
346
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
353
return tree, state, expected
356
class TestTreeToDirState(TestCaseWithDirState):
358
def test_empty_to_dirstate(self):
359
"""We should be able to create a dirstate for an empty tree."""
360
# There are no files on disk and no parents
361
tree = self.make_branch_and_tree('tree')
362
expected_result = ([], [
363
(('', '', tree.path2id('')), # common details
364
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.from_tree(tree, 'dirstate')
368
self.check_state_with_reopen(expected_result, state)
370
def test_1_parents_empty_to_dirstate(self):
371
# create a parent by doing a commit
372
tree = self.make_branch_and_tree('tree')
373
rev_id = tree.commit('first post').encode('utf8')
374
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
expected_result = ([rev_id], [
376
(('', '', tree.path2id('')), # common details
377
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
('d', '', 0, False, rev_id), # first parent details
380
state = dirstate.DirState.from_tree(tree, 'dirstate')
381
self.check_state_with_reopen(expected_result, state)
388
def test_2_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post')
392
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
393
rev_id2 = tree2.commit('second post', allow_pointless=True)
394
tree.merge_from_branch(tree2.branch)
395
expected_result = ([rev_id, rev_id2], [
396
(('', '', tree.path2id('')), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
399
('d', '', 0, False, rev_id2), # second parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_empty_unknowns_are_ignored_to_dirstate(self):
410
"""We should be able to create a dirstate for an empty tree."""
411
# There are no files on disk and no parents
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree(['tree/unknown'])
414
expected_result = ([], [
415
(('', '', tree.path2id('')), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
421
def get_tree_with_a_file(self):
422
tree = self.make_branch_and_tree('tree')
423
self.build_tree(['tree/a file'])
424
tree.add('a file', 'a file id')
427
def test_non_empty_no_parents_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are files on disk and no parents
430
tree = self.get_tree_with_a_file()
431
expected_result = ([], [
432
(('', '', tree.path2id('')), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
(('', 'a file', 'a file id'), # common
436
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def test_1_parents_not_empty_to_dirstate(self):
443
# create a parent by doing a commit
444
tree = self.get_tree_with_a_file()
445
rev_id = tree.commit('first post').encode('utf8')
446
# change the current content to be different this will alter stat, sha
448
self.build_tree_contents([('tree/a file', 'new content\n')])
449
expected_result = ([rev_id], [
450
(('', '', tree.path2id('')), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
('d', '', 0, False, rev_id), # first parent details
454
(('', 'a file', 'a file id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
rev_id), # first parent
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_2_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
468
# change the current content to be different this will alter stat, sha
470
self.build_tree_contents([('tree2/a file', 'merge content\n')])
471
rev_id2 = tree2.commit('second post').encode('utf8')
472
tree.merge_from_branch(tree2.branch)
473
# change the current content to be different this will alter stat, sha
474
# and length again, giving us three distinct values:
475
self.build_tree_contents([('tree/a file', 'new content\n')])
476
expected_result = ([rev_id, rev_id2], [
477
(('', '', tree.path2id('')), # common details
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
('d', '', 0, False, rev_id), # first parent details
480
('d', '', 0, False, rev_id2), # second parent details
482
(('', 'a file', 'a file id'), # common
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
rev_id), # first parent
486
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
487
rev_id2), # second parent
490
state = dirstate.DirState.from_tree(tree, 'dirstate')
491
self.check_state_with_reopen(expected_result, state)
493
def test_colliding_fileids(self):
494
# test insertion of parents creating several entries at the same path.
495
# we used to have a bug where they could cause the dirstate to break
496
# its ordering invariants.
497
# create some trees to test from
500
tree = self.make_branch_and_tree('tree%d' % i)
501
self.build_tree(['tree%d/name' % i,])
502
tree.add(['name'], ['file-id%d' % i])
503
revision_id = 'revid-%d' % i
504
tree.commit('message', rev_id=revision_id)
505
parents.append((revision_id,
506
tree.branch.repository.revision_tree(revision_id)))
507
# now fold these trees into a dirstate
508
state = dirstate.DirState.initialize('dirstate')
510
state.set_parent_trees(parents, [])
516
class TestDirStateOnFile(TestCaseWithDirState):
518
def test_construct_with_path(self):
519
tree = self.make_branch_and_tree('tree')
520
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
521
# we want to be able to get the lines of the dirstate that we will
523
lines = state.get_lines()
525
self.build_tree_contents([('dirstate', ''.join(lines))])
527
# no parents, default tree content
528
expected_result = ([], [
529
(('', '', tree.path2id('')), # common details
530
# current tree details, but new from_tree skips statting, it
531
# uses set_state_from_inventory, and thus depends on the
533
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
536
state = dirstate.DirState.on_file('dirstate')
537
state.lock_write() # check_state_with_reopen will save() and unlock it
538
self.check_state_with_reopen(expected_result, state)
540
def test_can_save_clean_on_file(self):
541
tree = self.make_branch_and_tree('tree')
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
544
# doing a save should work here as there have been no changes.
546
# TODO: stat it and check it hasn't changed; may require waiting
547
# for the state accuracy window.
551
def test_can_save_in_read_lock(self):
552
self.build_tree(['a-file'])
553
state = dirstate.DirState.initialize('dirstate')
555
# No stat and no sha1 sum.
556
state.add('a-file', 'a-file-id', 'file', None, '')
561
# Now open in readonly mode
562
state = dirstate.DirState.on_file('dirstate')
565
entry = state._get_entry(0, path_utf8='a-file')
566
# The current sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
568
# We should have a real entry.
569
self.assertNotEqual((None, None), entry)
570
# Make sure everything is old enough
571
state._sha_cutoff_time()
572
state._cutoff_time += 10
573
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
578
# The dirblock has been updated
579
self.assertEqual(sha1sum, entry[1][0][1])
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
state._dirblock_state)
584
# Now, since we are the only one holding a lock, we should be able
585
# to save and have it written to disk
590
# Re-open the file, and ensure that the state has been updated.
591
state = dirstate.DirState.on_file('dirstate')
594
entry = state._get_entry(0, path_utf8='a-file')
595
self.assertEqual(sha1sum, entry[1][0][1])
599
def test_save_fails_quietly_if_locked(self):
600
"""If dirstate is locked, save will fail without complaining."""
601
self.build_tree(['a-file'])
602
state = dirstate.DirState.initialize('dirstate')
604
# No stat and no sha1 sum.
605
state.add('a-file', 'a-file-id', 'file', None, '')
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
state._dirblock_state)
621
# Now, before we try to save, grab another dirstate, and take out a
623
# TODO: jam 20070315 Ideally this would be locked by another
624
# process. To make sure the file is really OS locked.
625
state2 = dirstate.DirState.on_file('dirstate')
628
# This won't actually write anything, because it couldn't grab
629
# a write lock. But it shouldn't raise an error, either.
630
# TODO: jam 20070315 We should probably distinguish between
631
# being dirty because of 'update_entry'. And dirty
632
# because of real modification. So that save() *does*
633
# raise a real error if it fails when we have real
641
# The file on disk should not be modified.
642
state = dirstate.DirState.on_file('dirstate')
645
entry = state._get_entry(0, path_utf8='a-file')
646
self.assertEqual('', entry[1][0][1])
651
class TestDirStateInitialize(TestCaseWithDirState):
653
def test_initialize(self):
654
expected_result = ([], [
655
(('', '', 'TREE_ROOT'), # common details
656
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
659
state = dirstate.DirState.initialize('dirstate')
661
self.assertIsInstance(state, dirstate.DirState)
662
lines = state.get_lines()
665
# On win32 you can't read from a locked file, even within the same
666
# process. So we have to unlock and release before we check the file
668
self.assertFileEqual(''.join(lines), 'dirstate')
669
state.lock_read() # check_state_with_reopen will unlock
670
self.check_state_with_reopen(expected_result, state)
673
class TestDirStateManipulations(TestCaseWithDirState):
675
def test_set_state_from_inventory_no_content_no_parents(self):
676
# setting the current inventory is a slow but important api to support.
677
tree1 = self.make_branch_and_memory_tree('tree1')
681
revid1 = tree1.commit('foo').encode('utf8')
682
root_id = tree1.inventory.root.file_id
683
inv = tree1.inventory
686
expected_result = [], [
687
(('', '', root_id), [
688
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
689
state = dirstate.DirState.initialize('dirstate')
691
state.set_state_from_inventory(inv)
692
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
694
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
695
state._dirblock_state)
700
# This will unlock it
701
self.check_state_with_reopen(expected_result, state)
703
def test_set_state_from_inventory_mixed_paths(self):
704
tree1 = self.make_branch_and_tree('tree1')
705
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
706
'tree1/a/b/foo', 'tree1/a-b/bar'])
709
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
710
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
711
tree1.commit('rev1', rev_id='rev1')
712
root_id = tree1.get_root_id()
713
inv = tree1.inventory
716
expected_result1 = [('', '', root_id, 'd'),
717
('', 'a', 'a-id', 'd'),
718
('', 'a-b', 'a-b-id', 'd'),
719
('a', 'b', 'b-id', 'd'),
720
('a/b', 'foo', 'foo-id', 'f'),
721
('a-b', 'bar', 'bar-id', 'f'),
723
expected_result2 = [('', '', root_id, 'd'),
724
('', 'a', 'a-id', 'd'),
725
('', 'a-b', 'a-b-id', 'd'),
726
('a-b', 'bar', 'bar-id', 'f'),
728
state = dirstate.DirState.initialize('dirstate')
730
state.set_state_from_inventory(inv)
732
for entry in state._iter_entries():
733
values.append(entry[0] + entry[1][0][:1])
734
self.assertEqual(expected_result1, values)
736
state.set_state_from_inventory(inv)
738
for entry in state._iter_entries():
739
values.append(entry[0] + entry[1][0][:1])
740
self.assertEqual(expected_result2, values)
744
def test_set_path_id_no_parents(self):
745
"""The id of a path can be changed trivally with no parents."""
746
state = dirstate.DirState.initialize('dirstate')
748
# check precondition to be sure the state does change appropriately.
750
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
751
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
752
list(state._iter_entries()))
753
state.set_path_id('', 'foobarbaz')
755
(('', '', 'foobarbaz'), [('d', '', 0, False,
756
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
757
self.assertEqual(expected_rows, list(state._iter_entries()))
758
# should work across save too
762
state = dirstate.DirState.on_file('dirstate')
766
self.assertEqual(expected_rows, list(state._iter_entries()))
770
def test_set_path_id_with_parents(self):
771
"""Set the root file id in a dirstate with parents"""
772
mt = self.make_branch_and_tree('mt')
773
# in case the default tree format uses a different root id
774
mt.set_root_id('TREE_ROOT')
775
mt.commit('foo', rev_id='parent-revid')
776
rt = mt.branch.repository.revision_tree('parent-revid')
777
state = dirstate.DirState.initialize('dirstate')
780
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
781
state.set_path_id('', 'foobarbaz')
783
# now see that it is what we expected
785
(('', '', 'TREE_ROOT'),
786
[('a', '', 0, False, ''),
787
('d', '', 0, False, 'parent-revid'),
789
(('', '', 'foobarbaz'),
790
[('d', '', 0, False, ''),
791
('a', '', 0, False, ''),
795
self.assertEqual(expected_rows, list(state._iter_entries()))
796
# should work across save too
800
# now flush & check we get the same
801
state = dirstate.DirState.on_file('dirstate')
805
self.assertEqual(expected_rows, list(state._iter_entries()))
808
# now change within an existing file-backed state
812
state.set_path_id('', 'tree-root-2')
818
def test_set_parent_trees_no_content(self):
819
# set_parent_trees is a slow but important api to support.
820
tree1 = self.make_branch_and_memory_tree('tree1')
824
revid1 = tree1.commit('foo')
827
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
828
tree2 = MemoryTree.create_on_branch(branch2)
831
revid2 = tree2.commit('foo')
832
root_id = tree2.inventory.root.file_id
835
state = dirstate.DirState.initialize('dirstate')
837
state.set_path_id('', root_id)
838
state.set_parent_trees(
839
((revid1, tree1.branch.repository.revision_tree(revid1)),
840
(revid2, tree2.branch.repository.revision_tree(revid2)),
841
('ghost-rev', None)),
843
# check we can reopen and use the dirstate after setting parent
850
state = dirstate.DirState.on_file('dirstate')
853
self.assertEqual([revid1, revid2, 'ghost-rev'],
854
state.get_parent_ids())
855
# iterating the entire state ensures that the state is parsable.
856
list(state._iter_entries())
857
# be sure that it sets not appends - change it
858
state.set_parent_trees(
859
((revid1, tree1.branch.repository.revision_tree(revid1)),
860
('ghost-rev', None)),
862
# and now put it back.
863
state.set_parent_trees(
864
((revid1, tree1.branch.repository.revision_tree(revid1)),
865
(revid2, tree2.branch.repository.revision_tree(revid2)),
866
('ghost-rev', tree2.branch.repository.revision_tree(None))),
868
self.assertEqual([revid1, revid2, 'ghost-rev'],
869
state.get_parent_ids())
870
# the ghost should be recorded as such by set_parent_trees.
871
self.assertEqual(['ghost-rev'], state.get_ghosts())
873
[(('', '', root_id), [
874
('d', '', 0, False, dirstate.DirState.NULLSTAT),
875
('d', '', 0, False, revid1),
876
('d', '', 0, False, revid2)
878
list(state._iter_entries()))
882
def test_set_parent_trees_file_missing_from_tree(self):
883
# Adding a parent tree may reference files not in the current state.
884
# they should get listed just once by id, even if they are in two
886
# set_parent_trees is a slow but important api to support.
887
tree1 = self.make_branch_and_memory_tree('tree1')
891
tree1.add(['a file'], ['file-id'], ['file'])
892
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
893
revid1 = tree1.commit('foo')
896
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
897
tree2 = MemoryTree.create_on_branch(branch2)
900
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
901
revid2 = tree2.commit('foo')
902
root_id = tree2.inventory.root.file_id
905
# check the layout in memory
906
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
907
(('', '', root_id), [
908
('d', '', 0, False, dirstate.DirState.NULLSTAT),
909
('d', '', 0, False, revid1.encode('utf8')),
910
('d', '', 0, False, revid2.encode('utf8'))
912
(('', 'a file', 'file-id'), [
913
('a', '', 0, False, ''),
914
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
915
revid1.encode('utf8')),
916
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
917
revid2.encode('utf8'))
920
state = dirstate.DirState.initialize('dirstate')
922
state.set_path_id('', root_id)
923
state.set_parent_trees(
924
((revid1, tree1.branch.repository.revision_tree(revid1)),
925
(revid2, tree2.branch.repository.revision_tree(revid2)),
931
# check_state_with_reopen will unlock
932
self.check_state_with_reopen(expected_result, state)
934
### add a path via _set_data - so we dont need delta work, just
935
# raw data in, and ensure that it comes out via get_lines happily.
937
def test_add_path_to_root_no_parents_all_data(self):
938
# The most trivial addition of a path is when there are no parents and
939
# its in the root and all data about the file is supplied
940
self.build_tree(['a file'])
941
stat = os.lstat('a file')
942
# the 1*20 is the sha1 pretend value.
943
state = dirstate.DirState.initialize('dirstate')
945
(('', '', 'TREE_ROOT'), [
946
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
948
(('', 'a file', 'a file id'), [
949
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
953
state.add('a file', 'a file id', 'file', stat, '1'*20)
954
# having added it, it should be in the output of iter_entries.
955
self.assertEqual(expected_entries, list(state._iter_entries()))
956
# saving and reloading should not affect this.
960
state = dirstate.DirState.on_file('dirstate')
963
self.assertEqual(expected_entries, list(state._iter_entries()))
967
def test_add_path_to_unversioned_directory(self):
968
"""Adding a path to an unversioned directory should error.
970
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
971
once dirstate is stable and if it is merged with WorkingTree3, consider
972
removing this copy of the test.
974
self.build_tree(['unversioned/', 'unversioned/a file'])
975
state = dirstate.DirState.initialize('dirstate')
977
self.assertRaises(errors.NotVersionedError, state.add,
978
'unversioned/a file', 'a file id', 'file', None, None)
982
def test_add_directory_to_root_no_parents_all_data(self):
983
# The most trivial addition of a dir is when there are no parents and
984
# its in the root and all data about the file is supplied
985
self.build_tree(['a dir/'])
986
stat = os.lstat('a dir')
988
(('', '', 'TREE_ROOT'), [
989
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
991
(('', 'a dir', 'a dir id'), [
992
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
995
state = dirstate.DirState.initialize('dirstate')
997
state.add('a dir', 'a dir id', 'directory', stat, None)
998
# having added it, it should be in the output of iter_entries.
999
self.assertEqual(expected_entries, list(state._iter_entries()))
1000
# saving and reloading should not affect this.
1004
state = dirstate.DirState.on_file('dirstate')
1008
self.assertEqual(expected_entries, list(state._iter_entries()))
1012
def test_add_symlink_to_root_no_parents_all_data(self):
1013
# The most trivial addition of a symlink when there are no parents and
1014
# its in the root and all data about the file is supplied
1015
# bzr doesn't support fake symlinks on windows, yet.
1016
if not has_symlinks():
1017
raise TestSkipped("No symlink support")
1018
os.symlink('target', 'a link')
1019
stat = os.lstat('a link')
1020
expected_entries = [
1021
(('', '', 'TREE_ROOT'), [
1022
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1024
(('', 'a link', 'a link id'), [
1025
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1028
state = dirstate.DirState.initialize('dirstate')
1030
state.add('a link', 'a link id', 'symlink', stat, 'target')
1031
# having added it, it should be in the output of iter_entries.
1032
self.assertEqual(expected_entries, list(state._iter_entries()))
1033
# saving and reloading should not affect this.
1037
state = dirstate.DirState.on_file('dirstate')
1040
self.assertEqual(expected_entries, list(state._iter_entries()))
1044
def test_add_directory_and_child_no_parents_all_data(self):
1045
# after adding a directory, we should be able to add children to it.
1046
self.build_tree(['a dir/', 'a dir/a file'])
1047
dirstat = os.lstat('a dir')
1048
filestat = os.lstat('a dir/a file')
1049
expected_entries = [
1050
(('', '', 'TREE_ROOT'), [
1051
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1053
(('', 'a dir', 'a dir id'), [
1054
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1056
(('a dir', 'a file', 'a file id'), [
1057
('f', '1'*20, 25, False,
1058
dirstate.pack_stat(filestat)), # current tree details
1061
state = dirstate.DirState.initialize('dirstate')
1063
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1064
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1065
# added it, it should be in the output of iter_entries.
1066
self.assertEqual(expected_entries, list(state._iter_entries()))
1067
# saving and reloading should not affect this.
1071
state = dirstate.DirState.on_file('dirstate')
1074
self.assertEqual(expected_entries, list(state._iter_entries()))
1078
def test_add_tree_reference(self):
1079
# make a dirstate and add a tree reference
1080
state = dirstate.DirState.initialize('dirstate')
1082
('', 'subdir', 'subdir-id'),
1083
[('t', 'subtree-123123', 0, False,
1084
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1087
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1088
entry = state._get_entry(0, 'subdir-id', 'subdir')
1089
self.assertEqual(entry, expected_entry)
1094
# now check we can read it back
1098
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1099
self.assertEqual(entry, entry2)
1100
self.assertEqual(entry, expected_entry)
1101
# and lookup by id should work too
1102
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1103
self.assertEqual(entry, expected_entry)
1107
def test_add_forbidden_names(self):
1108
state = dirstate.DirState.initialize('dirstate')
1109
self.addCleanup(state.unlock)
1110
self.assertRaises(errors.BzrError,
1111
state.add, '.', 'ass-id', 'directory', None, None)
1112
self.assertRaises(errors.BzrError,
1113
state.add, '..', 'ass-id', 'directory', None, None)
1116
class TestGetLines(TestCaseWithDirState):
1118
def test_get_line_with_2_rows(self):
1119
state = self.create_dirstate_with_root_and_subdir()
1121
self.assertEqual(['#bazaar dirstate flat format 3\n',
1122
'crc32: 41262208\n',
1126
'\x00\x00a-root-value\x00'
1127
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1128
'\x00subdir\x00subdir-id\x00'
1129
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1130
], state.get_lines())
1134
def test_entry_to_line(self):
1135
state = self.create_dirstate_with_root()
1138
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1139
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1140
state._entry_to_line(state._dirblocks[0][1][0]))
1144
def test_entry_to_line_with_parent(self):
1145
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1146
root_entry = ('', '', 'a-root-value'), [
1147
('d', '', 0, False, packed_stat), # current tree details
1148
# first: a pointer to the current location
1149
('a', 'dirname/basename', 0, False, ''),
1151
state = dirstate.DirState.initialize('dirstate')
1154
'\x00\x00a-root-value\x00'
1155
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1156
'a\x00dirname/basename\x000\x00n\x00',
1157
state._entry_to_line(root_entry))
1161
def test_entry_to_line_with_two_parents_at_different_paths(self):
1162
# / in the tree, at / in one parent and /dirname/basename in the other.
1163
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1164
root_entry = ('', '', 'a-root-value'), [
1165
('d', '', 0, False, packed_stat), # current tree details
1166
('d', '', 0, False, 'rev_id'), # first parent details
1167
# second: a pointer to the current location
1168
('a', 'dirname/basename', 0, False, ''),
1170
state = dirstate.DirState.initialize('dirstate')
1173
'\x00\x00a-root-value\x00'
1174
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1175
'd\x00\x000\x00n\x00rev_id\x00'
1176
'a\x00dirname/basename\x000\x00n\x00',
1177
state._entry_to_line(root_entry))
1181
def test_iter_entries(self):
1182
# we should be able to iterate the dirstate entries from end to end
1183
# this is for get_lines to be easy to read.
1184
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1186
root_entries = [(('', '', 'a-root-value'), [
1187
('d', '', 0, False, packed_stat), # current tree details
1189
dirblocks.append(('', root_entries))
1190
# add two files in the root
1191
subdir_entry = ('', 'subdir', 'subdir-id'), [
1192
('d', '', 0, False, packed_stat), # current tree details
1194
afile_entry = ('', 'afile', 'afile-id'), [
1195
('f', 'sha1value', 34, False, packed_stat), # current tree details
1197
dirblocks.append(('', [subdir_entry, afile_entry]))
1199
file_entry2 = ('subdir', '2file', '2file-id'), [
1200
('f', 'sha1value', 23, False, packed_stat), # current tree details
1202
dirblocks.append(('subdir', [file_entry2]))
1203
state = dirstate.DirState.initialize('dirstate')
1205
state._set_data([], dirblocks)
1206
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1208
self.assertEqual(expected_entries, list(state._iter_entries()))
1213
class TestGetBlockRowIndex(TestCaseWithDirState):
1215
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1216
file_present, state, dirname, basename, tree_index):
1217
self.assertEqual((block_index, row_index, dir_present, file_present),
1218
state._get_block_entry_index(dirname, basename, tree_index))
1220
block = state._dirblocks[block_index]
1221
self.assertEqual(dirname, block[0])
1222
if dir_present and file_present:
1223
row = state._dirblocks[block_index][1][row_index]
1224
self.assertEqual(dirname, row[0][0])
1225
self.assertEqual(basename, row[0][1])
1227
def test_simple_structure(self):
1228
state = self.create_dirstate_with_root_and_subdir()
1229
self.addCleanup(state.unlock)
1230
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1231
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1232
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1233
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1234
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1237
def test_complex_structure_exists(self):
1238
state = self.create_complex_dirstate()
1239
self.addCleanup(state.unlock)
1240
# Make sure we can find everything that exists
1241
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1242
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1243
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1244
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1245
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1246
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1247
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1248
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1249
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1250
'b', 'h\xc3\xa5', 0)
1252
def test_complex_structure_missing(self):
1253
state = self.create_complex_dirstate()
1254
self.addCleanup(state.unlock)
1255
# Make sure things would be inserted in the right locations
1256
# '_' comes before 'a'
1257
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1258
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1259
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1260
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1262
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1263
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1264
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1265
# This would be inserted between a/ and b/
1266
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1268
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1271
class TestGetEntry(TestCaseWithDirState):
1273
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1274
"""Check that the right entry is returned for a request to getEntry."""
1275
entry = state._get_entry(index, path_utf8=path)
1277
self.assertEqual((None, None), entry)
1280
self.assertEqual((dirname, basename, file_id), cur[:3])
1282
def test_simple_structure(self):
1283
state = self.create_dirstate_with_root_and_subdir()
1284
self.addCleanup(state.unlock)
1285
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1286
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1287
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1288
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1289
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1291
def test_complex_structure_exists(self):
1292
state = self.create_complex_dirstate()
1293
self.addCleanup(state.unlock)
1294
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1295
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1296
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1297
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1298
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1299
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1300
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1301
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1302
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1305
def test_complex_structure_missing(self):
1306
state = self.create_complex_dirstate()
1307
self.addCleanup(state.unlock)
1308
self.assertEntryEqual(None, None, None, state, '_', 0)
1309
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1310
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1311
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1313
def test_get_entry_uninitialized(self):
1314
"""Calling get_entry will load data if it needs to"""
1315
state = self.create_dirstate_with_root()
1321
state = dirstate.DirState.on_file('dirstate')
1324
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1325
state._header_state)
1326
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1327
state._dirblock_state)
1328
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1333
class TestDirstateSortOrder(TestCaseWithTransport):
1334
"""Test that DirState adds entries in the right order."""
1336
def test_add_sorting(self):
1337
"""Add entries in lexicographical order, we get path sorted order.
1339
This tests it to a depth of 4, to make sure we don't just get it right
1340
at a single depth. 'a/a' should come before 'a-a', even though it
1341
doesn't lexicographically.
1343
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1344
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1347
state = dirstate.DirState.initialize('dirstate')
1348
self.addCleanup(state.unlock)
1350
fake_stat = os.stat('dirstate')
1352
d_id = d.replace('/', '_')+'-id'
1353
file_path = d + '/f'
1354
file_id = file_path.replace('/', '_')+'-id'
1355
state.add(d, d_id, 'directory', fake_stat, null_sha)
1356
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1358
expected = ['', '', 'a',
1359
'a/a', 'a/a/a', 'a/a/a/a',
1360
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1362
split = lambda p:p.split('/')
1363
self.assertEqual(sorted(expected, key=split), expected)
1364
dirblock_names = [d[0] for d in state._dirblocks]
1365
self.assertEqual(expected, dirblock_names)
1367
def test_set_parent_trees_correct_order(self):
1368
"""After calling set_parent_trees() we should maintain the order."""
1369
dirs = ['a', 'a-a', 'a/a']
1371
state = dirstate.DirState.initialize('dirstate')
1372
self.addCleanup(state.unlock)
1374
fake_stat = os.stat('dirstate')
1376
d_id = d.replace('/', '_')+'-id'
1377
file_path = d + '/f'
1378
file_id = file_path.replace('/', '_')+'-id'
1379
state.add(d, d_id, 'directory', fake_stat, null_sha)
1380
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1382
expected = ['', '', 'a', 'a/a', 'a-a']
1383
dirblock_names = [d[0] for d in state._dirblocks]
1384
self.assertEqual(expected, dirblock_names)
1386
# *really* cheesy way to just get an empty tree
1387
repo = self.make_repository('repo')
1388
empty_tree = repo.revision_tree(None)
1389
state.set_parent_trees([('null:', empty_tree)], [])
1391
dirblock_names = [d[0] for d in state._dirblocks]
1392
self.assertEqual(expected, dirblock_names)
1395
class InstrumentedDirState(dirstate.DirState):
1396
"""An DirState with instrumented sha1 functionality."""
1398
def __init__(self, path):
1399
super(InstrumentedDirState, self).__init__(path)
1400
self._time_offset = 0
1403
def _sha_cutoff_time(self):
1404
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1405
self._cutoff_time = timestamp + self._time_offset
1407
def _sha1_file(self, abspath, entry):
1408
self._log.append(('sha1', abspath))
1409
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1411
def _read_link(self, abspath, old_link):
1412
self._log.append(('read_link', abspath, old_link))
1413
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1415
def _lstat(self, abspath, entry):
1416
self._log.append(('lstat', abspath))
1417
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1419
def _is_executable(self, mode, old_executable):
1420
self._log.append(('is_exec', mode, old_executable))
1421
return super(InstrumentedDirState, self)._is_executable(mode,
1424
def adjust_time(self, secs):
1425
"""Move the clock forward or back.
1427
:param secs: The amount to adjust the clock by. Positive values make it
1428
seem as if we are in the future, negative values make it seem like we
1431
self._time_offset += secs
1432
self._cutoff_time = None
1435
class _FakeStat(object):
1436
"""A class with the same attributes as a real stat result."""
1438
def __init__(self, size, mtime, ctime, dev, ino, mode):
1440
self.st_mtime = mtime
1441
self.st_ctime = ctime
1447
class TestUpdateEntry(TestCaseWithDirState):
1448
"""Test the DirState.update_entry functions"""
1450
def get_state_with_a(self):
1451
"""Create a DirState tracking a single object named 'a'"""
1452
state = InstrumentedDirState.initialize('dirstate')
1453
self.addCleanup(state.unlock)
1454
state.add('a', 'a-id', 'file', None, '')
1455
entry = state._get_entry(0, path_utf8='a')
1458
def test_update_entry(self):
1459
state, entry = self.get_state_with_a()
1460
self.build_tree(['a'])
1461
# Add one where we don't provide the stat or sha already
1462
self.assertEqual(('', 'a', 'a-id'), entry[0])
1463
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1465
# Flush the buffers to disk
1467
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1468
state._dirblock_state)
1470
stat_value = os.lstat('a')
1471
packed_stat = dirstate.pack_stat(stat_value)
1472
link_or_sha1 = state.update_entry(entry, abspath='a',
1473
stat_value=stat_value)
1474
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1477
# The dirblock entry should not cache the file's sha1
1478
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1480
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1481
state._dirblock_state)
1482
mode = stat_value.st_mode
1483
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1486
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1487
state._dirblock_state)
1489
# If we do it again right away, we don't know if the file has changed
1490
# so we will re-read the file. Roll the clock back so the file is
1491
# guaranteed to look too new.
1492
state.adjust_time(-10)
1494
link_or_sha1 = state.update_entry(entry, abspath='a',
1495
stat_value=stat_value)
1496
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1497
('sha1', 'a'), ('is_exec', mode, False),
1499
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1501
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1502
state._dirblock_state)
1503
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1507
# However, if we move the clock forward so the file is considered
1508
# "stable", it should just cache the value.
1509
state.adjust_time(+20)
1510
link_or_sha1 = state.update_entry(entry, abspath='a',
1511
stat_value=stat_value)
1512
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1514
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1515
('sha1', 'a'), ('is_exec', mode, False),
1516
('sha1', 'a'), ('is_exec', mode, False),
1518
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1521
# Subsequent calls will just return the cached value
1522
link_or_sha1 = state.update_entry(entry, abspath='a',
1523
stat_value=stat_value)
1524
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1526
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1527
('sha1', 'a'), ('is_exec', mode, False),
1528
('sha1', 'a'), ('is_exec', mode, False),
1530
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1533
def test_update_entry_symlink(self):
1534
"""Update entry should read symlinks."""
1535
if not osutils.has_symlinks():
1536
# PlatformDeficiency / TestSkipped
1537
raise TestSkipped("No symlink support")
1538
state, entry = self.get_state_with_a()
1540
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1541
state._dirblock_state)
1542
os.symlink('target', 'a')
1544
state.adjust_time(-10) # Make the symlink look new
1545
stat_value = os.lstat('a')
1546
packed_stat = dirstate.pack_stat(stat_value)
1547
link_or_sha1 = state.update_entry(entry, abspath='a',
1548
stat_value=stat_value)
1549
self.assertEqual('target', link_or_sha1)
1550
self.assertEqual([('read_link', 'a', '')], state._log)
1551
# Dirblock is not updated (the link is too new)
1552
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1554
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1555
state._dirblock_state)
1557
# Because the stat_value looks new, we should re-read the target
1558
link_or_sha1 = state.update_entry(entry, abspath='a',
1559
stat_value=stat_value)
1560
self.assertEqual('target', link_or_sha1)
1561
self.assertEqual([('read_link', 'a', ''),
1562
('read_link', 'a', ''),
1564
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1566
state.adjust_time(+20) # Skip into the future, all files look old
1567
link_or_sha1 = state.update_entry(entry, abspath='a',
1568
stat_value=stat_value)
1569
self.assertEqual('target', link_or_sha1)
1570
# We need to re-read the link because only now can we cache it
1571
self.assertEqual([('read_link', 'a', ''),
1572
('read_link', 'a', ''),
1573
('read_link', 'a', ''),
1575
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1578
# Another call won't re-read the link
1579
self.assertEqual([('read_link', 'a', ''),
1580
('read_link', 'a', ''),
1581
('read_link', 'a', ''),
1583
link_or_sha1 = state.update_entry(entry, abspath='a',
1584
stat_value=stat_value)
1585
self.assertEqual('target', link_or_sha1)
1586
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1589
def do_update_entry(self, state, entry, abspath):
1590
stat_value = os.lstat(abspath)
1591
return state.update_entry(entry, abspath, stat_value)
1593
def test_update_entry_dir(self):
1594
state, entry = self.get_state_with_a()
1595
self.build_tree(['a/'])
1596
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1598
def test_update_entry_dir_unchanged(self):
1599
state, entry = self.get_state_with_a()
1600
self.build_tree(['a/'])
1601
state.adjust_time(+20)
1602
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1603
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1604
state._dirblock_state)
1606
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1607
state._dirblock_state)
1608
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1609
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1610
state._dirblock_state)
1612
def test_update_entry_file_unchanged(self):
1613
state, entry = self.get_state_with_a()
1614
self.build_tree(['a'])
1615
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1616
state.adjust_time(+20)
1617
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1619
state._dirblock_state)
1621
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1622
state._dirblock_state)
1623
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1624
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1625
state._dirblock_state)
1627
def create_and_test_file(self, state, entry):
1628
"""Create a file at 'a' and verify the state finds it.
1630
The state should already be versioning *something* at 'a'. This makes
1631
sure that state.update_entry recognizes it as a file.
1633
self.build_tree(['a'])
1634
stat_value = os.lstat('a')
1635
packed_stat = dirstate.pack_stat(stat_value)
1637
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1638
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1640
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1644
def create_and_test_dir(self, state, entry):
1645
"""Create a directory at 'a' and verify the state finds it.
1647
The state should already be versioning *something* at 'a'. This makes
1648
sure that state.update_entry recognizes it as a directory.
1650
self.build_tree(['a/'])
1651
stat_value = os.lstat('a')
1652
packed_stat = dirstate.pack_stat(stat_value)
1654
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1655
self.assertIs(None, link_or_sha1)
1656
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1660
def create_and_test_symlink(self, state, entry):
1661
"""Create a symlink at 'a' and verify the state finds it.
1663
The state should already be versioning *something* at 'a'. This makes
1664
sure that state.update_entry recognizes it as a symlink.
1666
This should not be called if this platform does not have symlink
1669
# caller should care about skipping test on platforms without symlinks
1670
os.symlink('path/to/foo', 'a')
1672
stat_value = os.lstat('a')
1673
packed_stat = dirstate.pack_stat(stat_value)
1675
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1676
self.assertEqual('path/to/foo', link_or_sha1)
1677
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1681
def test_update_file_to_dir(self):
1682
"""If a file changes to a directory we return None for the sha.
1683
We also update the inventory record.
1685
state, entry = self.get_state_with_a()
1686
# The file sha1 won't be cached unless the file is old
1687
state.adjust_time(+10)
1688
self.create_and_test_file(state, entry)
1690
self.create_and_test_dir(state, entry)
1692
def test_update_file_to_symlink(self):
1693
"""File becomes a symlink"""
1694
if not osutils.has_symlinks():
1695
# PlatformDeficiency / TestSkipped
1696
raise TestSkipped("No symlink support")
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_file(state, entry)
1702
self.create_and_test_symlink(state, entry)
1704
def test_update_dir_to_file(self):
1705
"""Directory becoming a file updates the entry."""
1706
state, entry = self.get_state_with_a()
1707
# The file sha1 won't be cached unless the file is old
1708
state.adjust_time(+10)
1709
self.create_and_test_dir(state, entry)
1711
self.create_and_test_file(state, entry)
1713
def test_update_dir_to_symlink(self):
1714
"""Directory becomes a symlink"""
1715
if not osutils.has_symlinks():
1716
# PlatformDeficiency / TestSkipped
1717
raise TestSkipped("No symlink support")
1718
state, entry = self.get_state_with_a()
1719
# The symlink target won't be cached if it isn't old
1720
state.adjust_time(+10)
1721
self.create_and_test_dir(state, entry)
1723
self.create_and_test_symlink(state, entry)
1725
def test_update_symlink_to_file(self):
1726
"""Symlink becomes a file"""
1727
if not has_symlinks():
1728
raise TestSkipped("No symlink support")
1729
state, entry = self.get_state_with_a()
1730
# The symlink and file info won't be cached unless old
1731
state.adjust_time(+10)
1732
self.create_and_test_symlink(state, entry)
1734
self.create_and_test_file(state, entry)
1736
def test_update_symlink_to_dir(self):
1737
"""Symlink becomes a directory"""
1738
if not has_symlinks():
1739
raise TestSkipped("No symlink support")
1740
state, entry = self.get_state_with_a()
1741
# The symlink target won't be cached if it isn't old
1742
state.adjust_time(+10)
1743
self.create_and_test_symlink(state, entry)
1745
self.create_and_test_dir(state, entry)
1747
def test__is_executable_win32(self):
1748
state, entry = self.get_state_with_a()
1749
self.build_tree(['a'])
1751
# Make sure we are using the win32 implementation of _is_executable
1752
state._is_executable = state._is_executable_win32
1754
# The file on disk is not executable, but we are marking it as though
1755
# it is. With _is_executable_win32 we ignore what is on disk.
1756
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1758
stat_value = os.lstat('a')
1759
packed_stat = dirstate.pack_stat(stat_value)
1761
state.adjust_time(-10) # Make sure everything is new
1762
state.update_entry(entry, abspath='a', stat_value=stat_value)
1764
# The row is updated, but the executable bit stays set.
1765
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1768
# Make the disk object look old enough to cache
1769
state.adjust_time(+20)
1770
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1771
state.update_entry(entry, abspath='a', stat_value=stat_value)
1772
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1775
class TestPackStat(TestCaseWithTransport):
1777
def assertPackStat(self, expected, stat_value):
1778
"""Check the packed and serialized form of a stat value."""
1779
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1781
def test_pack_stat_int(self):
1782
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1783
# Make sure that all parameters have an impact on the packed stat.
1784
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1787
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1788
st.st_mtime = 1172758620
1790
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1791
st.st_ctime = 1172758630
1793
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1796
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1797
st.st_ino = 6499540L
1799
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1800
st.st_mode = 0100744
1802
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1804
def test_pack_stat_float(self):
1805
"""On some platforms mtime and ctime are floats.
1807
Make sure we don't get warnings or errors, and that we ignore changes <
1810
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1811
777L, 6499538L, 0100644)
1812
# These should all be the same as the integer counterparts
1813
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1814
st.st_mtime = 1172758620.0
1816
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1817
st.st_ctime = 1172758630.0
1819
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1820
# fractional seconds are discarded, so no change from above
1821
st.st_mtime = 1172758620.453
1822
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1823
st.st_ctime = 1172758630.228
1824
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1827
class TestBisect(TestCaseWithDirState):
1828
"""Test the ability to bisect into the disk format."""
1830
def assertBisect(self, expected_map, map_keys, state, paths):
1831
"""Assert that bisecting for paths returns the right result.
1833
:param expected_map: A map from key => entry value
1834
:param map_keys: The keys to expect for each path
1835
:param state: The DirState object.
1836
:param paths: A list of paths, these will automatically be split into
1837
(dir, name) tuples, and sorted according to how _bisect
1840
result = state._bisect(paths)
1841
# For now, results are just returned in whatever order we read them.
1842
# We could sort by (dir, name, file_id) or something like that, but in
1843
# the end it would still be fairly arbitrary, and we don't want the
1844
# extra overhead if we can avoid it. So sort everything to make sure
1846
assert len(map_keys) == len(paths)
1848
for path, keys in zip(paths, map_keys):
1850
# This should not be present in the output
1852
expected[path] = sorted(expected_map[k] for k in keys)
1854
# The returned values are just arranged randomly based on when they
1855
# were read, for testing, make sure it is properly sorted.
1859
self.assertEqual(expected, result)
1861
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1862
"""Assert that bisecting for dirbblocks returns the right result.
1864
:param expected_map: A map from key => expected values
1865
:param map_keys: A nested list of paths we expect to be returned.
1866
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1867
:param state: The DirState object.
1868
:param paths: A list of directories
1870
result = state._bisect_dirblocks(paths)
1871
assert len(map_keys) == len(paths)
1874
for path, keys in zip(paths, map_keys):
1876
# This should not be present in the output
1878
expected[path] = sorted(expected_map[k] for k in keys)
1882
self.assertEqual(expected, result)
1884
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1885
"""Assert the return value of a recursive bisection.
1887
:param expected_map: A map from key => entry value
1888
:param map_keys: A list of paths we expect to be returned.
1889
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1890
:param state: The DirState object.
1891
:param paths: A list of files and directories. It will be broken up
1892
into (dir, name) pairs and sorted before calling _bisect_recursive.
1895
for key in map_keys:
1896
entry = expected_map[key]
1897
dir_name_id, trees_info = entry
1898
expected[dir_name_id] = trees_info
1900
result = state._bisect_recursive(paths)
1902
self.assertEqual(expected, result)
1904
def test_bisect_each(self):
1905
"""Find a single record using bisect."""
1906
tree, state, expected = self.create_basic_dirstate()
1908
# Bisect should return the rows for the specified files.
1909
self.assertBisect(expected, [['']], state, [''])
1910
self.assertBisect(expected, [['a']], state, ['a'])
1911
self.assertBisect(expected, [['b']], state, ['b'])
1912
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1913
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1914
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1915
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1916
self.assertBisect(expected, [['f']], state, ['f'])
1918
def test_bisect_multi(self):
1919
"""Bisect can be used to find multiple records at the same time."""
1920
tree, state, expected = self.create_basic_dirstate()
1921
# Bisect should be capable of finding multiple entries at the same time
1922
self.assertBisect(expected, [['a'], ['b'], ['f']],
1923
state, ['a', 'b', 'f'])
1924
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1925
state, ['f', 'b/d', 'b/d/e'])
1926
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1927
state, ['b', 'b-c', 'b/c'])
1929
def test_bisect_one_page(self):
1930
"""Test bisect when there is only 1 page to read"""
1931
tree, state, expected = self.create_basic_dirstate()
1932
state._bisect_page_size = 5000
1933
self.assertBisect(expected,[['']], state, [''])
1934
self.assertBisect(expected,[['a']], state, ['a'])
1935
self.assertBisect(expected,[['b']], state, ['b'])
1936
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1937
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1938
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1939
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1940
self.assertBisect(expected,[['f']], state, ['f'])
1941
self.assertBisect(expected,[['a'], ['b'], ['f']],
1942
state, ['a', 'b', 'f'])
1943
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1944
state, ['b/d', 'b/d/e', 'f'])
1945
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1946
state, ['b', 'b/c', 'b-c'])
1948
def test_bisect_duplicate_paths(self):
1949
"""When bisecting for a path, handle multiple entries."""
1950
tree, state, expected = self.create_duplicated_dirstate()
1952
# Now make sure that both records are properly returned.
1953
self.assertBisect(expected, [['']], state, [''])
1954
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1955
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1956
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1957
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1958
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1960
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1961
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1963
def test_bisect_page_size_too_small(self):
1964
"""If the page size is too small, we will auto increase it."""
1965
tree, state, expected = self.create_basic_dirstate()
1966
state._bisect_page_size = 50
1967
self.assertBisect(expected, [None], state, ['b/e'])
1968
self.assertBisect(expected, [['a']], state, ['a'])
1969
self.assertBisect(expected, [['b']], state, ['b'])
1970
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1971
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1972
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1973
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1974
self.assertBisect(expected, [['f']], state, ['f'])
1976
def test_bisect_missing(self):
1977
"""Test that bisect return None if it cannot find a path."""
1978
tree, state, expected = self.create_basic_dirstate()
1979
self.assertBisect(expected, [None], state, ['foo'])
1980
self.assertBisect(expected, [None], state, ['b/foo'])
1981
self.assertBisect(expected, [None], state, ['bar/foo'])
1982
self.assertBisect(expected, [None], state, ['b-c/foo'])
1984
self.assertBisect(expected, [['a'], None, ['b/d']],
1985
state, ['a', 'foo', 'b/d'])
1987
def test_bisect_rename(self):
1988
"""Check that we find a renamed row."""
1989
tree, state, expected = self.create_renamed_dirstate()
1991
# Search for the pre and post renamed entries
1992
self.assertBisect(expected, [['a']], state, ['a'])
1993
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1994
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1995
self.assertBisect(expected, [['h']], state, ['h'])
1997
# What about b/d/e? shouldn't that also get 2 directory entries?
1998
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1999
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2001
def test_bisect_dirblocks(self):
2002
tree, state, expected = self.create_duplicated_dirstate()
2003
self.assertBisectDirBlocks(expected,
2004
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2006
self.assertBisectDirBlocks(expected,
2007
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2008
self.assertBisectDirBlocks(expected,
2009
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2010
self.assertBisectDirBlocks(expected,
2011
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2012
['b/c', 'b/c2', 'b/d', 'b/d2'],
2013
['b/d/e', 'b/d/e2'],
2014
], state, ['', 'b', 'b/d'])
2016
def test_bisect_dirblocks_missing(self):
2017
tree, state, expected = self.create_basic_dirstate()
2018
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2019
state, ['b/d', 'b/e'])
2020
# Files don't show up in this search
2021
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2022
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2023
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2024
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2025
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2027
def test_bisect_recursive_each(self):
2028
tree, state, expected = self.create_basic_dirstate()
2029
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2030
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2031
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2032
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2033
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2035
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2037
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2041
def test_bisect_recursive_multiple(self):
2042
tree, state, expected = self.create_basic_dirstate()
2043
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2044
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2045
state, ['b/d', 'b/d/e'])
2047
def test_bisect_recursive_missing(self):
2048
tree, state, expected = self.create_basic_dirstate()
2049
self.assertBisectRecursive(expected, [], state, ['d'])
2050
self.assertBisectRecursive(expected, [], state, ['b/e'])
2051
self.assertBisectRecursive(expected, [], state, ['g'])
2052
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2054
def test_bisect_recursive_renamed(self):
2055
tree, state, expected = self.create_renamed_dirstate()
2057
# Looking for either renamed item should find the other
2058
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2059
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2060
# Looking in the containing directory should find the rename target,
2061
# and anything in a subdir of the renamed target.
2062
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2063
'b/d/e', 'b/g', 'h', 'h/e'],
2067
class TestDirstateValidation(TestCaseWithDirState):
2069
def test_validate_correct_dirstate(self):
2070
state = self.create_complex_dirstate()
2073
# and make sure we can also validate with a read lock
2080
def test_dirblock_not_sorted(self):
2081
tree, state, expected = self.create_renamed_dirstate()
2082
state._read_dirblocks_if_needed()
2083
last_dirblock = state._dirblocks[-1]
2084
# we're appending to the dirblock, but this name comes before some of
2085
# the existing names; that's wrong
2086
last_dirblock[1].append(
2087
(('h', 'aaaa', 'a-id'),
2088
[('a', '', 0, False, ''),
2089
('a', '', 0, False, '')]))
2090
e = self.assertRaises(AssertionError,
2092
self.assertContainsRe(str(e), 'not sorted')
2094
def test_dirblock_name_mismatch(self):
2095
tree, state, expected = self.create_renamed_dirstate()
2096
state._read_dirblocks_if_needed()
2097
last_dirblock = state._dirblocks[-1]
2098
# add an entry with the wrong directory name
2099
last_dirblock[1].append(
2101
[('a', '', 0, False, ''),
2102
('a', '', 0, False, '')]))
2103
e = self.assertRaises(AssertionError,
2105
self.assertContainsRe(str(e),
2106
"doesn't match directory name")
2108
def test_dirblock_missing_rename(self):
2109
tree, state, expected = self.create_renamed_dirstate()
2110
state._read_dirblocks_if_needed()
2111
last_dirblock = state._dirblocks[-1]
2112
# make another entry for a-id, without a correct 'r' pointer to
2113
# the real occurrence in the working tree
2114
last_dirblock[1].append(
2115
(('h', 'z', 'a-id'),
2116
[('a', '', 0, False, ''),
2117
('a', '', 0, False, '')]))
2118
e = self.assertRaises(AssertionError,
2120
self.assertContainsRe(str(e),
2121
'file a-id is absent in row')