1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 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_id2), # 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_id2), # 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.
877
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
878
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
879
list(state._iter_entries()))
880
state.set_path_id('', 'foobarbaz')
882
(('', '', 'foobarbaz'), [('d', '', 0, False,
883
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
884
self.assertEqual(expected_rows, list(state._iter_entries()))
885
# should work across save too
889
state = dirstate.DirState.on_file('dirstate')
893
self.assertEqual(expected_rows, list(state._iter_entries()))
897
def test_set_path_id_with_parents(self):
898
"""Set the root file id in a dirstate with parents"""
899
mt = self.make_branch_and_tree('mt')
900
# in case the default tree format uses a different root id
901
mt.set_root_id('TREE_ROOT')
902
mt.commit('foo', rev_id='parent-revid')
903
rt = mt.branch.repository.revision_tree('parent-revid')
904
state = dirstate.DirState.initialize('dirstate')
907
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
908
state.set_path_id('', 'foobarbaz')
910
# now see that it is what we expected
912
(('', '', 'TREE_ROOT'),
913
[('a', '', 0, False, ''),
914
('d', '', 0, False, 'parent-revid'),
916
(('', '', 'foobarbaz'),
917
[('d', '', 0, False, ''),
918
('a', '', 0, False, ''),
922
self.assertEqual(expected_rows, list(state._iter_entries()))
923
# should work across save too
927
# now flush & check we get the same
928
state = dirstate.DirState.on_file('dirstate')
932
self.assertEqual(expected_rows, list(state._iter_entries()))
935
# now change within an existing file-backed state
939
state.set_path_id('', 'tree-root-2')
944
def test_set_parent_trees_no_content(self):
945
# set_parent_trees is a slow but important api to support.
946
tree1 = self.make_branch_and_memory_tree('tree1')
950
revid1 = tree1.commit('foo')
953
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
954
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
957
revid2 = tree2.commit('foo')
958
root_id = tree2.get_root_id()
961
state = dirstate.DirState.initialize('dirstate')
963
state.set_path_id('', root_id)
964
state.set_parent_trees(
965
((revid1, tree1.branch.repository.revision_tree(revid1)),
966
(revid2, tree2.branch.repository.revision_tree(revid2)),
967
('ghost-rev', None)),
969
# check we can reopen and use the dirstate after setting parent
976
state = dirstate.DirState.on_file('dirstate')
979
self.assertEqual([revid1, revid2, 'ghost-rev'],
980
state.get_parent_ids())
981
# iterating the entire state ensures that the state is parsable.
982
list(state._iter_entries())
983
# be sure that it sets not appends - change it
984
state.set_parent_trees(
985
((revid1, tree1.branch.repository.revision_tree(revid1)),
986
('ghost-rev', None)),
988
# and now put it back.
989
state.set_parent_trees(
990
((revid1, tree1.branch.repository.revision_tree(revid1)),
991
(revid2, tree2.branch.repository.revision_tree(revid2)),
992
('ghost-rev', tree2.branch.repository.revision_tree(
993
_mod_revision.NULL_REVISION))),
995
self.assertEqual([revid1, revid2, 'ghost-rev'],
996
state.get_parent_ids())
997
# the ghost should be recorded as such by set_parent_trees.
998
self.assertEqual(['ghost-rev'], state.get_ghosts())
1000
[(('', '', root_id), [
1001
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1002
('d', '', 0, False, revid1),
1003
('d', '', 0, False, revid2)
1005
list(state._iter_entries()))
1009
def test_set_parent_trees_file_missing_from_tree(self):
1010
# Adding a parent tree may reference files not in the current state.
1011
# they should get listed just once by id, even if they are in two
1013
# set_parent_trees is a slow but important api to support.
1014
tree1 = self.make_branch_and_memory_tree('tree1')
1018
tree1.add(['a file'], ['file-id'], ['file'])
1019
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1020
revid1 = tree1.commit('foo')
1023
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1024
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1027
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1028
revid2 = tree2.commit('foo')
1029
root_id = tree2.get_root_id()
1032
# check the layout in memory
1033
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1034
(('', '', root_id), [
1035
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1036
('d', '', 0, False, revid1.encode('utf8')),
1037
('d', '', 0, False, revid2.encode('utf8'))
1039
(('', 'a file', 'file-id'), [
1040
('a', '', 0, False, ''),
1041
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1042
revid1.encode('utf8')),
1043
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1044
revid2.encode('utf8'))
1047
state = dirstate.DirState.initialize('dirstate')
1049
state.set_path_id('', root_id)
1050
state.set_parent_trees(
1051
((revid1, tree1.branch.repository.revision_tree(revid1)),
1052
(revid2, tree2.branch.repository.revision_tree(revid2)),
1058
# check_state_with_reopen will unlock
1059
self.check_state_with_reopen(expected_result, state)
1061
### add a path via _set_data - so we dont need delta work, just
1062
# raw data in, and ensure that it comes out via get_lines happily.
1064
def test_add_path_to_root_no_parents_all_data(self):
1065
# The most trivial addition of a path is when there are no parents and
1066
# its in the root and all data about the file is supplied
1067
self.build_tree(['a file'])
1068
stat = os.lstat('a file')
1069
# the 1*20 is the sha1 pretend value.
1070
state = dirstate.DirState.initialize('dirstate')
1071
expected_entries = [
1072
(('', '', 'TREE_ROOT'), [
1073
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1075
(('', 'a file', 'a-file-id'), [
1076
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1080
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1081
# having added it, it should be in the output of iter_entries.
1082
self.assertEqual(expected_entries, list(state._iter_entries()))
1083
# saving and reloading should not affect this.
1087
state = dirstate.DirState.on_file('dirstate')
1089
self.addCleanup(state.unlock)
1090
self.assertEqual(expected_entries, list(state._iter_entries()))
1092
def test_add_path_to_unversioned_directory(self):
1093
"""Adding a path to an unversioned directory should error.
1095
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1096
once dirstate is stable and if it is merged with WorkingTree3, consider
1097
removing this copy of the test.
1099
self.build_tree(['unversioned/', 'unversioned/a file'])
1100
state = dirstate.DirState.initialize('dirstate')
1101
self.addCleanup(state.unlock)
1102
self.assertRaises(errors.NotVersionedError, state.add,
1103
'unversioned/a file', 'a-file-id', 'file', None, None)
1105
def test_add_directory_to_root_no_parents_all_data(self):
1106
# The most trivial addition of a dir is when there are no parents and
1107
# its in the root and all data about the file is supplied
1108
self.build_tree(['a dir/'])
1109
stat = os.lstat('a dir')
1110
expected_entries = [
1111
(('', '', 'TREE_ROOT'), [
1112
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1114
(('', 'a dir', 'a dir id'), [
1115
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1118
state = dirstate.DirState.initialize('dirstate')
1120
state.add('a dir', 'a dir id', 'directory', stat, None)
1121
# having added it, it should be in the output of iter_entries.
1122
self.assertEqual(expected_entries, list(state._iter_entries()))
1123
# saving and reloading should not affect this.
1127
state = dirstate.DirState.on_file('dirstate')
1129
self.addCleanup(state.unlock)
1131
self.assertEqual(expected_entries, list(state._iter_entries()))
1133
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1134
# The most trivial addition of a symlink when there are no parents and
1135
# its in the root and all data about the file is supplied
1136
# bzr doesn't support fake symlinks on windows, yet.
1137
self.requireFeature(tests.SymlinkFeature)
1138
os.symlink(target, link_name)
1139
stat = os.lstat(link_name)
1140
expected_entries = [
1141
(('', '', 'TREE_ROOT'), [
1142
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1144
(('', link_name.encode('UTF-8'), 'a link id'), [
1145
('l', target.encode('UTF-8'), stat[6],
1146
False, dirstate.pack_stat(stat)), # current tree
1149
state = dirstate.DirState.initialize('dirstate')
1151
state.add(link_name, 'a link id', 'symlink', stat,
1152
target.encode('UTF-8'))
1153
# having added it, it should be in the output of iter_entries.
1154
self.assertEqual(expected_entries, list(state._iter_entries()))
1155
# saving and reloading should not affect this.
1159
state = dirstate.DirState.on_file('dirstate')
1161
self.addCleanup(state.unlock)
1162
self.assertEqual(expected_entries, list(state._iter_entries()))
1164
def test_add_symlink_to_root_no_parents_all_data(self):
1165
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1167
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1168
self.requireFeature(tests.UnicodeFilenameFeature)
1169
self._test_add_symlink_to_root_no_parents_all_data(
1170
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1172
def test_add_directory_and_child_no_parents_all_data(self):
1173
# after adding a directory, we should be able to add children to it.
1174
self.build_tree(['a dir/', 'a dir/a file'])
1175
dirstat = os.lstat('a dir')
1176
filestat = os.lstat('a dir/a file')
1177
expected_entries = [
1178
(('', '', 'TREE_ROOT'), [
1179
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1181
(('', 'a dir', 'a dir id'), [
1182
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1184
(('a dir', 'a file', 'a-file-id'), [
1185
('f', '1'*20, 25, False,
1186
dirstate.pack_stat(filestat)), # current tree details
1189
state = dirstate.DirState.initialize('dirstate')
1191
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1192
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1193
# added it, it should be in the output of iter_entries.
1194
self.assertEqual(expected_entries, list(state._iter_entries()))
1195
# saving and reloading should not affect this.
1199
state = dirstate.DirState.on_file('dirstate')
1201
self.addCleanup(state.unlock)
1202
self.assertEqual(expected_entries, list(state._iter_entries()))
1204
def test_add_tree_reference(self):
1205
# make a dirstate and add a tree reference
1206
state = dirstate.DirState.initialize('dirstate')
1208
('', 'subdir', 'subdir-id'),
1209
[('t', 'subtree-123123', 0, False,
1210
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1213
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1214
entry = state._get_entry(0, 'subdir-id', 'subdir')
1215
self.assertEqual(entry, expected_entry)
1220
# now check we can read it back
1222
self.addCleanup(state.unlock)
1224
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1225
self.assertEqual(entry, entry2)
1226
self.assertEqual(entry, expected_entry)
1227
# and lookup by id should work too
1228
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1229
self.assertEqual(entry, expected_entry)
1231
def test_add_forbidden_names(self):
1232
state = dirstate.DirState.initialize('dirstate')
1233
self.addCleanup(state.unlock)
1234
self.assertRaises(errors.BzrError,
1235
state.add, '.', 'ass-id', 'directory', None, None)
1236
self.assertRaises(errors.BzrError,
1237
state.add, '..', 'ass-id', 'directory', None, None)
1239
def test_set_state_with_rename_b_a_bug_395556(self):
1240
# bug 395556 uncovered a bug where the dirstate ends up with a false
1241
# relocation record - in a tree with no parents there should be no
1242
# absent or relocated records. This then leads to further corruption
1243
# when a commit occurs, as the incorrect relocation gathers an
1244
# incorrect absent in tree 1, and future changes go to pot.
1245
tree1 = self.make_branch_and_tree('tree1')
1246
self.build_tree(['tree1/b'])
1249
tree1.add(['b'], ['b-id'])
1250
root_id = tree1.get_root_id()
1251
inv = tree1.inventory
1252
state = dirstate.DirState.initialize('dirstate')
1254
# Set the initial state with 'b'
1255
state.set_state_from_inventory(inv)
1256
inv.rename('b-id', root_id, 'a')
1257
# Set the new state with 'a', which currently corrupts.
1258
state.set_state_from_inventory(inv)
1259
expected_result1 = [('', '', root_id, 'd'),
1260
('', 'a', 'b-id', 'f'),
1263
for entry in state._iter_entries():
1264
values.append(entry[0] + entry[1][0][:1])
1265
self.assertEqual(expected_result1, values)
1272
class TestGetLines(TestCaseWithDirState):
1274
def test_get_line_with_2_rows(self):
1275
state = self.create_dirstate_with_root_and_subdir()
1277
self.assertEqual(['#bazaar dirstate flat format 3\n',
1278
'crc32: 41262208\n',
1282
'\x00\x00a-root-value\x00'
1283
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1284
'\x00subdir\x00subdir-id\x00'
1285
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1286
], state.get_lines())
1290
def test_entry_to_line(self):
1291
state = self.create_dirstate_with_root()
1294
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1295
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1296
state._entry_to_line(state._dirblocks[0][1][0]))
1300
def test_entry_to_line_with_parent(self):
1301
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1302
root_entry = ('', '', 'a-root-value'), [
1303
('d', '', 0, False, packed_stat), # current tree details
1304
# first: a pointer to the current location
1305
('a', 'dirname/basename', 0, False, ''),
1307
state = dirstate.DirState.initialize('dirstate')
1310
'\x00\x00a-root-value\x00'
1311
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1312
'a\x00dirname/basename\x000\x00n\x00',
1313
state._entry_to_line(root_entry))
1317
def test_entry_to_line_with_two_parents_at_different_paths(self):
1318
# / in the tree, at / in one parent and /dirname/basename in the other.
1319
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1320
root_entry = ('', '', 'a-root-value'), [
1321
('d', '', 0, False, packed_stat), # current tree details
1322
('d', '', 0, False, 'rev_id'), # first parent details
1323
# second: a pointer to the current location
1324
('a', 'dirname/basename', 0, False, ''),
1326
state = dirstate.DirState.initialize('dirstate')
1329
'\x00\x00a-root-value\x00'
1330
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1331
'd\x00\x000\x00n\x00rev_id\x00'
1332
'a\x00dirname/basename\x000\x00n\x00',
1333
state._entry_to_line(root_entry))
1337
def test_iter_entries(self):
1338
# we should be able to iterate the dirstate entries from end to end
1339
# this is for get_lines to be easy to read.
1340
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1342
root_entries = [(('', '', 'a-root-value'), [
1343
('d', '', 0, False, packed_stat), # current tree details
1345
dirblocks.append(('', root_entries))
1346
# add two files in the root
1347
subdir_entry = ('', 'subdir', 'subdir-id'), [
1348
('d', '', 0, False, packed_stat), # current tree details
1350
afile_entry = ('', 'afile', 'afile-id'), [
1351
('f', 'sha1value', 34, False, packed_stat), # current tree details
1353
dirblocks.append(('', [subdir_entry, afile_entry]))
1355
file_entry2 = ('subdir', '2file', '2file-id'), [
1356
('f', 'sha1value', 23, False, packed_stat), # current tree details
1358
dirblocks.append(('subdir', [file_entry2]))
1359
state = dirstate.DirState.initialize('dirstate')
1361
state._set_data([], dirblocks)
1362
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1364
self.assertEqual(expected_entries, list(state._iter_entries()))
1369
class TestGetBlockRowIndex(TestCaseWithDirState):
1371
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1372
file_present, state, dirname, basename, tree_index):
1373
self.assertEqual((block_index, row_index, dir_present, file_present),
1374
state._get_block_entry_index(dirname, basename, tree_index))
1376
block = state._dirblocks[block_index]
1377
self.assertEqual(dirname, block[0])
1378
if dir_present and file_present:
1379
row = state._dirblocks[block_index][1][row_index]
1380
self.assertEqual(dirname, row[0][0])
1381
self.assertEqual(basename, row[0][1])
1383
def test_simple_structure(self):
1384
state = self.create_dirstate_with_root_and_subdir()
1385
self.addCleanup(state.unlock)
1386
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1387
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1388
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1389
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1390
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1393
def test_complex_structure_exists(self):
1394
state = self.create_complex_dirstate()
1395
self.addCleanup(state.unlock)
1396
# Make sure we can find everything that exists
1397
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1398
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1399
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1400
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1401
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1402
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1403
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1404
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1405
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1406
'b', 'h\xc3\xa5', 0)
1408
def test_complex_structure_missing(self):
1409
state = self.create_complex_dirstate()
1410
self.addCleanup(state.unlock)
1411
# Make sure things would be inserted in the right locations
1412
# '_' comes before 'a'
1413
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1414
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1415
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1416
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1418
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1419
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1420
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1421
# This would be inserted between a/ and b/
1422
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1424
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1427
class TestGetEntry(TestCaseWithDirState):
1429
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1430
"""Check that the right entry is returned for a request to getEntry."""
1431
entry = state._get_entry(index, path_utf8=path)
1433
self.assertEqual((None, None), entry)
1436
self.assertEqual((dirname, basename, file_id), cur[:3])
1438
def test_simple_structure(self):
1439
state = self.create_dirstate_with_root_and_subdir()
1440
self.addCleanup(state.unlock)
1441
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1442
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1443
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1444
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1445
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1447
def test_complex_structure_exists(self):
1448
state = self.create_complex_dirstate()
1449
self.addCleanup(state.unlock)
1450
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1451
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1452
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1453
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1454
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1455
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1456
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1457
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1458
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1461
def test_complex_structure_missing(self):
1462
state = self.create_complex_dirstate()
1463
self.addCleanup(state.unlock)
1464
self.assertEntryEqual(None, None, None, state, '_', 0)
1465
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1466
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1467
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1469
def test_get_entry_uninitialized(self):
1470
"""Calling get_entry will load data if it needs to"""
1471
state = self.create_dirstate_with_root()
1477
state = dirstate.DirState.on_file('dirstate')
1480
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1481
state._header_state)
1482
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1483
state._dirblock_state)
1484
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1489
class TestIterChildEntries(TestCaseWithDirState):
1491
def create_dirstate_with_two_trees(self):
1492
"""This dirstate contains multiple files and directories.
1502
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1504
Notice that a/e is an empty directory.
1506
There is one parent tree, which has the same shape with the following variations:
1507
b/g in the parent is gone.
1508
b/h in the parent has a different id
1509
b/i is new in the parent
1510
c is renamed to b/j in the parent
1512
:return: The dirstate, still write-locked.
1514
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1515
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1516
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1517
root_entry = ('', '', 'a-root-value'), [
1518
('d', '', 0, False, packed_stat),
1519
('d', '', 0, False, 'parent-revid'),
1521
a_entry = ('', 'a', 'a-dir'), [
1522
('d', '', 0, False, packed_stat),
1523
('d', '', 0, False, 'parent-revid'),
1525
b_entry = ('', 'b', 'b-dir'), [
1526
('d', '', 0, False, packed_stat),
1527
('d', '', 0, False, 'parent-revid'),
1529
c_entry = ('', 'c', 'c-file'), [
1530
('f', null_sha, 10, False, packed_stat),
1531
('r', 'b/j', 0, False, ''),
1533
d_entry = ('', 'd', 'd-file'), [
1534
('f', null_sha, 20, False, packed_stat),
1535
('f', 'd', 20, False, 'parent-revid'),
1537
e_entry = ('a', 'e', 'e-dir'), [
1538
('d', '', 0, False, packed_stat),
1539
('d', '', 0, False, 'parent-revid'),
1541
f_entry = ('a', 'f', 'f-file'), [
1542
('f', null_sha, 30, False, packed_stat),
1543
('f', 'f', 20, False, 'parent-revid'),
1545
g_entry = ('b', 'g', 'g-file'), [
1546
('f', null_sha, 30, False, packed_stat),
1547
NULL_PARENT_DETAILS,
1549
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1550
('f', null_sha, 40, False, packed_stat),
1551
NULL_PARENT_DETAILS,
1553
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1554
NULL_PARENT_DETAILS,
1555
('f', 'h', 20, False, 'parent-revid'),
1557
i_entry = ('b', 'i', 'i-file'), [
1558
NULL_PARENT_DETAILS,
1559
('f', 'h', 20, False, 'parent-revid'),
1561
j_entry = ('b', 'j', 'c-file'), [
1562
('r', 'c', 0, False, ''),
1563
('f', 'j', 20, False, 'parent-revid'),
1566
dirblocks.append(('', [root_entry]))
1567
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1568
dirblocks.append(('a', [e_entry, f_entry]))
1569
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1570
state = dirstate.DirState.initialize('dirstate')
1573
state._set_data(['parent'], dirblocks)
1577
return state, dirblocks
1579
def test_iter_children_b(self):
1580
state, dirblocks = self.create_dirstate_with_two_trees()
1581
self.addCleanup(state.unlock)
1582
expected_result = []
1583
expected_result.append(dirblocks[3][1][2]) # h2
1584
expected_result.append(dirblocks[3][1][3]) # i
1585
expected_result.append(dirblocks[3][1][4]) # j
1586
self.assertEqual(expected_result,
1587
list(state._iter_child_entries(1, 'b')))
1589
def test_iter_child_root(self):
1590
state, dirblocks = self.create_dirstate_with_two_trees()
1591
self.addCleanup(state.unlock)
1592
expected_result = []
1593
expected_result.append(dirblocks[1][1][0]) # a
1594
expected_result.append(dirblocks[1][1][1]) # b
1595
expected_result.append(dirblocks[1][1][3]) # d
1596
expected_result.append(dirblocks[2][1][0]) # e
1597
expected_result.append(dirblocks[2][1][1]) # f
1598
expected_result.append(dirblocks[3][1][2]) # h2
1599
expected_result.append(dirblocks[3][1][3]) # i
1600
expected_result.append(dirblocks[3][1][4]) # j
1601
self.assertEqual(expected_result,
1602
list(state._iter_child_entries(1, '')))
1605
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1606
"""Test that DirState adds entries in the right order."""
1608
def test_add_sorting(self):
1609
"""Add entries in lexicographical order, we get path sorted order.
1611
This tests it to a depth of 4, to make sure we don't just get it right
1612
at a single depth. 'a/a' should come before 'a-a', even though it
1613
doesn't lexicographically.
1615
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1616
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1619
state = dirstate.DirState.initialize('dirstate')
1620
self.addCleanup(state.unlock)
1622
fake_stat = os.stat('dirstate')
1624
d_id = d.replace('/', '_')+'-id'
1625
file_path = d + '/f'
1626
file_id = file_path.replace('/', '_')+'-id'
1627
state.add(d, d_id, 'directory', fake_stat, null_sha)
1628
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1630
expected = ['', '', 'a',
1631
'a/a', 'a/a/a', 'a/a/a/a',
1632
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1634
split = lambda p:p.split('/')
1635
self.assertEqual(sorted(expected, key=split), expected)
1636
dirblock_names = [d[0] for d in state._dirblocks]
1637
self.assertEqual(expected, dirblock_names)
1639
def test_set_parent_trees_correct_order(self):
1640
"""After calling set_parent_trees() we should maintain the order."""
1641
dirs = ['a', 'a-a', 'a/a']
1643
state = dirstate.DirState.initialize('dirstate')
1644
self.addCleanup(state.unlock)
1646
fake_stat = os.stat('dirstate')
1648
d_id = d.replace('/', '_')+'-id'
1649
file_path = d + '/f'
1650
file_id = file_path.replace('/', '_')+'-id'
1651
state.add(d, d_id, 'directory', fake_stat, null_sha)
1652
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1654
expected = ['', '', 'a', 'a/a', 'a-a']
1655
dirblock_names = [d[0] for d in state._dirblocks]
1656
self.assertEqual(expected, dirblock_names)
1658
# *really* cheesy way to just get an empty tree
1659
repo = self.make_repository('repo')
1660
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1661
state.set_parent_trees([('null:', empty_tree)], [])
1663
dirblock_names = [d[0] for d in state._dirblocks]
1664
self.assertEqual(expected, dirblock_names)
1667
class InstrumentedDirState(dirstate.DirState):
1668
"""An DirState with instrumented sha1 functionality."""
1670
def __init__(self, path, sha1_provider):
1671
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1672
self._time_offset = 0
1674
# member is dynamically set in DirState.__init__ to turn on trace
1675
self._sha1_provider = sha1_provider
1676
self._sha1_file = self._sha1_file_and_log
1678
def _sha_cutoff_time(self):
1679
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1680
self._cutoff_time = timestamp + self._time_offset
1682
def _sha1_file_and_log(self, abspath):
1683
self._log.append(('sha1', abspath))
1684
return self._sha1_provider.sha1(abspath)
1686
def _read_link(self, abspath, old_link):
1687
self._log.append(('read_link', abspath, old_link))
1688
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1690
def _lstat(self, abspath, entry):
1691
self._log.append(('lstat', abspath))
1692
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1694
def _is_executable(self, mode, old_executable):
1695
self._log.append(('is_exec', mode, old_executable))
1696
return super(InstrumentedDirState, self)._is_executable(mode,
1699
def adjust_time(self, secs):
1700
"""Move the clock forward or back.
1702
:param secs: The amount to adjust the clock by. Positive values make it
1703
seem as if we are in the future, negative values make it seem like we
1706
self._time_offset += secs
1707
self._cutoff_time = None
1710
class _FakeStat(object):
1711
"""A class with the same attributes as a real stat result."""
1713
def __init__(self, size, mtime, ctime, dev, ino, mode):
1715
self.st_mtime = mtime
1716
self.st_ctime = ctime
1723
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1724
st.st_ino, st.st_mode)
1727
class TestPackStat(tests.TestCaseWithTransport):
1729
def assertPackStat(self, expected, stat_value):
1730
"""Check the packed and serialized form of a stat value."""
1731
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1733
def test_pack_stat_int(self):
1734
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1735
# Make sure that all parameters have an impact on the packed stat.
1736
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1739
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1740
st.st_mtime = 1172758620
1742
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1743
st.st_ctime = 1172758630
1745
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1748
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1749
st.st_ino = 6499540L
1751
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1752
st.st_mode = 0100744
1754
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1756
def test_pack_stat_float(self):
1757
"""On some platforms mtime and ctime are floats.
1759
Make sure we don't get warnings or errors, and that we ignore changes <
1762
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1763
777L, 6499538L, 0100644)
1764
# These should all be the same as the integer counterparts
1765
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1766
st.st_mtime = 1172758620.0
1768
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1769
st.st_ctime = 1172758630.0
1771
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1772
# fractional seconds are discarded, so no change from above
1773
st.st_mtime = 1172758620.453
1774
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1775
st.st_ctime = 1172758630.228
1776
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1779
class TestBisect(TestCaseWithDirState):
1780
"""Test the ability to bisect into the disk format."""
1782
def assertBisect(self, expected_map, map_keys, state, paths):
1783
"""Assert that bisecting for paths returns the right result.
1785
:param expected_map: A map from key => entry value
1786
:param map_keys: The keys to expect for each path
1787
:param state: The DirState object.
1788
:param paths: A list of paths, these will automatically be split into
1789
(dir, name) tuples, and sorted according to how _bisect
1792
result = state._bisect(paths)
1793
# For now, results are just returned in whatever order we read them.
1794
# We could sort by (dir, name, file_id) or something like that, but in
1795
# the end it would still be fairly arbitrary, and we don't want the
1796
# extra overhead if we can avoid it. So sort everything to make sure
1798
self.assertEqual(len(map_keys), len(paths))
1800
for path, keys in zip(paths, map_keys):
1802
# This should not be present in the output
1804
expected[path] = sorted(expected_map[k] for k in keys)
1806
# The returned values are just arranged randomly based on when they
1807
# were read, for testing, make sure it is properly sorted.
1811
self.assertEqual(expected, result)
1813
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1814
"""Assert that bisecting for dirbblocks returns the right result.
1816
:param expected_map: A map from key => expected values
1817
:param map_keys: A nested list of paths we expect to be returned.
1818
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1819
:param state: The DirState object.
1820
:param paths: A list of directories
1822
result = state._bisect_dirblocks(paths)
1823
self.assertEqual(len(map_keys), len(paths))
1825
for path, keys in zip(paths, map_keys):
1827
# This should not be present in the output
1829
expected[path] = sorted(expected_map[k] for k in keys)
1833
self.assertEqual(expected, result)
1835
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1836
"""Assert the return value of a recursive bisection.
1838
:param expected_map: A map from key => entry value
1839
:param map_keys: A list of paths we expect to be returned.
1840
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1841
:param state: The DirState object.
1842
:param paths: A list of files and directories. It will be broken up
1843
into (dir, name) pairs and sorted before calling _bisect_recursive.
1846
for key in map_keys:
1847
entry = expected_map[key]
1848
dir_name_id, trees_info = entry
1849
expected[dir_name_id] = trees_info
1851
result = state._bisect_recursive(paths)
1853
self.assertEqual(expected, result)
1855
def test_bisect_each(self):
1856
"""Find a single record using bisect."""
1857
tree, state, expected = self.create_basic_dirstate()
1859
# Bisect should return the rows for the specified files.
1860
self.assertBisect(expected, [['']], state, [''])
1861
self.assertBisect(expected, [['a']], state, ['a'])
1862
self.assertBisect(expected, [['b']], state, ['b'])
1863
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1864
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1865
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1866
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1867
self.assertBisect(expected, [['f']], state, ['f'])
1869
def test_bisect_multi(self):
1870
"""Bisect can be used to find multiple records at the same time."""
1871
tree, state, expected = self.create_basic_dirstate()
1872
# Bisect should be capable of finding multiple entries at the same time
1873
self.assertBisect(expected, [['a'], ['b'], ['f']],
1874
state, ['a', 'b', 'f'])
1875
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1876
state, ['f', 'b/d', 'b/d/e'])
1877
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1878
state, ['b', 'b-c', 'b/c'])
1880
def test_bisect_one_page(self):
1881
"""Test bisect when there is only 1 page to read"""
1882
tree, state, expected = self.create_basic_dirstate()
1883
state._bisect_page_size = 5000
1884
self.assertBisect(expected,[['']], state, [''])
1885
self.assertBisect(expected,[['a']], state, ['a'])
1886
self.assertBisect(expected,[['b']], state, ['b'])
1887
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1888
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1889
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1890
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1891
self.assertBisect(expected,[['f']], state, ['f'])
1892
self.assertBisect(expected,[['a'], ['b'], ['f']],
1893
state, ['a', 'b', 'f'])
1894
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1895
state, ['b/d', 'b/d/e', 'f'])
1896
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1897
state, ['b', 'b/c', 'b-c'])
1899
def test_bisect_duplicate_paths(self):
1900
"""When bisecting for a path, handle multiple entries."""
1901
tree, state, expected = self.create_duplicated_dirstate()
1903
# Now make sure that both records are properly returned.
1904
self.assertBisect(expected, [['']], state, [''])
1905
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1906
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1907
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1908
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1909
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1911
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1912
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1914
def test_bisect_page_size_too_small(self):
1915
"""If the page size is too small, we will auto increase it."""
1916
tree, state, expected = self.create_basic_dirstate()
1917
state._bisect_page_size = 50
1918
self.assertBisect(expected, [None], state, ['b/e'])
1919
self.assertBisect(expected, [['a']], state, ['a'])
1920
self.assertBisect(expected, [['b']], state, ['b'])
1921
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1922
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1923
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1924
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1925
self.assertBisect(expected, [['f']], state, ['f'])
1927
def test_bisect_missing(self):
1928
"""Test that bisect return None if it cannot find a path."""
1929
tree, state, expected = self.create_basic_dirstate()
1930
self.assertBisect(expected, [None], state, ['foo'])
1931
self.assertBisect(expected, [None], state, ['b/foo'])
1932
self.assertBisect(expected, [None], state, ['bar/foo'])
1933
self.assertBisect(expected, [None], state, ['b-c/foo'])
1935
self.assertBisect(expected, [['a'], None, ['b/d']],
1936
state, ['a', 'foo', 'b/d'])
1938
def test_bisect_rename(self):
1939
"""Check that we find a renamed row."""
1940
tree, state, expected = self.create_renamed_dirstate()
1942
# Search for the pre and post renamed entries
1943
self.assertBisect(expected, [['a']], state, ['a'])
1944
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1945
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1946
self.assertBisect(expected, [['h']], state, ['h'])
1948
# What about b/d/e? shouldn't that also get 2 directory entries?
1949
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1950
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1952
def test_bisect_dirblocks(self):
1953
tree, state, expected = self.create_duplicated_dirstate()
1954
self.assertBisectDirBlocks(expected,
1955
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1957
self.assertBisectDirBlocks(expected,
1958
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1959
self.assertBisectDirBlocks(expected,
1960
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1961
self.assertBisectDirBlocks(expected,
1962
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1963
['b/c', 'b/c2', 'b/d', 'b/d2'],
1964
['b/d/e', 'b/d/e2'],
1965
], state, ['', 'b', 'b/d'])
1967
def test_bisect_dirblocks_missing(self):
1968
tree, state, expected = self.create_basic_dirstate()
1969
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1970
state, ['b/d', 'b/e'])
1971
# Files don't show up in this search
1972
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1973
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1974
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1975
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1976
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1978
def test_bisect_recursive_each(self):
1979
tree, state, expected = self.create_basic_dirstate()
1980
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1981
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1982
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1983
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1984
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1986
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1988
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1992
def test_bisect_recursive_multiple(self):
1993
tree, state, expected = self.create_basic_dirstate()
1994
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1995
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1996
state, ['b/d', 'b/d/e'])
1998
def test_bisect_recursive_missing(self):
1999
tree, state, expected = self.create_basic_dirstate()
2000
self.assertBisectRecursive(expected, [], state, ['d'])
2001
self.assertBisectRecursive(expected, [], state, ['b/e'])
2002
self.assertBisectRecursive(expected, [], state, ['g'])
2003
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2005
def test_bisect_recursive_renamed(self):
2006
tree, state, expected = self.create_renamed_dirstate()
2008
# Looking for either renamed item should find the other
2009
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2010
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2011
# Looking in the containing directory should find the rename target,
2012
# and anything in a subdir of the renamed target.
2013
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2014
'b/d/e', 'b/g', 'h', 'h/e'],
2018
class TestDirstateValidation(TestCaseWithDirState):
2020
def test_validate_correct_dirstate(self):
2021
state = self.create_complex_dirstate()
2024
# and make sure we can also validate with a read lock
2031
def test_dirblock_not_sorted(self):
2032
tree, state, expected = self.create_renamed_dirstate()
2033
state._read_dirblocks_if_needed()
2034
last_dirblock = state._dirblocks[-1]
2035
# we're appending to the dirblock, but this name comes before some of
2036
# the existing names; that's wrong
2037
last_dirblock[1].append(
2038
(('h', 'aaaa', 'a-id'),
2039
[('a', '', 0, False, ''),
2040
('a', '', 0, False, '')]))
2041
e = self.assertRaises(AssertionError,
2043
self.assertContainsRe(str(e), 'not sorted')
2045
def test_dirblock_name_mismatch(self):
2046
tree, state, expected = self.create_renamed_dirstate()
2047
state._read_dirblocks_if_needed()
2048
last_dirblock = state._dirblocks[-1]
2049
# add an entry with the wrong directory name
2050
last_dirblock[1].append(
2052
[('a', '', 0, False, ''),
2053
('a', '', 0, False, '')]))
2054
e = self.assertRaises(AssertionError,
2056
self.assertContainsRe(str(e),
2057
"doesn't match directory name")
2059
def test_dirblock_missing_rename(self):
2060
tree, state, expected = self.create_renamed_dirstate()
2061
state._read_dirblocks_if_needed()
2062
last_dirblock = state._dirblocks[-1]
2063
# make another entry for a-id, without a correct 'r' pointer to
2064
# the real occurrence in the working tree
2065
last_dirblock[1].append(
2066
(('h', 'z', 'a-id'),
2067
[('a', '', 0, False, ''),
2068
('a', '', 0, False, '')]))
2069
e = self.assertRaises(AssertionError,
2071
self.assertContainsRe(str(e),
2072
'file a-id is absent in row')
2075
class TestDirstateTreeReference(TestCaseWithDirState):
2077
def test_reference_revision_is_none(self):
2078
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2079
subtree = self.make_branch_and_tree('tree/subtree',
2080
format='dirstate-with-subtree')
2081
subtree.set_root_id('subtree')
2082
tree.add_reference(subtree)
2084
state = dirstate.DirState.from_tree(tree, 'dirstate')
2085
key = ('', 'subtree', 'subtree')
2086
expected = ('', [(key,
2087
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2090
self.assertEqual(expected, state._find_block(key))
2095
class TestDiscardMergeParents(TestCaseWithDirState):
2097
def test_discard_no_parents(self):
2098
# This should be a no-op
2099
state = self.create_empty_dirstate()
2100
self.addCleanup(state.unlock)
2101
state._discard_merge_parents()
2104
def test_discard_one_parent(self):
2106
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2107
root_entry_direntry = ('', '', 'a-root-value'), [
2108
('d', '', 0, False, packed_stat),
2109
('d', '', 0, False, packed_stat),
2112
dirblocks.append(('', [root_entry_direntry]))
2113
dirblocks.append(('', []))
2115
state = self.create_empty_dirstate()
2116
self.addCleanup(state.unlock)
2117
state._set_data(['parent-id'], dirblocks[:])
2120
state._discard_merge_parents()
2122
self.assertEqual(dirblocks, state._dirblocks)
2124
def test_discard_simple(self):
2126
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2127
root_entry_direntry = ('', '', 'a-root-value'), [
2128
('d', '', 0, False, packed_stat),
2129
('d', '', 0, False, packed_stat),
2130
('d', '', 0, False, packed_stat),
2132
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2133
('d', '', 0, False, packed_stat),
2134
('d', '', 0, False, packed_stat),
2137
dirblocks.append(('', [root_entry_direntry]))
2138
dirblocks.append(('', []))
2140
state = self.create_empty_dirstate()
2141
self.addCleanup(state.unlock)
2142
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2145
# This should strip of the extra column
2146
state._discard_merge_parents()
2148
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2149
self.assertEqual(expected_dirblocks, state._dirblocks)
2151
def test_discard_absent(self):
2152
"""If entries are only in a merge, discard should remove the entries"""
2153
null_stat = dirstate.DirState.NULLSTAT
2154
present_dir = ('d', '', 0, False, null_stat)
2155
present_file = ('f', '', 0, False, null_stat)
2156
absent = dirstate.DirState.NULL_PARENT_DETAILS
2157
root_key = ('', '', 'a-root-value')
2158
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2159
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2160
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2161
('', [(file_in_merged_key,
2162
[absent, absent, present_file]),
2164
[present_file, present_file, present_file]),
2168
state = self.create_empty_dirstate()
2169
self.addCleanup(state.unlock)
2170
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2173
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2174
('', [(file_in_root_key,
2175
[present_file, present_file]),
2178
state._discard_merge_parents()
2180
self.assertEqual(exp_dirblocks, state._dirblocks)
2182
def test_discard_renamed(self):
2183
null_stat = dirstate.DirState.NULLSTAT
2184
present_dir = ('d', '', 0, False, null_stat)
2185
present_file = ('f', '', 0, False, null_stat)
2186
absent = dirstate.DirState.NULL_PARENT_DETAILS
2187
root_key = ('', '', 'a-root-value')
2188
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2189
# Renamed relative to parent
2190
file_rename_s_key = ('', 'file-s', 'b-file-id')
2191
file_rename_t_key = ('', 'file-t', 'b-file-id')
2192
# And one that is renamed between the parents, but absent in this
2193
key_in_1 = ('', 'file-in-1', 'c-file-id')
2194
key_in_2 = ('', 'file-in-2', 'c-file-id')
2197
('', [(root_key, [present_dir, present_dir, present_dir])]),
2199
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2201
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2203
[present_file, present_file, present_file]),
2205
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2207
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2211
('', [(root_key, [present_dir, present_dir])]),
2212
('', [(key_in_1, [absent, present_file]),
2213
(file_in_root_key, [present_file, present_file]),
2214
(file_rename_t_key, [present_file, absent]),
2217
state = self.create_empty_dirstate()
2218
self.addCleanup(state.unlock)
2219
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2222
state._discard_merge_parents()
2224
self.assertEqual(exp_dirblocks, state._dirblocks)
2226
def test_discard_all_subdir(self):
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
subdir_key = ('', 'sub', 'dir-id')
2233
child1_key = ('sub', 'child1', 'child1-id')
2234
child2_key = ('sub', 'child2', 'child2-id')
2235
child3_key = ('sub', 'child3', 'child3-id')
2238
('', [(root_key, [present_dir, present_dir, present_dir])]),
2239
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2240
('sub', [(child1_key, [absent, absent, present_file]),
2241
(child2_key, [absent, absent, present_file]),
2242
(child3_key, [absent, absent, present_file]),
2246
('', [(root_key, [present_dir, present_dir])]),
2247
('', [(subdir_key, [present_dir, present_dir])]),
2250
state = self.create_empty_dirstate()
2251
self.addCleanup(state.unlock)
2252
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2255
state._discard_merge_parents()
2257
self.assertEqual(exp_dirblocks, state._dirblocks)
2260
class Test_InvEntryToDetails(tests.TestCase):
2262
def assertDetails(self, expected, inv_entry):
2263
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2264
self.assertEqual(expected, details)
2265
# details should always allow join() and always be a plain str when
2267
(minikind, fingerprint, size, executable, tree_data) = details
2268
self.assertIsInstance(minikind, str)
2269
self.assertIsInstance(fingerprint, str)
2270
self.assertIsInstance(tree_data, str)
2272
def test_unicode_symlink(self):
2273
inv_entry = inventory.InventoryLink('link-file-id',
2274
u'nam\N{Euro Sign}e',
2276
inv_entry.revision = 'link-revision-id'
2277
target = u'link-targ\N{Euro Sign}t'
2278
inv_entry.symlink_target = target
2279
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2280
'link-revision-id'), inv_entry)
2283
class TestSHA1Provider(tests.TestCaseInTempDir):
2285
def test_sha1provider_is_an_interface(self):
2286
p = dirstate.SHA1Provider()
2287
self.assertRaises(NotImplementedError, p.sha1, "foo")
2288
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2290
def test_defaultsha1provider_sha1(self):
2291
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2292
self.build_tree_contents([('foo', text)])
2293
expected_sha = osutils.sha_string(text)
2294
p = dirstate.DefaultSHA1Provider()
2295
self.assertEqual(expected_sha, p.sha1('foo'))
2297
def test_defaultsha1provider_stat_and_sha1(self):
2298
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2299
self.build_tree_contents([('foo', text)])
2300
expected_sha = osutils.sha_string(text)
2301
p = dirstate.DefaultSHA1Provider()
2302
statvalue, sha1 = p.stat_and_sha1('foo')
2303
self.assertTrue(len(statvalue) >= 10)
2304
self.assertEqual(len(text), statvalue.st_size)
2305
self.assertEqual(expected_sha, sha1)