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_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_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 TestGetLines(TestCaseWithDirState):
1344
def test_get_line_with_2_rows(self):
1345
state = self.create_dirstate_with_root_and_subdir()
1347
self.assertEqual(['#bazaar dirstate flat format 3\n',
1348
'crc32: 41262208\n',
1352
'\x00\x00a-root-value\x00'
1353
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1354
'\x00subdir\x00subdir-id\x00'
1355
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1356
], state.get_lines())
1360
def test_entry_to_line(self):
1361
state = self.create_dirstate_with_root()
1364
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1365
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1366
state._entry_to_line(state._dirblocks[0][1][0]))
1370
def test_entry_to_line_with_parent(self):
1371
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1372
root_entry = ('', '', 'a-root-value'), [
1373
('d', '', 0, False, packed_stat), # current tree details
1374
# first: a pointer to the current location
1375
('a', 'dirname/basename', 0, False, ''),
1377
state = dirstate.DirState.initialize('dirstate')
1380
'\x00\x00a-root-value\x00'
1381
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1382
'a\x00dirname/basename\x000\x00n\x00',
1383
state._entry_to_line(root_entry))
1387
def test_entry_to_line_with_two_parents_at_different_paths(self):
1388
# / in the tree, at / in one parent and /dirname/basename in the other.
1389
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1390
root_entry = ('', '', 'a-root-value'), [
1391
('d', '', 0, False, packed_stat), # current tree details
1392
('d', '', 0, False, 'rev_id'), # first parent details
1393
# second: a pointer to the current location
1394
('a', 'dirname/basename', 0, False, ''),
1396
state = dirstate.DirState.initialize('dirstate')
1399
'\x00\x00a-root-value\x00'
1400
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1401
'd\x00\x000\x00n\x00rev_id\x00'
1402
'a\x00dirname/basename\x000\x00n\x00',
1403
state._entry_to_line(root_entry))
1407
def test_iter_entries(self):
1408
# we should be able to iterate the dirstate entries from end to end
1409
# this is for get_lines to be easy to read.
1410
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1412
root_entries = [(('', '', 'a-root-value'), [
1413
('d', '', 0, False, packed_stat), # current tree details
1415
dirblocks.append(('', root_entries))
1416
# add two files in the root
1417
subdir_entry = ('', 'subdir', 'subdir-id'), [
1418
('d', '', 0, False, packed_stat), # current tree details
1420
afile_entry = ('', 'afile', 'afile-id'), [
1421
('f', 'sha1value', 34, False, packed_stat), # current tree details
1423
dirblocks.append(('', [subdir_entry, afile_entry]))
1425
file_entry2 = ('subdir', '2file', '2file-id'), [
1426
('f', 'sha1value', 23, False, packed_stat), # current tree details
1428
dirblocks.append(('subdir', [file_entry2]))
1429
state = dirstate.DirState.initialize('dirstate')
1431
state._set_data([], dirblocks)
1432
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1434
self.assertEqual(expected_entries, list(state._iter_entries()))
1439
class TestGetBlockRowIndex(TestCaseWithDirState):
1441
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1442
file_present, state, dirname, basename, tree_index):
1443
self.assertEqual((block_index, row_index, dir_present, file_present),
1444
state._get_block_entry_index(dirname, basename, tree_index))
1446
block = state._dirblocks[block_index]
1447
self.assertEqual(dirname, block[0])
1448
if dir_present and file_present:
1449
row = state._dirblocks[block_index][1][row_index]
1450
self.assertEqual(dirname, row[0][0])
1451
self.assertEqual(basename, row[0][1])
1453
def test_simple_structure(self):
1454
state = self.create_dirstate_with_root_and_subdir()
1455
self.addCleanup(state.unlock)
1456
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1457
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1458
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1459
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1460
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1463
def test_complex_structure_exists(self):
1464
state = self.create_complex_dirstate()
1465
self.addCleanup(state.unlock)
1466
# Make sure we can find everything that exists
1467
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1468
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1469
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1470
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1471
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1472
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1473
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1474
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1475
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1476
'b', 'h\xc3\xa5', 0)
1478
def test_complex_structure_missing(self):
1479
state = self.create_complex_dirstate()
1480
self.addCleanup(state.unlock)
1481
# Make sure things would be inserted in the right locations
1482
# '_' comes before 'a'
1483
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1484
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1485
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1486
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1488
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1489
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1490
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1491
# This would be inserted between a/ and b/
1492
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1494
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1497
class TestGetEntry(TestCaseWithDirState):
1499
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1500
"""Check that the right entry is returned for a request to getEntry."""
1501
entry = state._get_entry(index, path_utf8=path)
1503
self.assertEqual((None, None), entry)
1506
self.assertEqual((dirname, basename, file_id), cur[:3])
1508
def test_simple_structure(self):
1509
state = self.create_dirstate_with_root_and_subdir()
1510
self.addCleanup(state.unlock)
1511
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1512
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1513
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1514
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1515
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1517
def test_complex_structure_exists(self):
1518
state = self.create_complex_dirstate()
1519
self.addCleanup(state.unlock)
1520
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1521
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1522
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1523
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1524
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1525
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1526
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1527
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1528
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1531
def test_complex_structure_missing(self):
1532
state = self.create_complex_dirstate()
1533
self.addCleanup(state.unlock)
1534
self.assertEntryEqual(None, None, None, state, '_', 0)
1535
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1536
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1537
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1539
def test_get_entry_uninitialized(self):
1540
"""Calling get_entry will load data if it needs to"""
1541
state = self.create_dirstate_with_root()
1547
state = dirstate.DirState.on_file('dirstate')
1550
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1551
state._header_state)
1552
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1553
state._dirblock_state)
1554
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1559
class TestIterChildEntries(TestCaseWithDirState):
1561
def create_dirstate_with_two_trees(self):
1562
"""This dirstate contains multiple files and directories.
1572
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1574
Notice that a/e is an empty directory.
1576
There is one parent tree, which has the same shape with the following variations:
1577
b/g in the parent is gone.
1578
b/h in the parent has a different id
1579
b/i is new in the parent
1580
c is renamed to b/j in the parent
1582
:return: The dirstate, still write-locked.
1584
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1585
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1586
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1587
root_entry = ('', '', 'a-root-value'), [
1588
('d', '', 0, False, packed_stat),
1589
('d', '', 0, False, 'parent-revid'),
1591
a_entry = ('', 'a', 'a-dir'), [
1592
('d', '', 0, False, packed_stat),
1593
('d', '', 0, False, 'parent-revid'),
1595
b_entry = ('', 'b', 'b-dir'), [
1596
('d', '', 0, False, packed_stat),
1597
('d', '', 0, False, 'parent-revid'),
1599
c_entry = ('', 'c', 'c-file'), [
1600
('f', null_sha, 10, False, packed_stat),
1601
('r', 'b/j', 0, False, ''),
1603
d_entry = ('', 'd', 'd-file'), [
1604
('f', null_sha, 20, False, packed_stat),
1605
('f', 'd', 20, False, 'parent-revid'),
1607
e_entry = ('a', 'e', 'e-dir'), [
1608
('d', '', 0, False, packed_stat),
1609
('d', '', 0, False, 'parent-revid'),
1611
f_entry = ('a', 'f', 'f-file'), [
1612
('f', null_sha, 30, False, packed_stat),
1613
('f', 'f', 20, False, 'parent-revid'),
1615
g_entry = ('b', 'g', 'g-file'), [
1616
('f', null_sha, 30, False, packed_stat),
1617
NULL_PARENT_DETAILS,
1619
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1620
('f', null_sha, 40, False, packed_stat),
1621
NULL_PARENT_DETAILS,
1623
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1624
NULL_PARENT_DETAILS,
1625
('f', 'h', 20, False, 'parent-revid'),
1627
i_entry = ('b', 'i', 'i-file'), [
1628
NULL_PARENT_DETAILS,
1629
('f', 'h', 20, False, 'parent-revid'),
1631
j_entry = ('b', 'j', 'c-file'), [
1632
('r', 'c', 0, False, ''),
1633
('f', 'j', 20, False, 'parent-revid'),
1636
dirblocks.append(('', [root_entry]))
1637
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1638
dirblocks.append(('a', [e_entry, f_entry]))
1639
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1640
state = dirstate.DirState.initialize('dirstate')
1643
state._set_data(['parent'], dirblocks)
1647
return state, dirblocks
1649
def test_iter_children_b(self):
1650
state, dirblocks = self.create_dirstate_with_two_trees()
1651
self.addCleanup(state.unlock)
1652
expected_result = []
1653
expected_result.append(dirblocks[3][1][2]) # h2
1654
expected_result.append(dirblocks[3][1][3]) # i
1655
expected_result.append(dirblocks[3][1][4]) # j
1656
self.assertEqual(expected_result,
1657
list(state._iter_child_entries(1, 'b')))
1659
def test_iter_child_root(self):
1660
state, dirblocks = self.create_dirstate_with_two_trees()
1661
self.addCleanup(state.unlock)
1662
expected_result = []
1663
expected_result.append(dirblocks[1][1][0]) # a
1664
expected_result.append(dirblocks[1][1][1]) # b
1665
expected_result.append(dirblocks[1][1][3]) # d
1666
expected_result.append(dirblocks[2][1][0]) # e
1667
expected_result.append(dirblocks[2][1][1]) # f
1668
expected_result.append(dirblocks[3][1][2]) # h2
1669
expected_result.append(dirblocks[3][1][3]) # i
1670
expected_result.append(dirblocks[3][1][4]) # j
1671
self.assertEqual(expected_result,
1672
list(state._iter_child_entries(1, '')))
1675
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1676
"""Test that DirState adds entries in the right order."""
1678
def test_add_sorting(self):
1679
"""Add entries in lexicographical order, we get path sorted order.
1681
This tests it to a depth of 4, to make sure we don't just get it right
1682
at a single depth. 'a/a' should come before 'a-a', even though it
1683
doesn't lexicographically.
1685
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1686
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1689
state = dirstate.DirState.initialize('dirstate')
1690
self.addCleanup(state.unlock)
1692
fake_stat = os.stat('dirstate')
1694
d_id = d.replace('/', '_')+'-id'
1695
file_path = d + '/f'
1696
file_id = file_path.replace('/', '_')+'-id'
1697
state.add(d, d_id, 'directory', fake_stat, null_sha)
1698
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1700
expected = ['', '', 'a',
1701
'a/a', 'a/a/a', 'a/a/a/a',
1702
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1704
split = lambda p:p.split('/')
1705
self.assertEqual(sorted(expected, key=split), expected)
1706
dirblock_names = [d[0] for d in state._dirblocks]
1707
self.assertEqual(expected, dirblock_names)
1709
def test_set_parent_trees_correct_order(self):
1710
"""After calling set_parent_trees() we should maintain the order."""
1711
dirs = ['a', 'a-a', 'a/a']
1713
state = dirstate.DirState.initialize('dirstate')
1714
self.addCleanup(state.unlock)
1716
fake_stat = os.stat('dirstate')
1718
d_id = d.replace('/', '_')+'-id'
1719
file_path = d + '/f'
1720
file_id = file_path.replace('/', '_')+'-id'
1721
state.add(d, d_id, 'directory', fake_stat, null_sha)
1722
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1724
expected = ['', '', 'a', 'a/a', 'a-a']
1725
dirblock_names = [d[0] for d in state._dirblocks]
1726
self.assertEqual(expected, dirblock_names)
1728
# *really* cheesy way to just get an empty tree
1729
repo = self.make_repository('repo')
1730
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1731
state.set_parent_trees([('null:', empty_tree)], [])
1733
dirblock_names = [d[0] for d in state._dirblocks]
1734
self.assertEqual(expected, dirblock_names)
1737
class InstrumentedDirState(dirstate.DirState):
1738
"""An DirState with instrumented sha1 functionality."""
1740
def __init__(self, path, sha1_provider):
1741
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1742
self._time_offset = 0
1744
# member is dynamically set in DirState.__init__ to turn on trace
1745
self._sha1_provider = sha1_provider
1746
self._sha1_file = self._sha1_file_and_log
1748
def _sha_cutoff_time(self):
1749
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1750
self._cutoff_time = timestamp + self._time_offset
1752
def _sha1_file_and_log(self, abspath):
1753
self._log.append(('sha1', abspath))
1754
return self._sha1_provider.sha1(abspath)
1756
def _read_link(self, abspath, old_link):
1757
self._log.append(('read_link', abspath, old_link))
1758
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1760
def _lstat(self, abspath, entry):
1761
self._log.append(('lstat', abspath))
1762
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1764
def _is_executable(self, mode, old_executable):
1765
self._log.append(('is_exec', mode, old_executable))
1766
return super(InstrumentedDirState, self)._is_executable(mode,
1769
def adjust_time(self, secs):
1770
"""Move the clock forward or back.
1772
:param secs: The amount to adjust the clock by. Positive values make it
1773
seem as if we are in the future, negative values make it seem like we
1776
self._time_offset += secs
1777
self._cutoff_time = None
1780
class _FakeStat(object):
1781
"""A class with the same attributes as a real stat result."""
1783
def __init__(self, size, mtime, ctime, dev, ino, mode):
1785
self.st_mtime = mtime
1786
self.st_ctime = ctime
1793
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1794
st.st_ino, st.st_mode)
1797
class TestPackStat(tests.TestCaseWithTransport):
1799
def assertPackStat(self, expected, stat_value):
1800
"""Check the packed and serialized form of a stat value."""
1801
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1803
def test_pack_stat_int(self):
1804
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1805
# Make sure that all parameters have an impact on the packed stat.
1806
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1809
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1810
st.st_mtime = 1172758620
1812
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1813
st.st_ctime = 1172758630
1815
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1818
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1819
st.st_ino = 6499540L
1821
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1822
st.st_mode = 0100744
1824
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1826
def test_pack_stat_float(self):
1827
"""On some platforms mtime and ctime are floats.
1829
Make sure we don't get warnings or errors, and that we ignore changes <
1832
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1833
777L, 6499538L, 0100644)
1834
# These should all be the same as the integer counterparts
1835
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1836
st.st_mtime = 1172758620.0
1838
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1839
st.st_ctime = 1172758630.0
1841
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1842
# fractional seconds are discarded, so no change from above
1843
st.st_mtime = 1172758620.453
1844
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1845
st.st_ctime = 1172758630.228
1846
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1849
class TestBisect(TestCaseWithDirState):
1850
"""Test the ability to bisect into the disk format."""
1852
def assertBisect(self, expected_map, map_keys, state, paths):
1853
"""Assert that bisecting for paths returns the right result.
1855
:param expected_map: A map from key => entry value
1856
:param map_keys: The keys to expect for each path
1857
:param state: The DirState object.
1858
:param paths: A list of paths, these will automatically be split into
1859
(dir, name) tuples, and sorted according to how _bisect
1862
result = state._bisect(paths)
1863
# For now, results are just returned in whatever order we read them.
1864
# We could sort by (dir, name, file_id) or something like that, but in
1865
# the end it would still be fairly arbitrary, and we don't want the
1866
# extra overhead if we can avoid it. So sort everything to make sure
1868
self.assertEqual(len(map_keys), len(paths))
1870
for path, keys in zip(paths, map_keys):
1872
# This should not be present in the output
1874
expected[path] = sorted(expected_map[k] for k in keys)
1876
# The returned values are just arranged randomly based on when they
1877
# were read, for testing, make sure it is properly sorted.
1881
self.assertEqual(expected, result)
1883
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1884
"""Assert that bisecting for dirbblocks returns the right result.
1886
:param expected_map: A map from key => expected values
1887
:param map_keys: A nested list of paths we expect to be returned.
1888
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1889
:param state: The DirState object.
1890
:param paths: A list of directories
1892
result = state._bisect_dirblocks(paths)
1893
self.assertEqual(len(map_keys), len(paths))
1895
for path, keys in zip(paths, map_keys):
1897
# This should not be present in the output
1899
expected[path] = sorted(expected_map[k] for k in keys)
1903
self.assertEqual(expected, result)
1905
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1906
"""Assert the return value of a recursive bisection.
1908
:param expected_map: A map from key => entry value
1909
:param map_keys: A list of paths we expect to be returned.
1910
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1911
:param state: The DirState object.
1912
:param paths: A list of files and directories. It will be broken up
1913
into (dir, name) pairs and sorted before calling _bisect_recursive.
1916
for key in map_keys:
1917
entry = expected_map[key]
1918
dir_name_id, trees_info = entry
1919
expected[dir_name_id] = trees_info
1921
result = state._bisect_recursive(paths)
1923
self.assertEqual(expected, result)
1925
def test_bisect_each(self):
1926
"""Find a single record using bisect."""
1927
tree, state, expected = self.create_basic_dirstate()
1929
# Bisect should return the rows for the specified files.
1930
self.assertBisect(expected, [['']], state, [''])
1931
self.assertBisect(expected, [['a']], state, ['a'])
1932
self.assertBisect(expected, [['b']], state, ['b'])
1933
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1934
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1935
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1936
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1937
self.assertBisect(expected, [['f']], state, ['f'])
1939
def test_bisect_multi(self):
1940
"""Bisect can be used to find multiple records at the same time."""
1941
tree, state, expected = self.create_basic_dirstate()
1942
# Bisect should be capable of finding multiple entries at the same time
1943
self.assertBisect(expected, [['a'], ['b'], ['f']],
1944
state, ['a', 'b', 'f'])
1945
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1946
state, ['f', 'b/d', 'b/d/e'])
1947
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1948
state, ['b', 'b-c', 'b/c'])
1950
def test_bisect_one_page(self):
1951
"""Test bisect when there is only 1 page to read"""
1952
tree, state, expected = self.create_basic_dirstate()
1953
state._bisect_page_size = 5000
1954
self.assertBisect(expected,[['']], state, [''])
1955
self.assertBisect(expected,[['a']], state, ['a'])
1956
self.assertBisect(expected,[['b']], state, ['b'])
1957
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1958
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1959
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1960
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1961
self.assertBisect(expected,[['f']], state, ['f'])
1962
self.assertBisect(expected,[['a'], ['b'], ['f']],
1963
state, ['a', 'b', 'f'])
1964
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1965
state, ['b/d', 'b/d/e', 'f'])
1966
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1967
state, ['b', 'b/c', 'b-c'])
1969
def test_bisect_duplicate_paths(self):
1970
"""When bisecting for a path, handle multiple entries."""
1971
tree, state, expected = self.create_duplicated_dirstate()
1973
# Now make sure that both records are properly returned.
1974
self.assertBisect(expected, [['']], state, [''])
1975
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1976
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1977
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1978
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1979
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1981
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1982
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1984
def test_bisect_page_size_too_small(self):
1985
"""If the page size is too small, we will auto increase it."""
1986
tree, state, expected = self.create_basic_dirstate()
1987
state._bisect_page_size = 50
1988
self.assertBisect(expected, [None], state, ['b/e'])
1989
self.assertBisect(expected, [['a']], state, ['a'])
1990
self.assertBisect(expected, [['b']], state, ['b'])
1991
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1992
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1993
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1994
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1995
self.assertBisect(expected, [['f']], state, ['f'])
1997
def test_bisect_missing(self):
1998
"""Test that bisect return None if it cannot find a path."""
1999
tree, state, expected = self.create_basic_dirstate()
2000
self.assertBisect(expected, [None], state, ['foo'])
2001
self.assertBisect(expected, [None], state, ['b/foo'])
2002
self.assertBisect(expected, [None], state, ['bar/foo'])
2003
self.assertBisect(expected, [None], state, ['b-c/foo'])
2005
self.assertBisect(expected, [['a'], None, ['b/d']],
2006
state, ['a', 'foo', 'b/d'])
2008
def test_bisect_rename(self):
2009
"""Check that we find a renamed row."""
2010
tree, state, expected = self.create_renamed_dirstate()
2012
# Search for the pre and post renamed entries
2013
self.assertBisect(expected, [['a']], state, ['a'])
2014
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2015
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2016
self.assertBisect(expected, [['h']], state, ['h'])
2018
# What about b/d/e? shouldn't that also get 2 directory entries?
2019
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2020
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2022
def test_bisect_dirblocks(self):
2023
tree, state, expected = self.create_duplicated_dirstate()
2024
self.assertBisectDirBlocks(expected,
2025
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2027
self.assertBisectDirBlocks(expected,
2028
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2029
self.assertBisectDirBlocks(expected,
2030
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2031
self.assertBisectDirBlocks(expected,
2032
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2033
['b/c', 'b/c2', 'b/d', 'b/d2'],
2034
['b/d/e', 'b/d/e2'],
2035
], state, ['', 'b', 'b/d'])
2037
def test_bisect_dirblocks_missing(self):
2038
tree, state, expected = self.create_basic_dirstate()
2039
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2040
state, ['b/d', 'b/e'])
2041
# Files don't show up in this search
2042
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2043
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2044
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2045
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2046
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2048
def test_bisect_recursive_each(self):
2049
tree, state, expected = self.create_basic_dirstate()
2050
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2051
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2052
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2053
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2054
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2056
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2058
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2062
def test_bisect_recursive_multiple(self):
2063
tree, state, expected = self.create_basic_dirstate()
2064
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2065
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2066
state, ['b/d', 'b/d/e'])
2068
def test_bisect_recursive_missing(self):
2069
tree, state, expected = self.create_basic_dirstate()
2070
self.assertBisectRecursive(expected, [], state, ['d'])
2071
self.assertBisectRecursive(expected, [], state, ['b/e'])
2072
self.assertBisectRecursive(expected, [], state, ['g'])
2073
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2075
def test_bisect_recursive_renamed(self):
2076
tree, state, expected = self.create_renamed_dirstate()
2078
# Looking for either renamed item should find the other
2079
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2080
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2081
# Looking in the containing directory should find the rename target,
2082
# and anything in a subdir of the renamed target.
2083
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2084
'b/d/e', 'b/g', 'h', 'h/e'],
2088
class TestDirstateValidation(TestCaseWithDirState):
2090
def test_validate_correct_dirstate(self):
2091
state = self.create_complex_dirstate()
2094
# and make sure we can also validate with a read lock
2101
def test_dirblock_not_sorted(self):
2102
tree, state, expected = self.create_renamed_dirstate()
2103
state._read_dirblocks_if_needed()
2104
last_dirblock = state._dirblocks[-1]
2105
# we're appending to the dirblock, but this name comes before some of
2106
# the existing names; that's wrong
2107
last_dirblock[1].append(
2108
(('h', 'aaaa', 'a-id'),
2109
[('a', '', 0, False, ''),
2110
('a', '', 0, False, '')]))
2111
e = self.assertRaises(AssertionError,
2113
self.assertContainsRe(str(e), 'not sorted')
2115
def test_dirblock_name_mismatch(self):
2116
tree, state, expected = self.create_renamed_dirstate()
2117
state._read_dirblocks_if_needed()
2118
last_dirblock = state._dirblocks[-1]
2119
# add an entry with the wrong directory name
2120
last_dirblock[1].append(
2122
[('a', '', 0, False, ''),
2123
('a', '', 0, False, '')]))
2124
e = self.assertRaises(AssertionError,
2126
self.assertContainsRe(str(e),
2127
"doesn't match directory name")
2129
def test_dirblock_missing_rename(self):
2130
tree, state, expected = self.create_renamed_dirstate()
2131
state._read_dirblocks_if_needed()
2132
last_dirblock = state._dirblocks[-1]
2133
# make another entry for a-id, without a correct 'r' pointer to
2134
# the real occurrence in the working tree
2135
last_dirblock[1].append(
2136
(('h', 'z', 'a-id'),
2137
[('a', '', 0, False, ''),
2138
('a', '', 0, False, '')]))
2139
e = self.assertRaises(AssertionError,
2141
self.assertContainsRe(str(e),
2142
'file a-id is absent in row')
2145
class TestDirstateTreeReference(TestCaseWithDirState):
2147
def test_reference_revision_is_none(self):
2148
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2149
subtree = self.make_branch_and_tree('tree/subtree',
2150
format='dirstate-with-subtree')
2151
subtree.set_root_id('subtree')
2152
tree.add_reference(subtree)
2154
state = dirstate.DirState.from_tree(tree, 'dirstate')
2155
key = ('', 'subtree', 'subtree')
2156
expected = ('', [(key,
2157
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2160
self.assertEqual(expected, state._find_block(key))
2165
class TestDiscardMergeParents(TestCaseWithDirState):
2167
def test_discard_no_parents(self):
2168
# This should be a no-op
2169
state = self.create_empty_dirstate()
2170
self.addCleanup(state.unlock)
2171
state._discard_merge_parents()
2174
def test_discard_one_parent(self):
2176
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2177
root_entry_direntry = ('', '', 'a-root-value'), [
2178
('d', '', 0, False, packed_stat),
2179
('d', '', 0, False, packed_stat),
2182
dirblocks.append(('', [root_entry_direntry]))
2183
dirblocks.append(('', []))
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id'], dirblocks[:])
2190
state._discard_merge_parents()
2192
self.assertEqual(dirblocks, state._dirblocks)
2194
def test_discard_simple(self):
2196
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2197
root_entry_direntry = ('', '', 'a-root-value'), [
2198
('d', '', 0, False, packed_stat),
2199
('d', '', 0, False, packed_stat),
2200
('d', '', 0, False, packed_stat),
2202
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2203
('d', '', 0, False, packed_stat),
2204
('d', '', 0, False, packed_stat),
2207
dirblocks.append(('', [root_entry_direntry]))
2208
dirblocks.append(('', []))
2210
state = self.create_empty_dirstate()
2211
self.addCleanup(state.unlock)
2212
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2215
# This should strip of the extra column
2216
state._discard_merge_parents()
2218
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2219
self.assertEqual(expected_dirblocks, state._dirblocks)
2221
def test_discard_absent(self):
2222
"""If entries are only in a merge, discard should remove the entries"""
2223
null_stat = dirstate.DirState.NULLSTAT
2224
present_dir = ('d', '', 0, False, null_stat)
2225
present_file = ('f', '', 0, False, null_stat)
2226
absent = dirstate.DirState.NULL_PARENT_DETAILS
2227
root_key = ('', '', 'a-root-value')
2228
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2229
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2230
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2231
('', [(file_in_merged_key,
2232
[absent, absent, present_file]),
2234
[present_file, present_file, present_file]),
2238
state = self.create_empty_dirstate()
2239
self.addCleanup(state.unlock)
2240
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2243
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2244
('', [(file_in_root_key,
2245
[present_file, present_file]),
2248
state._discard_merge_parents()
2250
self.assertEqual(exp_dirblocks, state._dirblocks)
2252
def test_discard_renamed(self):
2253
null_stat = dirstate.DirState.NULLSTAT
2254
present_dir = ('d', '', 0, False, null_stat)
2255
present_file = ('f', '', 0, False, null_stat)
2256
absent = dirstate.DirState.NULL_PARENT_DETAILS
2257
root_key = ('', '', 'a-root-value')
2258
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2259
# Renamed relative to parent
2260
file_rename_s_key = ('', 'file-s', 'b-file-id')
2261
file_rename_t_key = ('', 'file-t', 'b-file-id')
2262
# And one that is renamed between the parents, but absent in this
2263
key_in_1 = ('', 'file-in-1', 'c-file-id')
2264
key_in_2 = ('', 'file-in-2', 'c-file-id')
2267
('', [(root_key, [present_dir, present_dir, present_dir])]),
2269
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2271
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2273
[present_file, present_file, present_file]),
2275
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2277
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2281
('', [(root_key, [present_dir, present_dir])]),
2282
('', [(key_in_1, [absent, present_file]),
2283
(file_in_root_key, [present_file, present_file]),
2284
(file_rename_t_key, [present_file, absent]),
2287
state = self.create_empty_dirstate()
2288
self.addCleanup(state.unlock)
2289
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2292
state._discard_merge_parents()
2294
self.assertEqual(exp_dirblocks, state._dirblocks)
2296
def test_discard_all_subdir(self):
2297
null_stat = dirstate.DirState.NULLSTAT
2298
present_dir = ('d', '', 0, False, null_stat)
2299
present_file = ('f', '', 0, False, null_stat)
2300
absent = dirstate.DirState.NULL_PARENT_DETAILS
2301
root_key = ('', '', 'a-root-value')
2302
subdir_key = ('', 'sub', 'dir-id')
2303
child1_key = ('sub', 'child1', 'child1-id')
2304
child2_key = ('sub', 'child2', 'child2-id')
2305
child3_key = ('sub', 'child3', 'child3-id')
2308
('', [(root_key, [present_dir, present_dir, present_dir])]),
2309
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2310
('sub', [(child1_key, [absent, absent, present_file]),
2311
(child2_key, [absent, absent, present_file]),
2312
(child3_key, [absent, absent, present_file]),
2316
('', [(root_key, [present_dir, present_dir])]),
2317
('', [(subdir_key, [present_dir, present_dir])]),
2320
state = self.create_empty_dirstate()
2321
self.addCleanup(state.unlock)
2322
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2325
state._discard_merge_parents()
2327
self.assertEqual(exp_dirblocks, state._dirblocks)
2330
class Test_InvEntryToDetails(tests.TestCase):
2332
def assertDetails(self, expected, inv_entry):
2333
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2334
self.assertEqual(expected, details)
2335
# details should always allow join() and always be a plain str when
2337
(minikind, fingerprint, size, executable, tree_data) = details
2338
self.assertIsInstance(minikind, str)
2339
self.assertIsInstance(fingerprint, str)
2340
self.assertIsInstance(tree_data, str)
2342
def test_unicode_symlink(self):
2343
inv_entry = inventory.InventoryLink('link-file-id',
2344
u'nam\N{Euro Sign}e',
2346
inv_entry.revision = 'link-revision-id'
2347
target = u'link-targ\N{Euro Sign}t'
2348
inv_entry.symlink_target = target
2349
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2350
'link-revision-id'), inv_entry)
2353
class TestSHA1Provider(tests.TestCaseInTempDir):
2355
def test_sha1provider_is_an_interface(self):
2356
p = dirstate.SHA1Provider()
2357
self.assertRaises(NotImplementedError, p.sha1, "foo")
2358
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2360
def test_defaultsha1provider_sha1(self):
2361
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2362
self.build_tree_contents([('foo', text)])
2363
expected_sha = osutils.sha_string(text)
2364
p = dirstate.DefaultSHA1Provider()
2365
self.assertEqual(expected_sha, p.sha1('foo'))
2367
def test_defaultsha1provider_stat_and_sha1(self):
2368
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2369
self.build_tree_contents([('foo', text)])
2370
expected_sha = osutils.sha_string(text)
2371
p = dirstate.DefaultSHA1Provider()
2372
statvalue, sha1 = p.stat_and_sha1('foo')
2373
self.assertTrue(len(statvalue) >= 10)
2374
self.assertEqual(len(text), statvalue.st_size)
2375
self.assertEqual(expected_sha, sha1)