1
# Copyright (C) 2006-2010 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."""
28
revision as _mod_revision,
31
from bzrlib.tests import test_osutils
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
def load_tests(basic_tests, module, loader):
48
suite = loader.suiteClass()
49
dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
50
basic_tests, tests.condition_isinstance(TestCaseWithDirState))
51
tests.multiply_tests(dir_reader_tests,
52
test_osutils.dir_reader_scenarios(), suite)
53
suite.addTest(remaining_tests)
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
61
_dir_reader_class = None
62
_native_to_unicode = None # Not used yet
65
tests.TestCaseWithTransport.setUp(self)
67
# Save platform specific info and reset it
68
cur_dir_reader = osutils._selected_dir_reader
71
osutils._selected_dir_reader = cur_dir_reader
72
self.addCleanup(restore)
74
osutils._selected_dir_reader = self._dir_reader_class()
76
def create_empty_dirstate(self):
77
"""Return a locked but empty dirstate"""
78
state = dirstate.DirState.initialize('dirstate')
81
def create_dirstate_with_root(self):
82
"""Return a write-locked state with a single root entry."""
83
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
84
root_entry_direntry = ('', '', 'a-root-value'), [
85
('d', '', 0, False, packed_stat),
88
dirblocks.append(('', [root_entry_direntry]))
89
dirblocks.append(('', []))
90
state = self.create_empty_dirstate()
92
state._set_data([], dirblocks)
99
def create_dirstate_with_root_and_subdir(self):
100
"""Return a locked DirState with a root and a subdir"""
101
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
102
subdir_entry = ('', 'subdir', 'subdir-id'), [
103
('d', '', 0, False, packed_stat),
105
state = self.create_dirstate_with_root()
107
dirblocks = list(state._dirblocks)
108
dirblocks[1][1].append(subdir_entry)
109
state._set_data([], dirblocks)
115
def create_complex_dirstate(self):
116
"""This dirstate contains multiple files and directories.
126
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
128
Notice that a/e is an empty directory.
130
:return: The dirstate, still write-locked.
132
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
133
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
134
root_entry = ('', '', 'a-root-value'), [
135
('d', '', 0, False, packed_stat),
137
a_entry = ('', 'a', 'a-dir'), [
138
('d', '', 0, False, packed_stat),
140
b_entry = ('', 'b', 'b-dir'), [
141
('d', '', 0, False, packed_stat),
143
c_entry = ('', 'c', 'c-file'), [
144
('f', null_sha, 10, False, packed_stat),
146
d_entry = ('', 'd', 'd-file'), [
147
('f', null_sha, 20, False, packed_stat),
149
e_entry = ('a', 'e', 'e-dir'), [
150
('d', '', 0, False, packed_stat),
152
f_entry = ('a', 'f', 'f-file'), [
153
('f', null_sha, 30, False, packed_stat),
155
g_entry = ('b', 'g', 'g-file'), [
156
('f', null_sha, 30, False, packed_stat),
158
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
159
('f', null_sha, 40, False, packed_stat),
162
dirblocks.append(('', [root_entry]))
163
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
164
dirblocks.append(('a', [e_entry, f_entry]))
165
dirblocks.append(('b', [g_entry, h_entry]))
166
state = dirstate.DirState.initialize('dirstate')
169
state._set_data([], dirblocks)
175
def check_state_with_reopen(self, expected_result, state):
176
"""Check that state has current state expected_result.
178
This will check the current state, open the file anew and check it
180
This function expects the current state to be locked for writing, and
181
will unlock it before re-opening.
182
This is required because we can't open a lock_read() while something
183
else has a lock_write().
184
write => mutually exclusive lock
187
# The state should already be write locked, since we just had to do
188
# some operation to get here.
189
self.assertTrue(state._lock_token is not None)
191
self.assertEqual(expected_result[0], state.get_parent_ids())
192
# there should be no ghosts in this tree.
193
self.assertEqual([], state.get_ghosts())
194
# there should be one fileid in this tree - the root of the tree.
195
self.assertEqual(expected_result[1], list(state._iter_entries()))
200
state = dirstate.DirState.on_file('dirstate')
203
self.assertEqual(expected_result[1], list(state._iter_entries()))
207
def create_basic_dirstate(self):
208
"""Create a dirstate with a few files and directories.
218
tree = self.make_branch_and_tree('tree')
219
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
220
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
221
self.build_tree(['tree/' + p for p in paths])
222
tree.set_root_id('TREE_ROOT')
223
tree.add([p.rstrip('/') for p in paths], file_ids)
224
tree.commit('initial', rev_id='rev-1')
225
revision_id = 'rev-1'
226
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
227
t = self.get_transport('tree')
228
a_text = t.get_bytes('a')
229
a_sha = osutils.sha_string(a_text)
231
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
232
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
233
c_text = t.get_bytes('b/c')
234
c_sha = osutils.sha_string(c_text)
236
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
237
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
238
e_text = t.get_bytes('b/d/e')
239
e_sha = osutils.sha_string(e_text)
241
b_c_text = t.get_bytes('b-c')
242
b_c_sha = osutils.sha_string(b_c_text)
243
b_c_len = len(b_c_text)
244
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
245
f_text = t.get_bytes('f')
246
f_sha = osutils.sha_string(f_text)
248
null_stat = dirstate.DirState.NULLSTAT
250
'':(('', '', 'TREE_ROOT'), [
251
('d', '', 0, False, null_stat),
252
('d', '', 0, False, revision_id),
254
'a':(('', 'a', 'a-id'), [
255
('f', '', 0, False, null_stat),
256
('f', a_sha, a_len, False, revision_id),
258
'b':(('', 'b', 'b-id'), [
259
('d', '', 0, False, null_stat),
260
('d', '', 0, False, revision_id),
262
'b/c':(('b', 'c', 'c-id'), [
263
('f', '', 0, False, null_stat),
264
('f', c_sha, c_len, False, revision_id),
266
'b/d':(('b', 'd', 'd-id'), [
267
('d', '', 0, False, null_stat),
268
('d', '', 0, False, revision_id),
270
'b/d/e':(('b/d', 'e', 'e-id'), [
271
('f', '', 0, False, null_stat),
272
('f', e_sha, e_len, False, revision_id),
274
'b-c':(('', 'b-c', 'b-c-id'), [
275
('f', '', 0, False, null_stat),
276
('f', b_c_sha, b_c_len, False, revision_id),
278
'f':(('', 'f', 'f-id'), [
279
('f', '', 0, False, null_stat),
280
('f', f_sha, f_len, False, revision_id),
283
state = dirstate.DirState.from_tree(tree, 'dirstate')
288
# Use a different object, to make sure nothing is pre-cached in memory.
289
state = dirstate.DirState.on_file('dirstate')
291
self.addCleanup(state.unlock)
292
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
293
state._dirblock_state)
294
# This is code is only really tested if we actually have to make more
295
# than one read, so set the page size to something smaller.
296
# We want it to contain about 2.2 records, so that we have a couple
297
# records that we can read per attempt
298
state._bisect_page_size = 200
299
return tree, state, expected
301
def create_duplicated_dirstate(self):
302
"""Create a dirstate with a deleted and added entries.
304
This grabs a basic_dirstate, and then removes and re adds every entry
307
tree, state, expected = self.create_basic_dirstate()
308
# Now we will just remove and add every file so we get an extra entry
309
# per entry. Unversion in reverse order so we handle subdirs
310
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
311
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
312
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
314
# Update the expected dictionary.
315
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
316
orig = expected[path]
318
# This record was deleted in the current tree
319
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
321
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
322
# And didn't exist in the basis tree
323
expected[path2] = (new_key, [orig[1][0],
324
dirstate.DirState.NULL_PARENT_DETAILS])
326
# We will replace the 'dirstate' file underneath 'state', but that is
327
# okay as lock as we unlock 'state' first.
330
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
336
# But we need to leave state in a read-lock because we already have
337
# a cleanup scheduled
339
return tree, state, expected
341
def create_renamed_dirstate(self):
342
"""Create a dirstate with a few internal renames.
344
This takes the basic dirstate, and moves the paths around.
346
tree, state, expected = self.create_basic_dirstate()
348
tree.rename_one('a', 'b/g')
350
tree.rename_one('b/d', 'h')
352
old_a = expected['a']
353
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
354
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
355
('r', 'a', 0, False, '')])
356
old_d = expected['b/d']
357
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
358
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
359
('r', 'b/d', 0, False, '')])
361
old_e = expected['b/d/e']
362
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
364
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
365
('r', 'b/d/e', 0, False, '')])
369
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
376
return tree, state, expected
379
class TestTreeToDirState(TestCaseWithDirState):
381
def test_empty_to_dirstate(self):
382
"""We should be able to create a dirstate for an empty tree."""
383
# There are no files on disk and no parents
384
tree = self.make_branch_and_tree('tree')
385
expected_result = ([], [
386
(('', '', tree.get_root_id()), # common details
387
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
389
state = dirstate.DirState.from_tree(tree, 'dirstate')
391
self.check_state_with_reopen(expected_result, state)
393
def test_1_parents_empty_to_dirstate(self):
394
# create a parent by doing a commit
395
tree = self.make_branch_and_tree('tree')
396
rev_id = tree.commit('first post').encode('utf8')
397
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
398
expected_result = ([rev_id], [
399
(('', '', tree.get_root_id()), # common details
400
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
401
('d', '', 0, False, rev_id), # first parent details
403
state = dirstate.DirState.from_tree(tree, 'dirstate')
404
self.check_state_with_reopen(expected_result, state)
411
def test_2_parents_empty_to_dirstate(self):
412
# create a parent by doing a commit
413
tree = self.make_branch_and_tree('tree')
414
rev_id = tree.commit('first post')
415
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
416
rev_id2 = tree2.commit('second post', allow_pointless=True)
417
tree.merge_from_branch(tree2.branch)
418
expected_result = ([rev_id, rev_id2], [
419
(('', '', tree.get_root_id()), # common details
420
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
421
('d', '', 0, False, rev_id), # first parent details
422
('d', '', 0, False, rev_id), # second parent details
424
state = dirstate.DirState.from_tree(tree, 'dirstate')
425
self.check_state_with_reopen(expected_result, state)
432
def test_empty_unknowns_are_ignored_to_dirstate(self):
433
"""We should be able to create a dirstate for an empty tree."""
434
# There are no files on disk and no parents
435
tree = self.make_branch_and_tree('tree')
436
self.build_tree(['tree/unknown'])
437
expected_result = ([], [
438
(('', '', tree.get_root_id()), # common details
439
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
441
state = dirstate.DirState.from_tree(tree, 'dirstate')
442
self.check_state_with_reopen(expected_result, state)
444
def get_tree_with_a_file(self):
445
tree = self.make_branch_and_tree('tree')
446
self.build_tree(['tree/a file'])
447
tree.add('a file', 'a-file-id')
450
def test_non_empty_no_parents_to_dirstate(self):
451
"""We should be able to create a dirstate for an empty tree."""
452
# There are files on disk and no parents
453
tree = self.get_tree_with_a_file()
454
expected_result = ([], [
455
(('', '', tree.get_root_id()), # common details
456
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
458
(('', 'a file', 'a-file-id'), # common
459
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
462
state = dirstate.DirState.from_tree(tree, 'dirstate')
463
self.check_state_with_reopen(expected_result, state)
465
def test_1_parents_not_empty_to_dirstate(self):
466
# create a parent by doing a commit
467
tree = self.get_tree_with_a_file()
468
rev_id = tree.commit('first post').encode('utf8')
469
# change the current content to be different this will alter stat, sha
471
self.build_tree_contents([('tree/a file', 'new content\n')])
472
expected_result = ([rev_id], [
473
(('', '', tree.get_root_id()), # common details
474
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
475
('d', '', 0, False, rev_id), # first parent details
477
(('', 'a file', 'a-file-id'), # common
478
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
479
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
480
rev_id), # first parent
483
state = dirstate.DirState.from_tree(tree, 'dirstate')
484
self.check_state_with_reopen(expected_result, state)
486
def test_2_parents_not_empty_to_dirstate(self):
487
# create a parent by doing a commit
488
tree = self.get_tree_with_a_file()
489
rev_id = tree.commit('first post').encode('utf8')
490
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
491
# change the current content to be different this will alter stat, sha
493
self.build_tree_contents([('tree2/a file', 'merge content\n')])
494
rev_id2 = tree2.commit('second post').encode('utf8')
495
tree.merge_from_branch(tree2.branch)
496
# change the current content to be different this will alter stat, sha
497
# and length again, giving us three distinct values:
498
self.build_tree_contents([('tree/a file', 'new content\n')])
499
expected_result = ([rev_id, rev_id2], [
500
(('', '', tree.get_root_id()), # common details
501
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
502
('d', '', 0, False, rev_id), # first parent details
503
('d', '', 0, False, rev_id), # second parent details
505
(('', 'a file', 'a-file-id'), # common
506
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
507
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
508
rev_id), # first parent
509
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
510
rev_id2), # second parent
513
state = dirstate.DirState.from_tree(tree, 'dirstate')
514
self.check_state_with_reopen(expected_result, state)
516
def test_colliding_fileids(self):
517
# test insertion of parents creating several entries at the same path.
518
# we used to have a bug where they could cause the dirstate to break
519
# its ordering invariants.
520
# create some trees to test from
523
tree = self.make_branch_and_tree('tree%d' % i)
524
self.build_tree(['tree%d/name' % i,])
525
tree.add(['name'], ['file-id%d' % i])
526
revision_id = 'revid-%d' % i
527
tree.commit('message', rev_id=revision_id)
528
parents.append((revision_id,
529
tree.branch.repository.revision_tree(revision_id)))
530
# now fold these trees into a dirstate
531
state = dirstate.DirState.initialize('dirstate')
533
state.set_parent_trees(parents, [])
539
class TestDirStateOnFile(TestCaseWithDirState):
541
def test_construct_with_path(self):
542
tree = self.make_branch_and_tree('tree')
543
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
544
# we want to be able to get the lines of the dirstate that we will
546
lines = state.get_lines()
548
self.build_tree_contents([('dirstate', ''.join(lines))])
550
# no parents, default tree content
551
expected_result = ([], [
552
(('', '', tree.get_root_id()), # common details
553
# current tree details, but new from_tree skips statting, it
554
# uses set_state_from_inventory, and thus depends on the
556
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
559
state = dirstate.DirState.on_file('dirstate')
560
state.lock_write() # check_state_with_reopen will save() and unlock it
561
self.check_state_with_reopen(expected_result, state)
563
def test_can_save_clean_on_file(self):
564
tree = self.make_branch_and_tree('tree')
565
state = dirstate.DirState.from_tree(tree, 'dirstate')
567
# doing a save should work here as there have been no changes.
569
# TODO: stat it and check it hasn't changed; may require waiting
570
# for the state accuracy window.
574
def test_can_save_in_read_lock(self):
575
self.build_tree(['a-file'])
576
state = dirstate.DirState.initialize('dirstate')
578
# No stat and no sha1 sum.
579
state.add('a-file', 'a-file-id', 'file', None, '')
584
# Now open in readonly mode
585
state = dirstate.DirState.on_file('dirstate')
588
entry = state._get_entry(0, path_utf8='a-file')
589
# The current size should be 0 (default)
590
self.assertEqual(0, entry[1][0][2])
591
# We should have a real entry.
592
self.assertNotEqual((None, None), entry)
593
# Make sure everything is old enough
594
state._sha_cutoff_time()
595
state._cutoff_time += 10
596
# Change the file length
597
self.build_tree_contents([('a-file', 'shorter')])
598
sha1sum = dirstate.update_entry(state, entry, 'a-file',
600
# new file, no cached sha:
601
self.assertEqual(None, sha1sum)
603
# The dirblock has been updated
604
self.assertEqual(7, entry[1][0][2])
605
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
606
state._dirblock_state)
609
# Now, since we are the only one holding a lock, we should be able
610
# to save and have it written to disk
615
# Re-open the file, and ensure that the state has been updated.
616
state = dirstate.DirState.on_file('dirstate')
619
entry = state._get_entry(0, path_utf8='a-file')
620
self.assertEqual(7, entry[1][0][2])
624
def test_save_fails_quietly_if_locked(self):
625
"""If dirstate is locked, save will fail without complaining."""
626
self.build_tree(['a-file'])
627
state = dirstate.DirState.initialize('dirstate')
629
# No stat and no sha1 sum.
630
state.add('a-file', 'a-file-id', 'file', None, '')
635
state = dirstate.DirState.on_file('dirstate')
638
entry = state._get_entry(0, path_utf8='a-file')
639
sha1sum = dirstate.update_entry(state, entry, 'a-file',
642
self.assertEqual(None, sha1sum)
643
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
644
state._dirblock_state)
646
# Now, before we try to save, grab another dirstate, and take out a
648
# TODO: jam 20070315 Ideally this would be locked by another
649
# process. To make sure the file is really OS locked.
650
state2 = dirstate.DirState.on_file('dirstate')
653
# This won't actually write anything, because it couldn't grab
654
# a write lock. But it shouldn't raise an error, either.
655
# TODO: jam 20070315 We should probably distinguish between
656
# being dirty because of 'update_entry'. And dirty
657
# because of real modification. So that save() *does*
658
# raise a real error if it fails when we have real
666
# The file on disk should not be modified.
667
state = dirstate.DirState.on_file('dirstate')
670
entry = state._get_entry(0, path_utf8='a-file')
671
self.assertEqual('', entry[1][0][1])
675
def test_save_refuses_if_changes_aborted(self):
676
self.build_tree(['a-file', 'a-dir/'])
677
state = dirstate.DirState.initialize('dirstate')
679
# No stat and no sha1 sum.
680
state.add('a-file', 'a-file-id', 'file', None, '')
685
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
687
('', [(('', '', 'TREE_ROOT'),
688
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
689
('', [(('', 'a-file', 'a-file-id'),
690
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
693
state = dirstate.DirState.on_file('dirstate')
696
state._read_dirblocks_if_needed()
697
self.assertEqual(expected_blocks, state._dirblocks)
699
# Now modify the state, but mark it as inconsistent
700
state.add('a-dir', 'a-dir-id', 'directory', None, '')
701
state._changes_aborted = True
706
state = dirstate.DirState.on_file('dirstate')
709
state._read_dirblocks_if_needed()
710
self.assertEqual(expected_blocks, state._dirblocks)
715
class TestDirStateInitialize(TestCaseWithDirState):
717
def test_initialize(self):
718
expected_result = ([], [
719
(('', '', 'TREE_ROOT'), # common details
720
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
723
state = dirstate.DirState.initialize('dirstate')
725
self.assertIsInstance(state, dirstate.DirState)
726
lines = state.get_lines()
729
# On win32 you can't read from a locked file, even within the same
730
# process. So we have to unlock and release before we check the file
732
self.assertFileEqual(''.join(lines), 'dirstate')
733
state.lock_read() # check_state_with_reopen will unlock
734
self.check_state_with_reopen(expected_result, state)
737
class TestDirStateManipulations(TestCaseWithDirState):
739
def test_set_state_from_inventory_no_content_no_parents(self):
740
# setting the current inventory is a slow but important api to support.
741
tree1 = self.make_branch_and_memory_tree('tree1')
745
revid1 = tree1.commit('foo').encode('utf8')
746
root_id = tree1.get_root_id()
747
inv = tree1.inventory
750
expected_result = [], [
751
(('', '', root_id), [
752
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
753
state = dirstate.DirState.initialize('dirstate')
755
state.set_state_from_inventory(inv)
756
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
758
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
759
state._dirblock_state)
764
# This will unlock it
765
self.check_state_with_reopen(expected_result, state)
767
def test_set_state_from_inventory_preserves_hashcache(self):
768
# https://bugs.launchpad.net/bzr/+bug/146176
769
# set_state_from_inventory should preserve the stat and hash value for
770
# workingtree files that are not changed by the inventory.
772
tree = self.make_branch_and_tree('.')
773
# depends on the default format using dirstate...
776
# make a dirstate with some valid hashcache data
777
# file on disk, but that's not needed for this test
778
foo_contents = 'contents of foo'
779
self.build_tree_contents([('foo', foo_contents)])
780
tree.add('foo', 'foo-id')
782
foo_stat = os.stat('foo')
783
foo_packed = dirstate.pack_stat(foo_stat)
784
foo_sha = osutils.sha_string(foo_contents)
785
foo_size = len(foo_contents)
787
# should not be cached yet, because the file's too fresh
789
(('', 'foo', 'foo-id',),
790
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
791
tree._dirstate._get_entry(0, 'foo-id'))
792
# poke in some hashcache information - it wouldn't normally be
793
# stored because it's too fresh
794
tree._dirstate.update_minimal(
795
('', 'foo', 'foo-id'),
796
'f', False, foo_sha, foo_packed, foo_size, 'foo')
797
# now should be cached
799
(('', 'foo', 'foo-id',),
800
[('f', foo_sha, foo_size, False, foo_packed)]),
801
tree._dirstate._get_entry(0, 'foo-id'))
803
# extract the inventory, and add something to it
804
inv = tree._get_inventory()
805
# should see the file we poked in...
806
self.assertTrue(inv.has_id('foo-id'))
807
self.assertTrue(inv.has_filename('foo'))
808
inv.add_path('bar', 'file', 'bar-id')
809
tree._dirstate._validate()
810
# this used to cause it to lose its hashcache
811
tree._dirstate.set_state_from_inventory(inv)
812
tree._dirstate._validate()
818
# now check that the state still has the original hashcache value
819
state = tree._dirstate
821
foo_tuple = state._get_entry(0, path_utf8='foo')
823
(('', 'foo', 'foo-id',),
824
[('f', foo_sha, len(foo_contents), False,
825
dirstate.pack_stat(foo_stat))]),
830
def test_set_state_from_inventory_mixed_paths(self):
831
tree1 = self.make_branch_and_tree('tree1')
832
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
833
'tree1/a/b/foo', 'tree1/a-b/bar'])
836
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
837
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
838
tree1.commit('rev1', rev_id='rev1')
839
root_id = tree1.get_root_id()
840
inv = tree1.inventory
843
expected_result1 = [('', '', root_id, 'd'),
844
('', 'a', 'a-id', 'd'),
845
('', 'a-b', 'a-b-id', 'd'),
846
('a', 'b', 'b-id', 'd'),
847
('a/b', 'foo', 'foo-id', 'f'),
848
('a-b', 'bar', 'bar-id', 'f'),
850
expected_result2 = [('', '', root_id, 'd'),
851
('', 'a', 'a-id', 'd'),
852
('', 'a-b', 'a-b-id', 'd'),
853
('a-b', 'bar', 'bar-id', 'f'),
855
state = dirstate.DirState.initialize('dirstate')
857
state.set_state_from_inventory(inv)
859
for entry in state._iter_entries():
860
values.append(entry[0] + entry[1][0][:1])
861
self.assertEqual(expected_result1, values)
863
state.set_state_from_inventory(inv)
865
for entry in state._iter_entries():
866
values.append(entry[0] + entry[1][0][:1])
867
self.assertEqual(expected_result2, values)
871
def test_set_path_id_no_parents(self):
872
"""The id of a path can be changed trivally with no parents."""
873
state = dirstate.DirState.initialize('dirstate')
875
# check precondition to be sure the state does change appropriately.
876
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
877
self.assertEqual([root_entry], list(state._iter_entries()))
878
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
879
self.assertEqual(root_entry,
880
state._get_entry(0, fileid_utf8='TREE_ROOT'))
881
self.assertEqual((None, None),
882
state._get_entry(0, fileid_utf8='second-root-id'))
883
state.set_path_id('', 'second-root-id')
884
new_root_entry = (('', '', 'second-root-id'),
885
[('d', '', 0, False, 'x'*32)])
886
expected_rows = [new_root_entry]
887
self.assertEqual(expected_rows, list(state._iter_entries()))
888
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
889
self.assertEqual(new_root_entry,
890
state._get_entry(0, fileid_utf8='second-root-id'))
891
self.assertEqual((None, None),
892
state._get_entry(0, fileid_utf8='TREE_ROOT'))
893
# should work across save too
897
state = dirstate.DirState.on_file('dirstate')
901
self.assertEqual(expected_rows, list(state._iter_entries()))
905
def test_set_path_id_with_parents(self):
906
"""Set the root file id in a dirstate with parents"""
907
mt = self.make_branch_and_tree('mt')
908
# in case the default tree format uses a different root id
909
mt.set_root_id('TREE_ROOT')
910
mt.commit('foo', rev_id='parent-revid')
911
rt = mt.branch.repository.revision_tree('parent-revid')
912
state = dirstate.DirState.initialize('dirstate')
915
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
916
root_entry = (('', '', 'TREE_ROOT'),
917
[('d', '', 0, False, 'x'*32),
918
('d', '', 0, False, 'parent-revid')])
919
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
920
self.assertEqual(root_entry,
921
state._get_entry(0, fileid_utf8='TREE_ROOT'))
922
self.assertEqual((None, None),
923
state._get_entry(0, fileid_utf8='Asecond-root-id'))
924
state.set_path_id('', 'Asecond-root-id')
926
# now see that it is what we expected
927
old_root_entry = (('', '', 'TREE_ROOT'),
928
[('a', '', 0, False, ''),
929
('d', '', 0, False, 'parent-revid')])
930
new_root_entry = (('', '', 'Asecond-root-id'),
931
[('d', '', 0, False, ''),
932
('a', '', 0, False, '')])
933
expected_rows = [new_root_entry, old_root_entry]
935
self.assertEqual(expected_rows, list(state._iter_entries()))
936
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
937
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
938
self.assertEqual((None, None),
939
state._get_entry(0, fileid_utf8='TREE_ROOT'))
940
self.assertEqual(old_root_entry,
941
state._get_entry(1, fileid_utf8='TREE_ROOT'))
942
self.assertEqual(new_root_entry,
943
state._get_entry(0, fileid_utf8='Asecond-root-id'))
944
self.assertEqual((None, None),
945
state._get_entry(1, fileid_utf8='Asecond-root-id'))
946
# should work across save too
950
# now flush & check we get the same
951
state = dirstate.DirState.on_file('dirstate')
955
self.assertEqual(expected_rows, list(state._iter_entries()))
958
# now change within an existing file-backed state
962
state.set_path_id('', 'tree-root-2')
967
def test_set_parent_trees_no_content(self):
968
# set_parent_trees is a slow but important api to support.
969
tree1 = self.make_branch_and_memory_tree('tree1')
973
revid1 = tree1.commit('foo')
976
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
977
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
980
revid2 = tree2.commit('foo')
981
root_id = tree2.get_root_id()
984
state = dirstate.DirState.initialize('dirstate')
986
state.set_path_id('', root_id)
987
state.set_parent_trees(
988
((revid1, tree1.branch.repository.revision_tree(revid1)),
989
(revid2, tree2.branch.repository.revision_tree(revid2)),
990
('ghost-rev', None)),
992
# check we can reopen and use the dirstate after setting parent
999
state = dirstate.DirState.on_file('dirstate')
1002
self.assertEqual([revid1, revid2, 'ghost-rev'],
1003
state.get_parent_ids())
1004
# iterating the entire state ensures that the state is parsable.
1005
list(state._iter_entries())
1006
# be sure that it sets not appends - change it
1007
state.set_parent_trees(
1008
((revid1, tree1.branch.repository.revision_tree(revid1)),
1009
('ghost-rev', None)),
1011
# and now put it back.
1012
state.set_parent_trees(
1013
((revid1, tree1.branch.repository.revision_tree(revid1)),
1014
(revid2, tree2.branch.repository.revision_tree(revid2)),
1015
('ghost-rev', tree2.branch.repository.revision_tree(
1016
_mod_revision.NULL_REVISION))),
1018
self.assertEqual([revid1, revid2, 'ghost-rev'],
1019
state.get_parent_ids())
1020
# the ghost should be recorded as such by set_parent_trees.
1021
self.assertEqual(['ghost-rev'], state.get_ghosts())
1023
[(('', '', root_id), [
1024
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1025
('d', '', 0, False, revid1),
1026
('d', '', 0, False, revid1)
1028
list(state._iter_entries()))
1032
def test_set_parent_trees_file_missing_from_tree(self):
1033
# Adding a parent tree may reference files not in the current state.
1034
# they should get listed just once by id, even if they are in two
1036
# set_parent_trees is a slow but important api to support.
1037
tree1 = self.make_branch_and_memory_tree('tree1')
1041
tree1.add(['a file'], ['file-id'], ['file'])
1042
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1043
revid1 = tree1.commit('foo')
1046
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1047
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1050
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1051
revid2 = tree2.commit('foo')
1052
root_id = tree2.get_root_id()
1055
# check the layout in memory
1056
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1057
(('', '', root_id), [
1058
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1059
('d', '', 0, False, revid1.encode('utf8')),
1060
('d', '', 0, False, revid1.encode('utf8'))
1062
(('', 'a file', 'file-id'), [
1063
('a', '', 0, False, ''),
1064
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1065
revid1.encode('utf8')),
1066
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1067
revid2.encode('utf8'))
1070
state = dirstate.DirState.initialize('dirstate')
1072
state.set_path_id('', root_id)
1073
state.set_parent_trees(
1074
((revid1, tree1.branch.repository.revision_tree(revid1)),
1075
(revid2, tree2.branch.repository.revision_tree(revid2)),
1081
# check_state_with_reopen will unlock
1082
self.check_state_with_reopen(expected_result, state)
1084
### add a path via _set_data - so we dont need delta work, just
1085
# raw data in, and ensure that it comes out via get_lines happily.
1087
def test_add_path_to_root_no_parents_all_data(self):
1088
# The most trivial addition of a path is when there are no parents and
1089
# its in the root and all data about the file is supplied
1090
self.build_tree(['a file'])
1091
stat = os.lstat('a file')
1092
# the 1*20 is the sha1 pretend value.
1093
state = dirstate.DirState.initialize('dirstate')
1094
expected_entries = [
1095
(('', '', 'TREE_ROOT'), [
1096
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1098
(('', 'a file', 'a-file-id'), [
1099
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1103
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1104
# having added it, it should be in the output of iter_entries.
1105
self.assertEqual(expected_entries, list(state._iter_entries()))
1106
# saving and reloading should not affect this.
1110
state = dirstate.DirState.on_file('dirstate')
1112
self.addCleanup(state.unlock)
1113
self.assertEqual(expected_entries, list(state._iter_entries()))
1115
def test_add_path_to_unversioned_directory(self):
1116
"""Adding a path to an unversioned directory should error.
1118
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1119
once dirstate is stable and if it is merged with WorkingTree3, consider
1120
removing this copy of the test.
1122
self.build_tree(['unversioned/', 'unversioned/a file'])
1123
state = dirstate.DirState.initialize('dirstate')
1124
self.addCleanup(state.unlock)
1125
self.assertRaises(errors.NotVersionedError, state.add,
1126
'unversioned/a file', 'a-file-id', 'file', None, None)
1128
def test_add_directory_to_root_no_parents_all_data(self):
1129
# The most trivial addition of a dir is when there are no parents and
1130
# its in the root and all data about the file is supplied
1131
self.build_tree(['a dir/'])
1132
stat = os.lstat('a dir')
1133
expected_entries = [
1134
(('', '', 'TREE_ROOT'), [
1135
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1137
(('', 'a dir', 'a dir id'), [
1138
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1141
state = dirstate.DirState.initialize('dirstate')
1143
state.add('a dir', 'a dir id', 'directory', stat, None)
1144
# having added it, it should be in the output of iter_entries.
1145
self.assertEqual(expected_entries, list(state._iter_entries()))
1146
# saving and reloading should not affect this.
1150
state = dirstate.DirState.on_file('dirstate')
1152
self.addCleanup(state.unlock)
1154
self.assertEqual(expected_entries, list(state._iter_entries()))
1156
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1157
# The most trivial addition of a symlink when there are no parents and
1158
# its in the root and all data about the file is supplied
1159
# bzr doesn't support fake symlinks on windows, yet.
1160
self.requireFeature(tests.SymlinkFeature)
1161
os.symlink(target, link_name)
1162
stat = os.lstat(link_name)
1163
expected_entries = [
1164
(('', '', 'TREE_ROOT'), [
1165
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1167
(('', link_name.encode('UTF-8'), 'a link id'), [
1168
('l', target.encode('UTF-8'), stat[6],
1169
False, dirstate.pack_stat(stat)), # current tree
1172
state = dirstate.DirState.initialize('dirstate')
1174
state.add(link_name, 'a link id', 'symlink', stat,
1175
target.encode('UTF-8'))
1176
# having added it, it should be in the output of iter_entries.
1177
self.assertEqual(expected_entries, list(state._iter_entries()))
1178
# saving and reloading should not affect this.
1182
state = dirstate.DirState.on_file('dirstate')
1184
self.addCleanup(state.unlock)
1185
self.assertEqual(expected_entries, list(state._iter_entries()))
1187
def test_add_symlink_to_root_no_parents_all_data(self):
1188
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1190
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1191
self.requireFeature(tests.UnicodeFilenameFeature)
1192
self._test_add_symlink_to_root_no_parents_all_data(
1193
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1195
def test_add_directory_and_child_no_parents_all_data(self):
1196
# after adding a directory, we should be able to add children to it.
1197
self.build_tree(['a dir/', 'a dir/a file'])
1198
dirstat = os.lstat('a dir')
1199
filestat = os.lstat('a dir/a file')
1200
expected_entries = [
1201
(('', '', 'TREE_ROOT'), [
1202
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1204
(('', 'a dir', 'a dir id'), [
1205
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1207
(('a dir', 'a file', 'a-file-id'), [
1208
('f', '1'*20, 25, False,
1209
dirstate.pack_stat(filestat)), # current tree details
1212
state = dirstate.DirState.initialize('dirstate')
1214
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1215
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1216
# added it, it should be in the output of iter_entries.
1217
self.assertEqual(expected_entries, list(state._iter_entries()))
1218
# saving and reloading should not affect this.
1222
state = dirstate.DirState.on_file('dirstate')
1224
self.addCleanup(state.unlock)
1225
self.assertEqual(expected_entries, list(state._iter_entries()))
1227
def test_add_tree_reference(self):
1228
# make a dirstate and add a tree reference
1229
state = dirstate.DirState.initialize('dirstate')
1231
('', 'subdir', 'subdir-id'),
1232
[('t', 'subtree-123123', 0, False,
1233
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1236
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1237
entry = state._get_entry(0, 'subdir-id', 'subdir')
1238
self.assertEqual(entry, expected_entry)
1243
# now check we can read it back
1245
self.addCleanup(state.unlock)
1247
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1248
self.assertEqual(entry, entry2)
1249
self.assertEqual(entry, expected_entry)
1250
# and lookup by id should work too
1251
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1252
self.assertEqual(entry, expected_entry)
1254
def test_add_forbidden_names(self):
1255
state = dirstate.DirState.initialize('dirstate')
1256
self.addCleanup(state.unlock)
1257
self.assertRaises(errors.BzrError,
1258
state.add, '.', 'ass-id', 'directory', None, None)
1259
self.assertRaises(errors.BzrError,
1260
state.add, '..', 'ass-id', 'directory', None, None)
1262
def test_set_state_with_rename_b_a_bug_395556(self):
1263
# bug 395556 uncovered a bug where the dirstate ends up with a false
1264
# relocation record - in a tree with no parents there should be no
1265
# absent or relocated records. This then leads to further corruption
1266
# when a commit occurs, as the incorrect relocation gathers an
1267
# incorrect absent in tree 1, and future changes go to pot.
1268
tree1 = self.make_branch_and_tree('tree1')
1269
self.build_tree(['tree1/b'])
1272
tree1.add(['b'], ['b-id'])
1273
root_id = tree1.get_root_id()
1274
inv = tree1.inventory
1275
state = dirstate.DirState.initialize('dirstate')
1277
# Set the initial state with 'b'
1278
state.set_state_from_inventory(inv)
1279
inv.rename('b-id', root_id, 'a')
1280
# Set the new state with 'a', which currently corrupts.
1281
state.set_state_from_inventory(inv)
1282
expected_result1 = [('', '', root_id, 'd'),
1283
('', 'a', 'b-id', 'f'),
1286
for entry in state._iter_entries():
1287
values.append(entry[0] + entry[1][0][:1])
1288
self.assertEqual(expected_result1, values)
1295
class TestGetLines(TestCaseWithDirState):
1297
def test_get_line_with_2_rows(self):
1298
state = self.create_dirstate_with_root_and_subdir()
1300
self.assertEqual(['#bazaar dirstate flat format 3\n',
1301
'crc32: 41262208\n',
1305
'\x00\x00a-root-value\x00'
1306
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1307
'\x00subdir\x00subdir-id\x00'
1308
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1309
], state.get_lines())
1313
def test_entry_to_line(self):
1314
state = self.create_dirstate_with_root()
1317
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1318
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1319
state._entry_to_line(state._dirblocks[0][1][0]))
1323
def test_entry_to_line_with_parent(self):
1324
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1325
root_entry = ('', '', 'a-root-value'), [
1326
('d', '', 0, False, packed_stat), # current tree details
1327
# first: a pointer to the current location
1328
('a', 'dirname/basename', 0, False, ''),
1330
state = dirstate.DirState.initialize('dirstate')
1333
'\x00\x00a-root-value\x00'
1334
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1335
'a\x00dirname/basename\x000\x00n\x00',
1336
state._entry_to_line(root_entry))
1340
def test_entry_to_line_with_two_parents_at_different_paths(self):
1341
# / in the tree, at / in one parent and /dirname/basename in the other.
1342
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1343
root_entry = ('', '', 'a-root-value'), [
1344
('d', '', 0, False, packed_stat), # current tree details
1345
('d', '', 0, False, 'rev_id'), # first parent details
1346
# second: a pointer to the current location
1347
('a', 'dirname/basename', 0, False, ''),
1349
state = dirstate.DirState.initialize('dirstate')
1352
'\x00\x00a-root-value\x00'
1353
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1354
'd\x00\x000\x00n\x00rev_id\x00'
1355
'a\x00dirname/basename\x000\x00n\x00',
1356
state._entry_to_line(root_entry))
1360
def test_iter_entries(self):
1361
# we should be able to iterate the dirstate entries from end to end
1362
# this is for get_lines to be easy to read.
1363
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1365
root_entries = [(('', '', 'a-root-value'), [
1366
('d', '', 0, False, packed_stat), # current tree details
1368
dirblocks.append(('', root_entries))
1369
# add two files in the root
1370
subdir_entry = ('', 'subdir', 'subdir-id'), [
1371
('d', '', 0, False, packed_stat), # current tree details
1373
afile_entry = ('', 'afile', 'afile-id'), [
1374
('f', 'sha1value', 34, False, packed_stat), # current tree details
1376
dirblocks.append(('', [subdir_entry, afile_entry]))
1378
file_entry2 = ('subdir', '2file', '2file-id'), [
1379
('f', 'sha1value', 23, False, packed_stat), # current tree details
1381
dirblocks.append(('subdir', [file_entry2]))
1382
state = dirstate.DirState.initialize('dirstate')
1384
state._set_data([], dirblocks)
1385
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1387
self.assertEqual(expected_entries, list(state._iter_entries()))
1392
class TestGetBlockRowIndex(TestCaseWithDirState):
1394
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1395
file_present, state, dirname, basename, tree_index):
1396
self.assertEqual((block_index, row_index, dir_present, file_present),
1397
state._get_block_entry_index(dirname, basename, tree_index))
1399
block = state._dirblocks[block_index]
1400
self.assertEqual(dirname, block[0])
1401
if dir_present and file_present:
1402
row = state._dirblocks[block_index][1][row_index]
1403
self.assertEqual(dirname, row[0][0])
1404
self.assertEqual(basename, row[0][1])
1406
def test_simple_structure(self):
1407
state = self.create_dirstate_with_root_and_subdir()
1408
self.addCleanup(state.unlock)
1409
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1410
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1411
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1412
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1413
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1416
def test_complex_structure_exists(self):
1417
state = self.create_complex_dirstate()
1418
self.addCleanup(state.unlock)
1419
# Make sure we can find everything that exists
1420
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1421
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1422
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1423
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1424
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1425
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1426
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1427
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1428
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1429
'b', 'h\xc3\xa5', 0)
1431
def test_complex_structure_missing(self):
1432
state = self.create_complex_dirstate()
1433
self.addCleanup(state.unlock)
1434
# Make sure things would be inserted in the right locations
1435
# '_' comes before 'a'
1436
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1437
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1438
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1439
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1441
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1442
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1443
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1444
# This would be inserted between a/ and b/
1445
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1447
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1450
class TestGetEntry(TestCaseWithDirState):
1452
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1453
"""Check that the right entry is returned for a request to getEntry."""
1454
entry = state._get_entry(index, path_utf8=path)
1456
self.assertEqual((None, None), entry)
1459
self.assertEqual((dirname, basename, file_id), cur[:3])
1461
def test_simple_structure(self):
1462
state = self.create_dirstate_with_root_and_subdir()
1463
self.addCleanup(state.unlock)
1464
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1465
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1466
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1467
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1468
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1470
def test_complex_structure_exists(self):
1471
state = self.create_complex_dirstate()
1472
self.addCleanup(state.unlock)
1473
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1474
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1475
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1476
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1477
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1478
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1479
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1480
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1481
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1484
def test_complex_structure_missing(self):
1485
state = self.create_complex_dirstate()
1486
self.addCleanup(state.unlock)
1487
self.assertEntryEqual(None, None, None, state, '_', 0)
1488
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1489
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1490
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1492
def test_get_entry_uninitialized(self):
1493
"""Calling get_entry will load data if it needs to"""
1494
state = self.create_dirstate_with_root()
1500
state = dirstate.DirState.on_file('dirstate')
1503
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1504
state._header_state)
1505
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1506
state._dirblock_state)
1507
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1512
class TestIterChildEntries(TestCaseWithDirState):
1514
def create_dirstate_with_two_trees(self):
1515
"""This dirstate contains multiple files and directories.
1525
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1527
Notice that a/e is an empty directory.
1529
There is one parent tree, which has the same shape with the following variations:
1530
b/g in the parent is gone.
1531
b/h in the parent has a different id
1532
b/i is new in the parent
1533
c is renamed to b/j in the parent
1535
:return: The dirstate, still write-locked.
1537
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1538
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1539
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1540
root_entry = ('', '', 'a-root-value'), [
1541
('d', '', 0, False, packed_stat),
1542
('d', '', 0, False, 'parent-revid'),
1544
a_entry = ('', 'a', 'a-dir'), [
1545
('d', '', 0, False, packed_stat),
1546
('d', '', 0, False, 'parent-revid'),
1548
b_entry = ('', 'b', 'b-dir'), [
1549
('d', '', 0, False, packed_stat),
1550
('d', '', 0, False, 'parent-revid'),
1552
c_entry = ('', 'c', 'c-file'), [
1553
('f', null_sha, 10, False, packed_stat),
1554
('r', 'b/j', 0, False, ''),
1556
d_entry = ('', 'd', 'd-file'), [
1557
('f', null_sha, 20, False, packed_stat),
1558
('f', 'd', 20, False, 'parent-revid'),
1560
e_entry = ('a', 'e', 'e-dir'), [
1561
('d', '', 0, False, packed_stat),
1562
('d', '', 0, False, 'parent-revid'),
1564
f_entry = ('a', 'f', 'f-file'), [
1565
('f', null_sha, 30, False, packed_stat),
1566
('f', 'f', 20, False, 'parent-revid'),
1568
g_entry = ('b', 'g', 'g-file'), [
1569
('f', null_sha, 30, False, packed_stat),
1570
NULL_PARENT_DETAILS,
1572
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1573
('f', null_sha, 40, False, packed_stat),
1574
NULL_PARENT_DETAILS,
1576
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1577
NULL_PARENT_DETAILS,
1578
('f', 'h', 20, False, 'parent-revid'),
1580
i_entry = ('b', 'i', 'i-file'), [
1581
NULL_PARENT_DETAILS,
1582
('f', 'h', 20, False, 'parent-revid'),
1584
j_entry = ('b', 'j', 'c-file'), [
1585
('r', 'c', 0, False, ''),
1586
('f', 'j', 20, False, 'parent-revid'),
1589
dirblocks.append(('', [root_entry]))
1590
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1591
dirblocks.append(('a', [e_entry, f_entry]))
1592
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1593
state = dirstate.DirState.initialize('dirstate')
1596
state._set_data(['parent'], dirblocks)
1600
return state, dirblocks
1602
def test_iter_children_b(self):
1603
state, dirblocks = self.create_dirstate_with_two_trees()
1604
self.addCleanup(state.unlock)
1605
expected_result = []
1606
expected_result.append(dirblocks[3][1][2]) # h2
1607
expected_result.append(dirblocks[3][1][3]) # i
1608
expected_result.append(dirblocks[3][1][4]) # j
1609
self.assertEqual(expected_result,
1610
list(state._iter_child_entries(1, 'b')))
1612
def test_iter_child_root(self):
1613
state, dirblocks = self.create_dirstate_with_two_trees()
1614
self.addCleanup(state.unlock)
1615
expected_result = []
1616
expected_result.append(dirblocks[1][1][0]) # a
1617
expected_result.append(dirblocks[1][1][1]) # b
1618
expected_result.append(dirblocks[1][1][3]) # d
1619
expected_result.append(dirblocks[2][1][0]) # e
1620
expected_result.append(dirblocks[2][1][1]) # f
1621
expected_result.append(dirblocks[3][1][2]) # h2
1622
expected_result.append(dirblocks[3][1][3]) # i
1623
expected_result.append(dirblocks[3][1][4]) # j
1624
self.assertEqual(expected_result,
1625
list(state._iter_child_entries(1, '')))
1628
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1629
"""Test that DirState adds entries in the right order."""
1631
def test_add_sorting(self):
1632
"""Add entries in lexicographical order, we get path sorted order.
1634
This tests it to a depth of 4, to make sure we don't just get it right
1635
at a single depth. 'a/a' should come before 'a-a', even though it
1636
doesn't lexicographically.
1638
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1639
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1642
state = dirstate.DirState.initialize('dirstate')
1643
self.addCleanup(state.unlock)
1645
fake_stat = os.stat('dirstate')
1647
d_id = d.replace('/', '_')+'-id'
1648
file_path = d + '/f'
1649
file_id = file_path.replace('/', '_')+'-id'
1650
state.add(d, d_id, 'directory', fake_stat, null_sha)
1651
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1653
expected = ['', '', 'a',
1654
'a/a', 'a/a/a', 'a/a/a/a',
1655
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1657
split = lambda p:p.split('/')
1658
self.assertEqual(sorted(expected, key=split), expected)
1659
dirblock_names = [d[0] for d in state._dirblocks]
1660
self.assertEqual(expected, dirblock_names)
1662
def test_set_parent_trees_correct_order(self):
1663
"""After calling set_parent_trees() we should maintain the order."""
1664
dirs = ['a', 'a-a', 'a/a']
1666
state = dirstate.DirState.initialize('dirstate')
1667
self.addCleanup(state.unlock)
1669
fake_stat = os.stat('dirstate')
1671
d_id = d.replace('/', '_')+'-id'
1672
file_path = d + '/f'
1673
file_id = file_path.replace('/', '_')+'-id'
1674
state.add(d, d_id, 'directory', fake_stat, null_sha)
1675
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1677
expected = ['', '', 'a', 'a/a', 'a-a']
1678
dirblock_names = [d[0] for d in state._dirblocks]
1679
self.assertEqual(expected, dirblock_names)
1681
# *really* cheesy way to just get an empty tree
1682
repo = self.make_repository('repo')
1683
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1684
state.set_parent_trees([('null:', empty_tree)], [])
1686
dirblock_names = [d[0] for d in state._dirblocks]
1687
self.assertEqual(expected, dirblock_names)
1690
class InstrumentedDirState(dirstate.DirState):
1691
"""An DirState with instrumented sha1 functionality."""
1693
def __init__(self, path, sha1_provider):
1694
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1695
self._time_offset = 0
1697
# member is dynamically set in DirState.__init__ to turn on trace
1698
self._sha1_provider = sha1_provider
1699
self._sha1_file = self._sha1_file_and_log
1701
def _sha_cutoff_time(self):
1702
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1703
self._cutoff_time = timestamp + self._time_offset
1705
def _sha1_file_and_log(self, abspath):
1706
self._log.append(('sha1', abspath))
1707
return self._sha1_provider.sha1(abspath)
1709
def _read_link(self, abspath, old_link):
1710
self._log.append(('read_link', abspath, old_link))
1711
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1713
def _lstat(self, abspath, entry):
1714
self._log.append(('lstat', abspath))
1715
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1717
def _is_executable(self, mode, old_executable):
1718
self._log.append(('is_exec', mode, old_executable))
1719
return super(InstrumentedDirState, self)._is_executable(mode,
1722
def adjust_time(self, secs):
1723
"""Move the clock forward or back.
1725
:param secs: The amount to adjust the clock by. Positive values make it
1726
seem as if we are in the future, negative values make it seem like we
1729
self._time_offset += secs
1730
self._cutoff_time = None
1733
class _FakeStat(object):
1734
"""A class with the same attributes as a real stat result."""
1736
def __init__(self, size, mtime, ctime, dev, ino, mode):
1738
self.st_mtime = mtime
1739
self.st_ctime = ctime
1746
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1747
st.st_ino, st.st_mode)
1750
class TestPackStat(tests.TestCaseWithTransport):
1752
def assertPackStat(self, expected, stat_value):
1753
"""Check the packed and serialized form of a stat value."""
1754
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1756
def test_pack_stat_int(self):
1757
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1758
# Make sure that all parameters have an impact on the packed stat.
1759
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1762
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1763
st.st_mtime = 1172758620
1765
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1766
st.st_ctime = 1172758630
1768
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1771
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1772
st.st_ino = 6499540L
1774
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1775
st.st_mode = 0100744
1777
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1779
def test_pack_stat_float(self):
1780
"""On some platforms mtime and ctime are floats.
1782
Make sure we don't get warnings or errors, and that we ignore changes <
1785
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1786
777L, 6499538L, 0100644)
1787
# These should all be the same as the integer counterparts
1788
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1789
st.st_mtime = 1172758620.0
1791
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1792
st.st_ctime = 1172758630.0
1794
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1795
# fractional seconds are discarded, so no change from above
1796
st.st_mtime = 1172758620.453
1797
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1798
st.st_ctime = 1172758630.228
1799
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1802
class TestBisect(TestCaseWithDirState):
1803
"""Test the ability to bisect into the disk format."""
1805
def assertBisect(self, expected_map, map_keys, state, paths):
1806
"""Assert that bisecting for paths returns the right result.
1808
:param expected_map: A map from key => entry value
1809
:param map_keys: The keys to expect for each path
1810
:param state: The DirState object.
1811
:param paths: A list of paths, these will automatically be split into
1812
(dir, name) tuples, and sorted according to how _bisect
1815
result = state._bisect(paths)
1816
# For now, results are just returned in whatever order we read them.
1817
# We could sort by (dir, name, file_id) or something like that, but in
1818
# the end it would still be fairly arbitrary, and we don't want the
1819
# extra overhead if we can avoid it. So sort everything to make sure
1821
self.assertEqual(len(map_keys), len(paths))
1823
for path, keys in zip(paths, map_keys):
1825
# This should not be present in the output
1827
expected[path] = sorted(expected_map[k] for k in keys)
1829
# The returned values are just arranged randomly based on when they
1830
# were read, for testing, make sure it is properly sorted.
1834
self.assertEqual(expected, result)
1836
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1837
"""Assert that bisecting for dirbblocks returns the right result.
1839
:param expected_map: A map from key => expected values
1840
:param map_keys: A nested list of paths we expect to be returned.
1841
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1842
:param state: The DirState object.
1843
:param paths: A list of directories
1845
result = state._bisect_dirblocks(paths)
1846
self.assertEqual(len(map_keys), len(paths))
1848
for path, keys in zip(paths, map_keys):
1850
# This should not be present in the output
1852
expected[path] = sorted(expected_map[k] for k in keys)
1856
self.assertEqual(expected, result)
1858
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1859
"""Assert the return value of a recursive bisection.
1861
:param expected_map: A map from key => entry value
1862
:param map_keys: A list of paths we expect to be returned.
1863
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1864
:param state: The DirState object.
1865
:param paths: A list of files and directories. It will be broken up
1866
into (dir, name) pairs and sorted before calling _bisect_recursive.
1869
for key in map_keys:
1870
entry = expected_map[key]
1871
dir_name_id, trees_info = entry
1872
expected[dir_name_id] = trees_info
1874
result = state._bisect_recursive(paths)
1876
self.assertEqual(expected, result)
1878
def test_bisect_each(self):
1879
"""Find a single record using bisect."""
1880
tree, state, expected = self.create_basic_dirstate()
1882
# Bisect should return the rows for the specified files.
1883
self.assertBisect(expected, [['']], state, [''])
1884
self.assertBisect(expected, [['a']], state, ['a'])
1885
self.assertBisect(expected, [['b']], state, ['b'])
1886
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1887
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1888
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1889
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1890
self.assertBisect(expected, [['f']], state, ['f'])
1892
def test_bisect_multi(self):
1893
"""Bisect can be used to find multiple records at the same time."""
1894
tree, state, expected = self.create_basic_dirstate()
1895
# Bisect should be capable of finding multiple entries at the same time
1896
self.assertBisect(expected, [['a'], ['b'], ['f']],
1897
state, ['a', 'b', 'f'])
1898
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1899
state, ['f', 'b/d', 'b/d/e'])
1900
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1901
state, ['b', 'b-c', 'b/c'])
1903
def test_bisect_one_page(self):
1904
"""Test bisect when there is only 1 page to read"""
1905
tree, state, expected = self.create_basic_dirstate()
1906
state._bisect_page_size = 5000
1907
self.assertBisect(expected,[['']], state, [''])
1908
self.assertBisect(expected,[['a']], state, ['a'])
1909
self.assertBisect(expected,[['b']], state, ['b'])
1910
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1911
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1912
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1913
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1914
self.assertBisect(expected,[['f']], state, ['f'])
1915
self.assertBisect(expected,[['a'], ['b'], ['f']],
1916
state, ['a', 'b', 'f'])
1917
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1918
state, ['b/d', 'b/d/e', 'f'])
1919
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1920
state, ['b', 'b/c', 'b-c'])
1922
def test_bisect_duplicate_paths(self):
1923
"""When bisecting for a path, handle multiple entries."""
1924
tree, state, expected = self.create_duplicated_dirstate()
1926
# Now make sure that both records are properly returned.
1927
self.assertBisect(expected, [['']], state, [''])
1928
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1929
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1930
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1931
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1932
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1934
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1935
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1937
def test_bisect_page_size_too_small(self):
1938
"""If the page size is too small, we will auto increase it."""
1939
tree, state, expected = self.create_basic_dirstate()
1940
state._bisect_page_size = 50
1941
self.assertBisect(expected, [None], state, ['b/e'])
1942
self.assertBisect(expected, [['a']], state, ['a'])
1943
self.assertBisect(expected, [['b']], state, ['b'])
1944
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1945
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1946
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1947
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1948
self.assertBisect(expected, [['f']], state, ['f'])
1950
def test_bisect_missing(self):
1951
"""Test that bisect return None if it cannot find a path."""
1952
tree, state, expected = self.create_basic_dirstate()
1953
self.assertBisect(expected, [None], state, ['foo'])
1954
self.assertBisect(expected, [None], state, ['b/foo'])
1955
self.assertBisect(expected, [None], state, ['bar/foo'])
1956
self.assertBisect(expected, [None], state, ['b-c/foo'])
1958
self.assertBisect(expected, [['a'], None, ['b/d']],
1959
state, ['a', 'foo', 'b/d'])
1961
def test_bisect_rename(self):
1962
"""Check that we find a renamed row."""
1963
tree, state, expected = self.create_renamed_dirstate()
1965
# Search for the pre and post renamed entries
1966
self.assertBisect(expected, [['a']], state, ['a'])
1967
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1968
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1969
self.assertBisect(expected, [['h']], state, ['h'])
1971
# What about b/d/e? shouldn't that also get 2 directory entries?
1972
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1973
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1975
def test_bisect_dirblocks(self):
1976
tree, state, expected = self.create_duplicated_dirstate()
1977
self.assertBisectDirBlocks(expected,
1978
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1980
self.assertBisectDirBlocks(expected,
1981
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1982
self.assertBisectDirBlocks(expected,
1983
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1984
self.assertBisectDirBlocks(expected,
1985
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1986
['b/c', 'b/c2', 'b/d', 'b/d2'],
1987
['b/d/e', 'b/d/e2'],
1988
], state, ['', 'b', 'b/d'])
1990
def test_bisect_dirblocks_missing(self):
1991
tree, state, expected = self.create_basic_dirstate()
1992
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1993
state, ['b/d', 'b/e'])
1994
# Files don't show up in this search
1995
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1996
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1997
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1998
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1999
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2001
def test_bisect_recursive_each(self):
2002
tree, state, expected = self.create_basic_dirstate()
2003
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2004
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2005
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2006
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2007
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2009
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2011
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2015
def test_bisect_recursive_multiple(self):
2016
tree, state, expected = self.create_basic_dirstate()
2017
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2018
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2019
state, ['b/d', 'b/d/e'])
2021
def test_bisect_recursive_missing(self):
2022
tree, state, expected = self.create_basic_dirstate()
2023
self.assertBisectRecursive(expected, [], state, ['d'])
2024
self.assertBisectRecursive(expected, [], state, ['b/e'])
2025
self.assertBisectRecursive(expected, [], state, ['g'])
2026
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2028
def test_bisect_recursive_renamed(self):
2029
tree, state, expected = self.create_renamed_dirstate()
2031
# Looking for either renamed item should find the other
2032
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2033
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2034
# Looking in the containing directory should find the rename target,
2035
# and anything in a subdir of the renamed target.
2036
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2037
'b/d/e', 'b/g', 'h', 'h/e'],
2041
class TestDirstateValidation(TestCaseWithDirState):
2043
def test_validate_correct_dirstate(self):
2044
state = self.create_complex_dirstate()
2047
# and make sure we can also validate with a read lock
2054
def test_dirblock_not_sorted(self):
2055
tree, state, expected = self.create_renamed_dirstate()
2056
state._read_dirblocks_if_needed()
2057
last_dirblock = state._dirblocks[-1]
2058
# we're appending to the dirblock, but this name comes before some of
2059
# the existing names; that's wrong
2060
last_dirblock[1].append(
2061
(('h', 'aaaa', 'a-id'),
2062
[('a', '', 0, False, ''),
2063
('a', '', 0, False, '')]))
2064
e = self.assertRaises(AssertionError,
2066
self.assertContainsRe(str(e), 'not sorted')
2068
def test_dirblock_name_mismatch(self):
2069
tree, state, expected = self.create_renamed_dirstate()
2070
state._read_dirblocks_if_needed()
2071
last_dirblock = state._dirblocks[-1]
2072
# add an entry with the wrong directory name
2073
last_dirblock[1].append(
2075
[('a', '', 0, False, ''),
2076
('a', '', 0, False, '')]))
2077
e = self.assertRaises(AssertionError,
2079
self.assertContainsRe(str(e),
2080
"doesn't match directory name")
2082
def test_dirblock_missing_rename(self):
2083
tree, state, expected = self.create_renamed_dirstate()
2084
state._read_dirblocks_if_needed()
2085
last_dirblock = state._dirblocks[-1]
2086
# make another entry for a-id, without a correct 'r' pointer to
2087
# the real occurrence in the working tree
2088
last_dirblock[1].append(
2089
(('h', 'z', 'a-id'),
2090
[('a', '', 0, False, ''),
2091
('a', '', 0, False, '')]))
2092
e = self.assertRaises(AssertionError,
2094
self.assertContainsRe(str(e),
2095
'file a-id is absent in row')
2098
class TestDirstateTreeReference(TestCaseWithDirState):
2100
def test_reference_revision_is_none(self):
2101
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2102
subtree = self.make_branch_and_tree('tree/subtree',
2103
format='dirstate-with-subtree')
2104
subtree.set_root_id('subtree')
2105
tree.add_reference(subtree)
2107
state = dirstate.DirState.from_tree(tree, 'dirstate')
2108
key = ('', 'subtree', 'subtree')
2109
expected = ('', [(key,
2110
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2113
self.assertEqual(expected, state._find_block(key))
2118
class TestDiscardMergeParents(TestCaseWithDirState):
2120
def test_discard_no_parents(self):
2121
# This should be a no-op
2122
state = self.create_empty_dirstate()
2123
self.addCleanup(state.unlock)
2124
state._discard_merge_parents()
2127
def test_discard_one_parent(self):
2129
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2130
root_entry_direntry = ('', '', 'a-root-value'), [
2131
('d', '', 0, False, packed_stat),
2132
('d', '', 0, False, packed_stat),
2135
dirblocks.append(('', [root_entry_direntry]))
2136
dirblocks.append(('', []))
2138
state = self.create_empty_dirstate()
2139
self.addCleanup(state.unlock)
2140
state._set_data(['parent-id'], dirblocks[:])
2143
state._discard_merge_parents()
2145
self.assertEqual(dirblocks, state._dirblocks)
2147
def test_discard_simple(self):
2149
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2150
root_entry_direntry = ('', '', 'a-root-value'), [
2151
('d', '', 0, False, packed_stat),
2152
('d', '', 0, False, packed_stat),
2153
('d', '', 0, False, packed_stat),
2155
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2156
('d', '', 0, False, packed_stat),
2157
('d', '', 0, False, packed_stat),
2160
dirblocks.append(('', [root_entry_direntry]))
2161
dirblocks.append(('', []))
2163
state = self.create_empty_dirstate()
2164
self.addCleanup(state.unlock)
2165
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2168
# This should strip of the extra column
2169
state._discard_merge_parents()
2171
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2172
self.assertEqual(expected_dirblocks, state._dirblocks)
2174
def test_discard_absent(self):
2175
"""If entries are only in a merge, discard should remove the entries"""
2176
null_stat = dirstate.DirState.NULLSTAT
2177
present_dir = ('d', '', 0, False, null_stat)
2178
present_file = ('f', '', 0, False, null_stat)
2179
absent = dirstate.DirState.NULL_PARENT_DETAILS
2180
root_key = ('', '', 'a-root-value')
2181
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2182
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2183
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2184
('', [(file_in_merged_key,
2185
[absent, absent, present_file]),
2187
[present_file, present_file, present_file]),
2191
state = self.create_empty_dirstate()
2192
self.addCleanup(state.unlock)
2193
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2196
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2197
('', [(file_in_root_key,
2198
[present_file, present_file]),
2201
state._discard_merge_parents()
2203
self.assertEqual(exp_dirblocks, state._dirblocks)
2205
def test_discard_renamed(self):
2206
null_stat = dirstate.DirState.NULLSTAT
2207
present_dir = ('d', '', 0, False, null_stat)
2208
present_file = ('f', '', 0, False, null_stat)
2209
absent = dirstate.DirState.NULL_PARENT_DETAILS
2210
root_key = ('', '', 'a-root-value')
2211
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2212
# Renamed relative to parent
2213
file_rename_s_key = ('', 'file-s', 'b-file-id')
2214
file_rename_t_key = ('', 'file-t', 'b-file-id')
2215
# And one that is renamed between the parents, but absent in this
2216
key_in_1 = ('', 'file-in-1', 'c-file-id')
2217
key_in_2 = ('', 'file-in-2', 'c-file-id')
2220
('', [(root_key, [present_dir, present_dir, present_dir])]),
2222
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2224
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2226
[present_file, present_file, present_file]),
2228
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2230
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2234
('', [(root_key, [present_dir, present_dir])]),
2235
('', [(key_in_1, [absent, present_file]),
2236
(file_in_root_key, [present_file, present_file]),
2237
(file_rename_t_key, [present_file, absent]),
2240
state = self.create_empty_dirstate()
2241
self.addCleanup(state.unlock)
2242
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2245
state._discard_merge_parents()
2247
self.assertEqual(exp_dirblocks, state._dirblocks)
2249
def test_discard_all_subdir(self):
2250
null_stat = dirstate.DirState.NULLSTAT
2251
present_dir = ('d', '', 0, False, null_stat)
2252
present_file = ('f', '', 0, False, null_stat)
2253
absent = dirstate.DirState.NULL_PARENT_DETAILS
2254
root_key = ('', '', 'a-root-value')
2255
subdir_key = ('', 'sub', 'dir-id')
2256
child1_key = ('sub', 'child1', 'child1-id')
2257
child2_key = ('sub', 'child2', 'child2-id')
2258
child3_key = ('sub', 'child3', 'child3-id')
2261
('', [(root_key, [present_dir, present_dir, present_dir])]),
2262
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2263
('sub', [(child1_key, [absent, absent, present_file]),
2264
(child2_key, [absent, absent, present_file]),
2265
(child3_key, [absent, absent, present_file]),
2269
('', [(root_key, [present_dir, present_dir])]),
2270
('', [(subdir_key, [present_dir, present_dir])]),
2273
state = self.create_empty_dirstate()
2274
self.addCleanup(state.unlock)
2275
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2278
state._discard_merge_parents()
2280
self.assertEqual(exp_dirblocks, state._dirblocks)
2283
class Test_InvEntryToDetails(tests.TestCase):
2285
def assertDetails(self, expected, inv_entry):
2286
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2287
self.assertEqual(expected, details)
2288
# details should always allow join() and always be a plain str when
2290
(minikind, fingerprint, size, executable, tree_data) = details
2291
self.assertIsInstance(minikind, str)
2292
self.assertIsInstance(fingerprint, str)
2293
self.assertIsInstance(tree_data, str)
2295
def test_unicode_symlink(self):
2296
inv_entry = inventory.InventoryLink('link-file-id',
2297
u'nam\N{Euro Sign}e',
2299
inv_entry.revision = 'link-revision-id'
2300
target = u'link-targ\N{Euro Sign}t'
2301
inv_entry.symlink_target = target
2302
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2303
'link-revision-id'), inv_entry)
2306
class TestSHA1Provider(tests.TestCaseInTempDir):
2308
def test_sha1provider_is_an_interface(self):
2309
p = dirstate.SHA1Provider()
2310
self.assertRaises(NotImplementedError, p.sha1, "foo")
2311
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2313
def test_defaultsha1provider_sha1(self):
2314
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2315
self.build_tree_contents([('foo', text)])
2316
expected_sha = osutils.sha_string(text)
2317
p = dirstate.DefaultSHA1Provider()
2318
self.assertEqual(expected_sha, p.sha1('foo'))
2320
def test_defaultsha1provider_stat_and_sha1(self):
2321
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2322
self.build_tree_contents([('foo', text)])
2323
expected_sha = osutils.sha_string(text)
2324
p = dirstate.DefaultSHA1Provider()
2325
statvalue, sha1 = p.stat_and_sha1('foo')
2326
self.assertTrue(len(statvalue) >= 10)
2327
self.assertEqual(len(text), statvalue.st_size)
2328
self.assertEqual(expected_sha, sha1)