1
# Copyright (C) 2006-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
27
revision as _mod_revision,
30
from bzrlib.tests import test_osutils
31
from bzrlib.tests.scenarios import load_tests_apply_scenarios
36
# general checks for NOT_IN_MEMORY error conditions.
37
# set_path_id on a NOT_IN_MEMORY dirstate
38
# set_path_id unicode support
39
# set_path_id setting id of a path not root
40
# set_path_id setting id when there are parents without the id in the parents
41
# set_path_id setting id when there are parents with the id in the parents
42
# set_path_id setting id when state is not in memory
43
# set_path_id setting id when state is in memory unmodified
44
# set_path_id setting id when state is in memory modified
47
load_tests = load_tests_apply_scenarios
50
class TestCaseWithDirState(tests.TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
scenarios = test_osutils.dir_reader_scenarios()
56
_dir_reader_class = None
57
_native_to_unicode = None # Not used yet
60
tests.TestCaseWithTransport.setUp(self)
62
self.overrideAttr(osutils,
63
'_selected_dir_reader', self._dir_reader_class())
65
def create_empty_dirstate(self):
66
"""Return a locked but empty dirstate"""
67
state = dirstate.DirState.initialize('dirstate')
70
def create_dirstate_with_root(self):
71
"""Return a write-locked state with a single root entry."""
72
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
73
root_entry_direntry = ('', '', 'a-root-value'), [
74
('d', '', 0, False, packed_stat),
77
dirblocks.append(('', [root_entry_direntry]))
78
dirblocks.append(('', []))
79
state = self.create_empty_dirstate()
81
state._set_data([], dirblocks)
88
def create_dirstate_with_root_and_subdir(self):
89
"""Return a locked DirState with a root and a subdir"""
90
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
91
subdir_entry = ('', 'subdir', 'subdir-id'), [
92
('d', '', 0, False, packed_stat),
94
state = self.create_dirstate_with_root()
96
dirblocks = list(state._dirblocks)
97
dirblocks[1][1].append(subdir_entry)
98
state._set_data([], dirblocks)
104
def create_complex_dirstate(self):
105
"""This dirstate contains multiple files and directories.
115
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
117
Notice that a/e is an empty directory.
119
:return: The dirstate, still write-locked.
121
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
122
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
123
root_entry = ('', '', 'a-root-value'), [
124
('d', '', 0, False, packed_stat),
126
a_entry = ('', 'a', 'a-dir'), [
127
('d', '', 0, False, packed_stat),
129
b_entry = ('', 'b', 'b-dir'), [
130
('d', '', 0, False, packed_stat),
132
c_entry = ('', 'c', 'c-file'), [
133
('f', null_sha, 10, False, packed_stat),
135
d_entry = ('', 'd', 'd-file'), [
136
('f', null_sha, 20, False, packed_stat),
138
e_entry = ('a', 'e', 'e-dir'), [
139
('d', '', 0, False, packed_stat),
141
f_entry = ('a', 'f', 'f-file'), [
142
('f', null_sha, 30, False, packed_stat),
144
g_entry = ('b', 'g', 'g-file'), [
145
('f', null_sha, 30, False, packed_stat),
147
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
148
('f', null_sha, 40, False, packed_stat),
151
dirblocks.append(('', [root_entry]))
152
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
153
dirblocks.append(('a', [e_entry, f_entry]))
154
dirblocks.append(('b', [g_entry, h_entry]))
155
state = dirstate.DirState.initialize('dirstate')
158
state._set_data([], dirblocks)
164
def check_state_with_reopen(self, expected_result, state):
165
"""Check that state has current state expected_result.
167
This will check the current state, open the file anew and check it
169
This function expects the current state to be locked for writing, and
170
will unlock it before re-opening.
171
This is required because we can't open a lock_read() while something
172
else has a lock_write().
173
write => mutually exclusive lock
176
# The state should already be write locked, since we just had to do
177
# some operation to get here.
178
self.assertTrue(state._lock_token is not None)
180
self.assertEqual(expected_result[0], state.get_parent_ids())
181
# there should be no ghosts in this tree.
182
self.assertEqual([], state.get_ghosts())
183
# there should be one fileid in this tree - the root of the tree.
184
self.assertEqual(expected_result[1], list(state._iter_entries()))
189
state = dirstate.DirState.on_file('dirstate')
192
self.assertEqual(expected_result[1], list(state._iter_entries()))
196
def create_basic_dirstate(self):
197
"""Create a dirstate with a few files and directories.
207
tree = self.make_branch_and_tree('tree')
208
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
209
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
210
self.build_tree(['tree/' + p for p in paths])
211
tree.set_root_id('TREE_ROOT')
212
tree.add([p.rstrip('/') for p in paths], file_ids)
213
tree.commit('initial', rev_id='rev-1')
214
revision_id = 'rev-1'
215
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
216
t = self.get_transport('tree')
217
a_text = t.get_bytes('a')
218
a_sha = osutils.sha_string(a_text)
220
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
221
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
222
c_text = t.get_bytes('b/c')
223
c_sha = osutils.sha_string(c_text)
225
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
226
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
227
e_text = t.get_bytes('b/d/e')
228
e_sha = osutils.sha_string(e_text)
230
b_c_text = t.get_bytes('b-c')
231
b_c_sha = osutils.sha_string(b_c_text)
232
b_c_len = len(b_c_text)
233
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
234
f_text = t.get_bytes('f')
235
f_sha = osutils.sha_string(f_text)
237
null_stat = dirstate.DirState.NULLSTAT
239
'':(('', '', 'TREE_ROOT'), [
240
('d', '', 0, False, null_stat),
241
('d', '', 0, False, revision_id),
243
'a':(('', 'a', 'a-id'), [
244
('f', '', 0, False, null_stat),
245
('f', a_sha, a_len, False, revision_id),
247
'b':(('', 'b', 'b-id'), [
248
('d', '', 0, False, null_stat),
249
('d', '', 0, False, revision_id),
251
'b/c':(('b', 'c', 'c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', c_sha, c_len, False, revision_id),
255
'b/d':(('b', 'd', 'd-id'), [
256
('d', '', 0, False, null_stat),
257
('d', '', 0, False, revision_id),
259
'b/d/e':(('b/d', 'e', 'e-id'), [
260
('f', '', 0, False, null_stat),
261
('f', e_sha, e_len, False, revision_id),
263
'b-c':(('', 'b-c', 'b-c-id'), [
264
('f', '', 0, False, null_stat),
265
('f', b_c_sha, b_c_len, False, revision_id),
267
'f':(('', 'f', 'f-id'), [
268
('f', '', 0, False, null_stat),
269
('f', f_sha, f_len, False, revision_id),
272
state = dirstate.DirState.from_tree(tree, 'dirstate')
277
# Use a different object, to make sure nothing is pre-cached in memory.
278
state = dirstate.DirState.on_file('dirstate')
280
self.addCleanup(state.unlock)
281
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
282
state._dirblock_state)
283
# This is code is only really tested if we actually have to make more
284
# than one read, so set the page size to something smaller.
285
# We want it to contain about 2.2 records, so that we have a couple
286
# records that we can read per attempt
287
state._bisect_page_size = 200
288
return tree, state, expected
290
def create_duplicated_dirstate(self):
291
"""Create a dirstate with a deleted and added entries.
293
This grabs a basic_dirstate, and then removes and re adds every entry
296
tree, state, expected = self.create_basic_dirstate()
297
# Now we will just remove and add every file so we get an extra entry
298
# per entry. Unversion in reverse order so we handle subdirs
299
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
300
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
301
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
303
# Update the expected dictionary.
304
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
305
orig = expected[path]
307
# This record was deleted in the current tree
308
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
310
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
311
# And didn't exist in the basis tree
312
expected[path2] = (new_key, [orig[1][0],
313
dirstate.DirState.NULL_PARENT_DETAILS])
315
# We will replace the 'dirstate' file underneath 'state', but that is
316
# okay as lock as we unlock 'state' first.
319
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
325
# But we need to leave state in a read-lock because we already have
326
# a cleanup scheduled
328
return tree, state, expected
330
def create_renamed_dirstate(self):
331
"""Create a dirstate with a few internal renames.
333
This takes the basic dirstate, and moves the paths around.
335
tree, state, expected = self.create_basic_dirstate()
337
tree.rename_one('a', 'b/g')
339
tree.rename_one('b/d', 'h')
341
old_a = expected['a']
342
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
343
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
344
('r', 'a', 0, False, '')])
345
old_d = expected['b/d']
346
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
347
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
348
('r', 'b/d', 0, False, '')])
350
old_e = expected['b/d/e']
351
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
353
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
354
('r', 'b/d/e', 0, False, '')])
358
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
365
return tree, state, expected
368
class TestTreeToDirState(TestCaseWithDirState):
370
def test_empty_to_dirstate(self):
371
"""We should be able to create a dirstate for an empty tree."""
372
# There are no files on disk and no parents
373
tree = self.make_branch_and_tree('tree')
374
expected_result = ([], [
375
(('', '', tree.get_root_id()), # common details
376
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
state = dirstate.DirState.from_tree(tree, 'dirstate')
380
self.check_state_with_reopen(expected_result, state)
382
def test_1_parents_empty_to_dirstate(self):
383
# create a parent by doing a commit
384
tree = self.make_branch_and_tree('tree')
385
rev_id = tree.commit('first post').encode('utf8')
386
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
387
expected_result = ([rev_id], [
388
(('', '', tree.get_root_id()), # common details
389
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
390
('d', '', 0, False, rev_id), # first parent details
392
state = dirstate.DirState.from_tree(tree, 'dirstate')
393
self.check_state_with_reopen(expected_result, state)
400
def test_2_parents_empty_to_dirstate(self):
401
# create a parent by doing a commit
402
tree = self.make_branch_and_tree('tree')
403
rev_id = tree.commit('first post')
404
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
405
rev_id2 = tree2.commit('second post', allow_pointless=True)
406
tree.merge_from_branch(tree2.branch)
407
expected_result = ([rev_id, rev_id2], [
408
(('', '', tree.get_root_id()), # common details
409
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
410
('d', '', 0, False, rev_id), # first parent details
411
('d', '', 0, False, rev_id), # second parent details
413
state = dirstate.DirState.from_tree(tree, 'dirstate')
414
self.check_state_with_reopen(expected_result, state)
421
def test_empty_unknowns_are_ignored_to_dirstate(self):
422
"""We should be able to create a dirstate for an empty tree."""
423
# There are no files on disk and no parents
424
tree = self.make_branch_and_tree('tree')
425
self.build_tree(['tree/unknown'])
426
expected_result = ([], [
427
(('', '', tree.get_root_id()), # common details
428
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
430
state = dirstate.DirState.from_tree(tree, 'dirstate')
431
self.check_state_with_reopen(expected_result, state)
433
def get_tree_with_a_file(self):
434
tree = self.make_branch_and_tree('tree')
435
self.build_tree(['tree/a file'])
436
tree.add('a file', 'a-file-id')
439
def test_non_empty_no_parents_to_dirstate(self):
440
"""We should be able to create a dirstate for an empty tree."""
441
# There are files on disk and no parents
442
tree = self.get_tree_with_a_file()
443
expected_result = ([], [
444
(('', '', tree.get_root_id()), # common details
445
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
447
(('', 'a file', 'a-file-id'), # common
448
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
451
state = dirstate.DirState.from_tree(tree, 'dirstate')
452
self.check_state_with_reopen(expected_result, state)
454
def test_1_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
# change the current content to be different this will alter stat, sha
460
self.build_tree_contents([('tree/a file', 'new content\n')])
461
expected_result = ([rev_id], [
462
(('', '', tree.get_root_id()), # common details
463
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
464
('d', '', 0, False, rev_id), # first parent details
466
(('', 'a file', 'a-file-id'), # common
467
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
468
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
469
rev_id), # first parent
472
state = dirstate.DirState.from_tree(tree, 'dirstate')
473
self.check_state_with_reopen(expected_result, state)
475
def test_2_parents_not_empty_to_dirstate(self):
476
# create a parent by doing a commit
477
tree = self.get_tree_with_a_file()
478
rev_id = tree.commit('first post').encode('utf8')
479
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
480
# change the current content to be different this will alter stat, sha
482
self.build_tree_contents([('tree2/a file', 'merge content\n')])
483
rev_id2 = tree2.commit('second post').encode('utf8')
484
tree.merge_from_branch(tree2.branch)
485
# change the current content to be different this will alter stat, sha
486
# and length again, giving us three distinct values:
487
self.build_tree_contents([('tree/a file', 'new content\n')])
488
expected_result = ([rev_id, rev_id2], [
489
(('', '', tree.get_root_id()), # common details
490
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
491
('d', '', 0, False, rev_id), # first parent details
492
('d', '', 0, False, rev_id), # second parent details
494
(('', 'a file', 'a-file-id'), # common
495
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
496
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
497
rev_id), # first parent
498
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
499
rev_id2), # second parent
502
state = dirstate.DirState.from_tree(tree, 'dirstate')
503
self.check_state_with_reopen(expected_result, state)
505
def test_colliding_fileids(self):
506
# test insertion of parents creating several entries at the same path.
507
# we used to have a bug where they could cause the dirstate to break
508
# its ordering invariants.
509
# create some trees to test from
512
tree = self.make_branch_and_tree('tree%d' % i)
513
self.build_tree(['tree%d/name' % i,])
514
tree.add(['name'], ['file-id%d' % i])
515
revision_id = 'revid-%d' % i
516
tree.commit('message', rev_id=revision_id)
517
parents.append((revision_id,
518
tree.branch.repository.revision_tree(revision_id)))
519
# now fold these trees into a dirstate
520
state = dirstate.DirState.initialize('dirstate')
522
state.set_parent_trees(parents, [])
528
class TestDirStateOnFile(TestCaseWithDirState):
530
def create_updated_dirstate(self):
531
self.build_tree(['a-file'])
532
tree = self.make_branch_and_tree('.')
533
tree.add(['a-file'], ['a-id'])
534
tree.commit('add a-file')
535
# Save and unlock the state, re-open it in readonly mode
536
state = dirstate.DirState.from_tree(tree, 'dirstate')
539
state = dirstate.DirState.on_file('dirstate')
543
def test_construct_with_path(self):
544
tree = self.make_branch_and_tree('tree')
545
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
546
# we want to be able to get the lines of the dirstate that we will
548
lines = state.get_lines()
550
self.build_tree_contents([('dirstate', ''.join(lines))])
552
# no parents, default tree content
553
expected_result = ([], [
554
(('', '', tree.get_root_id()), # common details
555
# current tree details, but new from_tree skips statting, it
556
# uses set_state_from_inventory, and thus depends on the
558
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
561
state = dirstate.DirState.on_file('dirstate')
562
state.lock_write() # check_state_with_reopen will save() and unlock it
563
self.check_state_with_reopen(expected_result, state)
565
def test_can_save_clean_on_file(self):
566
tree = self.make_branch_and_tree('tree')
567
state = dirstate.DirState.from_tree(tree, 'dirstate')
569
# doing a save should work here as there have been no changes.
571
# TODO: stat it and check it hasn't changed; may require waiting
572
# for the state accuracy window.
576
def test_can_save_in_read_lock(self):
577
state = self.create_updated_dirstate()
579
entry = state._get_entry(0, path_utf8='a-file')
580
# The current size should be 0 (default)
581
self.assertEqual(0, entry[1][0][2])
582
# We should have a real entry.
583
self.assertNotEqual((None, None), entry)
584
# Set the cutoff-time into the future, so things look cacheable
585
state._sha_cutoff_time()
586
state._cutoff_time += 10.0
587
st = os.lstat('a-file')
588
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
589
# We updated the current sha1sum because the file is cacheable
590
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
593
# The dirblock has been updated
594
self.assertEqual(st.st_size, entry[1][0][2])
595
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
596
state._dirblock_state)
599
# Now, since we are the only one holding a lock, we should be able
600
# to save and have it written to disk
605
# Re-open the file, and ensure that the state has been updated.
606
state = dirstate.DirState.on_file('dirstate')
609
entry = state._get_entry(0, path_utf8='a-file')
610
self.assertEqual(st.st_size, entry[1][0][2])
614
def test_save_fails_quietly_if_locked(self):
615
"""If dirstate is locked, save will fail without complaining."""
616
state = self.create_updated_dirstate()
618
entry = state._get_entry(0, path_utf8='a-file')
619
# No cached sha1 yet.
620
self.assertEqual('', entry[1][0][1])
621
# Set the cutoff-time into the future, so things look cacheable
622
state._sha_cutoff_time()
623
state._cutoff_time += 10.0
624
st = os.lstat('a-file')
625
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
626
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
628
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
629
state._dirblock_state)
631
# Now, before we try to save, grab another dirstate, and take out a
633
# TODO: jam 20070315 Ideally this would be locked by another
634
# process. To make sure the file is really OS locked.
635
state2 = dirstate.DirState.on_file('dirstate')
638
# This won't actually write anything, because it couldn't grab
639
# a write lock. But it shouldn't raise an error, either.
640
# TODO: jam 20070315 We should probably distinguish between
641
# being dirty because of 'update_entry'. And dirty
642
# because of real modification. So that save() *does*
643
# raise a real error if it fails when we have real
651
# The file on disk should not be modified.
652
state = dirstate.DirState.on_file('dirstate')
655
entry = state._get_entry(0, path_utf8='a-file')
656
self.assertEqual('', entry[1][0][1])
660
def test_save_refuses_if_changes_aborted(self):
661
self.build_tree(['a-file', 'a-dir/'])
662
state = dirstate.DirState.initialize('dirstate')
664
# No stat and no sha1 sum.
665
state.add('a-file', 'a-file-id', 'file', None, '')
670
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
672
('', [(('', '', 'TREE_ROOT'),
673
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
674
('', [(('', 'a-file', 'a-file-id'),
675
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
678
state = dirstate.DirState.on_file('dirstate')
681
state._read_dirblocks_if_needed()
682
self.assertEqual(expected_blocks, state._dirblocks)
684
# Now modify the state, but mark it as inconsistent
685
state.add('a-dir', 'a-dir-id', 'directory', None, '')
686
state._changes_aborted = True
691
state = dirstate.DirState.on_file('dirstate')
694
state._read_dirblocks_if_needed()
695
self.assertEqual(expected_blocks, state._dirblocks)
700
class TestDirStateInitialize(TestCaseWithDirState):
702
def test_initialize(self):
703
expected_result = ([], [
704
(('', '', 'TREE_ROOT'), # common details
705
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
708
state = dirstate.DirState.initialize('dirstate')
710
self.assertIsInstance(state, dirstate.DirState)
711
lines = state.get_lines()
714
# On win32 you can't read from a locked file, even within the same
715
# process. So we have to unlock and release before we check the file
717
self.assertFileEqual(''.join(lines), 'dirstate')
718
state.lock_read() # check_state_with_reopen will unlock
719
self.check_state_with_reopen(expected_result, state)
722
class TestDirStateManipulations(TestCaseWithDirState):
724
def make_minimal_tree(self):
725
tree1 = self.make_branch_and_memory_tree('tree1')
727
self.addCleanup(tree1.unlock)
729
revid1 = tree1.commit('foo')
732
def test_update_minimal_updates_id_index(self):
733
state = self.create_dirstate_with_root_and_subdir()
734
self.addCleanup(state.unlock)
735
id_index = state._get_id_index()
736
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
737
state.add('file-name', 'file-id', 'file', None, '')
738
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
740
state.update_minimal(('', 'new-name', 'file-id'), 'f',
741
path_utf8='new-name')
742
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
744
self.assertEqual([('', 'new-name', 'file-id')],
745
sorted(id_index['file-id']))
748
def test_set_state_from_inventory_no_content_no_parents(self):
749
# setting the current inventory is a slow but important api to support.
750
tree1, revid1 = self.make_minimal_tree()
751
inv = tree1.inventory
752
root_id = inv.path2id('')
753
expected_result = [], [
754
(('', '', root_id), [
755
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
756
state = dirstate.DirState.initialize('dirstate')
758
state.set_state_from_inventory(inv)
759
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
761
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
762
state._dirblock_state)
767
# This will unlock it
768
self.check_state_with_reopen(expected_result, state)
770
def test_set_state_from_scratch_no_parents(self):
771
tree1, revid1 = self.make_minimal_tree()
772
inv = tree1.inventory
773
root_id = inv.path2id('')
774
expected_result = [], [
775
(('', '', root_id), [
776
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
777
state = dirstate.DirState.initialize('dirstate')
779
state.set_state_from_scratch(inv, [], [])
780
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
782
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
783
state._dirblock_state)
788
# This will unlock it
789
self.check_state_with_reopen(expected_result, state)
791
def test_set_state_from_scratch_identical_parent(self):
792
tree1, revid1 = self.make_minimal_tree()
793
inv = tree1.inventory
794
root_id = inv.path2id('')
795
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
796
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
797
parent_entry = ('d', '', 0, False, revid1)
798
expected_result = [revid1], [
799
(('', '', root_id), [d_entry, parent_entry])]
800
state = dirstate.DirState.initialize('dirstate')
802
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
803
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
805
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
806
state._dirblock_state)
811
# This will unlock it
812
self.check_state_with_reopen(expected_result, state)
814
def test_set_state_from_inventory_preserves_hashcache(self):
815
# https://bugs.launchpad.net/bzr/+bug/146176
816
# set_state_from_inventory should preserve the stat and hash value for
817
# workingtree files that are not changed by the inventory.
819
tree = self.make_branch_and_tree('.')
820
# depends on the default format using dirstate...
823
# make a dirstate with some valid hashcache data
824
# file on disk, but that's not needed for this test
825
foo_contents = 'contents of foo'
826
self.build_tree_contents([('foo', foo_contents)])
827
tree.add('foo', 'foo-id')
829
foo_stat = os.stat('foo')
830
foo_packed = dirstate.pack_stat(foo_stat)
831
foo_sha = osutils.sha_string(foo_contents)
832
foo_size = len(foo_contents)
834
# should not be cached yet, because the file's too fresh
836
(('', 'foo', 'foo-id',),
837
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
838
tree._dirstate._get_entry(0, 'foo-id'))
839
# poke in some hashcache information - it wouldn't normally be
840
# stored because it's too fresh
841
tree._dirstate.update_minimal(
842
('', 'foo', 'foo-id'),
843
'f', False, foo_sha, foo_packed, foo_size, 'foo')
844
# now should be cached
846
(('', 'foo', 'foo-id',),
847
[('f', foo_sha, foo_size, False, foo_packed)]),
848
tree._dirstate._get_entry(0, 'foo-id'))
850
# extract the inventory, and add something to it
851
inv = tree._get_inventory()
852
# should see the file we poked in...
853
self.assertTrue(inv.has_id('foo-id'))
854
self.assertTrue(inv.has_filename('foo'))
855
inv.add_path('bar', 'file', 'bar-id')
856
tree._dirstate._validate()
857
# this used to cause it to lose its hashcache
858
tree._dirstate.set_state_from_inventory(inv)
859
tree._dirstate._validate()
865
# now check that the state still has the original hashcache value
866
state = tree._dirstate
868
foo_tuple = state._get_entry(0, path_utf8='foo')
870
(('', 'foo', 'foo-id',),
871
[('f', foo_sha, len(foo_contents), False,
872
dirstate.pack_stat(foo_stat))]),
877
def test_set_state_from_inventory_mixed_paths(self):
878
tree1 = self.make_branch_and_tree('tree1')
879
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
880
'tree1/a/b/foo', 'tree1/a-b/bar'])
883
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
884
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
885
tree1.commit('rev1', rev_id='rev1')
886
root_id = tree1.get_root_id()
887
inv = tree1.inventory
890
expected_result1 = [('', '', root_id, 'd'),
891
('', 'a', 'a-id', 'd'),
892
('', 'a-b', 'a-b-id', 'd'),
893
('a', 'b', 'b-id', 'd'),
894
('a/b', 'foo', 'foo-id', 'f'),
895
('a-b', 'bar', 'bar-id', 'f'),
897
expected_result2 = [('', '', root_id, 'd'),
898
('', 'a', 'a-id', 'd'),
899
('', 'a-b', 'a-b-id', 'd'),
900
('a-b', 'bar', 'bar-id', 'f'),
902
state = dirstate.DirState.initialize('dirstate')
904
state.set_state_from_inventory(inv)
906
for entry in state._iter_entries():
907
values.append(entry[0] + entry[1][0][:1])
908
self.assertEqual(expected_result1, values)
910
state.set_state_from_inventory(inv)
912
for entry in state._iter_entries():
913
values.append(entry[0] + entry[1][0][:1])
914
self.assertEqual(expected_result2, values)
918
def test_set_path_id_no_parents(self):
919
"""The id of a path can be changed trivally with no parents."""
920
state = dirstate.DirState.initialize('dirstate')
922
# check precondition to be sure the state does change appropriately.
923
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
924
self.assertEqual([root_entry], list(state._iter_entries()))
925
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
926
self.assertEqual(root_entry,
927
state._get_entry(0, fileid_utf8='TREE_ROOT'))
928
self.assertEqual((None, None),
929
state._get_entry(0, fileid_utf8='second-root-id'))
930
state.set_path_id('', 'second-root-id')
931
new_root_entry = (('', '', 'second-root-id'),
932
[('d', '', 0, False, 'x'*32)])
933
expected_rows = [new_root_entry]
934
self.assertEqual(expected_rows, list(state._iter_entries()))
935
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='second-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(0, fileid_utf8='TREE_ROOT'))
940
# should work across save too
944
state = dirstate.DirState.on_file('dirstate')
948
self.assertEqual(expected_rows, list(state._iter_entries()))
952
def test_set_path_id_with_parents(self):
953
"""Set the root file id in a dirstate with parents"""
954
mt = self.make_branch_and_tree('mt')
955
# in case the default tree format uses a different root id
956
mt.set_root_id('TREE_ROOT')
957
mt.commit('foo', rev_id='parent-revid')
958
rt = mt.branch.repository.revision_tree('parent-revid')
959
state = dirstate.DirState.initialize('dirstate')
962
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
963
root_entry = (('', '', 'TREE_ROOT'),
964
[('d', '', 0, False, 'x'*32),
965
('d', '', 0, False, 'parent-revid')])
966
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
967
self.assertEqual(root_entry,
968
state._get_entry(0, fileid_utf8='TREE_ROOT'))
969
self.assertEqual((None, None),
970
state._get_entry(0, fileid_utf8='Asecond-root-id'))
971
state.set_path_id('', 'Asecond-root-id')
973
# now see that it is what we expected
974
old_root_entry = (('', '', 'TREE_ROOT'),
975
[('a', '', 0, False, ''),
976
('d', '', 0, False, 'parent-revid')])
977
new_root_entry = (('', '', 'Asecond-root-id'),
978
[('d', '', 0, False, ''),
979
('a', '', 0, False, '')])
980
expected_rows = [new_root_entry, old_root_entry]
982
self.assertEqual(expected_rows, list(state._iter_entries()))
983
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
984
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
985
self.assertEqual((None, None),
986
state._get_entry(0, fileid_utf8='TREE_ROOT'))
987
self.assertEqual(old_root_entry,
988
state._get_entry(1, fileid_utf8='TREE_ROOT'))
989
self.assertEqual(new_root_entry,
990
state._get_entry(0, fileid_utf8='Asecond-root-id'))
991
self.assertEqual((None, None),
992
state._get_entry(1, fileid_utf8='Asecond-root-id'))
993
# should work across save too
997
# now flush & check we get the same
998
state = dirstate.DirState.on_file('dirstate')
1002
self.assertEqual(expected_rows, list(state._iter_entries()))
1005
# now change within an existing file-backed state
1009
state.set_path_id('', 'tree-root-2')
1014
def test_set_parent_trees_no_content(self):
1015
# set_parent_trees is a slow but important api to support.
1016
tree1 = self.make_branch_and_memory_tree('tree1')
1020
revid1 = tree1.commit('foo')
1023
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1024
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1027
revid2 = tree2.commit('foo')
1028
root_id = tree2.get_root_id()
1031
state = dirstate.DirState.initialize('dirstate')
1033
state.set_path_id('', root_id)
1034
state.set_parent_trees(
1035
((revid1, tree1.branch.repository.revision_tree(revid1)),
1036
(revid2, tree2.branch.repository.revision_tree(revid2)),
1037
('ghost-rev', None)),
1039
# check we can reopen and use the dirstate after setting parent
1046
state = dirstate.DirState.on_file('dirstate')
1049
self.assertEqual([revid1, revid2, 'ghost-rev'],
1050
state.get_parent_ids())
1051
# iterating the entire state ensures that the state is parsable.
1052
list(state._iter_entries())
1053
# be sure that it sets not appends - change it
1054
state.set_parent_trees(
1055
((revid1, tree1.branch.repository.revision_tree(revid1)),
1056
('ghost-rev', None)),
1058
# and now put it back.
1059
state.set_parent_trees(
1060
((revid1, tree1.branch.repository.revision_tree(revid1)),
1061
(revid2, tree2.branch.repository.revision_tree(revid2)),
1062
('ghost-rev', tree2.branch.repository.revision_tree(
1063
_mod_revision.NULL_REVISION))),
1065
self.assertEqual([revid1, revid2, 'ghost-rev'],
1066
state.get_parent_ids())
1067
# the ghost should be recorded as such by set_parent_trees.
1068
self.assertEqual(['ghost-rev'], state.get_ghosts())
1070
[(('', '', root_id), [
1071
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1072
('d', '', 0, False, revid1),
1073
('d', '', 0, False, revid1)
1075
list(state._iter_entries()))
1079
def test_set_parent_trees_file_missing_from_tree(self):
1080
# Adding a parent tree may reference files not in the current state.
1081
# they should get listed just once by id, even if they are in two
1083
# set_parent_trees is a slow but important api to support.
1084
tree1 = self.make_branch_and_memory_tree('tree1')
1088
tree1.add(['a file'], ['file-id'], ['file'])
1089
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1090
revid1 = tree1.commit('foo')
1093
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1094
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1097
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1098
revid2 = tree2.commit('foo')
1099
root_id = tree2.get_root_id()
1102
# check the layout in memory
1103
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1104
(('', '', root_id), [
1105
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1106
('d', '', 0, False, revid1.encode('utf8')),
1107
('d', '', 0, False, revid1.encode('utf8'))
1109
(('', 'a file', 'file-id'), [
1110
('a', '', 0, False, ''),
1111
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1112
revid1.encode('utf8')),
1113
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1114
revid2.encode('utf8'))
1117
state = dirstate.DirState.initialize('dirstate')
1119
state.set_path_id('', root_id)
1120
state.set_parent_trees(
1121
((revid1, tree1.branch.repository.revision_tree(revid1)),
1122
(revid2, tree2.branch.repository.revision_tree(revid2)),
1128
# check_state_with_reopen will unlock
1129
self.check_state_with_reopen(expected_result, state)
1131
### add a path via _set_data - so we dont need delta work, just
1132
# raw data in, and ensure that it comes out via get_lines happily.
1134
def test_add_path_to_root_no_parents_all_data(self):
1135
# The most trivial addition of a path is when there are no parents and
1136
# its in the root and all data about the file is supplied
1137
self.build_tree(['a file'])
1138
stat = os.lstat('a file')
1139
# the 1*20 is the sha1 pretend value.
1140
state = dirstate.DirState.initialize('dirstate')
1141
expected_entries = [
1142
(('', '', 'TREE_ROOT'), [
1143
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1145
(('', 'a file', 'a-file-id'), [
1146
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1150
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1151
# having added it, it should be in the output of iter_entries.
1152
self.assertEqual(expected_entries, list(state._iter_entries()))
1153
# saving and reloading should not affect this.
1157
state = dirstate.DirState.on_file('dirstate')
1159
self.addCleanup(state.unlock)
1160
self.assertEqual(expected_entries, list(state._iter_entries()))
1162
def test_add_path_to_unversioned_directory(self):
1163
"""Adding a path to an unversioned directory should error.
1165
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1166
once dirstate is stable and if it is merged with WorkingTree3, consider
1167
removing this copy of the test.
1169
self.build_tree(['unversioned/', 'unversioned/a file'])
1170
state = dirstate.DirState.initialize('dirstate')
1171
self.addCleanup(state.unlock)
1172
self.assertRaises(errors.NotVersionedError, state.add,
1173
'unversioned/a file', 'a-file-id', 'file', None, None)
1175
def test_add_directory_to_root_no_parents_all_data(self):
1176
# The most trivial addition of a dir is when there are no parents and
1177
# its in the root and all data about the file is supplied
1178
self.build_tree(['a dir/'])
1179
stat = os.lstat('a dir')
1180
expected_entries = [
1181
(('', '', 'TREE_ROOT'), [
1182
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1184
(('', 'a dir', 'a dir id'), [
1185
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1188
state = dirstate.DirState.initialize('dirstate')
1190
state.add('a dir', 'a dir id', 'directory', stat, None)
1191
# having added it, it should be in the output of iter_entries.
1192
self.assertEqual(expected_entries, list(state._iter_entries()))
1193
# saving and reloading should not affect this.
1197
state = dirstate.DirState.on_file('dirstate')
1199
self.addCleanup(state.unlock)
1201
self.assertEqual(expected_entries, list(state._iter_entries()))
1203
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1204
# The most trivial addition of a symlink when there are no parents and
1205
# its in the root and all data about the file is supplied
1206
# bzr doesn't support fake symlinks on windows, yet.
1207
self.requireFeature(tests.SymlinkFeature)
1208
os.symlink(target, link_name)
1209
stat = os.lstat(link_name)
1210
expected_entries = [
1211
(('', '', 'TREE_ROOT'), [
1212
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1214
(('', link_name.encode('UTF-8'), 'a link id'), [
1215
('l', target.encode('UTF-8'), stat[6],
1216
False, dirstate.pack_stat(stat)), # current tree
1219
state = dirstate.DirState.initialize('dirstate')
1221
state.add(link_name, 'a link id', 'symlink', stat,
1222
target.encode('UTF-8'))
1223
# having added it, it should be in the output of iter_entries.
1224
self.assertEqual(expected_entries, list(state._iter_entries()))
1225
# saving and reloading should not affect this.
1229
state = dirstate.DirState.on_file('dirstate')
1231
self.addCleanup(state.unlock)
1232
self.assertEqual(expected_entries, list(state._iter_entries()))
1234
def test_add_symlink_to_root_no_parents_all_data(self):
1235
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1237
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1238
self.requireFeature(tests.UnicodeFilenameFeature)
1239
self._test_add_symlink_to_root_no_parents_all_data(
1240
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1242
def test_add_directory_and_child_no_parents_all_data(self):
1243
# after adding a directory, we should be able to add children to it.
1244
self.build_tree(['a dir/', 'a dir/a file'])
1245
dirstat = os.lstat('a dir')
1246
filestat = os.lstat('a dir/a file')
1247
expected_entries = [
1248
(('', '', 'TREE_ROOT'), [
1249
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1251
(('', 'a dir', 'a dir id'), [
1252
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1254
(('a dir', 'a file', 'a-file-id'), [
1255
('f', '1'*20, 25, False,
1256
dirstate.pack_stat(filestat)), # current tree details
1259
state = dirstate.DirState.initialize('dirstate')
1261
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1262
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1263
# added it, it should be in the output of iter_entries.
1264
self.assertEqual(expected_entries, list(state._iter_entries()))
1265
# saving and reloading should not affect this.
1269
state = dirstate.DirState.on_file('dirstate')
1271
self.addCleanup(state.unlock)
1272
self.assertEqual(expected_entries, list(state._iter_entries()))
1274
def test_add_tree_reference(self):
1275
# make a dirstate and add a tree reference
1276
state = dirstate.DirState.initialize('dirstate')
1278
('', 'subdir', 'subdir-id'),
1279
[('t', 'subtree-123123', 0, False,
1280
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1283
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1284
entry = state._get_entry(0, 'subdir-id', 'subdir')
1285
self.assertEqual(entry, expected_entry)
1290
# now check we can read it back
1292
self.addCleanup(state.unlock)
1294
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1295
self.assertEqual(entry, entry2)
1296
self.assertEqual(entry, expected_entry)
1297
# and lookup by id should work too
1298
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1299
self.assertEqual(entry, expected_entry)
1301
def test_add_forbidden_names(self):
1302
state = dirstate.DirState.initialize('dirstate')
1303
self.addCleanup(state.unlock)
1304
self.assertRaises(errors.BzrError,
1305
state.add, '.', 'ass-id', 'directory', None, None)
1306
self.assertRaises(errors.BzrError,
1307
state.add, '..', 'ass-id', 'directory', None, None)
1309
def test_set_state_with_rename_b_a_bug_395556(self):
1310
# bug 395556 uncovered a bug where the dirstate ends up with a false
1311
# relocation record - in a tree with no parents there should be no
1312
# absent or relocated records. This then leads to further corruption
1313
# when a commit occurs, as the incorrect relocation gathers an
1314
# incorrect absent in tree 1, and future changes go to pot.
1315
tree1 = self.make_branch_and_tree('tree1')
1316
self.build_tree(['tree1/b'])
1319
tree1.add(['b'], ['b-id'])
1320
root_id = tree1.get_root_id()
1321
inv = tree1.inventory
1322
state = dirstate.DirState.initialize('dirstate')
1324
# Set the initial state with 'b'
1325
state.set_state_from_inventory(inv)
1326
inv.rename('b-id', root_id, 'a')
1327
# Set the new state with 'a', which currently corrupts.
1328
state.set_state_from_inventory(inv)
1329
expected_result1 = [('', '', root_id, 'd'),
1330
('', 'a', 'b-id', 'f'),
1333
for entry in state._iter_entries():
1334
values.append(entry[0] + entry[1][0][:1])
1335
self.assertEqual(expected_result1, values)
1342
class TestDirStateHashUpdates(TestCaseWithDirState):
1344
def do_update_entry(self, state, path):
1345
entry = state._get_entry(0, path_utf8=path)
1346
stat = os.lstat(path)
1347
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1349
def test_worth_saving_limit_avoids_writing(self):
1350
tree = self.make_branch_and_tree('.')
1351
self.build_tree(['c', 'd'])
1353
tree.add(['c', 'd'], ['c-id', 'd-id'])
1354
tree.commit('add c and d')
1355
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1356
worth_saving_limit=2)
1359
self.addCleanup(state.unlock)
1360
state._read_dirblocks_if_needed()
1361
state.adjust_time(+20) # Allow things to be cached
1362
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1363
state._dirblock_state)
1364
f = open(state._filename, 'rb')
1369
self.do_update_entry(state, 'c')
1370
self.assertEqual(1, len(state._known_hash_changes))
1371
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1372
state._dirblock_state)
1374
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1375
# hash values haven't been written out.
1376
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1377
state._dirblock_state)
1378
self.assertFileEqual(content, state._filename)
1379
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1380
state._dirblock_state)
1381
self.do_update_entry(state, 'd')
1382
self.assertEqual(2, len(state._known_hash_changes))
1384
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1385
state._dirblock_state)
1386
self.assertEqual(0, len(state._known_hash_changes))
1389
class TestGetLines(TestCaseWithDirState):
1391
def test_get_line_with_2_rows(self):
1392
state = self.create_dirstate_with_root_and_subdir()
1394
self.assertEqual(['#bazaar dirstate flat format 3\n',
1395
'crc32: 41262208\n',
1399
'\x00\x00a-root-value\x00'
1400
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1401
'\x00subdir\x00subdir-id\x00'
1402
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1403
], state.get_lines())
1407
def test_entry_to_line(self):
1408
state = self.create_dirstate_with_root()
1411
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1412
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1413
state._entry_to_line(state._dirblocks[0][1][0]))
1417
def test_entry_to_line_with_parent(self):
1418
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1419
root_entry = ('', '', 'a-root-value'), [
1420
('d', '', 0, False, packed_stat), # current tree details
1421
# first: a pointer to the current location
1422
('a', 'dirname/basename', 0, False, ''),
1424
state = dirstate.DirState.initialize('dirstate')
1427
'\x00\x00a-root-value\x00'
1428
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1429
'a\x00dirname/basename\x000\x00n\x00',
1430
state._entry_to_line(root_entry))
1434
def test_entry_to_line_with_two_parents_at_different_paths(self):
1435
# / in the tree, at / in one parent and /dirname/basename in the other.
1436
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1437
root_entry = ('', '', 'a-root-value'), [
1438
('d', '', 0, False, packed_stat), # current tree details
1439
('d', '', 0, False, 'rev_id'), # first parent details
1440
# second: a pointer to the current location
1441
('a', 'dirname/basename', 0, False, ''),
1443
state = dirstate.DirState.initialize('dirstate')
1446
'\x00\x00a-root-value\x00'
1447
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1448
'd\x00\x000\x00n\x00rev_id\x00'
1449
'a\x00dirname/basename\x000\x00n\x00',
1450
state._entry_to_line(root_entry))
1454
def test_iter_entries(self):
1455
# we should be able to iterate the dirstate entries from end to end
1456
# this is for get_lines to be easy to read.
1457
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1459
root_entries = [(('', '', 'a-root-value'), [
1460
('d', '', 0, False, packed_stat), # current tree details
1462
dirblocks.append(('', root_entries))
1463
# add two files in the root
1464
subdir_entry = ('', 'subdir', 'subdir-id'), [
1465
('d', '', 0, False, packed_stat), # current tree details
1467
afile_entry = ('', 'afile', 'afile-id'), [
1468
('f', 'sha1value', 34, False, packed_stat), # current tree details
1470
dirblocks.append(('', [subdir_entry, afile_entry]))
1472
file_entry2 = ('subdir', '2file', '2file-id'), [
1473
('f', 'sha1value', 23, False, packed_stat), # current tree details
1475
dirblocks.append(('subdir', [file_entry2]))
1476
state = dirstate.DirState.initialize('dirstate')
1478
state._set_data([], dirblocks)
1479
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1481
self.assertEqual(expected_entries, list(state._iter_entries()))
1486
class TestGetBlockRowIndex(TestCaseWithDirState):
1488
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1489
file_present, state, dirname, basename, tree_index):
1490
self.assertEqual((block_index, row_index, dir_present, file_present),
1491
state._get_block_entry_index(dirname, basename, tree_index))
1493
block = state._dirblocks[block_index]
1494
self.assertEqual(dirname, block[0])
1495
if dir_present and file_present:
1496
row = state._dirblocks[block_index][1][row_index]
1497
self.assertEqual(dirname, row[0][0])
1498
self.assertEqual(basename, row[0][1])
1500
def test_simple_structure(self):
1501
state = self.create_dirstate_with_root_and_subdir()
1502
self.addCleanup(state.unlock)
1503
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1504
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1505
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1506
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1507
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1510
def test_complex_structure_exists(self):
1511
state = self.create_complex_dirstate()
1512
self.addCleanup(state.unlock)
1513
# Make sure we can find everything that exists
1514
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1515
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1516
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1517
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1518
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1519
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1520
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1521
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1522
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1523
'b', 'h\xc3\xa5', 0)
1525
def test_complex_structure_missing(self):
1526
state = self.create_complex_dirstate()
1527
self.addCleanup(state.unlock)
1528
# Make sure things would be inserted in the right locations
1529
# '_' comes before 'a'
1530
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1531
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1532
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1533
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1535
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1536
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1537
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1538
# This would be inserted between a/ and b/
1539
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1541
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1544
class TestGetEntry(TestCaseWithDirState):
1546
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1547
"""Check that the right entry is returned for a request to getEntry."""
1548
entry = state._get_entry(index, path_utf8=path)
1550
self.assertEqual((None, None), entry)
1553
self.assertEqual((dirname, basename, file_id), cur[:3])
1555
def test_simple_structure(self):
1556
state = self.create_dirstate_with_root_and_subdir()
1557
self.addCleanup(state.unlock)
1558
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1559
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1560
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1561
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1562
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1564
def test_complex_structure_exists(self):
1565
state = self.create_complex_dirstate()
1566
self.addCleanup(state.unlock)
1567
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1568
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1569
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1570
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1571
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1572
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1573
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1574
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1575
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1578
def test_complex_structure_missing(self):
1579
state = self.create_complex_dirstate()
1580
self.addCleanup(state.unlock)
1581
self.assertEntryEqual(None, None, None, state, '_', 0)
1582
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1583
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1584
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1586
def test_get_entry_uninitialized(self):
1587
"""Calling get_entry will load data if it needs to"""
1588
state = self.create_dirstate_with_root()
1594
state = dirstate.DirState.on_file('dirstate')
1597
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1598
state._header_state)
1599
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1600
state._dirblock_state)
1601
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1606
class TestIterChildEntries(TestCaseWithDirState):
1608
def create_dirstate_with_two_trees(self):
1609
"""This dirstate contains multiple files and directories.
1619
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1621
Notice that a/e is an empty directory.
1623
There is one parent tree, which has the same shape with the following variations:
1624
b/g in the parent is gone.
1625
b/h in the parent has a different id
1626
b/i is new in the parent
1627
c is renamed to b/j in the parent
1629
:return: The dirstate, still write-locked.
1631
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1632
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1633
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1634
root_entry = ('', '', 'a-root-value'), [
1635
('d', '', 0, False, packed_stat),
1636
('d', '', 0, False, 'parent-revid'),
1638
a_entry = ('', 'a', 'a-dir'), [
1639
('d', '', 0, False, packed_stat),
1640
('d', '', 0, False, 'parent-revid'),
1642
b_entry = ('', 'b', 'b-dir'), [
1643
('d', '', 0, False, packed_stat),
1644
('d', '', 0, False, 'parent-revid'),
1646
c_entry = ('', 'c', 'c-file'), [
1647
('f', null_sha, 10, False, packed_stat),
1648
('r', 'b/j', 0, False, ''),
1650
d_entry = ('', 'd', 'd-file'), [
1651
('f', null_sha, 20, False, packed_stat),
1652
('f', 'd', 20, False, 'parent-revid'),
1654
e_entry = ('a', 'e', 'e-dir'), [
1655
('d', '', 0, False, packed_stat),
1656
('d', '', 0, False, 'parent-revid'),
1658
f_entry = ('a', 'f', 'f-file'), [
1659
('f', null_sha, 30, False, packed_stat),
1660
('f', 'f', 20, False, 'parent-revid'),
1662
g_entry = ('b', 'g', 'g-file'), [
1663
('f', null_sha, 30, False, packed_stat),
1664
NULL_PARENT_DETAILS,
1666
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1667
('f', null_sha, 40, False, packed_stat),
1668
NULL_PARENT_DETAILS,
1670
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1671
NULL_PARENT_DETAILS,
1672
('f', 'h', 20, False, 'parent-revid'),
1674
i_entry = ('b', 'i', 'i-file'), [
1675
NULL_PARENT_DETAILS,
1676
('f', 'h', 20, False, 'parent-revid'),
1678
j_entry = ('b', 'j', 'c-file'), [
1679
('r', 'c', 0, False, ''),
1680
('f', 'j', 20, False, 'parent-revid'),
1683
dirblocks.append(('', [root_entry]))
1684
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1685
dirblocks.append(('a', [e_entry, f_entry]))
1686
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1687
state = dirstate.DirState.initialize('dirstate')
1690
state._set_data(['parent'], dirblocks)
1694
return state, dirblocks
1696
def test_iter_children_b(self):
1697
state, dirblocks = self.create_dirstate_with_two_trees()
1698
self.addCleanup(state.unlock)
1699
expected_result = []
1700
expected_result.append(dirblocks[3][1][2]) # h2
1701
expected_result.append(dirblocks[3][1][3]) # i
1702
expected_result.append(dirblocks[3][1][4]) # j
1703
self.assertEqual(expected_result,
1704
list(state._iter_child_entries(1, 'b')))
1706
def test_iter_child_root(self):
1707
state, dirblocks = self.create_dirstate_with_two_trees()
1708
self.addCleanup(state.unlock)
1709
expected_result = []
1710
expected_result.append(dirblocks[1][1][0]) # a
1711
expected_result.append(dirblocks[1][1][1]) # b
1712
expected_result.append(dirblocks[1][1][3]) # d
1713
expected_result.append(dirblocks[2][1][0]) # e
1714
expected_result.append(dirblocks[2][1][1]) # f
1715
expected_result.append(dirblocks[3][1][2]) # h2
1716
expected_result.append(dirblocks[3][1][3]) # i
1717
expected_result.append(dirblocks[3][1][4]) # j
1718
self.assertEqual(expected_result,
1719
list(state._iter_child_entries(1, '')))
1722
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1723
"""Test that DirState adds entries in the right order."""
1725
def test_add_sorting(self):
1726
"""Add entries in lexicographical order, we get path sorted order.
1728
This tests it to a depth of 4, to make sure we don't just get it right
1729
at a single depth. 'a/a' should come before 'a-a', even though it
1730
doesn't lexicographically.
1732
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1733
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1736
state = dirstate.DirState.initialize('dirstate')
1737
self.addCleanup(state.unlock)
1739
fake_stat = os.stat('dirstate')
1741
d_id = d.replace('/', '_')+'-id'
1742
file_path = d + '/f'
1743
file_id = file_path.replace('/', '_')+'-id'
1744
state.add(d, d_id, 'directory', fake_stat, null_sha)
1745
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1747
expected = ['', '', 'a',
1748
'a/a', 'a/a/a', 'a/a/a/a',
1749
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1751
split = lambda p:p.split('/')
1752
self.assertEqual(sorted(expected, key=split), expected)
1753
dirblock_names = [d[0] for d in state._dirblocks]
1754
self.assertEqual(expected, dirblock_names)
1756
def test_set_parent_trees_correct_order(self):
1757
"""After calling set_parent_trees() we should maintain the order."""
1758
dirs = ['a', 'a-a', 'a/a']
1760
state = dirstate.DirState.initialize('dirstate')
1761
self.addCleanup(state.unlock)
1763
fake_stat = os.stat('dirstate')
1765
d_id = d.replace('/', '_')+'-id'
1766
file_path = d + '/f'
1767
file_id = file_path.replace('/', '_')+'-id'
1768
state.add(d, d_id, 'directory', fake_stat, null_sha)
1769
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1771
expected = ['', '', 'a', 'a/a', 'a-a']
1772
dirblock_names = [d[0] for d in state._dirblocks]
1773
self.assertEqual(expected, dirblock_names)
1775
# *really* cheesy way to just get an empty tree
1776
repo = self.make_repository('repo')
1777
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1778
state.set_parent_trees([('null:', empty_tree)], [])
1780
dirblock_names = [d[0] for d in state._dirblocks]
1781
self.assertEqual(expected, dirblock_names)
1784
class InstrumentedDirState(dirstate.DirState):
1785
"""An DirState with instrumented sha1 functionality."""
1787
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1788
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1789
worth_saving_limit=worth_saving_limit)
1790
self._time_offset = 0
1792
# member is dynamically set in DirState.__init__ to turn on trace
1793
self._sha1_provider = sha1_provider
1794
self._sha1_file = self._sha1_file_and_log
1796
def _sha_cutoff_time(self):
1797
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1798
self._cutoff_time = timestamp + self._time_offset
1800
def _sha1_file_and_log(self, abspath):
1801
self._log.append(('sha1', abspath))
1802
return self._sha1_provider.sha1(abspath)
1804
def _read_link(self, abspath, old_link):
1805
self._log.append(('read_link', abspath, old_link))
1806
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1808
def _lstat(self, abspath, entry):
1809
self._log.append(('lstat', abspath))
1810
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1812
def _is_executable(self, mode, old_executable):
1813
self._log.append(('is_exec', mode, old_executable))
1814
return super(InstrumentedDirState, self)._is_executable(mode,
1817
def adjust_time(self, secs):
1818
"""Move the clock forward or back.
1820
:param secs: The amount to adjust the clock by. Positive values make it
1821
seem as if we are in the future, negative values make it seem like we
1824
self._time_offset += secs
1825
self._cutoff_time = None
1828
class _FakeStat(object):
1829
"""A class with the same attributes as a real stat result."""
1831
def __init__(self, size, mtime, ctime, dev, ino, mode):
1833
self.st_mtime = mtime
1834
self.st_ctime = ctime
1841
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1842
st.st_ino, st.st_mode)
1845
class TestPackStat(tests.TestCaseWithTransport):
1847
def assertPackStat(self, expected, stat_value):
1848
"""Check the packed and serialized form of a stat value."""
1849
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1851
def test_pack_stat_int(self):
1852
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1853
# Make sure that all parameters have an impact on the packed stat.
1854
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1857
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1858
st.st_mtime = 1172758620
1860
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1861
st.st_ctime = 1172758630
1863
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1866
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1867
st.st_ino = 6499540L
1869
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1870
st.st_mode = 0100744
1872
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1874
def test_pack_stat_float(self):
1875
"""On some platforms mtime and ctime are floats.
1877
Make sure we don't get warnings or errors, and that we ignore changes <
1880
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1881
777L, 6499538L, 0100644)
1882
# These should all be the same as the integer counterparts
1883
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1884
st.st_mtime = 1172758620.0
1886
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1887
st.st_ctime = 1172758630.0
1889
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1890
# fractional seconds are discarded, so no change from above
1891
st.st_mtime = 1172758620.453
1892
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1893
st.st_ctime = 1172758630.228
1894
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1897
class TestBisect(TestCaseWithDirState):
1898
"""Test the ability to bisect into the disk format."""
1900
def assertBisect(self, expected_map, map_keys, state, paths):
1901
"""Assert that bisecting for paths returns the right result.
1903
:param expected_map: A map from key => entry value
1904
:param map_keys: The keys to expect for each path
1905
:param state: The DirState object.
1906
:param paths: A list of paths, these will automatically be split into
1907
(dir, name) tuples, and sorted according to how _bisect
1910
result = state._bisect(paths)
1911
# For now, results are just returned in whatever order we read them.
1912
# We could sort by (dir, name, file_id) or something like that, but in
1913
# the end it would still be fairly arbitrary, and we don't want the
1914
# extra overhead if we can avoid it. So sort everything to make sure
1916
self.assertEqual(len(map_keys), len(paths))
1918
for path, keys in zip(paths, map_keys):
1920
# This should not be present in the output
1922
expected[path] = sorted(expected_map[k] for k in keys)
1924
# The returned values are just arranged randomly based on when they
1925
# were read, for testing, make sure it is properly sorted.
1929
self.assertEqual(expected, result)
1931
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1932
"""Assert that bisecting for dirbblocks returns the right result.
1934
:param expected_map: A map from key => expected values
1935
:param map_keys: A nested list of paths we expect to be returned.
1936
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1937
:param state: The DirState object.
1938
:param paths: A list of directories
1940
result = state._bisect_dirblocks(paths)
1941
self.assertEqual(len(map_keys), len(paths))
1943
for path, keys in zip(paths, map_keys):
1945
# This should not be present in the output
1947
expected[path] = sorted(expected_map[k] for k in keys)
1951
self.assertEqual(expected, result)
1953
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1954
"""Assert the return value of a recursive bisection.
1956
:param expected_map: A map from key => entry value
1957
:param map_keys: A list of paths we expect to be returned.
1958
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1959
:param state: The DirState object.
1960
:param paths: A list of files and directories. It will be broken up
1961
into (dir, name) pairs and sorted before calling _bisect_recursive.
1964
for key in map_keys:
1965
entry = expected_map[key]
1966
dir_name_id, trees_info = entry
1967
expected[dir_name_id] = trees_info
1969
result = state._bisect_recursive(paths)
1971
self.assertEqual(expected, result)
1973
def test_bisect_each(self):
1974
"""Find a single record using bisect."""
1975
tree, state, expected = self.create_basic_dirstate()
1977
# Bisect should return the rows for the specified files.
1978
self.assertBisect(expected, [['']], state, [''])
1979
self.assertBisect(expected, [['a']], state, ['a'])
1980
self.assertBisect(expected, [['b']], state, ['b'])
1981
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1982
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1983
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1984
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1985
self.assertBisect(expected, [['f']], state, ['f'])
1987
def test_bisect_multi(self):
1988
"""Bisect can be used to find multiple records at the same time."""
1989
tree, state, expected = self.create_basic_dirstate()
1990
# Bisect should be capable of finding multiple entries at the same time
1991
self.assertBisect(expected, [['a'], ['b'], ['f']],
1992
state, ['a', 'b', 'f'])
1993
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1994
state, ['f', 'b/d', 'b/d/e'])
1995
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1996
state, ['b', 'b-c', 'b/c'])
1998
def test_bisect_one_page(self):
1999
"""Test bisect when there is only 1 page to read"""
2000
tree, state, expected = self.create_basic_dirstate()
2001
state._bisect_page_size = 5000
2002
self.assertBisect(expected,[['']], state, [''])
2003
self.assertBisect(expected,[['a']], state, ['a'])
2004
self.assertBisect(expected,[['b']], state, ['b'])
2005
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2006
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2007
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2008
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2009
self.assertBisect(expected,[['f']], state, ['f'])
2010
self.assertBisect(expected,[['a'], ['b'], ['f']],
2011
state, ['a', 'b', 'f'])
2012
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2013
state, ['b/d', 'b/d/e', 'f'])
2014
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2015
state, ['b', 'b/c', 'b-c'])
2017
def test_bisect_duplicate_paths(self):
2018
"""When bisecting for a path, handle multiple entries."""
2019
tree, state, expected = self.create_duplicated_dirstate()
2021
# Now make sure that both records are properly returned.
2022
self.assertBisect(expected, [['']], state, [''])
2023
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2024
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2025
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2026
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2027
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2029
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2030
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2032
def test_bisect_page_size_too_small(self):
2033
"""If the page size is too small, we will auto increase it."""
2034
tree, state, expected = self.create_basic_dirstate()
2035
state._bisect_page_size = 50
2036
self.assertBisect(expected, [None], state, ['b/e'])
2037
self.assertBisect(expected, [['a']], state, ['a'])
2038
self.assertBisect(expected, [['b']], state, ['b'])
2039
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2040
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2041
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2042
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2043
self.assertBisect(expected, [['f']], state, ['f'])
2045
def test_bisect_missing(self):
2046
"""Test that bisect return None if it cannot find a path."""
2047
tree, state, expected = self.create_basic_dirstate()
2048
self.assertBisect(expected, [None], state, ['foo'])
2049
self.assertBisect(expected, [None], state, ['b/foo'])
2050
self.assertBisect(expected, [None], state, ['bar/foo'])
2051
self.assertBisect(expected, [None], state, ['b-c/foo'])
2053
self.assertBisect(expected, [['a'], None, ['b/d']],
2054
state, ['a', 'foo', 'b/d'])
2056
def test_bisect_rename(self):
2057
"""Check that we find a renamed row."""
2058
tree, state, expected = self.create_renamed_dirstate()
2060
# Search for the pre and post renamed entries
2061
self.assertBisect(expected, [['a']], state, ['a'])
2062
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2063
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2064
self.assertBisect(expected, [['h']], state, ['h'])
2066
# What about b/d/e? shouldn't that also get 2 directory entries?
2067
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2068
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2070
def test_bisect_dirblocks(self):
2071
tree, state, expected = self.create_duplicated_dirstate()
2072
self.assertBisectDirBlocks(expected,
2073
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2075
self.assertBisectDirBlocks(expected,
2076
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2077
self.assertBisectDirBlocks(expected,
2078
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2079
self.assertBisectDirBlocks(expected,
2080
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2081
['b/c', 'b/c2', 'b/d', 'b/d2'],
2082
['b/d/e', 'b/d/e2'],
2083
], state, ['', 'b', 'b/d'])
2085
def test_bisect_dirblocks_missing(self):
2086
tree, state, expected = self.create_basic_dirstate()
2087
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2088
state, ['b/d', 'b/e'])
2089
# Files don't show up in this search
2090
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2091
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2092
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2093
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2094
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2096
def test_bisect_recursive_each(self):
2097
tree, state, expected = self.create_basic_dirstate()
2098
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2099
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2100
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2101
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2102
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2104
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2106
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2110
def test_bisect_recursive_multiple(self):
2111
tree, state, expected = self.create_basic_dirstate()
2112
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2113
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2114
state, ['b/d', 'b/d/e'])
2116
def test_bisect_recursive_missing(self):
2117
tree, state, expected = self.create_basic_dirstate()
2118
self.assertBisectRecursive(expected, [], state, ['d'])
2119
self.assertBisectRecursive(expected, [], state, ['b/e'])
2120
self.assertBisectRecursive(expected, [], state, ['g'])
2121
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2123
def test_bisect_recursive_renamed(self):
2124
tree, state, expected = self.create_renamed_dirstate()
2126
# Looking for either renamed item should find the other
2127
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2128
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2129
# Looking in the containing directory should find the rename target,
2130
# and anything in a subdir of the renamed target.
2131
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2132
'b/d/e', 'b/g', 'h', 'h/e'],
2136
class TestDirstateValidation(TestCaseWithDirState):
2138
def test_validate_correct_dirstate(self):
2139
state = self.create_complex_dirstate()
2142
# and make sure we can also validate with a read lock
2149
def test_dirblock_not_sorted(self):
2150
tree, state, expected = self.create_renamed_dirstate()
2151
state._read_dirblocks_if_needed()
2152
last_dirblock = state._dirblocks[-1]
2153
# we're appending to the dirblock, but this name comes before some of
2154
# the existing names; that's wrong
2155
last_dirblock[1].append(
2156
(('h', 'aaaa', 'a-id'),
2157
[('a', '', 0, False, ''),
2158
('a', '', 0, False, '')]))
2159
e = self.assertRaises(AssertionError,
2161
self.assertContainsRe(str(e), 'not sorted')
2163
def test_dirblock_name_mismatch(self):
2164
tree, state, expected = self.create_renamed_dirstate()
2165
state._read_dirblocks_if_needed()
2166
last_dirblock = state._dirblocks[-1]
2167
# add an entry with the wrong directory name
2168
last_dirblock[1].append(
2170
[('a', '', 0, False, ''),
2171
('a', '', 0, False, '')]))
2172
e = self.assertRaises(AssertionError,
2174
self.assertContainsRe(str(e),
2175
"doesn't match directory name")
2177
def test_dirblock_missing_rename(self):
2178
tree, state, expected = self.create_renamed_dirstate()
2179
state._read_dirblocks_if_needed()
2180
last_dirblock = state._dirblocks[-1]
2181
# make another entry for a-id, without a correct 'r' pointer to
2182
# the real occurrence in the working tree
2183
last_dirblock[1].append(
2184
(('h', 'z', 'a-id'),
2185
[('a', '', 0, False, ''),
2186
('a', '', 0, False, '')]))
2187
e = self.assertRaises(AssertionError,
2189
self.assertContainsRe(str(e),
2190
'file a-id is absent in row')
2193
class TestDirstateTreeReference(TestCaseWithDirState):
2195
def test_reference_revision_is_none(self):
2196
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2197
subtree = self.make_branch_and_tree('tree/subtree',
2198
format='dirstate-with-subtree')
2199
subtree.set_root_id('subtree')
2200
tree.add_reference(subtree)
2202
state = dirstate.DirState.from_tree(tree, 'dirstate')
2203
key = ('', 'subtree', 'subtree')
2204
expected = ('', [(key,
2205
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2208
self.assertEqual(expected, state._find_block(key))
2213
class TestDiscardMergeParents(TestCaseWithDirState):
2215
def test_discard_no_parents(self):
2216
# This should be a no-op
2217
state = self.create_empty_dirstate()
2218
self.addCleanup(state.unlock)
2219
state._discard_merge_parents()
2222
def test_discard_one_parent(self):
2224
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2225
root_entry_direntry = ('', '', 'a-root-value'), [
2226
('d', '', 0, False, packed_stat),
2227
('d', '', 0, False, packed_stat),
2230
dirblocks.append(('', [root_entry_direntry]))
2231
dirblocks.append(('', []))
2233
state = self.create_empty_dirstate()
2234
self.addCleanup(state.unlock)
2235
state._set_data(['parent-id'], dirblocks[:])
2238
state._discard_merge_parents()
2240
self.assertEqual(dirblocks, state._dirblocks)
2242
def test_discard_simple(self):
2244
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2245
root_entry_direntry = ('', '', 'a-root-value'), [
2246
('d', '', 0, False, packed_stat),
2247
('d', '', 0, False, packed_stat),
2248
('d', '', 0, False, packed_stat),
2250
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2251
('d', '', 0, False, packed_stat),
2252
('d', '', 0, False, packed_stat),
2255
dirblocks.append(('', [root_entry_direntry]))
2256
dirblocks.append(('', []))
2258
state = self.create_empty_dirstate()
2259
self.addCleanup(state.unlock)
2260
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2263
# This should strip of the extra column
2264
state._discard_merge_parents()
2266
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2267
self.assertEqual(expected_dirblocks, state._dirblocks)
2269
def test_discard_absent(self):
2270
"""If entries are only in a merge, discard should remove the entries"""
2271
null_stat = dirstate.DirState.NULLSTAT
2272
present_dir = ('d', '', 0, False, null_stat)
2273
present_file = ('f', '', 0, False, null_stat)
2274
absent = dirstate.DirState.NULL_PARENT_DETAILS
2275
root_key = ('', '', 'a-root-value')
2276
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2277
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2278
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2279
('', [(file_in_merged_key,
2280
[absent, absent, present_file]),
2282
[present_file, present_file, present_file]),
2286
state = self.create_empty_dirstate()
2287
self.addCleanup(state.unlock)
2288
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2291
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2292
('', [(file_in_root_key,
2293
[present_file, present_file]),
2296
state._discard_merge_parents()
2298
self.assertEqual(exp_dirblocks, state._dirblocks)
2300
def test_discard_renamed(self):
2301
null_stat = dirstate.DirState.NULLSTAT
2302
present_dir = ('d', '', 0, False, null_stat)
2303
present_file = ('f', '', 0, False, null_stat)
2304
absent = dirstate.DirState.NULL_PARENT_DETAILS
2305
root_key = ('', '', 'a-root-value')
2306
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2307
# Renamed relative to parent
2308
file_rename_s_key = ('', 'file-s', 'b-file-id')
2309
file_rename_t_key = ('', 'file-t', 'b-file-id')
2310
# And one that is renamed between the parents, but absent in this
2311
key_in_1 = ('', 'file-in-1', 'c-file-id')
2312
key_in_2 = ('', 'file-in-2', 'c-file-id')
2315
('', [(root_key, [present_dir, present_dir, present_dir])]),
2317
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2319
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2321
[present_file, present_file, present_file]),
2323
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2325
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2329
('', [(root_key, [present_dir, present_dir])]),
2330
('', [(key_in_1, [absent, present_file]),
2331
(file_in_root_key, [present_file, present_file]),
2332
(file_rename_t_key, [present_file, absent]),
2335
state = self.create_empty_dirstate()
2336
self.addCleanup(state.unlock)
2337
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2340
state._discard_merge_parents()
2342
self.assertEqual(exp_dirblocks, state._dirblocks)
2344
def test_discard_all_subdir(self):
2345
null_stat = dirstate.DirState.NULLSTAT
2346
present_dir = ('d', '', 0, False, null_stat)
2347
present_file = ('f', '', 0, False, null_stat)
2348
absent = dirstate.DirState.NULL_PARENT_DETAILS
2349
root_key = ('', '', 'a-root-value')
2350
subdir_key = ('', 'sub', 'dir-id')
2351
child1_key = ('sub', 'child1', 'child1-id')
2352
child2_key = ('sub', 'child2', 'child2-id')
2353
child3_key = ('sub', 'child3', 'child3-id')
2356
('', [(root_key, [present_dir, present_dir, present_dir])]),
2357
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2358
('sub', [(child1_key, [absent, absent, present_file]),
2359
(child2_key, [absent, absent, present_file]),
2360
(child3_key, [absent, absent, present_file]),
2364
('', [(root_key, [present_dir, present_dir])]),
2365
('', [(subdir_key, [present_dir, present_dir])]),
2368
state = self.create_empty_dirstate()
2369
self.addCleanup(state.unlock)
2370
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2373
state._discard_merge_parents()
2375
self.assertEqual(exp_dirblocks, state._dirblocks)
2378
class Test_InvEntryToDetails(tests.TestCase):
2380
def assertDetails(self, expected, inv_entry):
2381
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2382
self.assertEqual(expected, details)
2383
# details should always allow join() and always be a plain str when
2385
(minikind, fingerprint, size, executable, tree_data) = details
2386
self.assertIsInstance(minikind, str)
2387
self.assertIsInstance(fingerprint, str)
2388
self.assertIsInstance(tree_data, str)
2390
def test_unicode_symlink(self):
2391
inv_entry = inventory.InventoryLink('link-file-id',
2392
u'nam\N{Euro Sign}e',
2394
inv_entry.revision = 'link-revision-id'
2395
target = u'link-targ\N{Euro Sign}t'
2396
inv_entry.symlink_target = target
2397
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2398
'link-revision-id'), inv_entry)
2401
class TestSHA1Provider(tests.TestCaseInTempDir):
2403
def test_sha1provider_is_an_interface(self):
2404
p = dirstate.SHA1Provider()
2405
self.assertRaises(NotImplementedError, p.sha1, "foo")
2406
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2408
def test_defaultsha1provider_sha1(self):
2409
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2410
self.build_tree_contents([('foo', text)])
2411
expected_sha = osutils.sha_string(text)
2412
p = dirstate.DefaultSHA1Provider()
2413
self.assertEqual(expected_sha, p.sha1('foo'))
2415
def test_defaultsha1provider_stat_and_sha1(self):
2416
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2417
self.build_tree_contents([('foo', text)])
2418
expected_sha = osutils.sha_string(text)
2419
p = dirstate.DefaultSHA1Provider()
2420
statvalue, sha1 = p.stat_and_sha1('foo')
2421
self.assertTrue(len(statvalue) >= 10)
2422
self.assertEqual(len(text), statvalue.st_size)
2423
self.assertEqual(expected_sha, sha1)