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 test_construct_with_path(self):
531
tree = self.make_branch_and_tree('tree')
532
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
533
# we want to be able to get the lines of the dirstate that we will
535
lines = state.get_lines()
537
self.build_tree_contents([('dirstate', ''.join(lines))])
539
# no parents, default tree content
540
expected_result = ([], [
541
(('', '', tree.get_root_id()), # common details
542
# current tree details, but new from_tree skips statting, it
543
# uses set_state_from_inventory, and thus depends on the
545
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
548
state = dirstate.DirState.on_file('dirstate')
549
state.lock_write() # check_state_with_reopen will save() and unlock it
550
self.check_state_with_reopen(expected_result, state)
552
def test_can_save_clean_on_file(self):
553
tree = self.make_branch_and_tree('tree')
554
state = dirstate.DirState.from_tree(tree, 'dirstate')
556
# doing a save should work here as there have been no changes.
558
# TODO: stat it and check it hasn't changed; may require waiting
559
# for the state accuracy window.
563
def test_can_save_in_read_lock(self):
564
self.build_tree(['a-file'])
565
state = dirstate.DirState.initialize('dirstate')
567
# No stat and no sha1 sum.
568
state.add('a-file', 'a-file-id', 'file', None, '')
573
# Now open in readonly mode
574
state = dirstate.DirState.on_file('dirstate')
577
entry = state._get_entry(0, path_utf8='a-file')
578
# The current size should be 0 (default)
579
self.assertEqual(0, entry[1][0][2])
580
# We should have a real entry.
581
self.assertNotEqual((None, None), entry)
582
# Make sure everything is old enough
583
state._sha_cutoff_time()
584
state._cutoff_time += 10
585
# Change the file length
586
self.build_tree_contents([('a-file', 'shorter')])
587
sha1sum = dirstate.update_entry(state, entry, 'a-file',
589
# new file, no cached sha:
590
self.assertEqual(None, sha1sum)
592
# The dirblock has been updated
593
self.assertEqual(7, entry[1][0][2])
594
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
595
state._dirblock_state)
598
# Now, since we are the only one holding a lock, we should be able
599
# to save and have it written to disk
604
# Re-open the file, and ensure that the state has been updated.
605
state = dirstate.DirState.on_file('dirstate')
608
entry = state._get_entry(0, path_utf8='a-file')
609
self.assertEqual(7, entry[1][0][2])
613
def test_save_fails_quietly_if_locked(self):
614
"""If dirstate is locked, save will fail without complaining."""
615
self.build_tree(['a-file'])
616
state = dirstate.DirState.initialize('dirstate')
618
# No stat and no sha1 sum.
619
state.add('a-file', 'a-file-id', 'file', None, '')
624
state = dirstate.DirState.on_file('dirstate')
627
entry = state._get_entry(0, path_utf8='a-file')
628
sha1sum = dirstate.update_entry(state, entry, 'a-file',
631
self.assertEqual(None, sha1sum)
632
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
633
state._dirblock_state)
635
# Now, before we try to save, grab another dirstate, and take out a
637
# TODO: jam 20070315 Ideally this would be locked by another
638
# process. To make sure the file is really OS locked.
639
state2 = dirstate.DirState.on_file('dirstate')
642
# This won't actually write anything, because it couldn't grab
643
# a write lock. But it shouldn't raise an error, either.
644
# TODO: jam 20070315 We should probably distinguish between
645
# being dirty because of 'update_entry'. And dirty
646
# because of real modification. So that save() *does*
647
# raise a real error if it fails when we have real
655
# The file on disk should not be modified.
656
state = dirstate.DirState.on_file('dirstate')
659
entry = state._get_entry(0, path_utf8='a-file')
660
self.assertEqual('', entry[1][0][1])
664
def test_save_refuses_if_changes_aborted(self):
665
self.build_tree(['a-file', 'a-dir/'])
666
state = dirstate.DirState.initialize('dirstate')
668
# No stat and no sha1 sum.
669
state.add('a-file', 'a-file-id', 'file', None, '')
674
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
676
('', [(('', '', 'TREE_ROOT'),
677
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
678
('', [(('', 'a-file', 'a-file-id'),
679
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
682
state = dirstate.DirState.on_file('dirstate')
685
state._read_dirblocks_if_needed()
686
self.assertEqual(expected_blocks, state._dirblocks)
688
# Now modify the state, but mark it as inconsistent
689
state.add('a-dir', 'a-dir-id', 'directory', None, '')
690
state._changes_aborted = True
695
state = dirstate.DirState.on_file('dirstate')
698
state._read_dirblocks_if_needed()
699
self.assertEqual(expected_blocks, state._dirblocks)
704
class TestDirStateInitialize(TestCaseWithDirState):
706
def test_initialize(self):
707
expected_result = ([], [
708
(('', '', 'TREE_ROOT'), # common details
709
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
712
state = dirstate.DirState.initialize('dirstate')
714
self.assertIsInstance(state, dirstate.DirState)
715
lines = state.get_lines()
718
# On win32 you can't read from a locked file, even within the same
719
# process. So we have to unlock and release before we check the file
721
self.assertFileEqual(''.join(lines), 'dirstate')
722
state.lock_read() # check_state_with_reopen will unlock
723
self.check_state_with_reopen(expected_result, state)
726
class TestDirStateManipulations(TestCaseWithDirState):
728
def make_minimal_tree(self):
729
tree1 = self.make_branch_and_memory_tree('tree1')
731
self.addCleanup(tree1.unlock)
733
revid1 = tree1.commit('foo')
736
def test_update_minimal_updates_id_index(self):
737
state = self.create_dirstate_with_root_and_subdir()
738
self.addCleanup(state.unlock)
739
id_index = state._get_id_index()
740
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
741
state.add('file-name', 'file-id', 'file', None, '')
742
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
744
state.update_minimal(('', 'new-name', 'file-id'), 'f',
745
path_utf8='new-name')
746
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
748
self.assertEqual([('', 'new-name', 'file-id')],
749
sorted(id_index['file-id']))
752
def test_set_state_from_inventory_no_content_no_parents(self):
753
# setting the current inventory is a slow but important api to support.
754
tree1, revid1 = self.make_minimal_tree()
755
inv = tree1.inventory
756
root_id = inv.path2id('')
757
expected_result = [], [
758
(('', '', root_id), [
759
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
760
state = dirstate.DirState.initialize('dirstate')
762
state.set_state_from_inventory(inv)
763
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
765
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
766
state._dirblock_state)
771
# This will unlock it
772
self.check_state_with_reopen(expected_result, state)
774
def test_set_state_from_scratch_no_parents(self):
775
tree1, revid1 = self.make_minimal_tree()
776
inv = tree1.inventory
777
root_id = inv.path2id('')
778
expected_result = [], [
779
(('', '', root_id), [
780
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
781
state = dirstate.DirState.initialize('dirstate')
783
state.set_state_from_scratch(inv, [], [])
784
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
786
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
787
state._dirblock_state)
792
# This will unlock it
793
self.check_state_with_reopen(expected_result, state)
795
def test_set_state_from_scratch_identical_parent(self):
796
tree1, revid1 = self.make_minimal_tree()
797
inv = tree1.inventory
798
root_id = inv.path2id('')
799
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
800
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
801
parent_entry = ('d', '', 0, False, revid1)
802
expected_result = [revid1], [
803
(('', '', root_id), [d_entry, parent_entry])]
804
state = dirstate.DirState.initialize('dirstate')
806
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
807
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
809
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
810
state._dirblock_state)
815
# This will unlock it
816
self.check_state_with_reopen(expected_result, state)
818
def test_set_state_from_inventory_preserves_hashcache(self):
819
# https://bugs.launchpad.net/bzr/+bug/146176
820
# set_state_from_inventory should preserve the stat and hash value for
821
# workingtree files that are not changed by the inventory.
823
tree = self.make_branch_and_tree('.')
824
# depends on the default format using dirstate...
827
# make a dirstate with some valid hashcache data
828
# file on disk, but that's not needed for this test
829
foo_contents = 'contents of foo'
830
self.build_tree_contents([('foo', foo_contents)])
831
tree.add('foo', 'foo-id')
833
foo_stat = os.stat('foo')
834
foo_packed = dirstate.pack_stat(foo_stat)
835
foo_sha = osutils.sha_string(foo_contents)
836
foo_size = len(foo_contents)
838
# should not be cached yet, because the file's too fresh
840
(('', 'foo', 'foo-id',),
841
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
842
tree._dirstate._get_entry(0, 'foo-id'))
843
# poke in some hashcache information - it wouldn't normally be
844
# stored because it's too fresh
845
tree._dirstate.update_minimal(
846
('', 'foo', 'foo-id'),
847
'f', False, foo_sha, foo_packed, foo_size, 'foo')
848
# now should be cached
850
(('', 'foo', 'foo-id',),
851
[('f', foo_sha, foo_size, False, foo_packed)]),
852
tree._dirstate._get_entry(0, 'foo-id'))
854
# extract the inventory, and add something to it
855
inv = tree._get_inventory()
856
# should see the file we poked in...
857
self.assertTrue(inv.has_id('foo-id'))
858
self.assertTrue(inv.has_filename('foo'))
859
inv.add_path('bar', 'file', 'bar-id')
860
tree._dirstate._validate()
861
# this used to cause it to lose its hashcache
862
tree._dirstate.set_state_from_inventory(inv)
863
tree._dirstate._validate()
869
# now check that the state still has the original hashcache value
870
state = tree._dirstate
872
foo_tuple = state._get_entry(0, path_utf8='foo')
874
(('', 'foo', 'foo-id',),
875
[('f', foo_sha, len(foo_contents), False,
876
dirstate.pack_stat(foo_stat))]),
881
def test_set_state_from_inventory_mixed_paths(self):
882
tree1 = self.make_branch_and_tree('tree1')
883
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
884
'tree1/a/b/foo', 'tree1/a-b/bar'])
887
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
888
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
889
tree1.commit('rev1', rev_id='rev1')
890
root_id = tree1.get_root_id()
891
inv = tree1.inventory
894
expected_result1 = [('', '', root_id, 'd'),
895
('', 'a', 'a-id', 'd'),
896
('', 'a-b', 'a-b-id', 'd'),
897
('a', 'b', 'b-id', 'd'),
898
('a/b', 'foo', 'foo-id', 'f'),
899
('a-b', 'bar', 'bar-id', 'f'),
901
expected_result2 = [('', '', root_id, 'd'),
902
('', 'a', 'a-id', 'd'),
903
('', 'a-b', 'a-b-id', 'd'),
904
('a-b', 'bar', 'bar-id', 'f'),
906
state = dirstate.DirState.initialize('dirstate')
908
state.set_state_from_inventory(inv)
910
for entry in state._iter_entries():
911
values.append(entry[0] + entry[1][0][:1])
912
self.assertEqual(expected_result1, values)
914
state.set_state_from_inventory(inv)
916
for entry in state._iter_entries():
917
values.append(entry[0] + entry[1][0][:1])
918
self.assertEqual(expected_result2, values)
922
def test_set_path_id_no_parents(self):
923
"""The id of a path can be changed trivally with no parents."""
924
state = dirstate.DirState.initialize('dirstate')
926
# check precondition to be sure the state does change appropriately.
927
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
928
self.assertEqual([root_entry], list(state._iter_entries()))
929
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
930
self.assertEqual(root_entry,
931
state._get_entry(0, fileid_utf8='TREE_ROOT'))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='second-root-id'))
934
state.set_path_id('', 'second-root-id')
935
new_root_entry = (('', '', 'second-root-id'),
936
[('d', '', 0, False, 'x'*32)])
937
expected_rows = [new_root_entry]
938
self.assertEqual(expected_rows, list(state._iter_entries()))
939
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
940
self.assertEqual(new_root_entry,
941
state._get_entry(0, fileid_utf8='second-root-id'))
942
self.assertEqual((None, None),
943
state._get_entry(0, fileid_utf8='TREE_ROOT'))
944
# should work across save too
948
state = dirstate.DirState.on_file('dirstate')
952
self.assertEqual(expected_rows, list(state._iter_entries()))
956
def test_set_path_id_with_parents(self):
957
"""Set the root file id in a dirstate with parents"""
958
mt = self.make_branch_and_tree('mt')
959
# in case the default tree format uses a different root id
960
mt.set_root_id('TREE_ROOT')
961
mt.commit('foo', rev_id='parent-revid')
962
rt = mt.branch.repository.revision_tree('parent-revid')
963
state = dirstate.DirState.initialize('dirstate')
966
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
967
root_entry = (('', '', 'TREE_ROOT'),
968
[('d', '', 0, False, 'x'*32),
969
('d', '', 0, False, 'parent-revid')])
970
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
971
self.assertEqual(root_entry,
972
state._get_entry(0, fileid_utf8='TREE_ROOT'))
973
self.assertEqual((None, None),
974
state._get_entry(0, fileid_utf8='Asecond-root-id'))
975
state.set_path_id('', 'Asecond-root-id')
977
# now see that it is what we expected
978
old_root_entry = (('', '', 'TREE_ROOT'),
979
[('a', '', 0, False, ''),
980
('d', '', 0, False, 'parent-revid')])
981
new_root_entry = (('', '', 'Asecond-root-id'),
982
[('d', '', 0, False, ''),
983
('a', '', 0, False, '')])
984
expected_rows = [new_root_entry, old_root_entry]
986
self.assertEqual(expected_rows, list(state._iter_entries()))
987
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
988
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
989
self.assertEqual((None, None),
990
state._get_entry(0, fileid_utf8='TREE_ROOT'))
991
self.assertEqual(old_root_entry,
992
state._get_entry(1, fileid_utf8='TREE_ROOT'))
993
self.assertEqual(new_root_entry,
994
state._get_entry(0, fileid_utf8='Asecond-root-id'))
995
self.assertEqual((None, None),
996
state._get_entry(1, fileid_utf8='Asecond-root-id'))
997
# should work across save too
1001
# now flush & check we get the same
1002
state = dirstate.DirState.on_file('dirstate')
1006
self.assertEqual(expected_rows, list(state._iter_entries()))
1009
# now change within an existing file-backed state
1013
state.set_path_id('', 'tree-root-2')
1018
def test_set_parent_trees_no_content(self):
1019
# set_parent_trees is a slow but important api to support.
1020
tree1 = self.make_branch_and_memory_tree('tree1')
1024
revid1 = tree1.commit('foo')
1027
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1028
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1031
revid2 = tree2.commit('foo')
1032
root_id = tree2.get_root_id()
1035
state = dirstate.DirState.initialize('dirstate')
1037
state.set_path_id('', root_id)
1038
state.set_parent_trees(
1039
((revid1, tree1.branch.repository.revision_tree(revid1)),
1040
(revid2, tree2.branch.repository.revision_tree(revid2)),
1041
('ghost-rev', None)),
1043
# check we can reopen and use the dirstate after setting parent
1050
state = dirstate.DirState.on_file('dirstate')
1053
self.assertEqual([revid1, revid2, 'ghost-rev'],
1054
state.get_parent_ids())
1055
# iterating the entire state ensures that the state is parsable.
1056
list(state._iter_entries())
1057
# be sure that it sets not appends - change it
1058
state.set_parent_trees(
1059
((revid1, tree1.branch.repository.revision_tree(revid1)),
1060
('ghost-rev', None)),
1062
# and now put it back.
1063
state.set_parent_trees(
1064
((revid1, tree1.branch.repository.revision_tree(revid1)),
1065
(revid2, tree2.branch.repository.revision_tree(revid2)),
1066
('ghost-rev', tree2.branch.repository.revision_tree(
1067
_mod_revision.NULL_REVISION))),
1069
self.assertEqual([revid1, revid2, 'ghost-rev'],
1070
state.get_parent_ids())
1071
# the ghost should be recorded as such by set_parent_trees.
1072
self.assertEqual(['ghost-rev'], state.get_ghosts())
1074
[(('', '', root_id), [
1075
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1076
('d', '', 0, False, revid1),
1077
('d', '', 0, False, revid1)
1079
list(state._iter_entries()))
1083
def test_set_parent_trees_file_missing_from_tree(self):
1084
# Adding a parent tree may reference files not in the current state.
1085
# they should get listed just once by id, even if they are in two
1087
# set_parent_trees is a slow but important api to support.
1088
tree1 = self.make_branch_and_memory_tree('tree1')
1092
tree1.add(['a file'], ['file-id'], ['file'])
1093
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1094
revid1 = tree1.commit('foo')
1097
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1098
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1101
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1102
revid2 = tree2.commit('foo')
1103
root_id = tree2.get_root_id()
1106
# check the layout in memory
1107
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1108
(('', '', root_id), [
1109
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1110
('d', '', 0, False, revid1.encode('utf8')),
1111
('d', '', 0, False, revid1.encode('utf8'))
1113
(('', 'a file', 'file-id'), [
1114
('a', '', 0, False, ''),
1115
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1116
revid1.encode('utf8')),
1117
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1118
revid2.encode('utf8'))
1121
state = dirstate.DirState.initialize('dirstate')
1123
state.set_path_id('', root_id)
1124
state.set_parent_trees(
1125
((revid1, tree1.branch.repository.revision_tree(revid1)),
1126
(revid2, tree2.branch.repository.revision_tree(revid2)),
1132
# check_state_with_reopen will unlock
1133
self.check_state_with_reopen(expected_result, state)
1135
### add a path via _set_data - so we dont need delta work, just
1136
# raw data in, and ensure that it comes out via get_lines happily.
1138
def test_add_path_to_root_no_parents_all_data(self):
1139
# The most trivial addition of a path is when there are no parents and
1140
# its in the root and all data about the file is supplied
1141
self.build_tree(['a file'])
1142
stat = os.lstat('a file')
1143
# the 1*20 is the sha1 pretend value.
1144
state = dirstate.DirState.initialize('dirstate')
1145
expected_entries = [
1146
(('', '', 'TREE_ROOT'), [
1147
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1149
(('', 'a file', 'a-file-id'), [
1150
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1154
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1155
# having added it, it should be in the output of iter_entries.
1156
self.assertEqual(expected_entries, list(state._iter_entries()))
1157
# saving and reloading should not affect this.
1161
state = dirstate.DirState.on_file('dirstate')
1163
self.addCleanup(state.unlock)
1164
self.assertEqual(expected_entries, list(state._iter_entries()))
1166
def test_add_path_to_unversioned_directory(self):
1167
"""Adding a path to an unversioned directory should error.
1169
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1170
once dirstate is stable and if it is merged with WorkingTree3, consider
1171
removing this copy of the test.
1173
self.build_tree(['unversioned/', 'unversioned/a file'])
1174
state = dirstate.DirState.initialize('dirstate')
1175
self.addCleanup(state.unlock)
1176
self.assertRaises(errors.NotVersionedError, state.add,
1177
'unversioned/a file', 'a-file-id', 'file', None, None)
1179
def test_add_directory_to_root_no_parents_all_data(self):
1180
# The most trivial addition of a dir is when there are no parents and
1181
# its in the root and all data about the file is supplied
1182
self.build_tree(['a dir/'])
1183
stat = os.lstat('a dir')
1184
expected_entries = [
1185
(('', '', 'TREE_ROOT'), [
1186
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1188
(('', 'a dir', 'a dir id'), [
1189
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1192
state = dirstate.DirState.initialize('dirstate')
1194
state.add('a dir', 'a dir id', 'directory', stat, None)
1195
# having added it, it should be in the output of iter_entries.
1196
self.assertEqual(expected_entries, list(state._iter_entries()))
1197
# saving and reloading should not affect this.
1201
state = dirstate.DirState.on_file('dirstate')
1203
self.addCleanup(state.unlock)
1205
self.assertEqual(expected_entries, list(state._iter_entries()))
1207
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1208
# The most trivial addition of a symlink when there are no parents and
1209
# its in the root and all data about the file is supplied
1210
# bzr doesn't support fake symlinks on windows, yet.
1211
self.requireFeature(tests.SymlinkFeature)
1212
os.symlink(target, link_name)
1213
stat = os.lstat(link_name)
1214
expected_entries = [
1215
(('', '', 'TREE_ROOT'), [
1216
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1218
(('', link_name.encode('UTF-8'), 'a link id'), [
1219
('l', target.encode('UTF-8'), stat[6],
1220
False, dirstate.pack_stat(stat)), # current tree
1223
state = dirstate.DirState.initialize('dirstate')
1225
state.add(link_name, 'a link id', 'symlink', stat,
1226
target.encode('UTF-8'))
1227
# having added it, it should be in the output of iter_entries.
1228
self.assertEqual(expected_entries, list(state._iter_entries()))
1229
# saving and reloading should not affect this.
1233
state = dirstate.DirState.on_file('dirstate')
1235
self.addCleanup(state.unlock)
1236
self.assertEqual(expected_entries, list(state._iter_entries()))
1238
def test_add_symlink_to_root_no_parents_all_data(self):
1239
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1241
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1242
self.requireFeature(tests.UnicodeFilenameFeature)
1243
self._test_add_symlink_to_root_no_parents_all_data(
1244
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1246
def test_add_directory_and_child_no_parents_all_data(self):
1247
# after adding a directory, we should be able to add children to it.
1248
self.build_tree(['a dir/', 'a dir/a file'])
1249
dirstat = os.lstat('a dir')
1250
filestat = os.lstat('a dir/a file')
1251
expected_entries = [
1252
(('', '', 'TREE_ROOT'), [
1253
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1255
(('', 'a dir', 'a dir id'), [
1256
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1258
(('a dir', 'a file', 'a-file-id'), [
1259
('f', '1'*20, 25, False,
1260
dirstate.pack_stat(filestat)), # current tree details
1263
state = dirstate.DirState.initialize('dirstate')
1265
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1266
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1267
# added it, it should be in the output of iter_entries.
1268
self.assertEqual(expected_entries, list(state._iter_entries()))
1269
# saving and reloading should not affect this.
1273
state = dirstate.DirState.on_file('dirstate')
1275
self.addCleanup(state.unlock)
1276
self.assertEqual(expected_entries, list(state._iter_entries()))
1278
def test_add_tree_reference(self):
1279
# make a dirstate and add a tree reference
1280
state = dirstate.DirState.initialize('dirstate')
1282
('', 'subdir', 'subdir-id'),
1283
[('t', 'subtree-123123', 0, False,
1284
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1287
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1288
entry = state._get_entry(0, 'subdir-id', 'subdir')
1289
self.assertEqual(entry, expected_entry)
1294
# now check we can read it back
1296
self.addCleanup(state.unlock)
1298
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1299
self.assertEqual(entry, entry2)
1300
self.assertEqual(entry, expected_entry)
1301
# and lookup by id should work too
1302
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1303
self.assertEqual(entry, expected_entry)
1305
def test_add_forbidden_names(self):
1306
state = dirstate.DirState.initialize('dirstate')
1307
self.addCleanup(state.unlock)
1308
self.assertRaises(errors.BzrError,
1309
state.add, '.', 'ass-id', 'directory', None, None)
1310
self.assertRaises(errors.BzrError,
1311
state.add, '..', 'ass-id', 'directory', None, None)
1313
def test_set_state_with_rename_b_a_bug_395556(self):
1314
# bug 395556 uncovered a bug where the dirstate ends up with a false
1315
# relocation record - in a tree with no parents there should be no
1316
# absent or relocated records. This then leads to further corruption
1317
# when a commit occurs, as the incorrect relocation gathers an
1318
# incorrect absent in tree 1, and future changes go to pot.
1319
tree1 = self.make_branch_and_tree('tree1')
1320
self.build_tree(['tree1/b'])
1323
tree1.add(['b'], ['b-id'])
1324
root_id = tree1.get_root_id()
1325
inv = tree1.inventory
1326
state = dirstate.DirState.initialize('dirstate')
1328
# Set the initial state with 'b'
1329
state.set_state_from_inventory(inv)
1330
inv.rename('b-id', root_id, 'a')
1331
# Set the new state with 'a', which currently corrupts.
1332
state.set_state_from_inventory(inv)
1333
expected_result1 = [('', '', root_id, 'd'),
1334
('', 'a', 'b-id', 'f'),
1337
for entry in state._iter_entries():
1338
values.append(entry[0] + entry[1][0][:1])
1339
self.assertEqual(expected_result1, values)
1346
class TestGetLines(TestCaseWithDirState):
1348
def test_get_line_with_2_rows(self):
1349
state = self.create_dirstate_with_root_and_subdir()
1351
self.assertEqual(['#bazaar dirstate flat format 3\n',
1352
'crc32: 41262208\n',
1356
'\x00\x00a-root-value\x00'
1357
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1358
'\x00subdir\x00subdir-id\x00'
1359
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1360
], state.get_lines())
1364
def test_entry_to_line(self):
1365
state = self.create_dirstate_with_root()
1368
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1369
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1370
state._entry_to_line(state._dirblocks[0][1][0]))
1374
def test_entry_to_line_with_parent(self):
1375
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1376
root_entry = ('', '', 'a-root-value'), [
1377
('d', '', 0, False, packed_stat), # current tree details
1378
# first: a pointer to the current location
1379
('a', 'dirname/basename', 0, False, ''),
1381
state = dirstate.DirState.initialize('dirstate')
1384
'\x00\x00a-root-value\x00'
1385
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1386
'a\x00dirname/basename\x000\x00n\x00',
1387
state._entry_to_line(root_entry))
1391
def test_entry_to_line_with_two_parents_at_different_paths(self):
1392
# / in the tree, at / in one parent and /dirname/basename in the other.
1393
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1394
root_entry = ('', '', 'a-root-value'), [
1395
('d', '', 0, False, packed_stat), # current tree details
1396
('d', '', 0, False, 'rev_id'), # first parent details
1397
# second: a pointer to the current location
1398
('a', 'dirname/basename', 0, False, ''),
1400
state = dirstate.DirState.initialize('dirstate')
1403
'\x00\x00a-root-value\x00'
1404
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1405
'd\x00\x000\x00n\x00rev_id\x00'
1406
'a\x00dirname/basename\x000\x00n\x00',
1407
state._entry_to_line(root_entry))
1411
def test_iter_entries(self):
1412
# we should be able to iterate the dirstate entries from end to end
1413
# this is for get_lines to be easy to read.
1414
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1416
root_entries = [(('', '', 'a-root-value'), [
1417
('d', '', 0, False, packed_stat), # current tree details
1419
dirblocks.append(('', root_entries))
1420
# add two files in the root
1421
subdir_entry = ('', 'subdir', 'subdir-id'), [
1422
('d', '', 0, False, packed_stat), # current tree details
1424
afile_entry = ('', 'afile', 'afile-id'), [
1425
('f', 'sha1value', 34, False, packed_stat), # current tree details
1427
dirblocks.append(('', [subdir_entry, afile_entry]))
1429
file_entry2 = ('subdir', '2file', '2file-id'), [
1430
('f', 'sha1value', 23, False, packed_stat), # current tree details
1432
dirblocks.append(('subdir', [file_entry2]))
1433
state = dirstate.DirState.initialize('dirstate')
1435
state._set_data([], dirblocks)
1436
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1438
self.assertEqual(expected_entries, list(state._iter_entries()))
1443
class TestGetBlockRowIndex(TestCaseWithDirState):
1445
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1446
file_present, state, dirname, basename, tree_index):
1447
self.assertEqual((block_index, row_index, dir_present, file_present),
1448
state._get_block_entry_index(dirname, basename, tree_index))
1450
block = state._dirblocks[block_index]
1451
self.assertEqual(dirname, block[0])
1452
if dir_present and file_present:
1453
row = state._dirblocks[block_index][1][row_index]
1454
self.assertEqual(dirname, row[0][0])
1455
self.assertEqual(basename, row[0][1])
1457
def test_simple_structure(self):
1458
state = self.create_dirstate_with_root_and_subdir()
1459
self.addCleanup(state.unlock)
1460
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1461
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1462
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1463
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1464
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1467
def test_complex_structure_exists(self):
1468
state = self.create_complex_dirstate()
1469
self.addCleanup(state.unlock)
1470
# Make sure we can find everything that exists
1471
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1472
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1473
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1474
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1475
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1476
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1477
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1478
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1479
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1480
'b', 'h\xc3\xa5', 0)
1482
def test_complex_structure_missing(self):
1483
state = self.create_complex_dirstate()
1484
self.addCleanup(state.unlock)
1485
# Make sure things would be inserted in the right locations
1486
# '_' comes before 'a'
1487
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1488
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1489
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1490
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1492
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1493
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1494
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1495
# This would be inserted between a/ and b/
1496
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1498
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1501
class TestGetEntry(TestCaseWithDirState):
1503
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1504
"""Check that the right entry is returned for a request to getEntry."""
1505
entry = state._get_entry(index, path_utf8=path)
1507
self.assertEqual((None, None), entry)
1510
self.assertEqual((dirname, basename, file_id), cur[:3])
1512
def test_simple_structure(self):
1513
state = self.create_dirstate_with_root_and_subdir()
1514
self.addCleanup(state.unlock)
1515
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1516
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1517
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1518
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1519
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1521
def test_complex_structure_exists(self):
1522
state = self.create_complex_dirstate()
1523
self.addCleanup(state.unlock)
1524
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1525
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1526
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1527
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1528
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1529
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1530
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1531
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1532
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1535
def test_complex_structure_missing(self):
1536
state = self.create_complex_dirstate()
1537
self.addCleanup(state.unlock)
1538
self.assertEntryEqual(None, None, None, state, '_', 0)
1539
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1540
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1541
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1543
def test_get_entry_uninitialized(self):
1544
"""Calling get_entry will load data if it needs to"""
1545
state = self.create_dirstate_with_root()
1551
state = dirstate.DirState.on_file('dirstate')
1554
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1555
state._header_state)
1556
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1557
state._dirblock_state)
1558
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1563
class TestIterChildEntries(TestCaseWithDirState):
1565
def create_dirstate_with_two_trees(self):
1566
"""This dirstate contains multiple files and directories.
1576
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1578
Notice that a/e is an empty directory.
1580
There is one parent tree, which has the same shape with the following variations:
1581
b/g in the parent is gone.
1582
b/h in the parent has a different id
1583
b/i is new in the parent
1584
c is renamed to b/j in the parent
1586
:return: The dirstate, still write-locked.
1588
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1589
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1590
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1591
root_entry = ('', '', 'a-root-value'), [
1592
('d', '', 0, False, packed_stat),
1593
('d', '', 0, False, 'parent-revid'),
1595
a_entry = ('', 'a', 'a-dir'), [
1596
('d', '', 0, False, packed_stat),
1597
('d', '', 0, False, 'parent-revid'),
1599
b_entry = ('', 'b', 'b-dir'), [
1600
('d', '', 0, False, packed_stat),
1601
('d', '', 0, False, 'parent-revid'),
1603
c_entry = ('', 'c', 'c-file'), [
1604
('f', null_sha, 10, False, packed_stat),
1605
('r', 'b/j', 0, False, ''),
1607
d_entry = ('', 'd', 'd-file'), [
1608
('f', null_sha, 20, False, packed_stat),
1609
('f', 'd', 20, False, 'parent-revid'),
1611
e_entry = ('a', 'e', 'e-dir'), [
1612
('d', '', 0, False, packed_stat),
1613
('d', '', 0, False, 'parent-revid'),
1615
f_entry = ('a', 'f', 'f-file'), [
1616
('f', null_sha, 30, False, packed_stat),
1617
('f', 'f', 20, False, 'parent-revid'),
1619
g_entry = ('b', 'g', 'g-file'), [
1620
('f', null_sha, 30, False, packed_stat),
1621
NULL_PARENT_DETAILS,
1623
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1624
('f', null_sha, 40, False, packed_stat),
1625
NULL_PARENT_DETAILS,
1627
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1628
NULL_PARENT_DETAILS,
1629
('f', 'h', 20, False, 'parent-revid'),
1631
i_entry = ('b', 'i', 'i-file'), [
1632
NULL_PARENT_DETAILS,
1633
('f', 'h', 20, False, 'parent-revid'),
1635
j_entry = ('b', 'j', 'c-file'), [
1636
('r', 'c', 0, False, ''),
1637
('f', 'j', 20, False, 'parent-revid'),
1640
dirblocks.append(('', [root_entry]))
1641
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1642
dirblocks.append(('a', [e_entry, f_entry]))
1643
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1644
state = dirstate.DirState.initialize('dirstate')
1647
state._set_data(['parent'], dirblocks)
1651
return state, dirblocks
1653
def test_iter_children_b(self):
1654
state, dirblocks = self.create_dirstate_with_two_trees()
1655
self.addCleanup(state.unlock)
1656
expected_result = []
1657
expected_result.append(dirblocks[3][1][2]) # h2
1658
expected_result.append(dirblocks[3][1][3]) # i
1659
expected_result.append(dirblocks[3][1][4]) # j
1660
self.assertEqual(expected_result,
1661
list(state._iter_child_entries(1, 'b')))
1663
def test_iter_child_root(self):
1664
state, dirblocks = self.create_dirstate_with_two_trees()
1665
self.addCleanup(state.unlock)
1666
expected_result = []
1667
expected_result.append(dirblocks[1][1][0]) # a
1668
expected_result.append(dirblocks[1][1][1]) # b
1669
expected_result.append(dirblocks[1][1][3]) # d
1670
expected_result.append(dirblocks[2][1][0]) # e
1671
expected_result.append(dirblocks[2][1][1]) # f
1672
expected_result.append(dirblocks[3][1][2]) # h2
1673
expected_result.append(dirblocks[3][1][3]) # i
1674
expected_result.append(dirblocks[3][1][4]) # j
1675
self.assertEqual(expected_result,
1676
list(state._iter_child_entries(1, '')))
1679
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1680
"""Test that DirState adds entries in the right order."""
1682
def test_add_sorting(self):
1683
"""Add entries in lexicographical order, we get path sorted order.
1685
This tests it to a depth of 4, to make sure we don't just get it right
1686
at a single depth. 'a/a' should come before 'a-a', even though it
1687
doesn't lexicographically.
1689
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1690
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1693
state = dirstate.DirState.initialize('dirstate')
1694
self.addCleanup(state.unlock)
1696
fake_stat = os.stat('dirstate')
1698
d_id = d.replace('/', '_')+'-id'
1699
file_path = d + '/f'
1700
file_id = file_path.replace('/', '_')+'-id'
1701
state.add(d, d_id, 'directory', fake_stat, null_sha)
1702
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1704
expected = ['', '', 'a',
1705
'a/a', 'a/a/a', 'a/a/a/a',
1706
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1708
split = lambda p:p.split('/')
1709
self.assertEqual(sorted(expected, key=split), expected)
1710
dirblock_names = [d[0] for d in state._dirblocks]
1711
self.assertEqual(expected, dirblock_names)
1713
def test_set_parent_trees_correct_order(self):
1714
"""After calling set_parent_trees() we should maintain the order."""
1715
dirs = ['a', 'a-a', 'a/a']
1717
state = dirstate.DirState.initialize('dirstate')
1718
self.addCleanup(state.unlock)
1720
fake_stat = os.stat('dirstate')
1722
d_id = d.replace('/', '_')+'-id'
1723
file_path = d + '/f'
1724
file_id = file_path.replace('/', '_')+'-id'
1725
state.add(d, d_id, 'directory', fake_stat, null_sha)
1726
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1728
expected = ['', '', 'a', 'a/a', 'a-a']
1729
dirblock_names = [d[0] for d in state._dirblocks]
1730
self.assertEqual(expected, dirblock_names)
1732
# *really* cheesy way to just get an empty tree
1733
repo = self.make_repository('repo')
1734
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1735
state.set_parent_trees([('null:', empty_tree)], [])
1737
dirblock_names = [d[0] for d in state._dirblocks]
1738
self.assertEqual(expected, dirblock_names)
1741
class InstrumentedDirState(dirstate.DirState):
1742
"""An DirState with instrumented sha1 functionality."""
1744
def __init__(self, path, sha1_provider):
1745
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1746
self._time_offset = 0
1748
# member is dynamically set in DirState.__init__ to turn on trace
1749
self._sha1_provider = sha1_provider
1750
self._sha1_file = self._sha1_file_and_log
1752
def _sha_cutoff_time(self):
1753
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1754
self._cutoff_time = timestamp + self._time_offset
1756
def _sha1_file_and_log(self, abspath):
1757
self._log.append(('sha1', abspath))
1758
return self._sha1_provider.sha1(abspath)
1760
def _read_link(self, abspath, old_link):
1761
self._log.append(('read_link', abspath, old_link))
1762
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1764
def _lstat(self, abspath, entry):
1765
self._log.append(('lstat', abspath))
1766
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1768
def _is_executable(self, mode, old_executable):
1769
self._log.append(('is_exec', mode, old_executable))
1770
return super(InstrumentedDirState, self)._is_executable(mode,
1773
def adjust_time(self, secs):
1774
"""Move the clock forward or back.
1776
:param secs: The amount to adjust the clock by. Positive values make it
1777
seem as if we are in the future, negative values make it seem like we
1780
self._time_offset += secs
1781
self._cutoff_time = None
1784
class _FakeStat(object):
1785
"""A class with the same attributes as a real stat result."""
1787
def __init__(self, size, mtime, ctime, dev, ino, mode):
1789
self.st_mtime = mtime
1790
self.st_ctime = ctime
1797
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1798
st.st_ino, st.st_mode)
1801
class TestPackStat(tests.TestCaseWithTransport):
1803
def assertPackStat(self, expected, stat_value):
1804
"""Check the packed and serialized form of a stat value."""
1805
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1807
def test_pack_stat_int(self):
1808
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1809
# Make sure that all parameters have an impact on the packed stat.
1810
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1813
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1814
st.st_mtime = 1172758620
1816
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1817
st.st_ctime = 1172758630
1819
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1822
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1823
st.st_ino = 6499540L
1825
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1826
st.st_mode = 0100744
1828
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1830
def test_pack_stat_float(self):
1831
"""On some platforms mtime and ctime are floats.
1833
Make sure we don't get warnings or errors, and that we ignore changes <
1836
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1837
777L, 6499538L, 0100644)
1838
# These should all be the same as the integer counterparts
1839
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1840
st.st_mtime = 1172758620.0
1842
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1843
st.st_ctime = 1172758630.0
1845
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1846
# fractional seconds are discarded, so no change from above
1847
st.st_mtime = 1172758620.453
1848
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1849
st.st_ctime = 1172758630.228
1850
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1853
class TestBisect(TestCaseWithDirState):
1854
"""Test the ability to bisect into the disk format."""
1856
def assertBisect(self, expected_map, map_keys, state, paths):
1857
"""Assert that bisecting for paths returns the right result.
1859
:param expected_map: A map from key => entry value
1860
:param map_keys: The keys to expect for each path
1861
:param state: The DirState object.
1862
:param paths: A list of paths, these will automatically be split into
1863
(dir, name) tuples, and sorted according to how _bisect
1866
result = state._bisect(paths)
1867
# For now, results are just returned in whatever order we read them.
1868
# We could sort by (dir, name, file_id) or something like that, but in
1869
# the end it would still be fairly arbitrary, and we don't want the
1870
# extra overhead if we can avoid it. So sort everything to make sure
1872
self.assertEqual(len(map_keys), len(paths))
1874
for path, keys in zip(paths, map_keys):
1876
# This should not be present in the output
1878
expected[path] = sorted(expected_map[k] for k in keys)
1880
# The returned values are just arranged randomly based on when they
1881
# were read, for testing, make sure it is properly sorted.
1885
self.assertEqual(expected, result)
1887
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1888
"""Assert that bisecting for dirbblocks returns the right result.
1890
:param expected_map: A map from key => expected values
1891
:param map_keys: A nested list of paths we expect to be returned.
1892
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1893
:param state: The DirState object.
1894
:param paths: A list of directories
1896
result = state._bisect_dirblocks(paths)
1897
self.assertEqual(len(map_keys), len(paths))
1899
for path, keys in zip(paths, map_keys):
1901
# This should not be present in the output
1903
expected[path] = sorted(expected_map[k] for k in keys)
1907
self.assertEqual(expected, result)
1909
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1910
"""Assert the return value of a recursive bisection.
1912
:param expected_map: A map from key => entry value
1913
:param map_keys: A list of paths we expect to be returned.
1914
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1915
:param state: The DirState object.
1916
:param paths: A list of files and directories. It will be broken up
1917
into (dir, name) pairs and sorted before calling _bisect_recursive.
1920
for key in map_keys:
1921
entry = expected_map[key]
1922
dir_name_id, trees_info = entry
1923
expected[dir_name_id] = trees_info
1925
result = state._bisect_recursive(paths)
1927
self.assertEqual(expected, result)
1929
def test_bisect_each(self):
1930
"""Find a single record using bisect."""
1931
tree, state, expected = self.create_basic_dirstate()
1933
# Bisect should return the rows for the specified files.
1934
self.assertBisect(expected, [['']], state, [''])
1935
self.assertBisect(expected, [['a']], state, ['a'])
1936
self.assertBisect(expected, [['b']], state, ['b'])
1937
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1938
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1939
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1940
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1941
self.assertBisect(expected, [['f']], state, ['f'])
1943
def test_bisect_multi(self):
1944
"""Bisect can be used to find multiple records at the same time."""
1945
tree, state, expected = self.create_basic_dirstate()
1946
# Bisect should be capable of finding multiple entries at the same time
1947
self.assertBisect(expected, [['a'], ['b'], ['f']],
1948
state, ['a', 'b', 'f'])
1949
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1950
state, ['f', 'b/d', 'b/d/e'])
1951
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1952
state, ['b', 'b-c', 'b/c'])
1954
def test_bisect_one_page(self):
1955
"""Test bisect when there is only 1 page to read"""
1956
tree, state, expected = self.create_basic_dirstate()
1957
state._bisect_page_size = 5000
1958
self.assertBisect(expected,[['']], state, [''])
1959
self.assertBisect(expected,[['a']], state, ['a'])
1960
self.assertBisect(expected,[['b']], state, ['b'])
1961
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1962
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1963
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1964
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1965
self.assertBisect(expected,[['f']], state, ['f'])
1966
self.assertBisect(expected,[['a'], ['b'], ['f']],
1967
state, ['a', 'b', 'f'])
1968
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1969
state, ['b/d', 'b/d/e', 'f'])
1970
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1971
state, ['b', 'b/c', 'b-c'])
1973
def test_bisect_duplicate_paths(self):
1974
"""When bisecting for a path, handle multiple entries."""
1975
tree, state, expected = self.create_duplicated_dirstate()
1977
# Now make sure that both records are properly returned.
1978
self.assertBisect(expected, [['']], state, [''])
1979
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1980
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1981
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1982
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1983
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1985
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1986
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1988
def test_bisect_page_size_too_small(self):
1989
"""If the page size is too small, we will auto increase it."""
1990
tree, state, expected = self.create_basic_dirstate()
1991
state._bisect_page_size = 50
1992
self.assertBisect(expected, [None], state, ['b/e'])
1993
self.assertBisect(expected, [['a']], state, ['a'])
1994
self.assertBisect(expected, [['b']], state, ['b'])
1995
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1996
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1997
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1998
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1999
self.assertBisect(expected, [['f']], state, ['f'])
2001
def test_bisect_missing(self):
2002
"""Test that bisect return None if it cannot find a path."""
2003
tree, state, expected = self.create_basic_dirstate()
2004
self.assertBisect(expected, [None], state, ['foo'])
2005
self.assertBisect(expected, [None], state, ['b/foo'])
2006
self.assertBisect(expected, [None], state, ['bar/foo'])
2007
self.assertBisect(expected, [None], state, ['b-c/foo'])
2009
self.assertBisect(expected, [['a'], None, ['b/d']],
2010
state, ['a', 'foo', 'b/d'])
2012
def test_bisect_rename(self):
2013
"""Check that we find a renamed row."""
2014
tree, state, expected = self.create_renamed_dirstate()
2016
# Search for the pre and post renamed entries
2017
self.assertBisect(expected, [['a']], state, ['a'])
2018
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2019
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2020
self.assertBisect(expected, [['h']], state, ['h'])
2022
# What about b/d/e? shouldn't that also get 2 directory entries?
2023
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2024
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2026
def test_bisect_dirblocks(self):
2027
tree, state, expected = self.create_duplicated_dirstate()
2028
self.assertBisectDirBlocks(expected,
2029
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2031
self.assertBisectDirBlocks(expected,
2032
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2033
self.assertBisectDirBlocks(expected,
2034
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2035
self.assertBisectDirBlocks(expected,
2036
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2037
['b/c', 'b/c2', 'b/d', 'b/d2'],
2038
['b/d/e', 'b/d/e2'],
2039
], state, ['', 'b', 'b/d'])
2041
def test_bisect_dirblocks_missing(self):
2042
tree, state, expected = self.create_basic_dirstate()
2043
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2044
state, ['b/d', 'b/e'])
2045
# Files don't show up in this search
2046
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2047
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2048
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2049
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2050
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2052
def test_bisect_recursive_each(self):
2053
tree, state, expected = self.create_basic_dirstate()
2054
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2055
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2056
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2057
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2058
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2060
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2062
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2066
def test_bisect_recursive_multiple(self):
2067
tree, state, expected = self.create_basic_dirstate()
2068
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2069
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2070
state, ['b/d', 'b/d/e'])
2072
def test_bisect_recursive_missing(self):
2073
tree, state, expected = self.create_basic_dirstate()
2074
self.assertBisectRecursive(expected, [], state, ['d'])
2075
self.assertBisectRecursive(expected, [], state, ['b/e'])
2076
self.assertBisectRecursive(expected, [], state, ['g'])
2077
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2079
def test_bisect_recursive_renamed(self):
2080
tree, state, expected = self.create_renamed_dirstate()
2082
# Looking for either renamed item should find the other
2083
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2084
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2085
# Looking in the containing directory should find the rename target,
2086
# and anything in a subdir of the renamed target.
2087
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2088
'b/d/e', 'b/g', 'h', 'h/e'],
2092
class TestDirstateValidation(TestCaseWithDirState):
2094
def test_validate_correct_dirstate(self):
2095
state = self.create_complex_dirstate()
2098
# and make sure we can also validate with a read lock
2105
def test_dirblock_not_sorted(self):
2106
tree, state, expected = self.create_renamed_dirstate()
2107
state._read_dirblocks_if_needed()
2108
last_dirblock = state._dirblocks[-1]
2109
# we're appending to the dirblock, but this name comes before some of
2110
# the existing names; that's wrong
2111
last_dirblock[1].append(
2112
(('h', 'aaaa', 'a-id'),
2113
[('a', '', 0, False, ''),
2114
('a', '', 0, False, '')]))
2115
e = self.assertRaises(AssertionError,
2117
self.assertContainsRe(str(e), 'not sorted')
2119
def test_dirblock_name_mismatch(self):
2120
tree, state, expected = self.create_renamed_dirstate()
2121
state._read_dirblocks_if_needed()
2122
last_dirblock = state._dirblocks[-1]
2123
# add an entry with the wrong directory name
2124
last_dirblock[1].append(
2126
[('a', '', 0, False, ''),
2127
('a', '', 0, False, '')]))
2128
e = self.assertRaises(AssertionError,
2130
self.assertContainsRe(str(e),
2131
"doesn't match directory name")
2133
def test_dirblock_missing_rename(self):
2134
tree, state, expected = self.create_renamed_dirstate()
2135
state._read_dirblocks_if_needed()
2136
last_dirblock = state._dirblocks[-1]
2137
# make another entry for a-id, without a correct 'r' pointer to
2138
# the real occurrence in the working tree
2139
last_dirblock[1].append(
2140
(('h', 'z', 'a-id'),
2141
[('a', '', 0, False, ''),
2142
('a', '', 0, False, '')]))
2143
e = self.assertRaises(AssertionError,
2145
self.assertContainsRe(str(e),
2146
'file a-id is absent in row')
2149
class TestDirstateTreeReference(TestCaseWithDirState):
2151
def test_reference_revision_is_none(self):
2152
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2153
subtree = self.make_branch_and_tree('tree/subtree',
2154
format='dirstate-with-subtree')
2155
subtree.set_root_id('subtree')
2156
tree.add_reference(subtree)
2158
state = dirstate.DirState.from_tree(tree, 'dirstate')
2159
key = ('', 'subtree', 'subtree')
2160
expected = ('', [(key,
2161
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2164
self.assertEqual(expected, state._find_block(key))
2169
class TestDiscardMergeParents(TestCaseWithDirState):
2171
def test_discard_no_parents(self):
2172
# This should be a no-op
2173
state = self.create_empty_dirstate()
2174
self.addCleanup(state.unlock)
2175
state._discard_merge_parents()
2178
def test_discard_one_parent(self):
2180
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2181
root_entry_direntry = ('', '', 'a-root-value'), [
2182
('d', '', 0, False, packed_stat),
2183
('d', '', 0, False, packed_stat),
2186
dirblocks.append(('', [root_entry_direntry]))
2187
dirblocks.append(('', []))
2189
state = self.create_empty_dirstate()
2190
self.addCleanup(state.unlock)
2191
state._set_data(['parent-id'], dirblocks[:])
2194
state._discard_merge_parents()
2196
self.assertEqual(dirblocks, state._dirblocks)
2198
def test_discard_simple(self):
2200
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2201
root_entry_direntry = ('', '', 'a-root-value'), [
2202
('d', '', 0, False, packed_stat),
2203
('d', '', 0, False, packed_stat),
2204
('d', '', 0, False, packed_stat),
2206
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2207
('d', '', 0, False, packed_stat),
2208
('d', '', 0, False, packed_stat),
2211
dirblocks.append(('', [root_entry_direntry]))
2212
dirblocks.append(('', []))
2214
state = self.create_empty_dirstate()
2215
self.addCleanup(state.unlock)
2216
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2219
# This should strip of the extra column
2220
state._discard_merge_parents()
2222
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2223
self.assertEqual(expected_dirblocks, state._dirblocks)
2225
def test_discard_absent(self):
2226
"""If entries are only in a merge, discard should remove the entries"""
2227
null_stat = dirstate.DirState.NULLSTAT
2228
present_dir = ('d', '', 0, False, null_stat)
2229
present_file = ('f', '', 0, False, null_stat)
2230
absent = dirstate.DirState.NULL_PARENT_DETAILS
2231
root_key = ('', '', 'a-root-value')
2232
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2233
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2234
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2235
('', [(file_in_merged_key,
2236
[absent, absent, present_file]),
2238
[present_file, present_file, present_file]),
2242
state = self.create_empty_dirstate()
2243
self.addCleanup(state.unlock)
2244
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2247
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2248
('', [(file_in_root_key,
2249
[present_file, present_file]),
2252
state._discard_merge_parents()
2254
self.assertEqual(exp_dirblocks, state._dirblocks)
2256
def test_discard_renamed(self):
2257
null_stat = dirstate.DirState.NULLSTAT
2258
present_dir = ('d', '', 0, False, null_stat)
2259
present_file = ('f', '', 0, False, null_stat)
2260
absent = dirstate.DirState.NULL_PARENT_DETAILS
2261
root_key = ('', '', 'a-root-value')
2262
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2263
# Renamed relative to parent
2264
file_rename_s_key = ('', 'file-s', 'b-file-id')
2265
file_rename_t_key = ('', 'file-t', 'b-file-id')
2266
# And one that is renamed between the parents, but absent in this
2267
key_in_1 = ('', 'file-in-1', 'c-file-id')
2268
key_in_2 = ('', 'file-in-2', 'c-file-id')
2271
('', [(root_key, [present_dir, present_dir, present_dir])]),
2273
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2275
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2277
[present_file, present_file, present_file]),
2279
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2281
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2285
('', [(root_key, [present_dir, present_dir])]),
2286
('', [(key_in_1, [absent, present_file]),
2287
(file_in_root_key, [present_file, present_file]),
2288
(file_rename_t_key, [present_file, absent]),
2291
state = self.create_empty_dirstate()
2292
self.addCleanup(state.unlock)
2293
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2296
state._discard_merge_parents()
2298
self.assertEqual(exp_dirblocks, state._dirblocks)
2300
def test_discard_all_subdir(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
subdir_key = ('', 'sub', 'dir-id')
2307
child1_key = ('sub', 'child1', 'child1-id')
2308
child2_key = ('sub', 'child2', 'child2-id')
2309
child3_key = ('sub', 'child3', 'child3-id')
2312
('', [(root_key, [present_dir, present_dir, present_dir])]),
2313
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2314
('sub', [(child1_key, [absent, absent, present_file]),
2315
(child2_key, [absent, absent, present_file]),
2316
(child3_key, [absent, absent, present_file]),
2320
('', [(root_key, [present_dir, present_dir])]),
2321
('', [(subdir_key, [present_dir, present_dir])]),
2324
state = self.create_empty_dirstate()
2325
self.addCleanup(state.unlock)
2326
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2329
state._discard_merge_parents()
2331
self.assertEqual(exp_dirblocks, state._dirblocks)
2334
class Test_InvEntryToDetails(tests.TestCase):
2336
def assertDetails(self, expected, inv_entry):
2337
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2338
self.assertEqual(expected, details)
2339
# details should always allow join() and always be a plain str when
2341
(minikind, fingerprint, size, executable, tree_data) = details
2342
self.assertIsInstance(minikind, str)
2343
self.assertIsInstance(fingerprint, str)
2344
self.assertIsInstance(tree_data, str)
2346
def test_unicode_symlink(self):
2347
inv_entry = inventory.InventoryLink('link-file-id',
2348
u'nam\N{Euro Sign}e',
2350
inv_entry.revision = 'link-revision-id'
2351
target = u'link-targ\N{Euro Sign}t'
2352
inv_entry.symlink_target = target
2353
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2354
'link-revision-id'), inv_entry)
2357
class TestSHA1Provider(tests.TestCaseInTempDir):
2359
def test_sha1provider_is_an_interface(self):
2360
p = dirstate.SHA1Provider()
2361
self.assertRaises(NotImplementedError, p.sha1, "foo")
2362
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2364
def test_defaultsha1provider_sha1(self):
2365
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2366
self.build_tree_contents([('foo', text)])
2367
expected_sha = osutils.sha_string(text)
2368
p = dirstate.DefaultSHA1Provider()
2369
self.assertEqual(expected_sha, p.sha1('foo'))
2371
def test_defaultsha1provider_stat_and_sha1(self):
2372
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2373
self.build_tree_contents([('foo', text)])
2374
expected_sha = osutils.sha_string(text)
2375
p = dirstate.DefaultSHA1Provider()
2376
statvalue, sha1 = p.stat_and_sha1('foo')
2377
self.assertTrue(len(statvalue) >= 10)
2378
self.assertEqual(len(text), statvalue.st_size)
2379
self.assertEqual(expected_sha, sha1)