1
# Copyright (C) 2006-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
revision as _mod_revision,
31
from bzrlib.tests import test_osutils
36
# general checks for NOT_IN_MEMORY error conditions.
37
# set_path_id on a NOT_IN_MEMORY dirstate
38
# set_path_id unicode support
39
# set_path_id setting id of a path not root
40
# set_path_id setting id when there are parents without the id in the parents
41
# set_path_id setting id when there are parents with the id in the parents
42
# set_path_id setting id when state is not in memory
43
# set_path_id setting id when state is in memory unmodified
44
# set_path_id setting id when state is in memory modified
47
def load_tests(basic_tests, module, loader):
48
suite = loader.suiteClass()
49
dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
50
basic_tests, tests.condition_isinstance(TestCaseWithDirState))
51
tests.multiply_tests(dir_reader_tests,
52
test_osutils.dir_reader_scenarios(), suite)
53
suite.addTest(remaining_tests)
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
61
_dir_reader_class = None
62
_native_to_unicode = None # Not used yet
65
tests.TestCaseWithTransport.setUp(self)
67
self.overrideAttr(osutils,
68
'_selected_dir_reader', self._dir_reader_class())
70
def create_empty_dirstate(self):
71
"""Return a locked but empty dirstate"""
72
state = dirstate.DirState.initialize('dirstate')
75
def create_dirstate_with_root(self):
76
"""Return a write-locked state with a single root entry."""
77
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
78
root_entry_direntry = ('', '', 'a-root-value'), [
79
('d', '', 0, False, packed_stat),
82
dirblocks.append(('', [root_entry_direntry]))
83
dirblocks.append(('', []))
84
state = self.create_empty_dirstate()
86
state._set_data([], dirblocks)
93
def create_dirstate_with_root_and_subdir(self):
94
"""Return a locked DirState with a root and a subdir"""
95
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
96
subdir_entry = ('', 'subdir', 'subdir-id'), [
97
('d', '', 0, False, packed_stat),
99
state = self.create_dirstate_with_root()
101
dirblocks = list(state._dirblocks)
102
dirblocks[1][1].append(subdir_entry)
103
state._set_data([], dirblocks)
109
def create_complex_dirstate(self):
110
"""This dirstate contains multiple files and directories.
120
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
122
Notice that a/e is an empty directory.
124
:return: The dirstate, still write-locked.
126
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
127
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
128
root_entry = ('', '', 'a-root-value'), [
129
('d', '', 0, False, packed_stat),
131
a_entry = ('', 'a', 'a-dir'), [
132
('d', '', 0, False, packed_stat),
134
b_entry = ('', 'b', 'b-dir'), [
135
('d', '', 0, False, packed_stat),
137
c_entry = ('', 'c', 'c-file'), [
138
('f', null_sha, 10, False, packed_stat),
140
d_entry = ('', 'd', 'd-file'), [
141
('f', null_sha, 20, False, packed_stat),
143
e_entry = ('a', 'e', 'e-dir'), [
144
('d', '', 0, False, packed_stat),
146
f_entry = ('a', 'f', 'f-file'), [
147
('f', null_sha, 30, False, packed_stat),
149
g_entry = ('b', 'g', 'g-file'), [
150
('f', null_sha, 30, False, packed_stat),
152
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
153
('f', null_sha, 40, False, packed_stat),
156
dirblocks.append(('', [root_entry]))
157
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
158
dirblocks.append(('a', [e_entry, f_entry]))
159
dirblocks.append(('b', [g_entry, h_entry]))
160
state = dirstate.DirState.initialize('dirstate')
163
state._set_data([], dirblocks)
169
def check_state_with_reopen(self, expected_result, state):
170
"""Check that state has current state expected_result.
172
This will check the current state, open the file anew and check it
174
This function expects the current state to be locked for writing, and
175
will unlock it before re-opening.
176
This is required because we can't open a lock_read() while something
177
else has a lock_write().
178
write => mutually exclusive lock
181
# The state should already be write locked, since we just had to do
182
# some operation to get here.
183
self.assertTrue(state._lock_token is not None)
185
self.assertEqual(expected_result[0], state.get_parent_ids())
186
# there should be no ghosts in this tree.
187
self.assertEqual([], state.get_ghosts())
188
# there should be one fileid in this tree - the root of the tree.
189
self.assertEqual(expected_result[1], list(state._iter_entries()))
194
state = dirstate.DirState.on_file('dirstate')
197
self.assertEqual(expected_result[1], list(state._iter_entries()))
201
def create_basic_dirstate(self):
202
"""Create a dirstate with a few files and directories.
212
tree = self.make_branch_and_tree('tree')
213
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
214
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
215
self.build_tree(['tree/' + p for p in paths])
216
tree.set_root_id('TREE_ROOT')
217
tree.add([p.rstrip('/') for p in paths], file_ids)
218
tree.commit('initial', rev_id='rev-1')
219
revision_id = 'rev-1'
220
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
221
t = self.get_transport('tree')
222
a_text = t.get_bytes('a')
223
a_sha = osutils.sha_string(a_text)
225
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
226
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
227
c_text = t.get_bytes('b/c')
228
c_sha = osutils.sha_string(c_text)
230
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
231
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
232
e_text = t.get_bytes('b/d/e')
233
e_sha = osutils.sha_string(e_text)
235
b_c_text = t.get_bytes('b-c')
236
b_c_sha = osutils.sha_string(b_c_text)
237
b_c_len = len(b_c_text)
238
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
239
f_text = t.get_bytes('f')
240
f_sha = osutils.sha_string(f_text)
242
null_stat = dirstate.DirState.NULLSTAT
244
'':(('', '', 'TREE_ROOT'), [
245
('d', '', 0, False, null_stat),
246
('d', '', 0, False, revision_id),
248
'a':(('', 'a', 'a-id'), [
249
('f', '', 0, False, null_stat),
250
('f', a_sha, a_len, False, revision_id),
252
'b':(('', 'b', 'b-id'), [
253
('d', '', 0, False, null_stat),
254
('d', '', 0, False, revision_id),
256
'b/c':(('b', 'c', 'c-id'), [
257
('f', '', 0, False, null_stat),
258
('f', c_sha, c_len, False, revision_id),
260
'b/d':(('b', 'd', 'd-id'), [
261
('d', '', 0, False, null_stat),
262
('d', '', 0, False, revision_id),
264
'b/d/e':(('b/d', 'e', 'e-id'), [
265
('f', '', 0, False, null_stat),
266
('f', e_sha, e_len, False, revision_id),
268
'b-c':(('', 'b-c', 'b-c-id'), [
269
('f', '', 0, False, null_stat),
270
('f', b_c_sha, b_c_len, False, revision_id),
272
'f':(('', 'f', 'f-id'), [
273
('f', '', 0, False, null_stat),
274
('f', f_sha, f_len, False, revision_id),
277
state = dirstate.DirState.from_tree(tree, 'dirstate')
282
# Use a different object, to make sure nothing is pre-cached in memory.
283
state = dirstate.DirState.on_file('dirstate')
285
self.addCleanup(state.unlock)
286
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
287
state._dirblock_state)
288
# This is code is only really tested if we actually have to make more
289
# than one read, so set the page size to something smaller.
290
# We want it to contain about 2.2 records, so that we have a couple
291
# records that we can read per attempt
292
state._bisect_page_size = 200
293
return tree, state, expected
295
def create_duplicated_dirstate(self):
296
"""Create a dirstate with a deleted and added entries.
298
This grabs a basic_dirstate, and then removes and re adds every entry
301
tree, state, expected = self.create_basic_dirstate()
302
# Now we will just remove and add every file so we get an extra entry
303
# per entry. Unversion in reverse order so we handle subdirs
304
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
305
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
306
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
308
# Update the expected dictionary.
309
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
310
orig = expected[path]
312
# This record was deleted in the current tree
313
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
315
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
316
# And didn't exist in the basis tree
317
expected[path2] = (new_key, [orig[1][0],
318
dirstate.DirState.NULL_PARENT_DETAILS])
320
# We will replace the 'dirstate' file underneath 'state', but that is
321
# okay as lock as we unlock 'state' first.
324
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
330
# But we need to leave state in a read-lock because we already have
331
# a cleanup scheduled
333
return tree, state, expected
335
def create_renamed_dirstate(self):
336
"""Create a dirstate with a few internal renames.
338
This takes the basic dirstate, and moves the paths around.
340
tree, state, expected = self.create_basic_dirstate()
342
tree.rename_one('a', 'b/g')
344
tree.rename_one('b/d', 'h')
346
old_a = expected['a']
347
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
348
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
349
('r', 'a', 0, False, '')])
350
old_d = expected['b/d']
351
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
352
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
353
('r', 'b/d', 0, False, '')])
355
old_e = expected['b/d/e']
356
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
358
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
359
('r', 'b/d/e', 0, False, '')])
363
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
370
return tree, state, expected
373
class TestTreeToDirState(TestCaseWithDirState):
375
def test_empty_to_dirstate(self):
376
"""We should be able to create a dirstate for an empty tree."""
377
# There are no files on disk and no parents
378
tree = self.make_branch_and_tree('tree')
379
expected_result = ([], [
380
(('', '', tree.get_root_id()), # common details
381
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
383
state = dirstate.DirState.from_tree(tree, 'dirstate')
385
self.check_state_with_reopen(expected_result, state)
387
def test_1_parents_empty_to_dirstate(self):
388
# create a parent by doing a commit
389
tree = self.make_branch_and_tree('tree')
390
rev_id = tree.commit('first post').encode('utf8')
391
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
392
expected_result = ([rev_id], [
393
(('', '', tree.get_root_id()), # common details
394
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
395
('d', '', 0, False, rev_id), # first parent details
397
state = dirstate.DirState.from_tree(tree, 'dirstate')
398
self.check_state_with_reopen(expected_result, state)
405
def test_2_parents_empty_to_dirstate(self):
406
# create a parent by doing a commit
407
tree = self.make_branch_and_tree('tree')
408
rev_id = tree.commit('first post')
409
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
410
rev_id2 = tree2.commit('second post', allow_pointless=True)
411
tree.merge_from_branch(tree2.branch)
412
expected_result = ([rev_id, rev_id2], [
413
(('', '', tree.get_root_id()), # common details
414
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
415
('d', '', 0, False, rev_id), # first parent details
416
('d', '', 0, False, rev_id), # second parent details
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
426
def test_empty_unknowns_are_ignored_to_dirstate(self):
427
"""We should be able to create a dirstate for an empty tree."""
428
# There are no files on disk and no parents
429
tree = self.make_branch_and_tree('tree')
430
self.build_tree(['tree/unknown'])
431
expected_result = ([], [
432
(('', '', tree.get_root_id()), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
state = dirstate.DirState.from_tree(tree, 'dirstate')
436
self.check_state_with_reopen(expected_result, state)
438
def get_tree_with_a_file(self):
439
tree = self.make_branch_and_tree('tree')
440
self.build_tree(['tree/a file'])
441
tree.add('a file', 'a-file-id')
444
def test_non_empty_no_parents_to_dirstate(self):
445
"""We should be able to create a dirstate for an empty tree."""
446
# There are files on disk and no parents
447
tree = self.get_tree_with_a_file()
448
expected_result = ([], [
449
(('', '', tree.get_root_id()), # common details
450
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
(('', 'a file', 'a-file-id'), # common
453
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
state = dirstate.DirState.from_tree(tree, 'dirstate')
457
self.check_state_with_reopen(expected_result, state)
459
def test_1_parents_not_empty_to_dirstate(self):
460
# create a parent by doing a commit
461
tree = self.get_tree_with_a_file()
462
rev_id = tree.commit('first post').encode('utf8')
463
# change the current content to be different this will alter stat, sha
465
self.build_tree_contents([('tree/a file', 'new content\n')])
466
expected_result = ([rev_id], [
467
(('', '', tree.get_root_id()), # common details
468
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
469
('d', '', 0, False, rev_id), # first parent details
471
(('', 'a file', 'a-file-id'), # common
472
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
473
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
474
rev_id), # first parent
477
state = dirstate.DirState.from_tree(tree, 'dirstate')
478
self.check_state_with_reopen(expected_result, state)
480
def test_2_parents_not_empty_to_dirstate(self):
481
# create a parent by doing a commit
482
tree = self.get_tree_with_a_file()
483
rev_id = tree.commit('first post').encode('utf8')
484
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
485
# change the current content to be different this will alter stat, sha
487
self.build_tree_contents([('tree2/a file', 'merge content\n')])
488
rev_id2 = tree2.commit('second post').encode('utf8')
489
tree.merge_from_branch(tree2.branch)
490
# change the current content to be different this will alter stat, sha
491
# and length again, giving us three distinct values:
492
self.build_tree_contents([('tree/a file', 'new content\n')])
493
expected_result = ([rev_id, rev_id2], [
494
(('', '', tree.get_root_id()), # common details
495
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
496
('d', '', 0, False, rev_id), # first parent details
497
('d', '', 0, False, rev_id), # second parent details
499
(('', 'a file', 'a-file-id'), # common
500
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
501
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
502
rev_id), # first parent
503
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
504
rev_id2), # second parent
507
state = dirstate.DirState.from_tree(tree, 'dirstate')
508
self.check_state_with_reopen(expected_result, state)
510
def test_colliding_fileids(self):
511
# test insertion of parents creating several entries at the same path.
512
# we used to have a bug where they could cause the dirstate to break
513
# its ordering invariants.
514
# create some trees to test from
517
tree = self.make_branch_and_tree('tree%d' % i)
518
self.build_tree(['tree%d/name' % i,])
519
tree.add(['name'], ['file-id%d' % i])
520
revision_id = 'revid-%d' % i
521
tree.commit('message', rev_id=revision_id)
522
parents.append((revision_id,
523
tree.branch.repository.revision_tree(revision_id)))
524
# now fold these trees into a dirstate
525
state = dirstate.DirState.initialize('dirstate')
527
state.set_parent_trees(parents, [])
533
class TestDirStateOnFile(TestCaseWithDirState):
535
def test_construct_with_path(self):
536
tree = self.make_branch_and_tree('tree')
537
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
538
# we want to be able to get the lines of the dirstate that we will
540
lines = state.get_lines()
542
self.build_tree_contents([('dirstate', ''.join(lines))])
544
# no parents, default tree content
545
expected_result = ([], [
546
(('', '', tree.get_root_id()), # common details
547
# current tree details, but new from_tree skips statting, it
548
# uses set_state_from_inventory, and thus depends on the
550
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
553
state = dirstate.DirState.on_file('dirstate')
554
state.lock_write() # check_state_with_reopen will save() and unlock it
555
self.check_state_with_reopen(expected_result, state)
557
def test_can_save_clean_on_file(self):
558
tree = self.make_branch_and_tree('tree')
559
state = dirstate.DirState.from_tree(tree, 'dirstate')
561
# doing a save should work here as there have been no changes.
563
# TODO: stat it and check it hasn't changed; may require waiting
564
# for the state accuracy window.
568
def test_can_save_in_read_lock(self):
569
self.build_tree(['a-file'])
570
state = dirstate.DirState.initialize('dirstate')
572
# No stat and no sha1 sum.
573
state.add('a-file', 'a-file-id', 'file', None, '')
578
# Now open in readonly mode
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
# The current size should be 0 (default)
584
self.assertEqual(0, entry[1][0][2])
585
# We should have a real entry.
586
self.assertNotEqual((None, None), entry)
587
# Make sure everything is old enough
588
state._sha_cutoff_time()
589
state._cutoff_time += 10
590
# Change the file length
591
self.build_tree_contents([('a-file', 'shorter')])
592
sha1sum = dirstate.update_entry(state, entry, 'a-file',
594
# new file, no cached sha:
595
self.assertEqual(None, sha1sum)
597
# The dirblock has been updated
598
self.assertEqual(7, entry[1][0][2])
599
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
600
state._dirblock_state)
603
# Now, since we are the only one holding a lock, we should be able
604
# to save and have it written to disk
609
# Re-open the file, and ensure that the state has been updated.
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
self.assertEqual(7, entry[1][0][2])
618
def test_save_fails_quietly_if_locked(self):
619
"""If dirstate is locked, save will fail without complaining."""
620
self.build_tree(['a-file'])
621
state = dirstate.DirState.initialize('dirstate')
623
# No stat and no sha1 sum.
624
state.add('a-file', 'a-file-id', 'file', None, '')
629
state = dirstate.DirState.on_file('dirstate')
632
entry = state._get_entry(0, path_utf8='a-file')
633
sha1sum = dirstate.update_entry(state, entry, 'a-file',
636
self.assertEqual(None, sha1sum)
637
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
638
state._dirblock_state)
640
# Now, before we try to save, grab another dirstate, and take out a
642
# TODO: jam 20070315 Ideally this would be locked by another
643
# process. To make sure the file is really OS locked.
644
state2 = dirstate.DirState.on_file('dirstate')
647
# This won't actually write anything, because it couldn't grab
648
# a write lock. But it shouldn't raise an error, either.
649
# TODO: jam 20070315 We should probably distinguish between
650
# being dirty because of 'update_entry'. And dirty
651
# because of real modification. So that save() *does*
652
# raise a real error if it fails when we have real
660
# The file on disk should not be modified.
661
state = dirstate.DirState.on_file('dirstate')
664
entry = state._get_entry(0, path_utf8='a-file')
665
self.assertEqual('', entry[1][0][1])
669
def test_save_refuses_if_changes_aborted(self):
670
self.build_tree(['a-file', 'a-dir/'])
671
state = dirstate.DirState.initialize('dirstate')
673
# No stat and no sha1 sum.
674
state.add('a-file', 'a-file-id', 'file', None, '')
679
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
681
('', [(('', '', 'TREE_ROOT'),
682
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
683
('', [(('', 'a-file', 'a-file-id'),
684
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
687
state = dirstate.DirState.on_file('dirstate')
690
state._read_dirblocks_if_needed()
691
self.assertEqual(expected_blocks, state._dirblocks)
693
# Now modify the state, but mark it as inconsistent
694
state.add('a-dir', 'a-dir-id', 'directory', None, '')
695
state._changes_aborted = True
700
state = dirstate.DirState.on_file('dirstate')
703
state._read_dirblocks_if_needed()
704
self.assertEqual(expected_blocks, state._dirblocks)
709
class TestDirStateInitialize(TestCaseWithDirState):
711
def test_initialize(self):
712
expected_result = ([], [
713
(('', '', 'TREE_ROOT'), # common details
714
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
717
state = dirstate.DirState.initialize('dirstate')
719
self.assertIsInstance(state, dirstate.DirState)
720
lines = state.get_lines()
723
# On win32 you can't read from a locked file, even within the same
724
# process. So we have to unlock and release before we check the file
726
self.assertFileEqual(''.join(lines), 'dirstate')
727
state.lock_read() # check_state_with_reopen will unlock
728
self.check_state_with_reopen(expected_result, state)
731
class TestDirStateManipulations(TestCaseWithDirState):
733
def test_set_state_from_inventory_no_content_no_parents(self):
734
# setting the current inventory is a slow but important api to support.
735
tree1 = self.make_branch_and_memory_tree('tree1')
739
revid1 = tree1.commit('foo').encode('utf8')
740
root_id = tree1.get_root_id()
741
inv = tree1.inventory
744
expected_result = [], [
745
(('', '', root_id), [
746
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
747
state = dirstate.DirState.initialize('dirstate')
749
state.set_state_from_inventory(inv)
750
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
752
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
753
state._dirblock_state)
758
# This will unlock it
759
self.check_state_with_reopen(expected_result, state)
761
def test_set_state_from_inventory_preserves_hashcache(self):
762
# https://bugs.launchpad.net/bzr/+bug/146176
763
# set_state_from_inventory should preserve the stat and hash value for
764
# workingtree files that are not changed by the inventory.
766
tree = self.make_branch_and_tree('.')
767
# depends on the default format using dirstate...
770
# make a dirstate with some valid hashcache data
771
# file on disk, but that's not needed for this test
772
foo_contents = 'contents of foo'
773
self.build_tree_contents([('foo', foo_contents)])
774
tree.add('foo', 'foo-id')
776
foo_stat = os.stat('foo')
777
foo_packed = dirstate.pack_stat(foo_stat)
778
foo_sha = osutils.sha_string(foo_contents)
779
foo_size = len(foo_contents)
781
# should not be cached yet, because the file's too fresh
783
(('', 'foo', 'foo-id',),
784
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
785
tree._dirstate._get_entry(0, 'foo-id'))
786
# poke in some hashcache information - it wouldn't normally be
787
# stored because it's too fresh
788
tree._dirstate.update_minimal(
789
('', 'foo', 'foo-id'),
790
'f', False, foo_sha, foo_packed, foo_size, 'foo')
791
# now should be cached
793
(('', 'foo', 'foo-id',),
794
[('f', foo_sha, foo_size, False, foo_packed)]),
795
tree._dirstate._get_entry(0, 'foo-id'))
797
# extract the inventory, and add something to it
798
inv = tree._get_inventory()
799
# should see the file we poked in...
800
self.assertTrue(inv.has_id('foo-id'))
801
self.assertTrue(inv.has_filename('foo'))
802
inv.add_path('bar', 'file', 'bar-id')
803
tree._dirstate._validate()
804
# this used to cause it to lose its hashcache
805
tree._dirstate.set_state_from_inventory(inv)
806
tree._dirstate._validate()
812
# now check that the state still has the original hashcache value
813
state = tree._dirstate
815
foo_tuple = state._get_entry(0, path_utf8='foo')
817
(('', 'foo', 'foo-id',),
818
[('f', foo_sha, len(foo_contents), False,
819
dirstate.pack_stat(foo_stat))]),
824
def test_set_state_from_inventory_mixed_paths(self):
825
tree1 = self.make_branch_and_tree('tree1')
826
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
827
'tree1/a/b/foo', 'tree1/a-b/bar'])
830
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
831
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
832
tree1.commit('rev1', rev_id='rev1')
833
root_id = tree1.get_root_id()
834
inv = tree1.inventory
837
expected_result1 = [('', '', root_id, 'd'),
838
('', 'a', 'a-id', 'd'),
839
('', 'a-b', 'a-b-id', 'd'),
840
('a', 'b', 'b-id', 'd'),
841
('a/b', 'foo', 'foo-id', 'f'),
842
('a-b', 'bar', 'bar-id', 'f'),
844
expected_result2 = [('', '', root_id, 'd'),
845
('', 'a', 'a-id', 'd'),
846
('', 'a-b', 'a-b-id', 'd'),
847
('a-b', 'bar', 'bar-id', 'f'),
849
state = dirstate.DirState.initialize('dirstate')
851
state.set_state_from_inventory(inv)
853
for entry in state._iter_entries():
854
values.append(entry[0] + entry[1][0][:1])
855
self.assertEqual(expected_result1, values)
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_result2, values)
865
def test_set_path_id_no_parents(self):
866
"""The id of a path can be changed trivally with no parents."""
867
state = dirstate.DirState.initialize('dirstate')
869
# check precondition to be sure the state does change appropriately.
870
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
871
self.assertEqual([root_entry], list(state._iter_entries()))
872
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
873
self.assertEqual(root_entry,
874
state._get_entry(0, fileid_utf8='TREE_ROOT'))
875
self.assertEqual((None, None),
876
state._get_entry(0, fileid_utf8='second-root-id'))
877
state.set_path_id('', 'second-root-id')
878
new_root_entry = (('', '', 'second-root-id'),
879
[('d', '', 0, False, 'x'*32)])
880
expected_rows = [new_root_entry]
881
self.assertEqual(expected_rows, list(state._iter_entries()))
882
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
883
self.assertEqual(new_root_entry,
884
state._get_entry(0, fileid_utf8='second-root-id'))
885
self.assertEqual((None, None),
886
state._get_entry(0, fileid_utf8='TREE_ROOT'))
887
# should work across save too
891
state = dirstate.DirState.on_file('dirstate')
895
self.assertEqual(expected_rows, list(state._iter_entries()))
899
def test_set_path_id_with_parents(self):
900
"""Set the root file id in a dirstate with parents"""
901
mt = self.make_branch_and_tree('mt')
902
# in case the default tree format uses a different root id
903
mt.set_root_id('TREE_ROOT')
904
mt.commit('foo', rev_id='parent-revid')
905
rt = mt.branch.repository.revision_tree('parent-revid')
906
state = dirstate.DirState.initialize('dirstate')
909
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
910
root_entry = (('', '', 'TREE_ROOT'),
911
[('d', '', 0, False, 'x'*32),
912
('d', '', 0, False, 'parent-revid')])
913
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
914
self.assertEqual(root_entry,
915
state._get_entry(0, fileid_utf8='TREE_ROOT'))
916
self.assertEqual((None, None),
917
state._get_entry(0, fileid_utf8='Asecond-root-id'))
918
state.set_path_id('', 'Asecond-root-id')
920
# now see that it is what we expected
921
old_root_entry = (('', '', 'TREE_ROOT'),
922
[('a', '', 0, False, ''),
923
('d', '', 0, False, 'parent-revid')])
924
new_root_entry = (('', '', 'Asecond-root-id'),
925
[('d', '', 0, False, ''),
926
('a', '', 0, False, '')])
927
expected_rows = [new_root_entry, old_root_entry]
929
self.assertEqual(expected_rows, list(state._iter_entries()))
930
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
931
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual(old_root_entry,
935
state._get_entry(1, fileid_utf8='TREE_ROOT'))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='Asecond-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(1, fileid_utf8='Asecond-root-id'))
940
# should work across save too
944
# now flush & check we get the same
945
state = dirstate.DirState.on_file('dirstate')
949
self.assertEqual(expected_rows, list(state._iter_entries()))
952
# now change within an existing file-backed state
956
state.set_path_id('', 'tree-root-2')
961
def test_set_parent_trees_no_content(self):
962
# set_parent_trees is a slow but important api to support.
963
tree1 = self.make_branch_and_memory_tree('tree1')
967
revid1 = tree1.commit('foo')
970
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
971
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
974
revid2 = tree2.commit('foo')
975
root_id = tree2.get_root_id()
978
state = dirstate.DirState.initialize('dirstate')
980
state.set_path_id('', root_id)
981
state.set_parent_trees(
982
((revid1, tree1.branch.repository.revision_tree(revid1)),
983
(revid2, tree2.branch.repository.revision_tree(revid2)),
984
('ghost-rev', None)),
986
# check we can reopen and use the dirstate after setting parent
993
state = dirstate.DirState.on_file('dirstate')
996
self.assertEqual([revid1, revid2, 'ghost-rev'],
997
state.get_parent_ids())
998
# iterating the entire state ensures that the state is parsable.
999
list(state._iter_entries())
1000
# be sure that it sets not appends - change it
1001
state.set_parent_trees(
1002
((revid1, tree1.branch.repository.revision_tree(revid1)),
1003
('ghost-rev', None)),
1005
# and now put it back.
1006
state.set_parent_trees(
1007
((revid1, tree1.branch.repository.revision_tree(revid1)),
1008
(revid2, tree2.branch.repository.revision_tree(revid2)),
1009
('ghost-rev', tree2.branch.repository.revision_tree(
1010
_mod_revision.NULL_REVISION))),
1012
self.assertEqual([revid1, revid2, 'ghost-rev'],
1013
state.get_parent_ids())
1014
# the ghost should be recorded as such by set_parent_trees.
1015
self.assertEqual(['ghost-rev'], state.get_ghosts())
1017
[(('', '', root_id), [
1018
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1019
('d', '', 0, False, revid1),
1020
('d', '', 0, False, revid1)
1022
list(state._iter_entries()))
1026
def test_set_parent_trees_file_missing_from_tree(self):
1027
# Adding a parent tree may reference files not in the current state.
1028
# they should get listed just once by id, even if they are in two
1030
# set_parent_trees is a slow but important api to support.
1031
tree1 = self.make_branch_and_memory_tree('tree1')
1035
tree1.add(['a file'], ['file-id'], ['file'])
1036
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1037
revid1 = tree1.commit('foo')
1040
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1041
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1044
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1045
revid2 = tree2.commit('foo')
1046
root_id = tree2.get_root_id()
1049
# check the layout in memory
1050
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1051
(('', '', root_id), [
1052
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1053
('d', '', 0, False, revid1.encode('utf8')),
1054
('d', '', 0, False, revid1.encode('utf8'))
1056
(('', 'a file', 'file-id'), [
1057
('a', '', 0, False, ''),
1058
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1059
revid1.encode('utf8')),
1060
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1061
revid2.encode('utf8'))
1064
state = dirstate.DirState.initialize('dirstate')
1066
state.set_path_id('', root_id)
1067
state.set_parent_trees(
1068
((revid1, tree1.branch.repository.revision_tree(revid1)),
1069
(revid2, tree2.branch.repository.revision_tree(revid2)),
1075
# check_state_with_reopen will unlock
1076
self.check_state_with_reopen(expected_result, state)
1078
### add a path via _set_data - so we dont need delta work, just
1079
# raw data in, and ensure that it comes out via get_lines happily.
1081
def test_add_path_to_root_no_parents_all_data(self):
1082
# The most trivial addition of a path is when there are no parents and
1083
# its in the root and all data about the file is supplied
1084
self.build_tree(['a file'])
1085
stat = os.lstat('a file')
1086
# the 1*20 is the sha1 pretend value.
1087
state = dirstate.DirState.initialize('dirstate')
1088
expected_entries = [
1089
(('', '', 'TREE_ROOT'), [
1090
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1092
(('', 'a file', 'a-file-id'), [
1093
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1097
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1098
# having added it, it should be in the output of iter_entries.
1099
self.assertEqual(expected_entries, list(state._iter_entries()))
1100
# saving and reloading should not affect this.
1104
state = dirstate.DirState.on_file('dirstate')
1106
self.addCleanup(state.unlock)
1107
self.assertEqual(expected_entries, list(state._iter_entries()))
1109
def test_add_path_to_unversioned_directory(self):
1110
"""Adding a path to an unversioned directory should error.
1112
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1113
once dirstate is stable and if it is merged with WorkingTree3, consider
1114
removing this copy of the test.
1116
self.build_tree(['unversioned/', 'unversioned/a file'])
1117
state = dirstate.DirState.initialize('dirstate')
1118
self.addCleanup(state.unlock)
1119
self.assertRaises(errors.NotVersionedError, state.add,
1120
'unversioned/a file', 'a-file-id', 'file', None, None)
1122
def test_add_directory_to_root_no_parents_all_data(self):
1123
# The most trivial addition of a dir is when there are no parents and
1124
# its in the root and all data about the file is supplied
1125
self.build_tree(['a dir/'])
1126
stat = os.lstat('a dir')
1127
expected_entries = [
1128
(('', '', 'TREE_ROOT'), [
1129
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1131
(('', 'a dir', 'a dir id'), [
1132
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1135
state = dirstate.DirState.initialize('dirstate')
1137
state.add('a dir', 'a dir id', 'directory', stat, None)
1138
# having added it, it should be in the output of iter_entries.
1139
self.assertEqual(expected_entries, list(state._iter_entries()))
1140
# saving and reloading should not affect this.
1144
state = dirstate.DirState.on_file('dirstate')
1146
self.addCleanup(state.unlock)
1148
self.assertEqual(expected_entries, list(state._iter_entries()))
1150
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1151
# The most trivial addition of a symlink when there are no parents and
1152
# its in the root and all data about the file is supplied
1153
# bzr doesn't support fake symlinks on windows, yet.
1154
self.requireFeature(tests.SymlinkFeature)
1155
os.symlink(target, link_name)
1156
stat = os.lstat(link_name)
1157
expected_entries = [
1158
(('', '', 'TREE_ROOT'), [
1159
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1161
(('', link_name.encode('UTF-8'), 'a link id'), [
1162
('l', target.encode('UTF-8'), stat[6],
1163
False, dirstate.pack_stat(stat)), # current tree
1166
state = dirstate.DirState.initialize('dirstate')
1168
state.add(link_name, 'a link id', 'symlink', stat,
1169
target.encode('UTF-8'))
1170
# having added it, it should be in the output of iter_entries.
1171
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
# saving and reloading should not affect this.
1176
state = dirstate.DirState.on_file('dirstate')
1178
self.addCleanup(state.unlock)
1179
self.assertEqual(expected_entries, list(state._iter_entries()))
1181
def test_add_symlink_to_root_no_parents_all_data(self):
1182
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1184
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1185
self.requireFeature(tests.UnicodeFilenameFeature)
1186
self._test_add_symlink_to_root_no_parents_all_data(
1187
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1189
def test_add_directory_and_child_no_parents_all_data(self):
1190
# after adding a directory, we should be able to add children to it.
1191
self.build_tree(['a dir/', 'a dir/a file'])
1192
dirstat = os.lstat('a dir')
1193
filestat = os.lstat('a dir/a file')
1194
expected_entries = [
1195
(('', '', 'TREE_ROOT'), [
1196
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1198
(('', 'a dir', 'a dir id'), [
1199
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1201
(('a dir', 'a file', 'a-file-id'), [
1202
('f', '1'*20, 25, False,
1203
dirstate.pack_stat(filestat)), # current tree details
1206
state = dirstate.DirState.initialize('dirstate')
1208
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1209
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1210
# added it, it should be in the output of iter_entries.
1211
self.assertEqual(expected_entries, list(state._iter_entries()))
1212
# saving and reloading should not affect this.
1216
state = dirstate.DirState.on_file('dirstate')
1218
self.addCleanup(state.unlock)
1219
self.assertEqual(expected_entries, list(state._iter_entries()))
1221
def test_add_tree_reference(self):
1222
# make a dirstate and add a tree reference
1223
state = dirstate.DirState.initialize('dirstate')
1225
('', 'subdir', 'subdir-id'),
1226
[('t', 'subtree-123123', 0, False,
1227
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1230
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1231
entry = state._get_entry(0, 'subdir-id', 'subdir')
1232
self.assertEqual(entry, expected_entry)
1237
# now check we can read it back
1239
self.addCleanup(state.unlock)
1241
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1242
self.assertEqual(entry, entry2)
1243
self.assertEqual(entry, expected_entry)
1244
# and lookup by id should work too
1245
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1246
self.assertEqual(entry, expected_entry)
1248
def test_add_forbidden_names(self):
1249
state = dirstate.DirState.initialize('dirstate')
1250
self.addCleanup(state.unlock)
1251
self.assertRaises(errors.BzrError,
1252
state.add, '.', 'ass-id', 'directory', None, None)
1253
self.assertRaises(errors.BzrError,
1254
state.add, '..', 'ass-id', 'directory', None, None)
1256
def test_set_state_with_rename_b_a_bug_395556(self):
1257
# bug 395556 uncovered a bug where the dirstate ends up with a false
1258
# relocation record - in a tree with no parents there should be no
1259
# absent or relocated records. This then leads to further corruption
1260
# when a commit occurs, as the incorrect relocation gathers an
1261
# incorrect absent in tree 1, and future changes go to pot.
1262
tree1 = self.make_branch_and_tree('tree1')
1263
self.build_tree(['tree1/b'])
1266
tree1.add(['b'], ['b-id'])
1267
root_id = tree1.get_root_id()
1268
inv = tree1.inventory
1269
state = dirstate.DirState.initialize('dirstate')
1271
# Set the initial state with 'b'
1272
state.set_state_from_inventory(inv)
1273
inv.rename('b-id', root_id, 'a')
1274
# Set the new state with 'a', which currently corrupts.
1275
state.set_state_from_inventory(inv)
1276
expected_result1 = [('', '', root_id, 'd'),
1277
('', 'a', 'b-id', 'f'),
1280
for entry in state._iter_entries():
1281
values.append(entry[0] + entry[1][0][:1])
1282
self.assertEqual(expected_result1, values)
1289
class TestGetLines(TestCaseWithDirState):
1291
def test_get_line_with_2_rows(self):
1292
state = self.create_dirstate_with_root_and_subdir()
1294
self.assertEqual(['#bazaar dirstate flat format 3\n',
1295
'crc32: 41262208\n',
1299
'\x00\x00a-root-value\x00'
1300
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1301
'\x00subdir\x00subdir-id\x00'
1302
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1303
], state.get_lines())
1307
def test_entry_to_line(self):
1308
state = self.create_dirstate_with_root()
1311
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1312
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1313
state._entry_to_line(state._dirblocks[0][1][0]))
1317
def test_entry_to_line_with_parent(self):
1318
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1319
root_entry = ('', '', 'a-root-value'), [
1320
('d', '', 0, False, packed_stat), # current tree details
1321
# first: a pointer to the current location
1322
('a', 'dirname/basename', 0, False, ''),
1324
state = dirstate.DirState.initialize('dirstate')
1327
'\x00\x00a-root-value\x00'
1328
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1329
'a\x00dirname/basename\x000\x00n\x00',
1330
state._entry_to_line(root_entry))
1334
def test_entry_to_line_with_two_parents_at_different_paths(self):
1335
# / in the tree, at / in one parent and /dirname/basename in the other.
1336
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1337
root_entry = ('', '', 'a-root-value'), [
1338
('d', '', 0, False, packed_stat), # current tree details
1339
('d', '', 0, False, 'rev_id'), # first parent details
1340
# second: a pointer to the current location
1341
('a', 'dirname/basename', 0, False, ''),
1343
state = dirstate.DirState.initialize('dirstate')
1346
'\x00\x00a-root-value\x00'
1347
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1348
'd\x00\x000\x00n\x00rev_id\x00'
1349
'a\x00dirname/basename\x000\x00n\x00',
1350
state._entry_to_line(root_entry))
1354
def test_iter_entries(self):
1355
# we should be able to iterate the dirstate entries from end to end
1356
# this is for get_lines to be easy to read.
1357
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1359
root_entries = [(('', '', 'a-root-value'), [
1360
('d', '', 0, False, packed_stat), # current tree details
1362
dirblocks.append(('', root_entries))
1363
# add two files in the root
1364
subdir_entry = ('', 'subdir', 'subdir-id'), [
1365
('d', '', 0, False, packed_stat), # current tree details
1367
afile_entry = ('', 'afile', 'afile-id'), [
1368
('f', 'sha1value', 34, False, packed_stat), # current tree details
1370
dirblocks.append(('', [subdir_entry, afile_entry]))
1372
file_entry2 = ('subdir', '2file', '2file-id'), [
1373
('f', 'sha1value', 23, False, packed_stat), # current tree details
1375
dirblocks.append(('subdir', [file_entry2]))
1376
state = dirstate.DirState.initialize('dirstate')
1378
state._set_data([], dirblocks)
1379
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1381
self.assertEqual(expected_entries, list(state._iter_entries()))
1386
class TestGetBlockRowIndex(TestCaseWithDirState):
1388
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1389
file_present, state, dirname, basename, tree_index):
1390
self.assertEqual((block_index, row_index, dir_present, file_present),
1391
state._get_block_entry_index(dirname, basename, tree_index))
1393
block = state._dirblocks[block_index]
1394
self.assertEqual(dirname, block[0])
1395
if dir_present and file_present:
1396
row = state._dirblocks[block_index][1][row_index]
1397
self.assertEqual(dirname, row[0][0])
1398
self.assertEqual(basename, row[0][1])
1400
def test_simple_structure(self):
1401
state = self.create_dirstate_with_root_and_subdir()
1402
self.addCleanup(state.unlock)
1403
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1404
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1405
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1406
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1407
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1410
def test_complex_structure_exists(self):
1411
state = self.create_complex_dirstate()
1412
self.addCleanup(state.unlock)
1413
# Make sure we can find everything that exists
1414
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1415
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1416
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1417
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1418
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1419
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1420
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1421
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1422
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1423
'b', 'h\xc3\xa5', 0)
1425
def test_complex_structure_missing(self):
1426
state = self.create_complex_dirstate()
1427
self.addCleanup(state.unlock)
1428
# Make sure things would be inserted in the right locations
1429
# '_' comes before 'a'
1430
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1431
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1432
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1433
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1435
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1436
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1437
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1438
# This would be inserted between a/ and b/
1439
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1441
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1444
class TestGetEntry(TestCaseWithDirState):
1446
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1447
"""Check that the right entry is returned for a request to getEntry."""
1448
entry = state._get_entry(index, path_utf8=path)
1450
self.assertEqual((None, None), entry)
1453
self.assertEqual((dirname, basename, file_id), cur[:3])
1455
def test_simple_structure(self):
1456
state = self.create_dirstate_with_root_and_subdir()
1457
self.addCleanup(state.unlock)
1458
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1459
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1460
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1461
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1462
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1464
def test_complex_structure_exists(self):
1465
state = self.create_complex_dirstate()
1466
self.addCleanup(state.unlock)
1467
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1468
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1469
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1470
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1471
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1472
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1473
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1474
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1475
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1478
def test_complex_structure_missing(self):
1479
state = self.create_complex_dirstate()
1480
self.addCleanup(state.unlock)
1481
self.assertEntryEqual(None, None, None, state, '_', 0)
1482
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1483
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1484
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1486
def test_get_entry_uninitialized(self):
1487
"""Calling get_entry will load data if it needs to"""
1488
state = self.create_dirstate_with_root()
1494
state = dirstate.DirState.on_file('dirstate')
1497
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1498
state._header_state)
1499
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1500
state._dirblock_state)
1501
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1506
class TestIterChildEntries(TestCaseWithDirState):
1508
def create_dirstate_with_two_trees(self):
1509
"""This dirstate contains multiple files and directories.
1519
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1521
Notice that a/e is an empty directory.
1523
There is one parent tree, which has the same shape with the following variations:
1524
b/g in the parent is gone.
1525
b/h in the parent has a different id
1526
b/i is new in the parent
1527
c is renamed to b/j in the parent
1529
:return: The dirstate, still write-locked.
1531
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1532
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1533
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1534
root_entry = ('', '', 'a-root-value'), [
1535
('d', '', 0, False, packed_stat),
1536
('d', '', 0, False, 'parent-revid'),
1538
a_entry = ('', 'a', 'a-dir'), [
1539
('d', '', 0, False, packed_stat),
1540
('d', '', 0, False, 'parent-revid'),
1542
b_entry = ('', 'b', 'b-dir'), [
1543
('d', '', 0, False, packed_stat),
1544
('d', '', 0, False, 'parent-revid'),
1546
c_entry = ('', 'c', 'c-file'), [
1547
('f', null_sha, 10, False, packed_stat),
1548
('r', 'b/j', 0, False, ''),
1550
d_entry = ('', 'd', 'd-file'), [
1551
('f', null_sha, 20, False, packed_stat),
1552
('f', 'd', 20, False, 'parent-revid'),
1554
e_entry = ('a', 'e', 'e-dir'), [
1555
('d', '', 0, False, packed_stat),
1556
('d', '', 0, False, 'parent-revid'),
1558
f_entry = ('a', 'f', 'f-file'), [
1559
('f', null_sha, 30, False, packed_stat),
1560
('f', 'f', 20, False, 'parent-revid'),
1562
g_entry = ('b', 'g', 'g-file'), [
1563
('f', null_sha, 30, False, packed_stat),
1564
NULL_PARENT_DETAILS,
1566
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1567
('f', null_sha, 40, False, packed_stat),
1568
NULL_PARENT_DETAILS,
1570
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1571
NULL_PARENT_DETAILS,
1572
('f', 'h', 20, False, 'parent-revid'),
1574
i_entry = ('b', 'i', 'i-file'), [
1575
NULL_PARENT_DETAILS,
1576
('f', 'h', 20, False, 'parent-revid'),
1578
j_entry = ('b', 'j', 'c-file'), [
1579
('r', 'c', 0, False, ''),
1580
('f', 'j', 20, False, 'parent-revid'),
1583
dirblocks.append(('', [root_entry]))
1584
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1585
dirblocks.append(('a', [e_entry, f_entry]))
1586
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1587
state = dirstate.DirState.initialize('dirstate')
1590
state._set_data(['parent'], dirblocks)
1594
return state, dirblocks
1596
def test_iter_children_b(self):
1597
state, dirblocks = self.create_dirstate_with_two_trees()
1598
self.addCleanup(state.unlock)
1599
expected_result = []
1600
expected_result.append(dirblocks[3][1][2]) # h2
1601
expected_result.append(dirblocks[3][1][3]) # i
1602
expected_result.append(dirblocks[3][1][4]) # j
1603
self.assertEqual(expected_result,
1604
list(state._iter_child_entries(1, 'b')))
1606
def test_iter_child_root(self):
1607
state, dirblocks = self.create_dirstate_with_two_trees()
1608
self.addCleanup(state.unlock)
1609
expected_result = []
1610
expected_result.append(dirblocks[1][1][0]) # a
1611
expected_result.append(dirblocks[1][1][1]) # b
1612
expected_result.append(dirblocks[1][1][3]) # d
1613
expected_result.append(dirblocks[2][1][0]) # e
1614
expected_result.append(dirblocks[2][1][1]) # f
1615
expected_result.append(dirblocks[3][1][2]) # h2
1616
expected_result.append(dirblocks[3][1][3]) # i
1617
expected_result.append(dirblocks[3][1][4]) # j
1618
self.assertEqual(expected_result,
1619
list(state._iter_child_entries(1, '')))
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1623
"""Test that DirState adds entries in the right order."""
1625
def test_add_sorting(self):
1626
"""Add entries in lexicographical order, we get path sorted order.
1628
This tests it to a depth of 4, to make sure we don't just get it right
1629
at a single depth. 'a/a' should come before 'a-a', even though it
1630
doesn't lexicographically.
1632
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1633
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1636
state = dirstate.DirState.initialize('dirstate')
1637
self.addCleanup(state.unlock)
1639
fake_stat = os.stat('dirstate')
1641
d_id = d.replace('/', '_')+'-id'
1642
file_path = d + '/f'
1643
file_id = file_path.replace('/', '_')+'-id'
1644
state.add(d, d_id, 'directory', fake_stat, null_sha)
1645
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1647
expected = ['', '', 'a',
1648
'a/a', 'a/a/a', 'a/a/a/a',
1649
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1651
split = lambda p:p.split('/')
1652
self.assertEqual(sorted(expected, key=split), expected)
1653
dirblock_names = [d[0] for d in state._dirblocks]
1654
self.assertEqual(expected, dirblock_names)
1656
def test_set_parent_trees_correct_order(self):
1657
"""After calling set_parent_trees() we should maintain the order."""
1658
dirs = ['a', 'a-a', 'a/a']
1660
state = dirstate.DirState.initialize('dirstate')
1661
self.addCleanup(state.unlock)
1663
fake_stat = os.stat('dirstate')
1665
d_id = d.replace('/', '_')+'-id'
1666
file_path = d + '/f'
1667
file_id = file_path.replace('/', '_')+'-id'
1668
state.add(d, d_id, 'directory', fake_stat, null_sha)
1669
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1671
expected = ['', '', 'a', 'a/a', 'a-a']
1672
dirblock_names = [d[0] for d in state._dirblocks]
1673
self.assertEqual(expected, dirblock_names)
1675
# *really* cheesy way to just get an empty tree
1676
repo = self.make_repository('repo')
1677
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1678
state.set_parent_trees([('null:', empty_tree)], [])
1680
dirblock_names = [d[0] for d in state._dirblocks]
1681
self.assertEqual(expected, dirblock_names)
1684
class InstrumentedDirState(dirstate.DirState):
1685
"""An DirState with instrumented sha1 functionality."""
1687
def __init__(self, path, sha1_provider):
1688
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1689
self._time_offset = 0
1691
# member is dynamically set in DirState.__init__ to turn on trace
1692
self._sha1_provider = sha1_provider
1693
self._sha1_file = self._sha1_file_and_log
1695
def _sha_cutoff_time(self):
1696
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1697
self._cutoff_time = timestamp + self._time_offset
1699
def _sha1_file_and_log(self, abspath):
1700
self._log.append(('sha1', abspath))
1701
return self._sha1_provider.sha1(abspath)
1703
def _read_link(self, abspath, old_link):
1704
self._log.append(('read_link', abspath, old_link))
1705
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1707
def _lstat(self, abspath, entry):
1708
self._log.append(('lstat', abspath))
1709
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1711
def _is_executable(self, mode, old_executable):
1712
self._log.append(('is_exec', mode, old_executable))
1713
return super(InstrumentedDirState, self)._is_executable(mode,
1716
def adjust_time(self, secs):
1717
"""Move the clock forward or back.
1719
:param secs: The amount to adjust the clock by. Positive values make it
1720
seem as if we are in the future, negative values make it seem like we
1723
self._time_offset += secs
1724
self._cutoff_time = None
1727
class _FakeStat(object):
1728
"""A class with the same attributes as a real stat result."""
1730
def __init__(self, size, mtime, ctime, dev, ino, mode):
1732
self.st_mtime = mtime
1733
self.st_ctime = ctime
1740
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1741
st.st_ino, st.st_mode)
1744
class TestPackStat(tests.TestCaseWithTransport):
1746
def assertPackStat(self, expected, stat_value):
1747
"""Check the packed and serialized form of a stat value."""
1748
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1750
def test_pack_stat_int(self):
1751
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1752
# Make sure that all parameters have an impact on the packed stat.
1753
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1756
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1757
st.st_mtime = 1172758620
1759
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1760
st.st_ctime = 1172758630
1762
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1765
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1766
st.st_ino = 6499540L
1768
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1769
st.st_mode = 0100744
1771
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1773
def test_pack_stat_float(self):
1774
"""On some platforms mtime and ctime are floats.
1776
Make sure we don't get warnings or errors, and that we ignore changes <
1779
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1780
777L, 6499538L, 0100644)
1781
# These should all be the same as the integer counterparts
1782
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1783
st.st_mtime = 1172758620.0
1785
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1786
st.st_ctime = 1172758630.0
1788
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1789
# fractional seconds are discarded, so no change from above
1790
st.st_mtime = 1172758620.453
1791
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1792
st.st_ctime = 1172758630.228
1793
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1796
class TestBisect(TestCaseWithDirState):
1797
"""Test the ability to bisect into the disk format."""
1799
def assertBisect(self, expected_map, map_keys, state, paths):
1800
"""Assert that bisecting for paths returns the right result.
1802
:param expected_map: A map from key => entry value
1803
:param map_keys: The keys to expect for each path
1804
:param state: The DirState object.
1805
:param paths: A list of paths, these will automatically be split into
1806
(dir, name) tuples, and sorted according to how _bisect
1809
result = state._bisect(paths)
1810
# For now, results are just returned in whatever order we read them.
1811
# We could sort by (dir, name, file_id) or something like that, but in
1812
# the end it would still be fairly arbitrary, and we don't want the
1813
# extra overhead if we can avoid it. So sort everything to make sure
1815
self.assertEqual(len(map_keys), len(paths))
1817
for path, keys in zip(paths, map_keys):
1819
# This should not be present in the output
1821
expected[path] = sorted(expected_map[k] for k in keys)
1823
# The returned values are just arranged randomly based on when they
1824
# were read, for testing, make sure it is properly sorted.
1828
self.assertEqual(expected, result)
1830
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1831
"""Assert that bisecting for dirbblocks returns the right result.
1833
:param expected_map: A map from key => expected values
1834
:param map_keys: A nested list of paths we expect to be returned.
1835
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1836
:param state: The DirState object.
1837
:param paths: A list of directories
1839
result = state._bisect_dirblocks(paths)
1840
self.assertEqual(len(map_keys), len(paths))
1842
for path, keys in zip(paths, map_keys):
1844
# This should not be present in the output
1846
expected[path] = sorted(expected_map[k] for k in keys)
1850
self.assertEqual(expected, result)
1852
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1853
"""Assert the return value of a recursive bisection.
1855
:param expected_map: A map from key => entry value
1856
:param map_keys: A list of paths we expect to be returned.
1857
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1858
:param state: The DirState object.
1859
:param paths: A list of files and directories. It will be broken up
1860
into (dir, name) pairs and sorted before calling _bisect_recursive.
1863
for key in map_keys:
1864
entry = expected_map[key]
1865
dir_name_id, trees_info = entry
1866
expected[dir_name_id] = trees_info
1868
result = state._bisect_recursive(paths)
1870
self.assertEqual(expected, result)
1872
def test_bisect_each(self):
1873
"""Find a single record using bisect."""
1874
tree, state, expected = self.create_basic_dirstate()
1876
# Bisect should return the rows for the specified files.
1877
self.assertBisect(expected, [['']], state, [''])
1878
self.assertBisect(expected, [['a']], state, ['a'])
1879
self.assertBisect(expected, [['b']], state, ['b'])
1880
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1881
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1882
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1883
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1884
self.assertBisect(expected, [['f']], state, ['f'])
1886
def test_bisect_multi(self):
1887
"""Bisect can be used to find multiple records at the same time."""
1888
tree, state, expected = self.create_basic_dirstate()
1889
# Bisect should be capable of finding multiple entries at the same time
1890
self.assertBisect(expected, [['a'], ['b'], ['f']],
1891
state, ['a', 'b', 'f'])
1892
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1893
state, ['f', 'b/d', 'b/d/e'])
1894
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1895
state, ['b', 'b-c', 'b/c'])
1897
def test_bisect_one_page(self):
1898
"""Test bisect when there is only 1 page to read"""
1899
tree, state, expected = self.create_basic_dirstate()
1900
state._bisect_page_size = 5000
1901
self.assertBisect(expected,[['']], state, [''])
1902
self.assertBisect(expected,[['a']], state, ['a'])
1903
self.assertBisect(expected,[['b']], state, ['b'])
1904
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1905
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1906
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1907
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1908
self.assertBisect(expected,[['f']], state, ['f'])
1909
self.assertBisect(expected,[['a'], ['b'], ['f']],
1910
state, ['a', 'b', 'f'])
1911
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1912
state, ['b/d', 'b/d/e', 'f'])
1913
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1914
state, ['b', 'b/c', 'b-c'])
1916
def test_bisect_duplicate_paths(self):
1917
"""When bisecting for a path, handle multiple entries."""
1918
tree, state, expected = self.create_duplicated_dirstate()
1920
# Now make sure that both records are properly returned.
1921
self.assertBisect(expected, [['']], state, [''])
1922
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1923
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1924
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1925
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1926
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1928
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1929
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1931
def test_bisect_page_size_too_small(self):
1932
"""If the page size is too small, we will auto increase it."""
1933
tree, state, expected = self.create_basic_dirstate()
1934
state._bisect_page_size = 50
1935
self.assertBisect(expected, [None], state, ['b/e'])
1936
self.assertBisect(expected, [['a']], state, ['a'])
1937
self.assertBisect(expected, [['b']], state, ['b'])
1938
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1939
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1940
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1941
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1942
self.assertBisect(expected, [['f']], state, ['f'])
1944
def test_bisect_missing(self):
1945
"""Test that bisect return None if it cannot find a path."""
1946
tree, state, expected = self.create_basic_dirstate()
1947
self.assertBisect(expected, [None], state, ['foo'])
1948
self.assertBisect(expected, [None], state, ['b/foo'])
1949
self.assertBisect(expected, [None], state, ['bar/foo'])
1950
self.assertBisect(expected, [None], state, ['b-c/foo'])
1952
self.assertBisect(expected, [['a'], None, ['b/d']],
1953
state, ['a', 'foo', 'b/d'])
1955
def test_bisect_rename(self):
1956
"""Check that we find a renamed row."""
1957
tree, state, expected = self.create_renamed_dirstate()
1959
# Search for the pre and post renamed entries
1960
self.assertBisect(expected, [['a']], state, ['a'])
1961
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1962
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1963
self.assertBisect(expected, [['h']], state, ['h'])
1965
# What about b/d/e? shouldn't that also get 2 directory entries?
1966
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1967
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1969
def test_bisect_dirblocks(self):
1970
tree, state, expected = self.create_duplicated_dirstate()
1971
self.assertBisectDirBlocks(expected,
1972
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1974
self.assertBisectDirBlocks(expected,
1975
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1976
self.assertBisectDirBlocks(expected,
1977
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1978
self.assertBisectDirBlocks(expected,
1979
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1980
['b/c', 'b/c2', 'b/d', 'b/d2'],
1981
['b/d/e', 'b/d/e2'],
1982
], state, ['', 'b', 'b/d'])
1984
def test_bisect_dirblocks_missing(self):
1985
tree, state, expected = self.create_basic_dirstate()
1986
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1987
state, ['b/d', 'b/e'])
1988
# Files don't show up in this search
1989
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1990
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1991
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1992
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1993
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1995
def test_bisect_recursive_each(self):
1996
tree, state, expected = self.create_basic_dirstate()
1997
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1998
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1999
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2000
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2001
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2003
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2005
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2009
def test_bisect_recursive_multiple(self):
2010
tree, state, expected = self.create_basic_dirstate()
2011
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2012
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2013
state, ['b/d', 'b/d/e'])
2015
def test_bisect_recursive_missing(self):
2016
tree, state, expected = self.create_basic_dirstate()
2017
self.assertBisectRecursive(expected, [], state, ['d'])
2018
self.assertBisectRecursive(expected, [], state, ['b/e'])
2019
self.assertBisectRecursive(expected, [], state, ['g'])
2020
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2022
def test_bisect_recursive_renamed(self):
2023
tree, state, expected = self.create_renamed_dirstate()
2025
# Looking for either renamed item should find the other
2026
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2027
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2028
# Looking in the containing directory should find the rename target,
2029
# and anything in a subdir of the renamed target.
2030
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2031
'b/d/e', 'b/g', 'h', 'h/e'],
2035
class TestDirstateValidation(TestCaseWithDirState):
2037
def test_validate_correct_dirstate(self):
2038
state = self.create_complex_dirstate()
2041
# and make sure we can also validate with a read lock
2048
def test_dirblock_not_sorted(self):
2049
tree, state, expected = self.create_renamed_dirstate()
2050
state._read_dirblocks_if_needed()
2051
last_dirblock = state._dirblocks[-1]
2052
# we're appending to the dirblock, but this name comes before some of
2053
# the existing names; that's wrong
2054
last_dirblock[1].append(
2055
(('h', 'aaaa', 'a-id'),
2056
[('a', '', 0, False, ''),
2057
('a', '', 0, False, '')]))
2058
e = self.assertRaises(AssertionError,
2060
self.assertContainsRe(str(e), 'not sorted')
2062
def test_dirblock_name_mismatch(self):
2063
tree, state, expected = self.create_renamed_dirstate()
2064
state._read_dirblocks_if_needed()
2065
last_dirblock = state._dirblocks[-1]
2066
# add an entry with the wrong directory name
2067
last_dirblock[1].append(
2069
[('a', '', 0, False, ''),
2070
('a', '', 0, False, '')]))
2071
e = self.assertRaises(AssertionError,
2073
self.assertContainsRe(str(e),
2074
"doesn't match directory name")
2076
def test_dirblock_missing_rename(self):
2077
tree, state, expected = self.create_renamed_dirstate()
2078
state._read_dirblocks_if_needed()
2079
last_dirblock = state._dirblocks[-1]
2080
# make another entry for a-id, without a correct 'r' pointer to
2081
# the real occurrence in the working tree
2082
last_dirblock[1].append(
2083
(('h', 'z', 'a-id'),
2084
[('a', '', 0, False, ''),
2085
('a', '', 0, False, '')]))
2086
e = self.assertRaises(AssertionError,
2088
self.assertContainsRe(str(e),
2089
'file a-id is absent in row')
2092
class TestDirstateTreeReference(TestCaseWithDirState):
2094
def test_reference_revision_is_none(self):
2095
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2096
subtree = self.make_branch_and_tree('tree/subtree',
2097
format='dirstate-with-subtree')
2098
subtree.set_root_id('subtree')
2099
tree.add_reference(subtree)
2101
state = dirstate.DirState.from_tree(tree, 'dirstate')
2102
key = ('', 'subtree', 'subtree')
2103
expected = ('', [(key,
2104
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2107
self.assertEqual(expected, state._find_block(key))
2112
class TestDiscardMergeParents(TestCaseWithDirState):
2114
def test_discard_no_parents(self):
2115
# This should be a no-op
2116
state = self.create_empty_dirstate()
2117
self.addCleanup(state.unlock)
2118
state._discard_merge_parents()
2121
def test_discard_one_parent(self):
2123
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2124
root_entry_direntry = ('', '', 'a-root-value'), [
2125
('d', '', 0, False, packed_stat),
2126
('d', '', 0, False, packed_stat),
2129
dirblocks.append(('', [root_entry_direntry]))
2130
dirblocks.append(('', []))
2132
state = self.create_empty_dirstate()
2133
self.addCleanup(state.unlock)
2134
state._set_data(['parent-id'], dirblocks[:])
2137
state._discard_merge_parents()
2139
self.assertEqual(dirblocks, state._dirblocks)
2141
def test_discard_simple(self):
2143
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2144
root_entry_direntry = ('', '', 'a-root-value'), [
2145
('d', '', 0, False, packed_stat),
2146
('d', '', 0, False, packed_stat),
2147
('d', '', 0, False, packed_stat),
2149
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2150
('d', '', 0, False, packed_stat),
2151
('d', '', 0, False, packed_stat),
2154
dirblocks.append(('', [root_entry_direntry]))
2155
dirblocks.append(('', []))
2157
state = self.create_empty_dirstate()
2158
self.addCleanup(state.unlock)
2159
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2162
# This should strip of the extra column
2163
state._discard_merge_parents()
2165
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2166
self.assertEqual(expected_dirblocks, state._dirblocks)
2168
def test_discard_absent(self):
2169
"""If entries are only in a merge, discard should remove the entries"""
2170
null_stat = dirstate.DirState.NULLSTAT
2171
present_dir = ('d', '', 0, False, null_stat)
2172
present_file = ('f', '', 0, False, null_stat)
2173
absent = dirstate.DirState.NULL_PARENT_DETAILS
2174
root_key = ('', '', 'a-root-value')
2175
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2176
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2177
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2178
('', [(file_in_merged_key,
2179
[absent, absent, present_file]),
2181
[present_file, present_file, present_file]),
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2190
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2191
('', [(file_in_root_key,
2192
[present_file, present_file]),
2195
state._discard_merge_parents()
2197
self.assertEqual(exp_dirblocks, state._dirblocks)
2199
def test_discard_renamed(self):
2200
null_stat = dirstate.DirState.NULLSTAT
2201
present_dir = ('d', '', 0, False, null_stat)
2202
present_file = ('f', '', 0, False, null_stat)
2203
absent = dirstate.DirState.NULL_PARENT_DETAILS
2204
root_key = ('', '', 'a-root-value')
2205
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2206
# Renamed relative to parent
2207
file_rename_s_key = ('', 'file-s', 'b-file-id')
2208
file_rename_t_key = ('', 'file-t', 'b-file-id')
2209
# And one that is renamed between the parents, but absent in this
2210
key_in_1 = ('', 'file-in-1', 'c-file-id')
2211
key_in_2 = ('', 'file-in-2', 'c-file-id')
2214
('', [(root_key, [present_dir, present_dir, present_dir])]),
2216
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2218
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2220
[present_file, present_file, present_file]),
2222
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2224
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2228
('', [(root_key, [present_dir, present_dir])]),
2229
('', [(key_in_1, [absent, present_file]),
2230
(file_in_root_key, [present_file, present_file]),
2231
(file_rename_t_key, [present_file, absent]),
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2239
state._discard_merge_parents()
2241
self.assertEqual(exp_dirblocks, state._dirblocks)
2243
def test_discard_all_subdir(self):
2244
null_stat = dirstate.DirState.NULLSTAT
2245
present_dir = ('d', '', 0, False, null_stat)
2246
present_file = ('f', '', 0, False, null_stat)
2247
absent = dirstate.DirState.NULL_PARENT_DETAILS
2248
root_key = ('', '', 'a-root-value')
2249
subdir_key = ('', 'sub', 'dir-id')
2250
child1_key = ('sub', 'child1', 'child1-id')
2251
child2_key = ('sub', 'child2', 'child2-id')
2252
child3_key = ('sub', 'child3', 'child3-id')
2255
('', [(root_key, [present_dir, present_dir, present_dir])]),
2256
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2257
('sub', [(child1_key, [absent, absent, present_file]),
2258
(child2_key, [absent, absent, present_file]),
2259
(child3_key, [absent, absent, present_file]),
2263
('', [(root_key, [present_dir, present_dir])]),
2264
('', [(subdir_key, [present_dir, present_dir])]),
2267
state = self.create_empty_dirstate()
2268
self.addCleanup(state.unlock)
2269
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2272
state._discard_merge_parents()
2274
self.assertEqual(exp_dirblocks, state._dirblocks)
2277
class Test_InvEntryToDetails(tests.TestCase):
2279
def assertDetails(self, expected, inv_entry):
2280
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2281
self.assertEqual(expected, details)
2282
# details should always allow join() and always be a plain str when
2284
(minikind, fingerprint, size, executable, tree_data) = details
2285
self.assertIsInstance(minikind, str)
2286
self.assertIsInstance(fingerprint, str)
2287
self.assertIsInstance(tree_data, str)
2289
def test_unicode_symlink(self):
2290
inv_entry = inventory.InventoryLink('link-file-id',
2291
u'nam\N{Euro Sign}e',
2293
inv_entry.revision = 'link-revision-id'
2294
target = u'link-targ\N{Euro Sign}t'
2295
inv_entry.symlink_target = target
2296
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2297
'link-revision-id'), inv_entry)
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
2302
def test_sha1provider_is_an_interface(self):
2303
p = dirstate.SHA1Provider()
2304
self.assertRaises(NotImplementedError, p.sha1, "foo")
2305
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2307
def test_defaultsha1provider_sha1(self):
2308
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2309
self.build_tree_contents([('foo', text)])
2310
expected_sha = osutils.sha_string(text)
2311
p = dirstate.DefaultSHA1Provider()
2312
self.assertEqual(expected_sha, p.sha1('foo'))
2314
def test_defaultsha1provider_stat_and_sha1(self):
2315
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2316
self.build_tree_contents([('foo', text)])
2317
expected_sha = osutils.sha_string(text)
2318
p = dirstate.DefaultSHA1Provider()
2319
statvalue, sha1 = p.stat_and_sha1('foo')
2320
self.assertTrue(len(statvalue) >= 10)
2321
self.assertEqual(len(text), statvalue.st_size)
2322
self.assertEqual(expected_sha, sha1)