1
# Copyright (C) 2006, 2007, 2008, 2009, 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='foobarbaz'))
883
state.set_path_id('', 'foobarbaz')
884
new_root_entry = (('', '', 'foobarbaz'),
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='foobarbaz'))
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
state.set_path_id('', 'foobarbaz')
918
# now see that it is what we expected
920
(('', '', 'TREE_ROOT'),
921
[('a', '', 0, False, ''),
922
('d', '', 0, False, 'parent-revid'),
924
(('', '', 'foobarbaz'),
925
[('d', '', 0, False, ''),
926
('a', '', 0, False, ''),
930
self.assertEqual(expected_rows, list(state._iter_entries()))
931
# should work across save too
935
# now flush & check we get the same
936
state = dirstate.DirState.on_file('dirstate')
940
self.assertEqual(expected_rows, list(state._iter_entries()))
943
# now change within an existing file-backed state
947
state.set_path_id('', 'tree-root-2')
952
def test_set_parent_trees_no_content(self):
953
# set_parent_trees is a slow but important api to support.
954
tree1 = self.make_branch_and_memory_tree('tree1')
958
revid1 = tree1.commit('foo')
961
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
962
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
965
revid2 = tree2.commit('foo')
966
root_id = tree2.get_root_id()
969
state = dirstate.DirState.initialize('dirstate')
971
state.set_path_id('', root_id)
972
state.set_parent_trees(
973
((revid1, tree1.branch.repository.revision_tree(revid1)),
974
(revid2, tree2.branch.repository.revision_tree(revid2)),
975
('ghost-rev', None)),
977
# check we can reopen and use the dirstate after setting parent
984
state = dirstate.DirState.on_file('dirstate')
987
self.assertEqual([revid1, revid2, 'ghost-rev'],
988
state.get_parent_ids())
989
# iterating the entire state ensures that the state is parsable.
990
list(state._iter_entries())
991
# be sure that it sets not appends - change it
992
state.set_parent_trees(
993
((revid1, tree1.branch.repository.revision_tree(revid1)),
994
('ghost-rev', None)),
996
# and now put it back.
997
state.set_parent_trees(
998
((revid1, tree1.branch.repository.revision_tree(revid1)),
999
(revid2, tree2.branch.repository.revision_tree(revid2)),
1000
('ghost-rev', tree2.branch.repository.revision_tree(
1001
_mod_revision.NULL_REVISION))),
1003
self.assertEqual([revid1, revid2, 'ghost-rev'],
1004
state.get_parent_ids())
1005
# the ghost should be recorded as such by set_parent_trees.
1006
self.assertEqual(['ghost-rev'], state.get_ghosts())
1008
[(('', '', root_id), [
1009
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1010
('d', '', 0, False, revid1),
1011
('d', '', 0, False, revid1)
1013
list(state._iter_entries()))
1017
def test_set_parent_trees_file_missing_from_tree(self):
1018
# Adding a parent tree may reference files not in the current state.
1019
# they should get listed just once by id, even if they are in two
1021
# set_parent_trees is a slow but important api to support.
1022
tree1 = self.make_branch_and_memory_tree('tree1')
1026
tree1.add(['a file'], ['file-id'], ['file'])
1027
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1028
revid1 = tree1.commit('foo')
1031
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1032
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1035
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1036
revid2 = tree2.commit('foo')
1037
root_id = tree2.get_root_id()
1040
# check the layout in memory
1041
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1042
(('', '', root_id), [
1043
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1044
('d', '', 0, False, revid1.encode('utf8')),
1045
('d', '', 0, False, revid1.encode('utf8'))
1047
(('', 'a file', 'file-id'), [
1048
('a', '', 0, False, ''),
1049
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1050
revid1.encode('utf8')),
1051
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1052
revid2.encode('utf8'))
1055
state = dirstate.DirState.initialize('dirstate')
1057
state.set_path_id('', root_id)
1058
state.set_parent_trees(
1059
((revid1, tree1.branch.repository.revision_tree(revid1)),
1060
(revid2, tree2.branch.repository.revision_tree(revid2)),
1066
# check_state_with_reopen will unlock
1067
self.check_state_with_reopen(expected_result, state)
1069
### add a path via _set_data - so we dont need delta work, just
1070
# raw data in, and ensure that it comes out via get_lines happily.
1072
def test_add_path_to_root_no_parents_all_data(self):
1073
# The most trivial addition of a path is when there are no parents and
1074
# its in the root and all data about the file is supplied
1075
self.build_tree(['a file'])
1076
stat = os.lstat('a file')
1077
# the 1*20 is the sha1 pretend value.
1078
state = dirstate.DirState.initialize('dirstate')
1079
expected_entries = [
1080
(('', '', 'TREE_ROOT'), [
1081
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1083
(('', 'a file', 'a-file-id'), [
1084
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1088
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1089
# having added it, it should be in the output of iter_entries.
1090
self.assertEqual(expected_entries, list(state._iter_entries()))
1091
# saving and reloading should not affect this.
1095
state = dirstate.DirState.on_file('dirstate')
1097
self.addCleanup(state.unlock)
1098
self.assertEqual(expected_entries, list(state._iter_entries()))
1100
def test_add_path_to_unversioned_directory(self):
1101
"""Adding a path to an unversioned directory should error.
1103
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1104
once dirstate is stable and if it is merged with WorkingTree3, consider
1105
removing this copy of the test.
1107
self.build_tree(['unversioned/', 'unversioned/a file'])
1108
state = dirstate.DirState.initialize('dirstate')
1109
self.addCleanup(state.unlock)
1110
self.assertRaises(errors.NotVersionedError, state.add,
1111
'unversioned/a file', 'a-file-id', 'file', None, None)
1113
def test_add_directory_to_root_no_parents_all_data(self):
1114
# The most trivial addition of a dir is when there are no parents and
1115
# its in the root and all data about the file is supplied
1116
self.build_tree(['a dir/'])
1117
stat = os.lstat('a dir')
1118
expected_entries = [
1119
(('', '', 'TREE_ROOT'), [
1120
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1122
(('', 'a dir', 'a dir id'), [
1123
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1126
state = dirstate.DirState.initialize('dirstate')
1128
state.add('a dir', 'a dir id', 'directory', stat, None)
1129
# having added it, it should be in the output of iter_entries.
1130
self.assertEqual(expected_entries, list(state._iter_entries()))
1131
# saving and reloading should not affect this.
1135
state = dirstate.DirState.on_file('dirstate')
1137
self.addCleanup(state.unlock)
1139
self.assertEqual(expected_entries, list(state._iter_entries()))
1141
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1142
# The most trivial addition of a symlink when there are no parents and
1143
# its in the root and all data about the file is supplied
1144
# bzr doesn't support fake symlinks on windows, yet.
1145
self.requireFeature(tests.SymlinkFeature)
1146
os.symlink(target, link_name)
1147
stat = os.lstat(link_name)
1148
expected_entries = [
1149
(('', '', 'TREE_ROOT'), [
1150
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1152
(('', link_name.encode('UTF-8'), 'a link id'), [
1153
('l', target.encode('UTF-8'), stat[6],
1154
False, dirstate.pack_stat(stat)), # current tree
1157
state = dirstate.DirState.initialize('dirstate')
1159
state.add(link_name, 'a link id', 'symlink', stat,
1160
target.encode('UTF-8'))
1161
# having added it, it should be in the output of iter_entries.
1162
self.assertEqual(expected_entries, list(state._iter_entries()))
1163
# saving and reloading should not affect this.
1167
state = dirstate.DirState.on_file('dirstate')
1169
self.addCleanup(state.unlock)
1170
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
def test_add_symlink_to_root_no_parents_all_data(self):
1173
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1175
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1176
self.requireFeature(tests.UnicodeFilenameFeature)
1177
self._test_add_symlink_to_root_no_parents_all_data(
1178
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1180
def test_add_directory_and_child_no_parents_all_data(self):
1181
# after adding a directory, we should be able to add children to it.
1182
self.build_tree(['a dir/', 'a dir/a file'])
1183
dirstat = os.lstat('a dir')
1184
filestat = os.lstat('a dir/a file')
1185
expected_entries = [
1186
(('', '', 'TREE_ROOT'), [
1187
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1189
(('', 'a dir', 'a dir id'), [
1190
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1192
(('a dir', 'a file', 'a-file-id'), [
1193
('f', '1'*20, 25, False,
1194
dirstate.pack_stat(filestat)), # current tree details
1197
state = dirstate.DirState.initialize('dirstate')
1199
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1200
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1201
# added it, it should be in the output of iter_entries.
1202
self.assertEqual(expected_entries, list(state._iter_entries()))
1203
# saving and reloading should not affect this.
1207
state = dirstate.DirState.on_file('dirstate')
1209
self.addCleanup(state.unlock)
1210
self.assertEqual(expected_entries, list(state._iter_entries()))
1212
def test_add_tree_reference(self):
1213
# make a dirstate and add a tree reference
1214
state = dirstate.DirState.initialize('dirstate')
1216
('', 'subdir', 'subdir-id'),
1217
[('t', 'subtree-123123', 0, False,
1218
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1221
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1222
entry = state._get_entry(0, 'subdir-id', 'subdir')
1223
self.assertEqual(entry, expected_entry)
1228
# now check we can read it back
1230
self.addCleanup(state.unlock)
1232
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1233
self.assertEqual(entry, entry2)
1234
self.assertEqual(entry, expected_entry)
1235
# and lookup by id should work too
1236
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1237
self.assertEqual(entry, expected_entry)
1239
def test_add_forbidden_names(self):
1240
state = dirstate.DirState.initialize('dirstate')
1241
self.addCleanup(state.unlock)
1242
self.assertRaises(errors.BzrError,
1243
state.add, '.', 'ass-id', 'directory', None, None)
1244
self.assertRaises(errors.BzrError,
1245
state.add, '..', 'ass-id', 'directory', None, None)
1247
def test_set_state_with_rename_b_a_bug_395556(self):
1248
# bug 395556 uncovered a bug where the dirstate ends up with a false
1249
# relocation record - in a tree with no parents there should be no
1250
# absent or relocated records. This then leads to further corruption
1251
# when a commit occurs, as the incorrect relocation gathers an
1252
# incorrect absent in tree 1, and future changes go to pot.
1253
tree1 = self.make_branch_and_tree('tree1')
1254
self.build_tree(['tree1/b'])
1257
tree1.add(['b'], ['b-id'])
1258
root_id = tree1.get_root_id()
1259
inv = tree1.inventory
1260
state = dirstate.DirState.initialize('dirstate')
1262
# Set the initial state with 'b'
1263
state.set_state_from_inventory(inv)
1264
inv.rename('b-id', root_id, 'a')
1265
# Set the new state with 'a', which currently corrupts.
1266
state.set_state_from_inventory(inv)
1267
expected_result1 = [('', '', root_id, 'd'),
1268
('', 'a', 'b-id', 'f'),
1271
for entry in state._iter_entries():
1272
values.append(entry[0] + entry[1][0][:1])
1273
self.assertEqual(expected_result1, values)
1280
class TestGetLines(TestCaseWithDirState):
1282
def test_get_line_with_2_rows(self):
1283
state = self.create_dirstate_with_root_and_subdir()
1285
self.assertEqual(['#bazaar dirstate flat format 3\n',
1286
'crc32: 41262208\n',
1290
'\x00\x00a-root-value\x00'
1291
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1292
'\x00subdir\x00subdir-id\x00'
1293
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1294
], state.get_lines())
1298
def test_entry_to_line(self):
1299
state = self.create_dirstate_with_root()
1302
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1303
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1304
state._entry_to_line(state._dirblocks[0][1][0]))
1308
def test_entry_to_line_with_parent(self):
1309
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1310
root_entry = ('', '', 'a-root-value'), [
1311
('d', '', 0, False, packed_stat), # current tree details
1312
# first: a pointer to the current location
1313
('a', 'dirname/basename', 0, False, ''),
1315
state = dirstate.DirState.initialize('dirstate')
1318
'\x00\x00a-root-value\x00'
1319
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1320
'a\x00dirname/basename\x000\x00n\x00',
1321
state._entry_to_line(root_entry))
1325
def test_entry_to_line_with_two_parents_at_different_paths(self):
1326
# / in the tree, at / in one parent and /dirname/basename in the other.
1327
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1328
root_entry = ('', '', 'a-root-value'), [
1329
('d', '', 0, False, packed_stat), # current tree details
1330
('d', '', 0, False, 'rev_id'), # first parent details
1331
# second: a pointer to the current location
1332
('a', 'dirname/basename', 0, False, ''),
1334
state = dirstate.DirState.initialize('dirstate')
1337
'\x00\x00a-root-value\x00'
1338
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1339
'd\x00\x000\x00n\x00rev_id\x00'
1340
'a\x00dirname/basename\x000\x00n\x00',
1341
state._entry_to_line(root_entry))
1345
def test_iter_entries(self):
1346
# we should be able to iterate the dirstate entries from end to end
1347
# this is for get_lines to be easy to read.
1348
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1350
root_entries = [(('', '', 'a-root-value'), [
1351
('d', '', 0, False, packed_stat), # current tree details
1353
dirblocks.append(('', root_entries))
1354
# add two files in the root
1355
subdir_entry = ('', 'subdir', 'subdir-id'), [
1356
('d', '', 0, False, packed_stat), # current tree details
1358
afile_entry = ('', 'afile', 'afile-id'), [
1359
('f', 'sha1value', 34, False, packed_stat), # current tree details
1361
dirblocks.append(('', [subdir_entry, afile_entry]))
1363
file_entry2 = ('subdir', '2file', '2file-id'), [
1364
('f', 'sha1value', 23, False, packed_stat), # current tree details
1366
dirblocks.append(('subdir', [file_entry2]))
1367
state = dirstate.DirState.initialize('dirstate')
1369
state._set_data([], dirblocks)
1370
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1372
self.assertEqual(expected_entries, list(state._iter_entries()))
1377
class TestGetBlockRowIndex(TestCaseWithDirState):
1379
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1380
file_present, state, dirname, basename, tree_index):
1381
self.assertEqual((block_index, row_index, dir_present, file_present),
1382
state._get_block_entry_index(dirname, basename, tree_index))
1384
block = state._dirblocks[block_index]
1385
self.assertEqual(dirname, block[0])
1386
if dir_present and file_present:
1387
row = state._dirblocks[block_index][1][row_index]
1388
self.assertEqual(dirname, row[0][0])
1389
self.assertEqual(basename, row[0][1])
1391
def test_simple_structure(self):
1392
state = self.create_dirstate_with_root_and_subdir()
1393
self.addCleanup(state.unlock)
1394
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1395
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1396
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1397
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1398
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1401
def test_complex_structure_exists(self):
1402
state = self.create_complex_dirstate()
1403
self.addCleanup(state.unlock)
1404
# Make sure we can find everything that exists
1405
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1406
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1407
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1408
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1409
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1410
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1411
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1412
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1413
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1414
'b', 'h\xc3\xa5', 0)
1416
def test_complex_structure_missing(self):
1417
state = self.create_complex_dirstate()
1418
self.addCleanup(state.unlock)
1419
# Make sure things would be inserted in the right locations
1420
# '_' comes before 'a'
1421
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1422
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1423
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1424
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1426
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1427
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1428
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1429
# This would be inserted between a/ and b/
1430
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1432
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1435
class TestGetEntry(TestCaseWithDirState):
1437
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1438
"""Check that the right entry is returned for a request to getEntry."""
1439
entry = state._get_entry(index, path_utf8=path)
1441
self.assertEqual((None, None), entry)
1444
self.assertEqual((dirname, basename, file_id), cur[:3])
1446
def test_simple_structure(self):
1447
state = self.create_dirstate_with_root_and_subdir()
1448
self.addCleanup(state.unlock)
1449
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1450
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1451
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1452
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1453
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1455
def test_complex_structure_exists(self):
1456
state = self.create_complex_dirstate()
1457
self.addCleanup(state.unlock)
1458
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1459
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1460
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1461
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1462
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1463
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1464
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1465
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1466
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1469
def test_complex_structure_missing(self):
1470
state = self.create_complex_dirstate()
1471
self.addCleanup(state.unlock)
1472
self.assertEntryEqual(None, None, None, state, '_', 0)
1473
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1474
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1475
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1477
def test_get_entry_uninitialized(self):
1478
"""Calling get_entry will load data if it needs to"""
1479
state = self.create_dirstate_with_root()
1485
state = dirstate.DirState.on_file('dirstate')
1488
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1489
state._header_state)
1490
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1491
state._dirblock_state)
1492
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1497
class TestIterChildEntries(TestCaseWithDirState):
1499
def create_dirstate_with_two_trees(self):
1500
"""This dirstate contains multiple files and directories.
1510
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1512
Notice that a/e is an empty directory.
1514
There is one parent tree, which has the same shape with the following variations:
1515
b/g in the parent is gone.
1516
b/h in the parent has a different id
1517
b/i is new in the parent
1518
c is renamed to b/j in the parent
1520
:return: The dirstate, still write-locked.
1522
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1523
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1524
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1525
root_entry = ('', '', 'a-root-value'), [
1526
('d', '', 0, False, packed_stat),
1527
('d', '', 0, False, 'parent-revid'),
1529
a_entry = ('', 'a', 'a-dir'), [
1530
('d', '', 0, False, packed_stat),
1531
('d', '', 0, False, 'parent-revid'),
1533
b_entry = ('', 'b', 'b-dir'), [
1534
('d', '', 0, False, packed_stat),
1535
('d', '', 0, False, 'parent-revid'),
1537
c_entry = ('', 'c', 'c-file'), [
1538
('f', null_sha, 10, False, packed_stat),
1539
('r', 'b/j', 0, False, ''),
1541
d_entry = ('', 'd', 'd-file'), [
1542
('f', null_sha, 20, False, packed_stat),
1543
('f', 'd', 20, False, 'parent-revid'),
1545
e_entry = ('a', 'e', 'e-dir'), [
1546
('d', '', 0, False, packed_stat),
1547
('d', '', 0, False, 'parent-revid'),
1549
f_entry = ('a', 'f', 'f-file'), [
1550
('f', null_sha, 30, False, packed_stat),
1551
('f', 'f', 20, False, 'parent-revid'),
1553
g_entry = ('b', 'g', 'g-file'), [
1554
('f', null_sha, 30, False, packed_stat),
1555
NULL_PARENT_DETAILS,
1557
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1558
('f', null_sha, 40, False, packed_stat),
1559
NULL_PARENT_DETAILS,
1561
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1562
NULL_PARENT_DETAILS,
1563
('f', 'h', 20, False, 'parent-revid'),
1565
i_entry = ('b', 'i', 'i-file'), [
1566
NULL_PARENT_DETAILS,
1567
('f', 'h', 20, False, 'parent-revid'),
1569
j_entry = ('b', 'j', 'c-file'), [
1570
('r', 'c', 0, False, ''),
1571
('f', 'j', 20, False, 'parent-revid'),
1574
dirblocks.append(('', [root_entry]))
1575
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1576
dirblocks.append(('a', [e_entry, f_entry]))
1577
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1578
state = dirstate.DirState.initialize('dirstate')
1581
state._set_data(['parent'], dirblocks)
1585
return state, dirblocks
1587
def test_iter_children_b(self):
1588
state, dirblocks = self.create_dirstate_with_two_trees()
1589
self.addCleanup(state.unlock)
1590
expected_result = []
1591
expected_result.append(dirblocks[3][1][2]) # h2
1592
expected_result.append(dirblocks[3][1][3]) # i
1593
expected_result.append(dirblocks[3][1][4]) # j
1594
self.assertEqual(expected_result,
1595
list(state._iter_child_entries(1, 'b')))
1597
def test_iter_child_root(self):
1598
state, dirblocks = self.create_dirstate_with_two_trees()
1599
self.addCleanup(state.unlock)
1600
expected_result = []
1601
expected_result.append(dirblocks[1][1][0]) # a
1602
expected_result.append(dirblocks[1][1][1]) # b
1603
expected_result.append(dirblocks[1][1][3]) # d
1604
expected_result.append(dirblocks[2][1][0]) # e
1605
expected_result.append(dirblocks[2][1][1]) # f
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, '')))
1613
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1614
"""Test that DirState adds entries in the right order."""
1616
def test_add_sorting(self):
1617
"""Add entries in lexicographical order, we get path sorted order.
1619
This tests it to a depth of 4, to make sure we don't just get it right
1620
at a single depth. 'a/a' should come before 'a-a', even though it
1621
doesn't lexicographically.
1623
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1624
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1627
state = dirstate.DirState.initialize('dirstate')
1628
self.addCleanup(state.unlock)
1630
fake_stat = os.stat('dirstate')
1632
d_id = d.replace('/', '_')+'-id'
1633
file_path = d + '/f'
1634
file_id = file_path.replace('/', '_')+'-id'
1635
state.add(d, d_id, 'directory', fake_stat, null_sha)
1636
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1638
expected = ['', '', 'a',
1639
'a/a', 'a/a/a', 'a/a/a/a',
1640
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1642
split = lambda p:p.split('/')
1643
self.assertEqual(sorted(expected, key=split), expected)
1644
dirblock_names = [d[0] for d in state._dirblocks]
1645
self.assertEqual(expected, dirblock_names)
1647
def test_set_parent_trees_correct_order(self):
1648
"""After calling set_parent_trees() we should maintain the order."""
1649
dirs = ['a', 'a-a', 'a/a']
1651
state = dirstate.DirState.initialize('dirstate')
1652
self.addCleanup(state.unlock)
1654
fake_stat = os.stat('dirstate')
1656
d_id = d.replace('/', '_')+'-id'
1657
file_path = d + '/f'
1658
file_id = file_path.replace('/', '_')+'-id'
1659
state.add(d, d_id, 'directory', fake_stat, null_sha)
1660
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1662
expected = ['', '', 'a', 'a/a', 'a-a']
1663
dirblock_names = [d[0] for d in state._dirblocks]
1664
self.assertEqual(expected, dirblock_names)
1666
# *really* cheesy way to just get an empty tree
1667
repo = self.make_repository('repo')
1668
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1669
state.set_parent_trees([('null:', empty_tree)], [])
1671
dirblock_names = [d[0] for d in state._dirblocks]
1672
self.assertEqual(expected, dirblock_names)
1675
class InstrumentedDirState(dirstate.DirState):
1676
"""An DirState with instrumented sha1 functionality."""
1678
def __init__(self, path, sha1_provider):
1679
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1680
self._time_offset = 0
1682
# member is dynamically set in DirState.__init__ to turn on trace
1683
self._sha1_provider = sha1_provider
1684
self._sha1_file = self._sha1_file_and_log
1686
def _sha_cutoff_time(self):
1687
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1688
self._cutoff_time = timestamp + self._time_offset
1690
def _sha1_file_and_log(self, abspath):
1691
self._log.append(('sha1', abspath))
1692
return self._sha1_provider.sha1(abspath)
1694
def _read_link(self, abspath, old_link):
1695
self._log.append(('read_link', abspath, old_link))
1696
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1698
def _lstat(self, abspath, entry):
1699
self._log.append(('lstat', abspath))
1700
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1702
def _is_executable(self, mode, old_executable):
1703
self._log.append(('is_exec', mode, old_executable))
1704
return super(InstrumentedDirState, self)._is_executable(mode,
1707
def adjust_time(self, secs):
1708
"""Move the clock forward or back.
1710
:param secs: The amount to adjust the clock by. Positive values make it
1711
seem as if we are in the future, negative values make it seem like we
1714
self._time_offset += secs
1715
self._cutoff_time = None
1718
class _FakeStat(object):
1719
"""A class with the same attributes as a real stat result."""
1721
def __init__(self, size, mtime, ctime, dev, ino, mode):
1723
self.st_mtime = mtime
1724
self.st_ctime = ctime
1731
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1732
st.st_ino, st.st_mode)
1735
class TestPackStat(tests.TestCaseWithTransport):
1737
def assertPackStat(self, expected, stat_value):
1738
"""Check the packed and serialized form of a stat value."""
1739
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1741
def test_pack_stat_int(self):
1742
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1743
# Make sure that all parameters have an impact on the packed stat.
1744
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1747
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1748
st.st_mtime = 1172758620
1750
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1751
st.st_ctime = 1172758630
1753
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1756
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1757
st.st_ino = 6499540L
1759
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1760
st.st_mode = 0100744
1762
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1764
def test_pack_stat_float(self):
1765
"""On some platforms mtime and ctime are floats.
1767
Make sure we don't get warnings or errors, and that we ignore changes <
1770
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1771
777L, 6499538L, 0100644)
1772
# These should all be the same as the integer counterparts
1773
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1774
st.st_mtime = 1172758620.0
1776
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1777
st.st_ctime = 1172758630.0
1779
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1780
# fractional seconds are discarded, so no change from above
1781
st.st_mtime = 1172758620.453
1782
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1783
st.st_ctime = 1172758630.228
1784
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1787
class TestBisect(TestCaseWithDirState):
1788
"""Test the ability to bisect into the disk format."""
1790
def assertBisect(self, expected_map, map_keys, state, paths):
1791
"""Assert that bisecting for paths returns the right result.
1793
:param expected_map: A map from key => entry value
1794
:param map_keys: The keys to expect for each path
1795
:param state: The DirState object.
1796
:param paths: A list of paths, these will automatically be split into
1797
(dir, name) tuples, and sorted according to how _bisect
1800
result = state._bisect(paths)
1801
# For now, results are just returned in whatever order we read them.
1802
# We could sort by (dir, name, file_id) or something like that, but in
1803
# the end it would still be fairly arbitrary, and we don't want the
1804
# extra overhead if we can avoid it. So sort everything to make sure
1806
self.assertEqual(len(map_keys), len(paths))
1808
for path, keys in zip(paths, map_keys):
1810
# This should not be present in the output
1812
expected[path] = sorted(expected_map[k] for k in keys)
1814
# The returned values are just arranged randomly based on when they
1815
# were read, for testing, make sure it is properly sorted.
1819
self.assertEqual(expected, result)
1821
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1822
"""Assert that bisecting for dirbblocks returns the right result.
1824
:param expected_map: A map from key => expected values
1825
:param map_keys: A nested list of paths we expect to be returned.
1826
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1827
:param state: The DirState object.
1828
:param paths: A list of directories
1830
result = state._bisect_dirblocks(paths)
1831
self.assertEqual(len(map_keys), len(paths))
1833
for path, keys in zip(paths, map_keys):
1835
# This should not be present in the output
1837
expected[path] = sorted(expected_map[k] for k in keys)
1841
self.assertEqual(expected, result)
1843
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1844
"""Assert the return value of a recursive bisection.
1846
:param expected_map: A map from key => entry value
1847
:param map_keys: A list of paths we expect to be returned.
1848
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1849
:param state: The DirState object.
1850
:param paths: A list of files and directories. It will be broken up
1851
into (dir, name) pairs and sorted before calling _bisect_recursive.
1854
for key in map_keys:
1855
entry = expected_map[key]
1856
dir_name_id, trees_info = entry
1857
expected[dir_name_id] = trees_info
1859
result = state._bisect_recursive(paths)
1861
self.assertEqual(expected, result)
1863
def test_bisect_each(self):
1864
"""Find a single record using bisect."""
1865
tree, state, expected = self.create_basic_dirstate()
1867
# Bisect should return the rows for the specified files.
1868
self.assertBisect(expected, [['']], state, [''])
1869
self.assertBisect(expected, [['a']], state, ['a'])
1870
self.assertBisect(expected, [['b']], state, ['b'])
1871
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1872
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1873
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1874
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1875
self.assertBisect(expected, [['f']], state, ['f'])
1877
def test_bisect_multi(self):
1878
"""Bisect can be used to find multiple records at the same time."""
1879
tree, state, expected = self.create_basic_dirstate()
1880
# Bisect should be capable of finding multiple entries at the same time
1881
self.assertBisect(expected, [['a'], ['b'], ['f']],
1882
state, ['a', 'b', 'f'])
1883
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1884
state, ['f', 'b/d', 'b/d/e'])
1885
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1886
state, ['b', 'b-c', 'b/c'])
1888
def test_bisect_one_page(self):
1889
"""Test bisect when there is only 1 page to read"""
1890
tree, state, expected = self.create_basic_dirstate()
1891
state._bisect_page_size = 5000
1892
self.assertBisect(expected,[['']], state, [''])
1893
self.assertBisect(expected,[['a']], state, ['a'])
1894
self.assertBisect(expected,[['b']], state, ['b'])
1895
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1896
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1897
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1898
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1899
self.assertBisect(expected,[['f']], state, ['f'])
1900
self.assertBisect(expected,[['a'], ['b'], ['f']],
1901
state, ['a', 'b', 'f'])
1902
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1903
state, ['b/d', 'b/d/e', 'f'])
1904
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1905
state, ['b', 'b/c', 'b-c'])
1907
def test_bisect_duplicate_paths(self):
1908
"""When bisecting for a path, handle multiple entries."""
1909
tree, state, expected = self.create_duplicated_dirstate()
1911
# Now make sure that both records are properly returned.
1912
self.assertBisect(expected, [['']], state, [''])
1913
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1914
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1915
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1916
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1917
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1919
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1920
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1922
def test_bisect_page_size_too_small(self):
1923
"""If the page size is too small, we will auto increase it."""
1924
tree, state, expected = self.create_basic_dirstate()
1925
state._bisect_page_size = 50
1926
self.assertBisect(expected, [None], state, ['b/e'])
1927
self.assertBisect(expected, [['a']], state, ['a'])
1928
self.assertBisect(expected, [['b']], state, ['b'])
1929
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1930
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1931
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1932
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1933
self.assertBisect(expected, [['f']], state, ['f'])
1935
def test_bisect_missing(self):
1936
"""Test that bisect return None if it cannot find a path."""
1937
tree, state, expected = self.create_basic_dirstate()
1938
self.assertBisect(expected, [None], state, ['foo'])
1939
self.assertBisect(expected, [None], state, ['b/foo'])
1940
self.assertBisect(expected, [None], state, ['bar/foo'])
1941
self.assertBisect(expected, [None], state, ['b-c/foo'])
1943
self.assertBisect(expected, [['a'], None, ['b/d']],
1944
state, ['a', 'foo', 'b/d'])
1946
def test_bisect_rename(self):
1947
"""Check that we find a renamed row."""
1948
tree, state, expected = self.create_renamed_dirstate()
1950
# Search for the pre and post renamed entries
1951
self.assertBisect(expected, [['a']], state, ['a'])
1952
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1953
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1954
self.assertBisect(expected, [['h']], state, ['h'])
1956
# What about b/d/e? shouldn't that also get 2 directory entries?
1957
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1958
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1960
def test_bisect_dirblocks(self):
1961
tree, state, expected = self.create_duplicated_dirstate()
1962
self.assertBisectDirBlocks(expected,
1963
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1965
self.assertBisectDirBlocks(expected,
1966
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1967
self.assertBisectDirBlocks(expected,
1968
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1969
self.assertBisectDirBlocks(expected,
1970
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1971
['b/c', 'b/c2', 'b/d', 'b/d2'],
1972
['b/d/e', 'b/d/e2'],
1973
], state, ['', 'b', 'b/d'])
1975
def test_bisect_dirblocks_missing(self):
1976
tree, state, expected = self.create_basic_dirstate()
1977
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1978
state, ['b/d', 'b/e'])
1979
# Files don't show up in this search
1980
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1981
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1982
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1983
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1984
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1986
def test_bisect_recursive_each(self):
1987
tree, state, expected = self.create_basic_dirstate()
1988
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1989
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1990
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1991
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1992
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1994
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1996
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2000
def test_bisect_recursive_multiple(self):
2001
tree, state, expected = self.create_basic_dirstate()
2002
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2003
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2004
state, ['b/d', 'b/d/e'])
2006
def test_bisect_recursive_missing(self):
2007
tree, state, expected = self.create_basic_dirstate()
2008
self.assertBisectRecursive(expected, [], state, ['d'])
2009
self.assertBisectRecursive(expected, [], state, ['b/e'])
2010
self.assertBisectRecursive(expected, [], state, ['g'])
2011
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2013
def test_bisect_recursive_renamed(self):
2014
tree, state, expected = self.create_renamed_dirstate()
2016
# Looking for either renamed item should find the other
2017
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2018
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2019
# Looking in the containing directory should find the rename target,
2020
# and anything in a subdir of the renamed target.
2021
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2022
'b/d/e', 'b/g', 'h', 'h/e'],
2026
class TestDirstateValidation(TestCaseWithDirState):
2028
def test_validate_correct_dirstate(self):
2029
state = self.create_complex_dirstate()
2032
# and make sure we can also validate with a read lock
2039
def test_dirblock_not_sorted(self):
2040
tree, state, expected = self.create_renamed_dirstate()
2041
state._read_dirblocks_if_needed()
2042
last_dirblock = state._dirblocks[-1]
2043
# we're appending to the dirblock, but this name comes before some of
2044
# the existing names; that's wrong
2045
last_dirblock[1].append(
2046
(('h', 'aaaa', 'a-id'),
2047
[('a', '', 0, False, ''),
2048
('a', '', 0, False, '')]))
2049
e = self.assertRaises(AssertionError,
2051
self.assertContainsRe(str(e), 'not sorted')
2053
def test_dirblock_name_mismatch(self):
2054
tree, state, expected = self.create_renamed_dirstate()
2055
state._read_dirblocks_if_needed()
2056
last_dirblock = state._dirblocks[-1]
2057
# add an entry with the wrong directory name
2058
last_dirblock[1].append(
2060
[('a', '', 0, False, ''),
2061
('a', '', 0, False, '')]))
2062
e = self.assertRaises(AssertionError,
2064
self.assertContainsRe(str(e),
2065
"doesn't match directory name")
2067
def test_dirblock_missing_rename(self):
2068
tree, state, expected = self.create_renamed_dirstate()
2069
state._read_dirblocks_if_needed()
2070
last_dirblock = state._dirblocks[-1]
2071
# make another entry for a-id, without a correct 'r' pointer to
2072
# the real occurrence in the working tree
2073
last_dirblock[1].append(
2074
(('h', 'z', 'a-id'),
2075
[('a', '', 0, False, ''),
2076
('a', '', 0, False, '')]))
2077
e = self.assertRaises(AssertionError,
2079
self.assertContainsRe(str(e),
2080
'file a-id is absent in row')
2083
class TestDirstateTreeReference(TestCaseWithDirState):
2085
def test_reference_revision_is_none(self):
2086
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2087
subtree = self.make_branch_and_tree('tree/subtree',
2088
format='dirstate-with-subtree')
2089
subtree.set_root_id('subtree')
2090
tree.add_reference(subtree)
2092
state = dirstate.DirState.from_tree(tree, 'dirstate')
2093
key = ('', 'subtree', 'subtree')
2094
expected = ('', [(key,
2095
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2098
self.assertEqual(expected, state._find_block(key))
2103
class TestDiscardMergeParents(TestCaseWithDirState):
2105
def test_discard_no_parents(self):
2106
# This should be a no-op
2107
state = self.create_empty_dirstate()
2108
self.addCleanup(state.unlock)
2109
state._discard_merge_parents()
2112
def test_discard_one_parent(self):
2114
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2115
root_entry_direntry = ('', '', 'a-root-value'), [
2116
('d', '', 0, False, packed_stat),
2117
('d', '', 0, False, packed_stat),
2120
dirblocks.append(('', [root_entry_direntry]))
2121
dirblocks.append(('', []))
2123
state = self.create_empty_dirstate()
2124
self.addCleanup(state.unlock)
2125
state._set_data(['parent-id'], dirblocks[:])
2128
state._discard_merge_parents()
2130
self.assertEqual(dirblocks, state._dirblocks)
2132
def test_discard_simple(self):
2134
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2135
root_entry_direntry = ('', '', 'a-root-value'), [
2136
('d', '', 0, False, packed_stat),
2137
('d', '', 0, False, packed_stat),
2138
('d', '', 0, False, packed_stat),
2140
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2141
('d', '', 0, False, packed_stat),
2142
('d', '', 0, False, packed_stat),
2145
dirblocks.append(('', [root_entry_direntry]))
2146
dirblocks.append(('', []))
2148
state = self.create_empty_dirstate()
2149
self.addCleanup(state.unlock)
2150
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2153
# This should strip of the extra column
2154
state._discard_merge_parents()
2156
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2157
self.assertEqual(expected_dirblocks, state._dirblocks)
2159
def test_discard_absent(self):
2160
"""If entries are only in a merge, discard should remove the entries"""
2161
null_stat = dirstate.DirState.NULLSTAT
2162
present_dir = ('d', '', 0, False, null_stat)
2163
present_file = ('f', '', 0, False, null_stat)
2164
absent = dirstate.DirState.NULL_PARENT_DETAILS
2165
root_key = ('', '', 'a-root-value')
2166
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2167
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2168
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2169
('', [(file_in_merged_key,
2170
[absent, absent, present_file]),
2172
[present_file, present_file, present_file]),
2176
state = self.create_empty_dirstate()
2177
self.addCleanup(state.unlock)
2178
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2181
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2182
('', [(file_in_root_key,
2183
[present_file, present_file]),
2186
state._discard_merge_parents()
2188
self.assertEqual(exp_dirblocks, state._dirblocks)
2190
def test_discard_renamed(self):
2191
null_stat = dirstate.DirState.NULLSTAT
2192
present_dir = ('d', '', 0, False, null_stat)
2193
present_file = ('f', '', 0, False, null_stat)
2194
absent = dirstate.DirState.NULL_PARENT_DETAILS
2195
root_key = ('', '', 'a-root-value')
2196
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2197
# Renamed relative to parent
2198
file_rename_s_key = ('', 'file-s', 'b-file-id')
2199
file_rename_t_key = ('', 'file-t', 'b-file-id')
2200
# And one that is renamed between the parents, but absent in this
2201
key_in_1 = ('', 'file-in-1', 'c-file-id')
2202
key_in_2 = ('', 'file-in-2', 'c-file-id')
2205
('', [(root_key, [present_dir, present_dir, present_dir])]),
2207
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2209
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2211
[present_file, present_file, present_file]),
2213
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2215
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2219
('', [(root_key, [present_dir, present_dir])]),
2220
('', [(key_in_1, [absent, present_file]),
2221
(file_in_root_key, [present_file, present_file]),
2222
(file_rename_t_key, [present_file, absent]),
2225
state = self.create_empty_dirstate()
2226
self.addCleanup(state.unlock)
2227
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2230
state._discard_merge_parents()
2232
self.assertEqual(exp_dirblocks, state._dirblocks)
2234
def test_discard_all_subdir(self):
2235
null_stat = dirstate.DirState.NULLSTAT
2236
present_dir = ('d', '', 0, False, null_stat)
2237
present_file = ('f', '', 0, False, null_stat)
2238
absent = dirstate.DirState.NULL_PARENT_DETAILS
2239
root_key = ('', '', 'a-root-value')
2240
subdir_key = ('', 'sub', 'dir-id')
2241
child1_key = ('sub', 'child1', 'child1-id')
2242
child2_key = ('sub', 'child2', 'child2-id')
2243
child3_key = ('sub', 'child3', 'child3-id')
2246
('', [(root_key, [present_dir, present_dir, present_dir])]),
2247
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2248
('sub', [(child1_key, [absent, absent, present_file]),
2249
(child2_key, [absent, absent, present_file]),
2250
(child3_key, [absent, absent, present_file]),
2254
('', [(root_key, [present_dir, present_dir])]),
2255
('', [(subdir_key, [present_dir, present_dir])]),
2258
state = self.create_empty_dirstate()
2259
self.addCleanup(state.unlock)
2260
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2263
state._discard_merge_parents()
2265
self.assertEqual(exp_dirblocks, state._dirblocks)
2268
class Test_InvEntryToDetails(tests.TestCase):
2270
def assertDetails(self, expected, inv_entry):
2271
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2272
self.assertEqual(expected, details)
2273
# details should always allow join() and always be a plain str when
2275
(minikind, fingerprint, size, executable, tree_data) = details
2276
self.assertIsInstance(minikind, str)
2277
self.assertIsInstance(fingerprint, str)
2278
self.assertIsInstance(tree_data, str)
2280
def test_unicode_symlink(self):
2281
inv_entry = inventory.InventoryLink('link-file-id',
2282
u'nam\N{Euro Sign}e',
2284
inv_entry.revision = 'link-revision-id'
2285
target = u'link-targ\N{Euro Sign}t'
2286
inv_entry.symlink_target = target
2287
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2288
'link-revision-id'), inv_entry)
2291
class TestSHA1Provider(tests.TestCaseInTempDir):
2293
def test_sha1provider_is_an_interface(self):
2294
p = dirstate.SHA1Provider()
2295
self.assertRaises(NotImplementedError, p.sha1, "foo")
2296
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2298
def test_defaultsha1provider_sha1(self):
2299
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2300
self.build_tree_contents([('foo', text)])
2301
expected_sha = osutils.sha_string(text)
2302
p = dirstate.DefaultSHA1Provider()
2303
self.assertEqual(expected_sha, p.sha1('foo'))
2305
def test_defaultsha1provider_stat_and_sha1(self):
2306
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2307
self.build_tree_contents([('foo', text)])
2308
expected_sha = osutils.sha_string(text)
2309
p = dirstate.DefaultSHA1Provider()
2310
statvalue, sha1 = p.stat_and_sha1('foo')
2311
self.assertTrue(len(statvalue) >= 10)
2312
self.assertEqual(len(text), statvalue.st_size)
2313
self.assertEqual(expected_sha, sha1)