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_update_minimal_updates_id_index(self):
734
state = self.create_dirstate_with_root_and_subdir()
735
self.addCleanup(state.unlock)
736
id_index = state._get_id_index()
737
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
738
state.add('file-name', 'file-id', 'file', None, '')
739
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
741
state.update_minimal(('', 'new-name', 'file-id'), 'f',
742
path_utf8='new-name')
743
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
745
self.assertEqual([('', 'new-name', 'file-id')],
746
sorted(id_index['file-id']))
749
def test_set_state_from_inventory_no_content_no_parents(self):
750
# setting the current inventory is a slow but important api to support.
751
tree1 = self.make_branch_and_memory_tree('tree1')
755
revid1 = tree1.commit('foo').encode('utf8')
756
root_id = tree1.get_root_id()
757
inv = tree1.inventory
760
expected_result = [], [
761
(('', '', root_id), [
762
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
763
state = dirstate.DirState.initialize('dirstate')
765
state.set_state_from_inventory(inv)
766
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
768
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
769
state._dirblock_state)
774
# This will unlock it
775
self.check_state_with_reopen(expected_result, state)
777
def test_set_state_from_inventory_preserves_hashcache(self):
778
# https://bugs.launchpad.net/bzr/+bug/146176
779
# set_state_from_inventory should preserve the stat and hash value for
780
# workingtree files that are not changed by the inventory.
782
tree = self.make_branch_and_tree('.')
783
# depends on the default format using dirstate...
786
# make a dirstate with some valid hashcache data
787
# file on disk, but that's not needed for this test
788
foo_contents = 'contents of foo'
789
self.build_tree_contents([('foo', foo_contents)])
790
tree.add('foo', 'foo-id')
792
foo_stat = os.stat('foo')
793
foo_packed = dirstate.pack_stat(foo_stat)
794
foo_sha = osutils.sha_string(foo_contents)
795
foo_size = len(foo_contents)
797
# should not be cached yet, because the file's too fresh
799
(('', 'foo', 'foo-id',),
800
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
801
tree._dirstate._get_entry(0, 'foo-id'))
802
# poke in some hashcache information - it wouldn't normally be
803
# stored because it's too fresh
804
tree._dirstate.update_minimal(
805
('', 'foo', 'foo-id'),
806
'f', False, foo_sha, foo_packed, foo_size, 'foo')
807
# now should be cached
809
(('', 'foo', 'foo-id',),
810
[('f', foo_sha, foo_size, False, foo_packed)]),
811
tree._dirstate._get_entry(0, 'foo-id'))
813
# extract the inventory, and add something to it
814
inv = tree._get_inventory()
815
# should see the file we poked in...
816
self.assertTrue(inv.has_id('foo-id'))
817
self.assertTrue(inv.has_filename('foo'))
818
inv.add_path('bar', 'file', 'bar-id')
819
tree._dirstate._validate()
820
# this used to cause it to lose its hashcache
821
tree._dirstate.set_state_from_inventory(inv)
822
tree._dirstate._validate()
828
# now check that the state still has the original hashcache value
829
state = tree._dirstate
831
foo_tuple = state._get_entry(0, path_utf8='foo')
833
(('', 'foo', 'foo-id',),
834
[('f', foo_sha, len(foo_contents), False,
835
dirstate.pack_stat(foo_stat))]),
840
def test_set_state_from_inventory_mixed_paths(self):
841
tree1 = self.make_branch_and_tree('tree1')
842
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
843
'tree1/a/b/foo', 'tree1/a-b/bar'])
846
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
847
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
848
tree1.commit('rev1', rev_id='rev1')
849
root_id = tree1.get_root_id()
850
inv = tree1.inventory
853
expected_result1 = [('', '', root_id, 'd'),
854
('', 'a', 'a-id', 'd'),
855
('', 'a-b', 'a-b-id', 'd'),
856
('a', 'b', 'b-id', 'd'),
857
('a/b', 'foo', 'foo-id', 'f'),
858
('a-b', 'bar', 'bar-id', 'f'),
860
expected_result2 = [('', '', root_id, 'd'),
861
('', 'a', 'a-id', 'd'),
862
('', 'a-b', 'a-b-id', 'd'),
863
('a-b', 'bar', 'bar-id', 'f'),
865
state = dirstate.DirState.initialize('dirstate')
867
state.set_state_from_inventory(inv)
869
for entry in state._iter_entries():
870
values.append(entry[0] + entry[1][0][:1])
871
self.assertEqual(expected_result1, values)
873
state.set_state_from_inventory(inv)
875
for entry in state._iter_entries():
876
values.append(entry[0] + entry[1][0][:1])
877
self.assertEqual(expected_result2, values)
881
def test_set_path_id_no_parents(self):
882
"""The id of a path can be changed trivally with no parents."""
883
state = dirstate.DirState.initialize('dirstate')
885
# check precondition to be sure the state does change appropriately.
886
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
887
self.assertEqual([root_entry], list(state._iter_entries()))
888
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
889
self.assertEqual(root_entry,
890
state._get_entry(0, fileid_utf8='TREE_ROOT'))
891
self.assertEqual((None, None),
892
state._get_entry(0, fileid_utf8='second-root-id'))
893
state.set_path_id('', 'second-root-id')
894
new_root_entry = (('', '', 'second-root-id'),
895
[('d', '', 0, False, 'x'*32)])
896
expected_rows = [new_root_entry]
897
self.assertEqual(expected_rows, list(state._iter_entries()))
898
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
899
self.assertEqual(new_root_entry,
900
state._get_entry(0, fileid_utf8='second-root-id'))
901
self.assertEqual((None, None),
902
state._get_entry(0, fileid_utf8='TREE_ROOT'))
903
# should work across save too
907
state = dirstate.DirState.on_file('dirstate')
911
self.assertEqual(expected_rows, list(state._iter_entries()))
915
def test_set_path_id_with_parents(self):
916
"""Set the root file id in a dirstate with parents"""
917
mt = self.make_branch_and_tree('mt')
918
# in case the default tree format uses a different root id
919
mt.set_root_id('TREE_ROOT')
920
mt.commit('foo', rev_id='parent-revid')
921
rt = mt.branch.repository.revision_tree('parent-revid')
922
state = dirstate.DirState.initialize('dirstate')
925
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
926
root_entry = (('', '', 'TREE_ROOT'),
927
[('d', '', 0, False, 'x'*32),
928
('d', '', 0, False, 'parent-revid')])
929
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
930
self.assertEqual(root_entry,
931
state._get_entry(0, fileid_utf8='TREE_ROOT'))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='Asecond-root-id'))
934
state.set_path_id('', 'Asecond-root-id')
936
# now see that it is what we expected
937
old_root_entry = (('', '', 'TREE_ROOT'),
938
[('a', '', 0, False, ''),
939
('d', '', 0, False, 'parent-revid')])
940
new_root_entry = (('', '', 'Asecond-root-id'),
941
[('d', '', 0, False, ''),
942
('a', '', 0, False, '')])
943
expected_rows = [new_root_entry, old_root_entry]
945
self.assertEqual(expected_rows, list(state._iter_entries()))
946
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
947
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
948
self.assertEqual((None, None),
949
state._get_entry(0, fileid_utf8='TREE_ROOT'))
950
self.assertEqual(old_root_entry,
951
state._get_entry(1, fileid_utf8='TREE_ROOT'))
952
self.assertEqual(new_root_entry,
953
state._get_entry(0, fileid_utf8='Asecond-root-id'))
954
self.assertEqual((None, None),
955
state._get_entry(1, fileid_utf8='Asecond-root-id'))
956
# should work across save too
960
# now flush & check we get the same
961
state = dirstate.DirState.on_file('dirstate')
965
self.assertEqual(expected_rows, list(state._iter_entries()))
968
# now change within an existing file-backed state
972
state.set_path_id('', 'tree-root-2')
977
def test_set_parent_trees_no_content(self):
978
# set_parent_trees is a slow but important api to support.
979
tree1 = self.make_branch_and_memory_tree('tree1')
983
revid1 = tree1.commit('foo')
986
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
987
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
990
revid2 = tree2.commit('foo')
991
root_id = tree2.get_root_id()
994
state = dirstate.DirState.initialize('dirstate')
996
state.set_path_id('', root_id)
997
state.set_parent_trees(
998
((revid1, tree1.branch.repository.revision_tree(revid1)),
999
(revid2, tree2.branch.repository.revision_tree(revid2)),
1000
('ghost-rev', None)),
1002
# check we can reopen and use the dirstate after setting parent
1009
state = dirstate.DirState.on_file('dirstate')
1012
self.assertEqual([revid1, revid2, 'ghost-rev'],
1013
state.get_parent_ids())
1014
# iterating the entire state ensures that the state is parsable.
1015
list(state._iter_entries())
1016
# be sure that it sets not appends - change it
1017
state.set_parent_trees(
1018
((revid1, tree1.branch.repository.revision_tree(revid1)),
1019
('ghost-rev', None)),
1021
# and now put it back.
1022
state.set_parent_trees(
1023
((revid1, tree1.branch.repository.revision_tree(revid1)),
1024
(revid2, tree2.branch.repository.revision_tree(revid2)),
1025
('ghost-rev', tree2.branch.repository.revision_tree(
1026
_mod_revision.NULL_REVISION))),
1028
self.assertEqual([revid1, revid2, 'ghost-rev'],
1029
state.get_parent_ids())
1030
# the ghost should be recorded as such by set_parent_trees.
1031
self.assertEqual(['ghost-rev'], state.get_ghosts())
1033
[(('', '', root_id), [
1034
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1035
('d', '', 0, False, revid1),
1036
('d', '', 0, False, revid1)
1038
list(state._iter_entries()))
1042
def test_set_parent_trees_file_missing_from_tree(self):
1043
# Adding a parent tree may reference files not in the current state.
1044
# they should get listed just once by id, even if they are in two
1046
# set_parent_trees is a slow but important api to support.
1047
tree1 = self.make_branch_and_memory_tree('tree1')
1051
tree1.add(['a file'], ['file-id'], ['file'])
1052
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1053
revid1 = tree1.commit('foo')
1056
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1057
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1060
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1061
revid2 = tree2.commit('foo')
1062
root_id = tree2.get_root_id()
1065
# check the layout in memory
1066
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1067
(('', '', root_id), [
1068
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1069
('d', '', 0, False, revid1.encode('utf8')),
1070
('d', '', 0, False, revid1.encode('utf8'))
1072
(('', 'a file', 'file-id'), [
1073
('a', '', 0, False, ''),
1074
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1075
revid1.encode('utf8')),
1076
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1077
revid2.encode('utf8'))
1080
state = dirstate.DirState.initialize('dirstate')
1082
state.set_path_id('', root_id)
1083
state.set_parent_trees(
1084
((revid1, tree1.branch.repository.revision_tree(revid1)),
1085
(revid2, tree2.branch.repository.revision_tree(revid2)),
1091
# check_state_with_reopen will unlock
1092
self.check_state_with_reopen(expected_result, state)
1094
### add a path via _set_data - so we dont need delta work, just
1095
# raw data in, and ensure that it comes out via get_lines happily.
1097
def test_add_path_to_root_no_parents_all_data(self):
1098
# The most trivial addition of a path is when there are no parents and
1099
# its in the root and all data about the file is supplied
1100
self.build_tree(['a file'])
1101
stat = os.lstat('a file')
1102
# the 1*20 is the sha1 pretend value.
1103
state = dirstate.DirState.initialize('dirstate')
1104
expected_entries = [
1105
(('', '', 'TREE_ROOT'), [
1106
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1108
(('', 'a file', 'a-file-id'), [
1109
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1113
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1114
# having added it, it should be in the output of iter_entries.
1115
self.assertEqual(expected_entries, list(state._iter_entries()))
1116
# saving and reloading should not affect this.
1120
state = dirstate.DirState.on_file('dirstate')
1122
self.addCleanup(state.unlock)
1123
self.assertEqual(expected_entries, list(state._iter_entries()))
1125
def test_add_path_to_unversioned_directory(self):
1126
"""Adding a path to an unversioned directory should error.
1128
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1129
once dirstate is stable and if it is merged with WorkingTree3, consider
1130
removing this copy of the test.
1132
self.build_tree(['unversioned/', 'unversioned/a file'])
1133
state = dirstate.DirState.initialize('dirstate')
1134
self.addCleanup(state.unlock)
1135
self.assertRaises(errors.NotVersionedError, state.add,
1136
'unversioned/a file', 'a-file-id', 'file', None, None)
1138
def test_add_directory_to_root_no_parents_all_data(self):
1139
# The most trivial addition of a dir is when there are no parents and
1140
# its in the root and all data about the file is supplied
1141
self.build_tree(['a dir/'])
1142
stat = os.lstat('a dir')
1143
expected_entries = [
1144
(('', '', 'TREE_ROOT'), [
1145
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1147
(('', 'a dir', 'a dir id'), [
1148
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1151
state = dirstate.DirState.initialize('dirstate')
1153
state.add('a dir', 'a dir id', 'directory', stat, None)
1154
# having added it, it should be in the output of iter_entries.
1155
self.assertEqual(expected_entries, list(state._iter_entries()))
1156
# saving and reloading should not affect this.
1160
state = dirstate.DirState.on_file('dirstate')
1162
self.addCleanup(state.unlock)
1164
self.assertEqual(expected_entries, list(state._iter_entries()))
1166
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1167
# The most trivial addition of a symlink when there are no parents and
1168
# its in the root and all data about the file is supplied
1169
# bzr doesn't support fake symlinks on windows, yet.
1170
self.requireFeature(tests.SymlinkFeature)
1171
os.symlink(target, link_name)
1172
stat = os.lstat(link_name)
1173
expected_entries = [
1174
(('', '', 'TREE_ROOT'), [
1175
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1177
(('', link_name.encode('UTF-8'), 'a link id'), [
1178
('l', target.encode('UTF-8'), stat[6],
1179
False, dirstate.pack_stat(stat)), # current tree
1182
state = dirstate.DirState.initialize('dirstate')
1184
state.add(link_name, 'a link id', 'symlink', stat,
1185
target.encode('UTF-8'))
1186
# having added it, it should be in the output of iter_entries.
1187
self.assertEqual(expected_entries, list(state._iter_entries()))
1188
# saving and reloading should not affect this.
1192
state = dirstate.DirState.on_file('dirstate')
1194
self.addCleanup(state.unlock)
1195
self.assertEqual(expected_entries, list(state._iter_entries()))
1197
def test_add_symlink_to_root_no_parents_all_data(self):
1198
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1200
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1201
self.requireFeature(tests.UnicodeFilenameFeature)
1202
self._test_add_symlink_to_root_no_parents_all_data(
1203
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1205
def test_add_directory_and_child_no_parents_all_data(self):
1206
# after adding a directory, we should be able to add children to it.
1207
self.build_tree(['a dir/', 'a dir/a file'])
1208
dirstat = os.lstat('a dir')
1209
filestat = os.lstat('a dir/a file')
1210
expected_entries = [
1211
(('', '', 'TREE_ROOT'), [
1212
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1214
(('', 'a dir', 'a dir id'), [
1215
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1217
(('a dir', 'a file', 'a-file-id'), [
1218
('f', '1'*20, 25, False,
1219
dirstate.pack_stat(filestat)), # current tree details
1222
state = dirstate.DirState.initialize('dirstate')
1224
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1225
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1226
# added it, it should be in the output of iter_entries.
1227
self.assertEqual(expected_entries, list(state._iter_entries()))
1228
# saving and reloading should not affect this.
1232
state = dirstate.DirState.on_file('dirstate')
1234
self.addCleanup(state.unlock)
1235
self.assertEqual(expected_entries, list(state._iter_entries()))
1237
def test_add_tree_reference(self):
1238
# make a dirstate and add a tree reference
1239
state = dirstate.DirState.initialize('dirstate')
1241
('', 'subdir', 'subdir-id'),
1242
[('t', 'subtree-123123', 0, False,
1243
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1246
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1247
entry = state._get_entry(0, 'subdir-id', 'subdir')
1248
self.assertEqual(entry, expected_entry)
1253
# now check we can read it back
1255
self.addCleanup(state.unlock)
1257
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1258
self.assertEqual(entry, entry2)
1259
self.assertEqual(entry, expected_entry)
1260
# and lookup by id should work too
1261
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1262
self.assertEqual(entry, expected_entry)
1264
def test_add_forbidden_names(self):
1265
state = dirstate.DirState.initialize('dirstate')
1266
self.addCleanup(state.unlock)
1267
self.assertRaises(errors.BzrError,
1268
state.add, '.', 'ass-id', 'directory', None, None)
1269
self.assertRaises(errors.BzrError,
1270
state.add, '..', 'ass-id', 'directory', None, None)
1272
def test_set_state_with_rename_b_a_bug_395556(self):
1273
# bug 395556 uncovered a bug where the dirstate ends up with a false
1274
# relocation record - in a tree with no parents there should be no
1275
# absent or relocated records. This then leads to further corruption
1276
# when a commit occurs, as the incorrect relocation gathers an
1277
# incorrect absent in tree 1, and future changes go to pot.
1278
tree1 = self.make_branch_and_tree('tree1')
1279
self.build_tree(['tree1/b'])
1282
tree1.add(['b'], ['b-id'])
1283
root_id = tree1.get_root_id()
1284
inv = tree1.inventory
1285
state = dirstate.DirState.initialize('dirstate')
1287
# Set the initial state with 'b'
1288
state.set_state_from_inventory(inv)
1289
inv.rename('b-id', root_id, 'a')
1290
# Set the new state with 'a', which currently corrupts.
1291
state.set_state_from_inventory(inv)
1292
expected_result1 = [('', '', root_id, 'd'),
1293
('', 'a', 'b-id', 'f'),
1296
for entry in state._iter_entries():
1297
values.append(entry[0] + entry[1][0][:1])
1298
self.assertEqual(expected_result1, values)
1305
class TestGetLines(TestCaseWithDirState):
1307
def test_get_line_with_2_rows(self):
1308
state = self.create_dirstate_with_root_and_subdir()
1310
self.assertEqual(['#bazaar dirstate flat format 3\n',
1311
'crc32: 41262208\n',
1315
'\x00\x00a-root-value\x00'
1316
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1317
'\x00subdir\x00subdir-id\x00'
1318
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1319
], state.get_lines())
1323
def test_entry_to_line(self):
1324
state = self.create_dirstate_with_root()
1327
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1328
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1329
state._entry_to_line(state._dirblocks[0][1][0]))
1333
def test_entry_to_line_with_parent(self):
1334
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1335
root_entry = ('', '', 'a-root-value'), [
1336
('d', '', 0, False, packed_stat), # current tree details
1337
# first: a pointer to the current location
1338
('a', 'dirname/basename', 0, False, ''),
1340
state = dirstate.DirState.initialize('dirstate')
1343
'\x00\x00a-root-value\x00'
1344
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1345
'a\x00dirname/basename\x000\x00n\x00',
1346
state._entry_to_line(root_entry))
1350
def test_entry_to_line_with_two_parents_at_different_paths(self):
1351
# / in the tree, at / in one parent and /dirname/basename in the other.
1352
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1353
root_entry = ('', '', 'a-root-value'), [
1354
('d', '', 0, False, packed_stat), # current tree details
1355
('d', '', 0, False, 'rev_id'), # first parent details
1356
# second: a pointer to the current location
1357
('a', 'dirname/basename', 0, False, ''),
1359
state = dirstate.DirState.initialize('dirstate')
1362
'\x00\x00a-root-value\x00'
1363
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1364
'd\x00\x000\x00n\x00rev_id\x00'
1365
'a\x00dirname/basename\x000\x00n\x00',
1366
state._entry_to_line(root_entry))
1370
def test_iter_entries(self):
1371
# we should be able to iterate the dirstate entries from end to end
1372
# this is for get_lines to be easy to read.
1373
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1375
root_entries = [(('', '', 'a-root-value'), [
1376
('d', '', 0, False, packed_stat), # current tree details
1378
dirblocks.append(('', root_entries))
1379
# add two files in the root
1380
subdir_entry = ('', 'subdir', 'subdir-id'), [
1381
('d', '', 0, False, packed_stat), # current tree details
1383
afile_entry = ('', 'afile', 'afile-id'), [
1384
('f', 'sha1value', 34, False, packed_stat), # current tree details
1386
dirblocks.append(('', [subdir_entry, afile_entry]))
1388
file_entry2 = ('subdir', '2file', '2file-id'), [
1389
('f', 'sha1value', 23, False, packed_stat), # current tree details
1391
dirblocks.append(('subdir', [file_entry2]))
1392
state = dirstate.DirState.initialize('dirstate')
1394
state._set_data([], dirblocks)
1395
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1397
self.assertEqual(expected_entries, list(state._iter_entries()))
1402
class TestGetBlockRowIndex(TestCaseWithDirState):
1404
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1405
file_present, state, dirname, basename, tree_index):
1406
self.assertEqual((block_index, row_index, dir_present, file_present),
1407
state._get_block_entry_index(dirname, basename, tree_index))
1409
block = state._dirblocks[block_index]
1410
self.assertEqual(dirname, block[0])
1411
if dir_present and file_present:
1412
row = state._dirblocks[block_index][1][row_index]
1413
self.assertEqual(dirname, row[0][0])
1414
self.assertEqual(basename, row[0][1])
1416
def test_simple_structure(self):
1417
state = self.create_dirstate_with_root_and_subdir()
1418
self.addCleanup(state.unlock)
1419
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1420
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1421
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1422
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1423
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1426
def test_complex_structure_exists(self):
1427
state = self.create_complex_dirstate()
1428
self.addCleanup(state.unlock)
1429
# Make sure we can find everything that exists
1430
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1431
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1432
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1433
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1434
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1435
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1436
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1437
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1438
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1439
'b', 'h\xc3\xa5', 0)
1441
def test_complex_structure_missing(self):
1442
state = self.create_complex_dirstate()
1443
self.addCleanup(state.unlock)
1444
# Make sure things would be inserted in the right locations
1445
# '_' comes before 'a'
1446
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1447
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1448
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1449
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1451
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1452
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1453
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1454
# This would be inserted between a/ and b/
1455
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1457
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1460
class TestGetEntry(TestCaseWithDirState):
1462
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1463
"""Check that the right entry is returned for a request to getEntry."""
1464
entry = state._get_entry(index, path_utf8=path)
1466
self.assertEqual((None, None), entry)
1469
self.assertEqual((dirname, basename, file_id), cur[:3])
1471
def test_simple_structure(self):
1472
state = self.create_dirstate_with_root_and_subdir()
1473
self.addCleanup(state.unlock)
1474
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1475
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1476
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1477
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1478
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1480
def test_complex_structure_exists(self):
1481
state = self.create_complex_dirstate()
1482
self.addCleanup(state.unlock)
1483
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1484
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1485
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1486
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1487
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1488
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1489
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1490
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1491
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1494
def test_complex_structure_missing(self):
1495
state = self.create_complex_dirstate()
1496
self.addCleanup(state.unlock)
1497
self.assertEntryEqual(None, None, None, state, '_', 0)
1498
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1499
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1500
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1502
def test_get_entry_uninitialized(self):
1503
"""Calling get_entry will load data if it needs to"""
1504
state = self.create_dirstate_with_root()
1510
state = dirstate.DirState.on_file('dirstate')
1513
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1514
state._header_state)
1515
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1516
state._dirblock_state)
1517
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1522
class TestIterChildEntries(TestCaseWithDirState):
1524
def create_dirstate_with_two_trees(self):
1525
"""This dirstate contains multiple files and directories.
1535
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1537
Notice that a/e is an empty directory.
1539
There is one parent tree, which has the same shape with the following variations:
1540
b/g in the parent is gone.
1541
b/h in the parent has a different id
1542
b/i is new in the parent
1543
c is renamed to b/j in the parent
1545
:return: The dirstate, still write-locked.
1547
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1548
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1549
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1550
root_entry = ('', '', 'a-root-value'), [
1551
('d', '', 0, False, packed_stat),
1552
('d', '', 0, False, 'parent-revid'),
1554
a_entry = ('', 'a', 'a-dir'), [
1555
('d', '', 0, False, packed_stat),
1556
('d', '', 0, False, 'parent-revid'),
1558
b_entry = ('', 'b', 'b-dir'), [
1559
('d', '', 0, False, packed_stat),
1560
('d', '', 0, False, 'parent-revid'),
1562
c_entry = ('', 'c', 'c-file'), [
1563
('f', null_sha, 10, False, packed_stat),
1564
('r', 'b/j', 0, False, ''),
1566
d_entry = ('', 'd', 'd-file'), [
1567
('f', null_sha, 20, False, packed_stat),
1568
('f', 'd', 20, False, 'parent-revid'),
1570
e_entry = ('a', 'e', 'e-dir'), [
1571
('d', '', 0, False, packed_stat),
1572
('d', '', 0, False, 'parent-revid'),
1574
f_entry = ('a', 'f', 'f-file'), [
1575
('f', null_sha, 30, False, packed_stat),
1576
('f', 'f', 20, False, 'parent-revid'),
1578
g_entry = ('b', 'g', 'g-file'), [
1579
('f', null_sha, 30, False, packed_stat),
1580
NULL_PARENT_DETAILS,
1582
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1583
('f', null_sha, 40, False, packed_stat),
1584
NULL_PARENT_DETAILS,
1586
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1587
NULL_PARENT_DETAILS,
1588
('f', 'h', 20, False, 'parent-revid'),
1590
i_entry = ('b', 'i', 'i-file'), [
1591
NULL_PARENT_DETAILS,
1592
('f', 'h', 20, False, 'parent-revid'),
1594
j_entry = ('b', 'j', 'c-file'), [
1595
('r', 'c', 0, False, ''),
1596
('f', 'j', 20, False, 'parent-revid'),
1599
dirblocks.append(('', [root_entry]))
1600
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1601
dirblocks.append(('a', [e_entry, f_entry]))
1602
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1603
state = dirstate.DirState.initialize('dirstate')
1606
state._set_data(['parent'], dirblocks)
1610
return state, dirblocks
1612
def test_iter_children_b(self):
1613
state, dirblocks = self.create_dirstate_with_two_trees()
1614
self.addCleanup(state.unlock)
1615
expected_result = []
1616
expected_result.append(dirblocks[3][1][2]) # h2
1617
expected_result.append(dirblocks[3][1][3]) # i
1618
expected_result.append(dirblocks[3][1][4]) # j
1619
self.assertEqual(expected_result,
1620
list(state._iter_child_entries(1, 'b')))
1622
def test_iter_child_root(self):
1623
state, dirblocks = self.create_dirstate_with_two_trees()
1624
self.addCleanup(state.unlock)
1625
expected_result = []
1626
expected_result.append(dirblocks[1][1][0]) # a
1627
expected_result.append(dirblocks[1][1][1]) # b
1628
expected_result.append(dirblocks[1][1][3]) # d
1629
expected_result.append(dirblocks[2][1][0]) # e
1630
expected_result.append(dirblocks[2][1][1]) # f
1631
expected_result.append(dirblocks[3][1][2]) # h2
1632
expected_result.append(dirblocks[3][1][3]) # i
1633
expected_result.append(dirblocks[3][1][4]) # j
1634
self.assertEqual(expected_result,
1635
list(state._iter_child_entries(1, '')))
1638
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1639
"""Test that DirState adds entries in the right order."""
1641
def test_add_sorting(self):
1642
"""Add entries in lexicographical order, we get path sorted order.
1644
This tests it to a depth of 4, to make sure we don't just get it right
1645
at a single depth. 'a/a' should come before 'a-a', even though it
1646
doesn't lexicographically.
1648
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1649
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1652
state = dirstate.DirState.initialize('dirstate')
1653
self.addCleanup(state.unlock)
1655
fake_stat = os.stat('dirstate')
1657
d_id = d.replace('/', '_')+'-id'
1658
file_path = d + '/f'
1659
file_id = file_path.replace('/', '_')+'-id'
1660
state.add(d, d_id, 'directory', fake_stat, null_sha)
1661
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1663
expected = ['', '', 'a',
1664
'a/a', 'a/a/a', 'a/a/a/a',
1665
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1667
split = lambda p:p.split('/')
1668
self.assertEqual(sorted(expected, key=split), expected)
1669
dirblock_names = [d[0] for d in state._dirblocks]
1670
self.assertEqual(expected, dirblock_names)
1672
def test_set_parent_trees_correct_order(self):
1673
"""After calling set_parent_trees() we should maintain the order."""
1674
dirs = ['a', 'a-a', 'a/a']
1676
state = dirstate.DirState.initialize('dirstate')
1677
self.addCleanup(state.unlock)
1679
fake_stat = os.stat('dirstate')
1681
d_id = d.replace('/', '_')+'-id'
1682
file_path = d + '/f'
1683
file_id = file_path.replace('/', '_')+'-id'
1684
state.add(d, d_id, 'directory', fake_stat, null_sha)
1685
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1687
expected = ['', '', 'a', 'a/a', 'a-a']
1688
dirblock_names = [d[0] for d in state._dirblocks]
1689
self.assertEqual(expected, dirblock_names)
1691
# *really* cheesy way to just get an empty tree
1692
repo = self.make_repository('repo')
1693
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1694
state.set_parent_trees([('null:', empty_tree)], [])
1696
dirblock_names = [d[0] for d in state._dirblocks]
1697
self.assertEqual(expected, dirblock_names)
1700
class InstrumentedDirState(dirstate.DirState):
1701
"""An DirState with instrumented sha1 functionality."""
1703
def __init__(self, path, sha1_provider):
1704
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1705
self._time_offset = 0
1707
# member is dynamically set in DirState.__init__ to turn on trace
1708
self._sha1_provider = sha1_provider
1709
self._sha1_file = self._sha1_file_and_log
1711
def _sha_cutoff_time(self):
1712
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1713
self._cutoff_time = timestamp + self._time_offset
1715
def _sha1_file_and_log(self, abspath):
1716
self._log.append(('sha1', abspath))
1717
return self._sha1_provider.sha1(abspath)
1719
def _read_link(self, abspath, old_link):
1720
self._log.append(('read_link', abspath, old_link))
1721
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1723
def _lstat(self, abspath, entry):
1724
self._log.append(('lstat', abspath))
1725
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1727
def _is_executable(self, mode, old_executable):
1728
self._log.append(('is_exec', mode, old_executable))
1729
return super(InstrumentedDirState, self)._is_executable(mode,
1732
def adjust_time(self, secs):
1733
"""Move the clock forward or back.
1735
:param secs: The amount to adjust the clock by. Positive values make it
1736
seem as if we are in the future, negative values make it seem like we
1739
self._time_offset += secs
1740
self._cutoff_time = None
1743
class _FakeStat(object):
1744
"""A class with the same attributes as a real stat result."""
1746
def __init__(self, size, mtime, ctime, dev, ino, mode):
1748
self.st_mtime = mtime
1749
self.st_ctime = ctime
1756
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1757
st.st_ino, st.st_mode)
1760
class TestPackStat(tests.TestCaseWithTransport):
1762
def assertPackStat(self, expected, stat_value):
1763
"""Check the packed and serialized form of a stat value."""
1764
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1766
def test_pack_stat_int(self):
1767
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1768
# Make sure that all parameters have an impact on the packed stat.
1769
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1772
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1773
st.st_mtime = 1172758620
1775
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1776
st.st_ctime = 1172758630
1778
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1781
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1782
st.st_ino = 6499540L
1784
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1785
st.st_mode = 0100744
1787
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1789
def test_pack_stat_float(self):
1790
"""On some platforms mtime and ctime are floats.
1792
Make sure we don't get warnings or errors, and that we ignore changes <
1795
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1796
777L, 6499538L, 0100644)
1797
# These should all be the same as the integer counterparts
1798
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1799
st.st_mtime = 1172758620.0
1801
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1802
st.st_ctime = 1172758630.0
1804
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1805
# fractional seconds are discarded, so no change from above
1806
st.st_mtime = 1172758620.453
1807
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1808
st.st_ctime = 1172758630.228
1809
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1812
class TestBisect(TestCaseWithDirState):
1813
"""Test the ability to bisect into the disk format."""
1815
def assertBisect(self, expected_map, map_keys, state, paths):
1816
"""Assert that bisecting for paths returns the right result.
1818
:param expected_map: A map from key => entry value
1819
:param map_keys: The keys to expect for each path
1820
:param state: The DirState object.
1821
:param paths: A list of paths, these will automatically be split into
1822
(dir, name) tuples, and sorted according to how _bisect
1825
result = state._bisect(paths)
1826
# For now, results are just returned in whatever order we read them.
1827
# We could sort by (dir, name, file_id) or something like that, but in
1828
# the end it would still be fairly arbitrary, and we don't want the
1829
# extra overhead if we can avoid it. So sort everything to make sure
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)
1839
# The returned values are just arranged randomly based on when they
1840
# were read, for testing, make sure it is properly sorted.
1844
self.assertEqual(expected, result)
1846
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1847
"""Assert that bisecting for dirbblocks returns the right result.
1849
:param expected_map: A map from key => expected values
1850
:param map_keys: A nested list of paths we expect to be returned.
1851
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1852
:param state: The DirState object.
1853
:param paths: A list of directories
1855
result = state._bisect_dirblocks(paths)
1856
self.assertEqual(len(map_keys), len(paths))
1858
for path, keys in zip(paths, map_keys):
1860
# This should not be present in the output
1862
expected[path] = sorted(expected_map[k] for k in keys)
1866
self.assertEqual(expected, result)
1868
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1869
"""Assert the return value of a recursive bisection.
1871
:param expected_map: A map from key => entry value
1872
:param map_keys: A list of paths we expect to be returned.
1873
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1874
:param state: The DirState object.
1875
:param paths: A list of files and directories. It will be broken up
1876
into (dir, name) pairs and sorted before calling _bisect_recursive.
1879
for key in map_keys:
1880
entry = expected_map[key]
1881
dir_name_id, trees_info = entry
1882
expected[dir_name_id] = trees_info
1884
result = state._bisect_recursive(paths)
1886
self.assertEqual(expected, result)
1888
def test_bisect_each(self):
1889
"""Find a single record using bisect."""
1890
tree, state, expected = self.create_basic_dirstate()
1892
# Bisect should return the rows for the specified files.
1893
self.assertBisect(expected, [['']], state, [''])
1894
self.assertBisect(expected, [['a']], state, ['a'])
1895
self.assertBisect(expected, [['b']], state, ['b'])
1896
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1897
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1898
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1899
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1900
self.assertBisect(expected, [['f']], state, ['f'])
1902
def test_bisect_multi(self):
1903
"""Bisect can be used to find multiple records at the same time."""
1904
tree, state, expected = self.create_basic_dirstate()
1905
# Bisect should be capable of finding multiple entries at the same time
1906
self.assertBisect(expected, [['a'], ['b'], ['f']],
1907
state, ['a', 'b', 'f'])
1908
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1909
state, ['f', 'b/d', 'b/d/e'])
1910
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1911
state, ['b', 'b-c', 'b/c'])
1913
def test_bisect_one_page(self):
1914
"""Test bisect when there is only 1 page to read"""
1915
tree, state, expected = self.create_basic_dirstate()
1916
state._bisect_page_size = 5000
1917
self.assertBisect(expected,[['']], state, [''])
1918
self.assertBisect(expected,[['a']], state, ['a'])
1919
self.assertBisect(expected,[['b']], state, ['b'])
1920
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1921
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1922
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1923
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1924
self.assertBisect(expected,[['f']], state, ['f'])
1925
self.assertBisect(expected,[['a'], ['b'], ['f']],
1926
state, ['a', 'b', 'f'])
1927
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1928
state, ['b/d', 'b/d/e', 'f'])
1929
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1930
state, ['b', 'b/c', 'b-c'])
1932
def test_bisect_duplicate_paths(self):
1933
"""When bisecting for a path, handle multiple entries."""
1934
tree, state, expected = self.create_duplicated_dirstate()
1936
# Now make sure that both records are properly returned.
1937
self.assertBisect(expected, [['']], state, [''])
1938
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1939
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1940
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1941
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1942
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1944
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1945
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1947
def test_bisect_page_size_too_small(self):
1948
"""If the page size is too small, we will auto increase it."""
1949
tree, state, expected = self.create_basic_dirstate()
1950
state._bisect_page_size = 50
1951
self.assertBisect(expected, [None], state, ['b/e'])
1952
self.assertBisect(expected, [['a']], state, ['a'])
1953
self.assertBisect(expected, [['b']], state, ['b'])
1954
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1955
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1956
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1957
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1958
self.assertBisect(expected, [['f']], state, ['f'])
1960
def test_bisect_missing(self):
1961
"""Test that bisect return None if it cannot find a path."""
1962
tree, state, expected = self.create_basic_dirstate()
1963
self.assertBisect(expected, [None], state, ['foo'])
1964
self.assertBisect(expected, [None], state, ['b/foo'])
1965
self.assertBisect(expected, [None], state, ['bar/foo'])
1966
self.assertBisect(expected, [None], state, ['b-c/foo'])
1968
self.assertBisect(expected, [['a'], None, ['b/d']],
1969
state, ['a', 'foo', 'b/d'])
1971
def test_bisect_rename(self):
1972
"""Check that we find a renamed row."""
1973
tree, state, expected = self.create_renamed_dirstate()
1975
# Search for the pre and post renamed entries
1976
self.assertBisect(expected, [['a']], state, ['a'])
1977
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1978
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1979
self.assertBisect(expected, [['h']], state, ['h'])
1981
# What about b/d/e? shouldn't that also get 2 directory entries?
1982
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1983
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1985
def test_bisect_dirblocks(self):
1986
tree, state, expected = self.create_duplicated_dirstate()
1987
self.assertBisectDirBlocks(expected,
1988
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1990
self.assertBisectDirBlocks(expected,
1991
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1992
self.assertBisectDirBlocks(expected,
1993
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1994
self.assertBisectDirBlocks(expected,
1995
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1996
['b/c', 'b/c2', 'b/d', 'b/d2'],
1997
['b/d/e', 'b/d/e2'],
1998
], state, ['', 'b', 'b/d'])
2000
def test_bisect_dirblocks_missing(self):
2001
tree, state, expected = self.create_basic_dirstate()
2002
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2003
state, ['b/d', 'b/e'])
2004
# Files don't show up in this search
2005
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2006
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2007
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2008
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2009
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2011
def test_bisect_recursive_each(self):
2012
tree, state, expected = self.create_basic_dirstate()
2013
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2014
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2015
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2016
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2017
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2019
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2021
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2025
def test_bisect_recursive_multiple(self):
2026
tree, state, expected = self.create_basic_dirstate()
2027
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2028
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2029
state, ['b/d', 'b/d/e'])
2031
def test_bisect_recursive_missing(self):
2032
tree, state, expected = self.create_basic_dirstate()
2033
self.assertBisectRecursive(expected, [], state, ['d'])
2034
self.assertBisectRecursive(expected, [], state, ['b/e'])
2035
self.assertBisectRecursive(expected, [], state, ['g'])
2036
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2038
def test_bisect_recursive_renamed(self):
2039
tree, state, expected = self.create_renamed_dirstate()
2041
# Looking for either renamed item should find the other
2042
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2043
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2044
# Looking in the containing directory should find the rename target,
2045
# and anything in a subdir of the renamed target.
2046
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2047
'b/d/e', 'b/g', 'h', 'h/e'],
2051
class TestDirstateValidation(TestCaseWithDirState):
2053
def test_validate_correct_dirstate(self):
2054
state = self.create_complex_dirstate()
2057
# and make sure we can also validate with a read lock
2064
def test_dirblock_not_sorted(self):
2065
tree, state, expected = self.create_renamed_dirstate()
2066
state._read_dirblocks_if_needed()
2067
last_dirblock = state._dirblocks[-1]
2068
# we're appending to the dirblock, but this name comes before some of
2069
# the existing names; that's wrong
2070
last_dirblock[1].append(
2071
(('h', 'aaaa', 'a-id'),
2072
[('a', '', 0, False, ''),
2073
('a', '', 0, False, '')]))
2074
e = self.assertRaises(AssertionError,
2076
self.assertContainsRe(str(e), 'not sorted')
2078
def test_dirblock_name_mismatch(self):
2079
tree, state, expected = self.create_renamed_dirstate()
2080
state._read_dirblocks_if_needed()
2081
last_dirblock = state._dirblocks[-1]
2082
# add an entry with the wrong directory name
2083
last_dirblock[1].append(
2085
[('a', '', 0, False, ''),
2086
('a', '', 0, False, '')]))
2087
e = self.assertRaises(AssertionError,
2089
self.assertContainsRe(str(e),
2090
"doesn't match directory name")
2092
def test_dirblock_missing_rename(self):
2093
tree, state, expected = self.create_renamed_dirstate()
2094
state._read_dirblocks_if_needed()
2095
last_dirblock = state._dirblocks[-1]
2096
# make another entry for a-id, without a correct 'r' pointer to
2097
# the real occurrence in the working tree
2098
last_dirblock[1].append(
2099
(('h', 'z', 'a-id'),
2100
[('a', '', 0, False, ''),
2101
('a', '', 0, False, '')]))
2102
e = self.assertRaises(AssertionError,
2104
self.assertContainsRe(str(e),
2105
'file a-id is absent in row')
2108
class TestDirstateTreeReference(TestCaseWithDirState):
2110
def test_reference_revision_is_none(self):
2111
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2112
subtree = self.make_branch_and_tree('tree/subtree',
2113
format='dirstate-with-subtree')
2114
subtree.set_root_id('subtree')
2115
tree.add_reference(subtree)
2117
state = dirstate.DirState.from_tree(tree, 'dirstate')
2118
key = ('', 'subtree', 'subtree')
2119
expected = ('', [(key,
2120
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2123
self.assertEqual(expected, state._find_block(key))
2128
class TestDiscardMergeParents(TestCaseWithDirState):
2130
def test_discard_no_parents(self):
2131
# This should be a no-op
2132
state = self.create_empty_dirstate()
2133
self.addCleanup(state.unlock)
2134
state._discard_merge_parents()
2137
def test_discard_one_parent(self):
2139
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2140
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'], dirblocks[:])
2153
state._discard_merge_parents()
2155
self.assertEqual(dirblocks, state._dirblocks)
2157
def test_discard_simple(self):
2159
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2160
root_entry_direntry = ('', '', 'a-root-value'), [
2161
('d', '', 0, False, packed_stat),
2162
('d', '', 0, False, packed_stat),
2163
('d', '', 0, False, packed_stat),
2165
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2166
('d', '', 0, False, packed_stat),
2167
('d', '', 0, False, packed_stat),
2170
dirblocks.append(('', [root_entry_direntry]))
2171
dirblocks.append(('', []))
2173
state = self.create_empty_dirstate()
2174
self.addCleanup(state.unlock)
2175
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2178
# This should strip of the extra column
2179
state._discard_merge_parents()
2181
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2182
self.assertEqual(expected_dirblocks, state._dirblocks)
2184
def test_discard_absent(self):
2185
"""If entries are only in a merge, discard should remove the entries"""
2186
null_stat = dirstate.DirState.NULLSTAT
2187
present_dir = ('d', '', 0, False, null_stat)
2188
present_file = ('f', '', 0, False, null_stat)
2189
absent = dirstate.DirState.NULL_PARENT_DETAILS
2190
root_key = ('', '', 'a-root-value')
2191
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2192
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2193
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2194
('', [(file_in_merged_key,
2195
[absent, absent, present_file]),
2197
[present_file, present_file, present_file]),
2201
state = self.create_empty_dirstate()
2202
self.addCleanup(state.unlock)
2203
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2206
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2207
('', [(file_in_root_key,
2208
[present_file, present_file]),
2211
state._discard_merge_parents()
2213
self.assertEqual(exp_dirblocks, state._dirblocks)
2215
def test_discard_renamed(self):
2216
null_stat = dirstate.DirState.NULLSTAT
2217
present_dir = ('d', '', 0, False, null_stat)
2218
present_file = ('f', '', 0, False, null_stat)
2219
absent = dirstate.DirState.NULL_PARENT_DETAILS
2220
root_key = ('', '', 'a-root-value')
2221
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2222
# Renamed relative to parent
2223
file_rename_s_key = ('', 'file-s', 'b-file-id')
2224
file_rename_t_key = ('', 'file-t', 'b-file-id')
2225
# And one that is renamed between the parents, but absent in this
2226
key_in_1 = ('', 'file-in-1', 'c-file-id')
2227
key_in_2 = ('', 'file-in-2', 'c-file-id')
2230
('', [(root_key, [present_dir, present_dir, present_dir])]),
2232
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2234
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2236
[present_file, present_file, present_file]),
2238
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2240
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2244
('', [(root_key, [present_dir, present_dir])]),
2245
('', [(key_in_1, [absent, present_file]),
2246
(file_in_root_key, [present_file, present_file]),
2247
(file_rename_t_key, [present_file, absent]),
2250
state = self.create_empty_dirstate()
2251
self.addCleanup(state.unlock)
2252
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2255
state._discard_merge_parents()
2257
self.assertEqual(exp_dirblocks, state._dirblocks)
2259
def test_discard_all_subdir(self):
2260
null_stat = dirstate.DirState.NULLSTAT
2261
present_dir = ('d', '', 0, False, null_stat)
2262
present_file = ('f', '', 0, False, null_stat)
2263
absent = dirstate.DirState.NULL_PARENT_DETAILS
2264
root_key = ('', '', 'a-root-value')
2265
subdir_key = ('', 'sub', 'dir-id')
2266
child1_key = ('sub', 'child1', 'child1-id')
2267
child2_key = ('sub', 'child2', 'child2-id')
2268
child3_key = ('sub', 'child3', 'child3-id')
2271
('', [(root_key, [present_dir, present_dir, present_dir])]),
2272
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2273
('sub', [(child1_key, [absent, absent, present_file]),
2274
(child2_key, [absent, absent, present_file]),
2275
(child3_key, [absent, absent, present_file]),
2279
('', [(root_key, [present_dir, present_dir])]),
2280
('', [(subdir_key, [present_dir, present_dir])]),
2283
state = self.create_empty_dirstate()
2284
self.addCleanup(state.unlock)
2285
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2288
state._discard_merge_parents()
2290
self.assertEqual(exp_dirblocks, state._dirblocks)
2293
class Test_InvEntryToDetails(tests.TestCase):
2295
def assertDetails(self, expected, inv_entry):
2296
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2297
self.assertEqual(expected, details)
2298
# details should always allow join() and always be a plain str when
2300
(minikind, fingerprint, size, executable, tree_data) = details
2301
self.assertIsInstance(minikind, str)
2302
self.assertIsInstance(fingerprint, str)
2303
self.assertIsInstance(tree_data, str)
2305
def test_unicode_symlink(self):
2306
inv_entry = inventory.InventoryLink('link-file-id',
2307
u'nam\N{Euro Sign}e',
2309
inv_entry.revision = 'link-revision-id'
2310
target = u'link-targ\N{Euro Sign}t'
2311
inv_entry.symlink_target = target
2312
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2313
'link-revision-id'), inv_entry)
2316
class TestSHA1Provider(tests.TestCaseInTempDir):
2318
def test_sha1provider_is_an_interface(self):
2319
p = dirstate.SHA1Provider()
2320
self.assertRaises(NotImplementedError, p.sha1, "foo")
2321
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2323
def test_defaultsha1provider_sha1(self):
2324
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2325
self.build_tree_contents([('foo', text)])
2326
expected_sha = osutils.sha_string(text)
2327
p = dirstate.DefaultSHA1Provider()
2328
self.assertEqual(expected_sha, p.sha1('foo'))
2330
def test_defaultsha1provider_stat_and_sha1(self):
2331
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2332
self.build_tree_contents([('foo', text)])
2333
expected_sha = osutils.sha_string(text)
2334
p = dirstate.DefaultSHA1Provider()
2335
statvalue, sha1 = p.stat_and_sha1('foo')
2336
self.assertTrue(len(statvalue) >= 10)
2337
self.assertEqual(len(text), statvalue.st_size)
2338
self.assertEqual(expected_sha, sha1)