1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
revision as _mod_revision,
31
from bzrlib.tests import test_osutils
36
# general checks for NOT_IN_MEMORY error conditions.
37
# set_path_id on a NOT_IN_MEMORY dirstate
38
# set_path_id unicode support
39
# set_path_id setting id of a path not root
40
# set_path_id setting id when there are parents without the id in the parents
41
# set_path_id setting id when there are parents with the id in the parents
42
# set_path_id setting id when state is not in memory
43
# set_path_id setting id when state is in memory unmodified
44
# set_path_id setting id when state is in memory modified
47
def load_tests(basic_tests, module, loader):
48
suite = loader.suiteClass()
49
dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
50
basic_tests, tests.condition_isinstance(TestCaseWithDirState))
51
tests.multiply_tests(dir_reader_tests,
52
test_osutils.dir_reader_scenarios(), suite)
53
suite.addTest(remaining_tests)
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
61
_dir_reader_class = None
62
_native_to_unicode = None # Not used yet
65
tests.TestCaseWithTransport.setUp(self)
67
# Save platform specific info and reset it
68
cur_dir_reader = osutils._selected_dir_reader
71
osutils._selected_dir_reader = cur_dir_reader
72
self.addCleanup(restore)
74
osutils._selected_dir_reader = self._dir_reader_class()
76
def create_empty_dirstate(self):
77
"""Return a locked but empty dirstate"""
78
state = dirstate.DirState.initialize('dirstate')
81
def create_dirstate_with_root(self):
82
"""Return a write-locked state with a single root entry."""
83
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
84
root_entry_direntry = ('', '', 'a-root-value'), [
85
('d', '', 0, False, packed_stat),
88
dirblocks.append(('', [root_entry_direntry]))
89
dirblocks.append(('', []))
90
state = self.create_empty_dirstate()
92
state._set_data([], dirblocks)
99
def create_dirstate_with_root_and_subdir(self):
100
"""Return a locked DirState with a root and a subdir"""
101
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
102
subdir_entry = ('', 'subdir', 'subdir-id'), [
103
('d', '', 0, False, packed_stat),
105
state = self.create_dirstate_with_root()
107
dirblocks = list(state._dirblocks)
108
dirblocks[1][1].append(subdir_entry)
109
state._set_data([], dirblocks)
115
def create_complex_dirstate(self):
116
"""This dirstate contains multiple files and directories.
126
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
128
Notice that a/e is an empty directory.
130
:return: The dirstate, still write-locked.
132
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
133
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
134
root_entry = ('', '', 'a-root-value'), [
135
('d', '', 0, False, packed_stat),
137
a_entry = ('', 'a', 'a-dir'), [
138
('d', '', 0, False, packed_stat),
140
b_entry = ('', 'b', 'b-dir'), [
141
('d', '', 0, False, packed_stat),
143
c_entry = ('', 'c', 'c-file'), [
144
('f', null_sha, 10, False, packed_stat),
146
d_entry = ('', 'd', 'd-file'), [
147
('f', null_sha, 20, False, packed_stat),
149
e_entry = ('a', 'e', 'e-dir'), [
150
('d', '', 0, False, packed_stat),
152
f_entry = ('a', 'f', 'f-file'), [
153
('f', null_sha, 30, False, packed_stat),
155
g_entry = ('b', 'g', 'g-file'), [
156
('f', null_sha, 30, False, packed_stat),
158
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
159
('f', null_sha, 40, False, packed_stat),
162
dirblocks.append(('', [root_entry]))
163
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
164
dirblocks.append(('a', [e_entry, f_entry]))
165
dirblocks.append(('b', [g_entry, h_entry]))
166
state = dirstate.DirState.initialize('dirstate')
169
state._set_data([], dirblocks)
175
def check_state_with_reopen(self, expected_result, state):
176
"""Check that state has current state expected_result.
178
This will check the current state, open the file anew and check it
180
This function expects the current state to be locked for writing, and
181
will unlock it before re-opening.
182
This is required because we can't open a lock_read() while something
183
else has a lock_write().
184
write => mutually exclusive lock
187
# The state should already be write locked, since we just had to do
188
# some operation to get here.
189
self.assertTrue(state._lock_token is not None)
191
self.assertEqual(expected_result[0], state.get_parent_ids())
192
# there should be no ghosts in this tree.
193
self.assertEqual([], state.get_ghosts())
194
# there should be one fileid in this tree - the root of the tree.
195
self.assertEqual(expected_result[1], list(state._iter_entries()))
200
state = dirstate.DirState.on_file('dirstate')
203
self.assertEqual(expected_result[1], list(state._iter_entries()))
207
def create_basic_dirstate(self):
208
"""Create a dirstate with a few files and directories.
218
tree = self.make_branch_and_tree('tree')
219
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
220
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
221
self.build_tree(['tree/' + p for p in paths])
222
tree.set_root_id('TREE_ROOT')
223
tree.add([p.rstrip('/') for p in paths], file_ids)
224
tree.commit('initial', rev_id='rev-1')
225
revision_id = 'rev-1'
226
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
227
t = self.get_transport('tree')
228
a_text = t.get_bytes('a')
229
a_sha = osutils.sha_string(a_text)
231
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
232
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
233
c_text = t.get_bytes('b/c')
234
c_sha = osutils.sha_string(c_text)
236
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
237
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
238
e_text = t.get_bytes('b/d/e')
239
e_sha = osutils.sha_string(e_text)
241
b_c_text = t.get_bytes('b-c')
242
b_c_sha = osutils.sha_string(b_c_text)
243
b_c_len = len(b_c_text)
244
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
245
f_text = t.get_bytes('f')
246
f_sha = osutils.sha_string(f_text)
248
null_stat = dirstate.DirState.NULLSTAT
250
'':(('', '', 'TREE_ROOT'), [
251
('d', '', 0, False, null_stat),
252
('d', '', 0, False, revision_id),
254
'a':(('', 'a', 'a-id'), [
255
('f', '', 0, False, null_stat),
256
('f', a_sha, a_len, False, revision_id),
258
'b':(('', 'b', 'b-id'), [
259
('d', '', 0, False, null_stat),
260
('d', '', 0, False, revision_id),
262
'b/c':(('b', 'c', 'c-id'), [
263
('f', '', 0, False, null_stat),
264
('f', c_sha, c_len, False, revision_id),
266
'b/d':(('b', 'd', 'd-id'), [
267
('d', '', 0, False, null_stat),
268
('d', '', 0, False, revision_id),
270
'b/d/e':(('b/d', 'e', 'e-id'), [
271
('f', '', 0, False, null_stat),
272
('f', e_sha, e_len, False, revision_id),
274
'b-c':(('', 'b-c', 'b-c-id'), [
275
('f', '', 0, False, null_stat),
276
('f', b_c_sha, b_c_len, False, revision_id),
278
'f':(('', 'f', 'f-id'), [
279
('f', '', 0, False, null_stat),
280
('f', f_sha, f_len, False, revision_id),
283
state = dirstate.DirState.from_tree(tree, 'dirstate')
288
# Use a different object, to make sure nothing is pre-cached in memory.
289
state = dirstate.DirState.on_file('dirstate')
291
self.addCleanup(state.unlock)
292
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
293
state._dirblock_state)
294
# This is code is only really tested if we actually have to make more
295
# than one read, so set the page size to something smaller.
296
# We want it to contain about 2.2 records, so that we have a couple
297
# records that we can read per attempt
298
state._bisect_page_size = 200
299
return tree, state, expected
301
def create_duplicated_dirstate(self):
302
"""Create a dirstate with a deleted and added entries.
304
This grabs a basic_dirstate, and then removes and re adds every entry
307
tree, state, expected = self.create_basic_dirstate()
308
# Now we will just remove and add every file so we get an extra entry
309
# per entry. Unversion in reverse order so we handle subdirs
310
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
311
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
312
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
314
# Update the expected dictionary.
315
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
316
orig = expected[path]
318
# This record was deleted in the current tree
319
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
321
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
322
# And didn't exist in the basis tree
323
expected[path2] = (new_key, [orig[1][0],
324
dirstate.DirState.NULL_PARENT_DETAILS])
326
# We will replace the 'dirstate' file underneath 'state', but that is
327
# okay as lock as we unlock 'state' first.
330
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
336
# But we need to leave state in a read-lock because we already have
337
# a cleanup scheduled
339
return tree, state, expected
341
def create_renamed_dirstate(self):
342
"""Create a dirstate with a few internal renames.
344
This takes the basic dirstate, and moves the paths around.
346
tree, state, expected = self.create_basic_dirstate()
348
tree.rename_one('a', 'b/g')
350
tree.rename_one('b/d', 'h')
352
old_a = expected['a']
353
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
354
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
355
('r', 'a', 0, False, '')])
356
old_d = expected['b/d']
357
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
358
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
359
('r', 'b/d', 0, False, '')])
361
old_e = expected['b/d/e']
362
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
364
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
365
('r', 'b/d/e', 0, False, '')])
369
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
376
return tree, state, expected
379
class TestTreeToDirState(TestCaseWithDirState):
381
def test_empty_to_dirstate(self):
382
"""We should be able to create a dirstate for an empty tree."""
383
# There are no files on disk and no parents
384
tree = self.make_branch_and_tree('tree')
385
expected_result = ([], [
386
(('', '', tree.get_root_id()), # common details
387
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
389
state = dirstate.DirState.from_tree(tree, 'dirstate')
391
self.check_state_with_reopen(expected_result, state)
393
def test_1_parents_empty_to_dirstate(self):
394
# create a parent by doing a commit
395
tree = self.make_branch_and_tree('tree')
396
rev_id = tree.commit('first post').encode('utf8')
397
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
398
expected_result = ([rev_id], [
399
(('', '', tree.get_root_id()), # common details
400
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
401
('d', '', 0, False, rev_id), # first parent details
403
state = dirstate.DirState.from_tree(tree, 'dirstate')
404
self.check_state_with_reopen(expected_result, state)
411
def test_2_parents_empty_to_dirstate(self):
412
# create a parent by doing a commit
413
tree = self.make_branch_and_tree('tree')
414
rev_id = tree.commit('first post')
415
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
416
rev_id2 = tree2.commit('second post', allow_pointless=True)
417
tree.merge_from_branch(tree2.branch)
418
expected_result = ([rev_id, rev_id2], [
419
(('', '', tree.get_root_id()), # common details
420
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
421
('d', '', 0, False, rev_id), # first parent details
422
('d', '', 0, False, rev_id2), # second parent details
424
state = dirstate.DirState.from_tree(tree, 'dirstate')
425
self.check_state_with_reopen(expected_result, state)
432
def test_empty_unknowns_are_ignored_to_dirstate(self):
433
"""We should be able to create a dirstate for an empty tree."""
434
# There are no files on disk and no parents
435
tree = self.make_branch_and_tree('tree')
436
self.build_tree(['tree/unknown'])
437
expected_result = ([], [
438
(('', '', tree.get_root_id()), # common details
439
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
441
state = dirstate.DirState.from_tree(tree, 'dirstate')
442
self.check_state_with_reopen(expected_result, state)
444
def get_tree_with_a_file(self):
445
tree = self.make_branch_and_tree('tree')
446
self.build_tree(['tree/a file'])
447
tree.add('a file', 'a-file-id')
450
def test_non_empty_no_parents_to_dirstate(self):
451
"""We should be able to create a dirstate for an empty tree."""
452
# There are files on disk and no parents
453
tree = self.get_tree_with_a_file()
454
expected_result = ([], [
455
(('', '', tree.get_root_id()), # common details
456
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
458
(('', 'a file', 'a-file-id'), # common
459
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
462
state = dirstate.DirState.from_tree(tree, 'dirstate')
463
self.check_state_with_reopen(expected_result, state)
465
def test_1_parents_not_empty_to_dirstate(self):
466
# create a parent by doing a commit
467
tree = self.get_tree_with_a_file()
468
rev_id = tree.commit('first post').encode('utf8')
469
# change the current content to be different this will alter stat, sha
471
self.build_tree_contents([('tree/a file', 'new content\n')])
472
expected_result = ([rev_id], [
473
(('', '', tree.get_root_id()), # common details
474
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
475
('d', '', 0, False, rev_id), # first parent details
477
(('', 'a file', 'a-file-id'), # common
478
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
479
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
480
rev_id), # first parent
483
state = dirstate.DirState.from_tree(tree, 'dirstate')
484
self.check_state_with_reopen(expected_result, state)
486
def test_2_parents_not_empty_to_dirstate(self):
487
# create a parent by doing a commit
488
tree = self.get_tree_with_a_file()
489
rev_id = tree.commit('first post').encode('utf8')
490
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
491
# change the current content to be different this will alter stat, sha
493
self.build_tree_contents([('tree2/a file', 'merge content\n')])
494
rev_id2 = tree2.commit('second post').encode('utf8')
495
tree.merge_from_branch(tree2.branch)
496
# change the current content to be different this will alter stat, sha
497
# and length again, giving us three distinct values:
498
self.build_tree_contents([('tree/a file', 'new content\n')])
499
expected_result = ([rev_id, rev_id2], [
500
(('', '', tree.get_root_id()), # common details
501
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
502
('d', '', 0, False, rev_id), # first parent details
503
('d', '', 0, False, rev_id2), # second parent details
505
(('', 'a file', 'a-file-id'), # common
506
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
507
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
508
rev_id), # first parent
509
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
510
rev_id2), # second parent
513
state = dirstate.DirState.from_tree(tree, 'dirstate')
514
self.check_state_with_reopen(expected_result, state)
516
def test_colliding_fileids(self):
517
# test insertion of parents creating several entries at the same path.
518
# we used to have a bug where they could cause the dirstate to break
519
# its ordering invariants.
520
# create some trees to test from
523
tree = self.make_branch_and_tree('tree%d' % i)
524
self.build_tree(['tree%d/name' % i,])
525
tree.add(['name'], ['file-id%d' % i])
526
revision_id = 'revid-%d' % i
527
tree.commit('message', rev_id=revision_id)
528
parents.append((revision_id,
529
tree.branch.repository.revision_tree(revision_id)))
530
# now fold these trees into a dirstate
531
state = dirstate.DirState.initialize('dirstate')
533
state.set_parent_trees(parents, [])
539
class TestDirStateOnFile(TestCaseWithDirState):
541
def test_construct_with_path(self):
542
tree = self.make_branch_and_tree('tree')
543
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
544
# we want to be able to get the lines of the dirstate that we will
546
lines = state.get_lines()
548
self.build_tree_contents([('dirstate', ''.join(lines))])
550
# no parents, default tree content
551
expected_result = ([], [
552
(('', '', tree.get_root_id()), # common details
553
# current tree details, but new from_tree skips statting, it
554
# uses set_state_from_inventory, and thus depends on the
556
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
559
state = dirstate.DirState.on_file('dirstate')
560
state.lock_write() # check_state_with_reopen will save() and unlock it
561
self.check_state_with_reopen(expected_result, state)
563
def test_can_save_clean_on_file(self):
564
tree = self.make_branch_and_tree('tree')
565
state = dirstate.DirState.from_tree(tree, 'dirstate')
567
# doing a save should work here as there have been no changes.
569
# TODO: stat it and check it hasn't changed; may require waiting
570
# for the state accuracy window.
574
def test_can_save_in_read_lock(self):
575
self.build_tree(['a-file'])
576
state = dirstate.DirState.initialize('dirstate')
578
# No stat and no sha1 sum.
579
state.add('a-file', 'a-file-id', 'file', None, '')
584
# Now open in readonly mode
585
state = dirstate.DirState.on_file('dirstate')
588
entry = state._get_entry(0, path_utf8='a-file')
589
# The current size should be 0 (default)
590
self.assertEqual(0, entry[1][0][2])
591
# We should have a real entry.
592
self.assertNotEqual((None, None), entry)
593
# Make sure everything is old enough
594
state._sha_cutoff_time()
595
state._cutoff_time += 10
596
# Change the file length
597
self.build_tree_contents([('a-file', 'shorter')])
598
sha1sum = dirstate.update_entry(state, entry, 'a-file',
600
# new file, no cached sha:
601
self.assertEqual(None, sha1sum)
603
# The dirblock has been updated
604
self.assertEqual(7, entry[1][0][2])
605
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
606
state._dirblock_state)
609
# Now, since we are the only one holding a lock, we should be able
610
# to save and have it written to disk
615
# Re-open the file, and ensure that the state has been updated.
616
state = dirstate.DirState.on_file('dirstate')
619
entry = state._get_entry(0, path_utf8='a-file')
620
self.assertEqual(7, entry[1][0][2])
624
def test_save_fails_quietly_if_locked(self):
625
"""If dirstate is locked, save will fail without complaining."""
626
self.build_tree(['a-file'])
627
state = dirstate.DirState.initialize('dirstate')
629
# No stat and no sha1 sum.
630
state.add('a-file', 'a-file-id', 'file', None, '')
635
state = dirstate.DirState.on_file('dirstate')
638
entry = state._get_entry(0, path_utf8='a-file')
639
sha1sum = dirstate.update_entry(state, entry, 'a-file',
642
self.assertEqual(None, sha1sum)
643
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
644
state._dirblock_state)
646
# Now, before we try to save, grab another dirstate, and take out a
648
# TODO: jam 20070315 Ideally this would be locked by another
649
# process. To make sure the file is really OS locked.
650
state2 = dirstate.DirState.on_file('dirstate')
653
# This won't actually write anything, because it couldn't grab
654
# a write lock. But it shouldn't raise an error, either.
655
# TODO: jam 20070315 We should probably distinguish between
656
# being dirty because of 'update_entry'. And dirty
657
# because of real modification. So that save() *does*
658
# raise a real error if it fails when we have real
666
# The file on disk should not be modified.
667
state = dirstate.DirState.on_file('dirstate')
670
entry = state._get_entry(0, path_utf8='a-file')
671
self.assertEqual('', entry[1][0][1])
675
def test_save_refuses_if_changes_aborted(self):
676
self.build_tree(['a-file', 'a-dir/'])
677
state = dirstate.DirState.initialize('dirstate')
679
# No stat and no sha1 sum.
680
state.add('a-file', 'a-file-id', 'file', None, '')
685
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
687
('', [(('', '', 'TREE_ROOT'),
688
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
689
('', [(('', 'a-file', 'a-file-id'),
690
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
693
state = dirstate.DirState.on_file('dirstate')
696
state._read_dirblocks_if_needed()
697
self.assertEqual(expected_blocks, state._dirblocks)
699
# Now modify the state, but mark it as inconsistent
700
state.add('a-dir', 'a-dir-id', 'directory', None, '')
701
state._changes_aborted = True
706
state = dirstate.DirState.on_file('dirstate')
709
state._read_dirblocks_if_needed()
710
self.assertEqual(expected_blocks, state._dirblocks)
715
class TestDirStateInitialize(TestCaseWithDirState):
717
def test_initialize(self):
718
expected_result = ([], [
719
(('', '', 'TREE_ROOT'), # common details
720
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
723
state = dirstate.DirState.initialize('dirstate')
725
self.assertIsInstance(state, dirstate.DirState)
726
lines = state.get_lines()
729
# On win32 you can't read from a locked file, even within the same
730
# process. So we have to unlock and release before we check the file
732
self.assertFileEqual(''.join(lines), 'dirstate')
733
state.lock_read() # check_state_with_reopen will unlock
734
self.check_state_with_reopen(expected_result, state)
737
class TestDirStateManipulations(TestCaseWithDirState):
739
def test_set_state_from_inventory_no_content_no_parents(self):
740
# setting the current inventory is a slow but important api to support.
741
tree1 = self.make_branch_and_memory_tree('tree1')
745
revid1 = tree1.commit('foo').encode('utf8')
746
root_id = tree1.get_root_id()
747
inv = tree1.inventory
750
expected_result = [], [
751
(('', '', root_id), [
752
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
753
state = dirstate.DirState.initialize('dirstate')
755
state.set_state_from_inventory(inv)
756
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
758
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
759
state._dirblock_state)
764
# This will unlock it
765
self.check_state_with_reopen(expected_result, state)
767
def test_set_state_from_inventory_preserves_hashcache(self):
768
# https://bugs.launchpad.net/bzr/+bug/146176
769
# set_state_from_inventory should preserve the stat and hash value for
770
# workingtree files that are not changed by the inventory.
772
tree = self.make_branch_and_tree('.')
773
# depends on the default format using dirstate...
776
# make a dirstate with some valid hashcache data
777
# file on disk, but that's not needed for this test
778
foo_contents = 'contents of foo'
779
self.build_tree_contents([('foo', foo_contents)])
780
tree.add('foo', 'foo-id')
782
foo_stat = os.stat('foo')
783
foo_packed = dirstate.pack_stat(foo_stat)
784
foo_sha = osutils.sha_string(foo_contents)
785
foo_size = len(foo_contents)
787
# should not be cached yet, because the file's too fresh
789
(('', 'foo', 'foo-id',),
790
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
791
tree._dirstate._get_entry(0, 'foo-id'))
792
# poke in some hashcache information - it wouldn't normally be
793
# stored because it's too fresh
794
tree._dirstate.update_minimal(
795
('', 'foo', 'foo-id'),
796
'f', False, foo_sha, foo_packed, foo_size, 'foo')
797
# now should be cached
799
(('', 'foo', 'foo-id',),
800
[('f', foo_sha, foo_size, False, foo_packed)]),
801
tree._dirstate._get_entry(0, 'foo-id'))
803
# extract the inventory, and add something to it
804
inv = tree._get_inventory()
805
# should see the file we poked in...
806
self.assertTrue(inv.has_id('foo-id'))
807
self.assertTrue(inv.has_filename('foo'))
808
inv.add_path('bar', 'file', 'bar-id')
809
tree._dirstate._validate()
810
# this used to cause it to lose its hashcache
811
tree._dirstate.set_state_from_inventory(inv)
812
tree._dirstate._validate()
818
# now check that the state still has the original hashcache value
819
state = tree._dirstate
821
foo_tuple = state._get_entry(0, path_utf8='foo')
823
(('', 'foo', 'foo-id',),
824
[('f', foo_sha, len(foo_contents), False,
825
dirstate.pack_stat(foo_stat))]),
831
def test_set_state_from_inventory_mixed_paths(self):
832
tree1 = self.make_branch_and_tree('tree1')
833
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
834
'tree1/a/b/foo', 'tree1/a-b/bar'])
837
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
838
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
839
tree1.commit('rev1', rev_id='rev1')
840
root_id = tree1.get_root_id()
841
inv = tree1.inventory
844
expected_result1 = [('', '', root_id, 'd'),
845
('', 'a', 'a-id', 'd'),
846
('', 'a-b', 'a-b-id', 'd'),
847
('a', 'b', 'b-id', 'd'),
848
('a/b', 'foo', 'foo-id', 'f'),
849
('a-b', 'bar', 'bar-id', 'f'),
851
expected_result2 = [('', '', root_id, 'd'),
852
('', 'a', 'a-id', 'd'),
853
('', 'a-b', 'a-b-id', 'd'),
854
('a-b', 'bar', 'bar-id', 'f'),
856
state = dirstate.DirState.initialize('dirstate')
858
state.set_state_from_inventory(inv)
860
for entry in state._iter_entries():
861
values.append(entry[0] + entry[1][0][:1])
862
self.assertEqual(expected_result1, values)
864
state.set_state_from_inventory(inv)
866
for entry in state._iter_entries():
867
values.append(entry[0] + entry[1][0][:1])
868
self.assertEqual(expected_result2, values)
872
def test_set_path_id_no_parents(self):
873
"""The id of a path can be changed trivally with no parents."""
874
state = dirstate.DirState.initialize('dirstate')
876
# check precondition to be sure the state does change appropriately.
878
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
879
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
880
list(state._iter_entries()))
881
state.set_path_id('', 'foobarbaz')
883
(('', '', 'foobarbaz'), [('d', '', 0, False,
884
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
885
self.assertEqual(expected_rows, list(state._iter_entries()))
886
# should work across save too
890
state = dirstate.DirState.on_file('dirstate')
894
self.assertEqual(expected_rows, list(state._iter_entries()))
898
def test_set_path_id_with_parents(self):
899
"""Set the root file id in a dirstate with parents"""
900
mt = self.make_branch_and_tree('mt')
901
# in case the default tree format uses a different root id
902
mt.set_root_id('TREE_ROOT')
903
mt.commit('foo', rev_id='parent-revid')
904
rt = mt.branch.repository.revision_tree('parent-revid')
905
state = dirstate.DirState.initialize('dirstate')
908
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
909
state.set_path_id('', 'foobarbaz')
911
# now see that it is what we expected
913
(('', '', 'TREE_ROOT'),
914
[('a', '', 0, False, ''),
915
('d', '', 0, False, 'parent-revid'),
917
(('', '', 'foobarbaz'),
918
[('d', '', 0, False, ''),
919
('a', '', 0, False, ''),
923
self.assertEqual(expected_rows, list(state._iter_entries()))
924
# should work across save too
928
# now flush & check we get the same
929
state = dirstate.DirState.on_file('dirstate')
933
self.assertEqual(expected_rows, list(state._iter_entries()))
936
# now change within an existing file-backed state
940
state.set_path_id('', 'tree-root-2')
946
def test_set_parent_trees_no_content(self):
947
# set_parent_trees is a slow but important api to support.
948
tree1 = self.make_branch_and_memory_tree('tree1')
952
revid1 = tree1.commit('foo')
955
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
956
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
959
revid2 = tree2.commit('foo')
960
root_id = tree2.get_root_id()
963
state = dirstate.DirState.initialize('dirstate')
965
state.set_path_id('', root_id)
966
state.set_parent_trees(
967
((revid1, tree1.branch.repository.revision_tree(revid1)),
968
(revid2, tree2.branch.repository.revision_tree(revid2)),
969
('ghost-rev', None)),
971
# check we can reopen and use the dirstate after setting parent
978
state = dirstate.DirState.on_file('dirstate')
981
self.assertEqual([revid1, revid2, 'ghost-rev'],
982
state.get_parent_ids())
983
# iterating the entire state ensures that the state is parsable.
984
list(state._iter_entries())
985
# be sure that it sets not appends - change it
986
state.set_parent_trees(
987
((revid1, tree1.branch.repository.revision_tree(revid1)),
988
('ghost-rev', None)),
990
# and now put it back.
991
state.set_parent_trees(
992
((revid1, tree1.branch.repository.revision_tree(revid1)),
993
(revid2, tree2.branch.repository.revision_tree(revid2)),
994
('ghost-rev', tree2.branch.repository.revision_tree(
995
_mod_revision.NULL_REVISION))),
997
self.assertEqual([revid1, revid2, 'ghost-rev'],
998
state.get_parent_ids())
999
# the ghost should be recorded as such by set_parent_trees.
1000
self.assertEqual(['ghost-rev'], state.get_ghosts())
1002
[(('', '', root_id), [
1003
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1004
('d', '', 0, False, revid1),
1005
('d', '', 0, False, revid2)
1007
list(state._iter_entries()))
1011
def test_set_parent_trees_file_missing_from_tree(self):
1012
# Adding a parent tree may reference files not in the current state.
1013
# they should get listed just once by id, even if they are in two
1015
# set_parent_trees is a slow but important api to support.
1016
tree1 = self.make_branch_and_memory_tree('tree1')
1020
tree1.add(['a file'], ['file-id'], ['file'])
1021
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1022
revid1 = tree1.commit('foo')
1025
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1026
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1029
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1030
revid2 = tree2.commit('foo')
1031
root_id = tree2.get_root_id()
1034
# check the layout in memory
1035
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1036
(('', '', root_id), [
1037
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1038
('d', '', 0, False, revid1.encode('utf8')),
1039
('d', '', 0, False, revid2.encode('utf8'))
1041
(('', 'a file', 'file-id'), [
1042
('a', '', 0, False, ''),
1043
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1044
revid1.encode('utf8')),
1045
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1046
revid2.encode('utf8'))
1049
state = dirstate.DirState.initialize('dirstate')
1051
state.set_path_id('', root_id)
1052
state.set_parent_trees(
1053
((revid1, tree1.branch.repository.revision_tree(revid1)),
1054
(revid2, tree2.branch.repository.revision_tree(revid2)),
1060
# check_state_with_reopen will unlock
1061
self.check_state_with_reopen(expected_result, state)
1063
### add a path via _set_data - so we dont need delta work, just
1064
# raw data in, and ensure that it comes out via get_lines happily.
1066
def test_add_path_to_root_no_parents_all_data(self):
1067
# The most trivial addition of a path is when there are no parents and
1068
# its in the root and all data about the file is supplied
1069
self.build_tree(['a file'])
1070
stat = os.lstat('a file')
1071
# the 1*20 is the sha1 pretend value.
1072
state = dirstate.DirState.initialize('dirstate')
1073
expected_entries = [
1074
(('', '', 'TREE_ROOT'), [
1075
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1077
(('', 'a file', 'a-file-id'), [
1078
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1082
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1083
# having added it, it should be in the output of iter_entries.
1084
self.assertEqual(expected_entries, list(state._iter_entries()))
1085
# saving and reloading should not affect this.
1089
state = dirstate.DirState.on_file('dirstate')
1091
self.addCleanup(state.unlock)
1092
self.assertEqual(expected_entries, list(state._iter_entries()))
1094
def test_add_path_to_unversioned_directory(self):
1095
"""Adding a path to an unversioned directory should error.
1097
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1098
once dirstate is stable and if it is merged with WorkingTree3, consider
1099
removing this copy of the test.
1101
self.build_tree(['unversioned/', 'unversioned/a file'])
1102
state = dirstate.DirState.initialize('dirstate')
1103
self.addCleanup(state.unlock)
1104
self.assertRaises(errors.NotVersionedError, state.add,
1105
'unversioned/a file', 'a-file-id', 'file', None, None)
1107
def test_add_directory_to_root_no_parents_all_data(self):
1108
# The most trivial addition of a dir is when there are no parents and
1109
# its in the root and all data about the file is supplied
1110
self.build_tree(['a dir/'])
1111
stat = os.lstat('a dir')
1112
expected_entries = [
1113
(('', '', 'TREE_ROOT'), [
1114
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1116
(('', 'a dir', 'a dir id'), [
1117
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1120
state = dirstate.DirState.initialize('dirstate')
1122
state.add('a dir', 'a dir id', 'directory', stat, None)
1123
# having added it, it should be in the output of iter_entries.
1124
self.assertEqual(expected_entries, list(state._iter_entries()))
1125
# saving and reloading should not affect this.
1129
state = dirstate.DirState.on_file('dirstate')
1131
self.addCleanup(state.unlock)
1133
self.assertEqual(expected_entries, list(state._iter_entries()))
1135
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1136
# The most trivial addition of a symlink when there are no parents and
1137
# its in the root and all data about the file is supplied
1138
# bzr doesn't support fake symlinks on windows, yet.
1139
self.requireFeature(tests.SymlinkFeature)
1140
os.symlink(target, link_name)
1141
stat = os.lstat(link_name)
1142
expected_entries = [
1143
(('', '', 'TREE_ROOT'), [
1144
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1146
(('', link_name.encode('UTF-8'), 'a link id'), [
1147
('l', target.encode('UTF-8'), stat[6],
1148
False, dirstate.pack_stat(stat)), # current tree
1151
state = dirstate.DirState.initialize('dirstate')
1153
state.add(link_name, 'a link id', 'symlink', stat,
1154
target.encode('UTF-8'))
1155
# having added it, it should be in the output of iter_entries.
1156
self.assertEqual(expected_entries, list(state._iter_entries()))
1157
# saving and reloading should not affect this.
1161
state = dirstate.DirState.on_file('dirstate')
1163
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):
1167
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1169
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1170
self.requireFeature(tests.UnicodeFilenameFeature)
1171
self._test_add_symlink_to_root_no_parents_all_data(
1172
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1174
def test_add_directory_and_child_no_parents_all_data(self):
1175
# after adding a directory, we should be able to add children to it.
1176
self.build_tree(['a dir/', 'a dir/a file'])
1177
dirstat = os.lstat('a dir')
1178
filestat = os.lstat('a dir/a file')
1179
expected_entries = [
1180
(('', '', 'TREE_ROOT'), [
1181
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1183
(('', 'a dir', 'a dir id'), [
1184
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1186
(('a dir', 'a file', 'a-file-id'), [
1187
('f', '1'*20, 25, False,
1188
dirstate.pack_stat(filestat)), # current tree details
1191
state = dirstate.DirState.initialize('dirstate')
1193
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1194
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1195
# added it, it should be in the output of iter_entries.
1196
self.assertEqual(expected_entries, list(state._iter_entries()))
1197
# saving and reloading should not affect this.
1201
state = dirstate.DirState.on_file('dirstate')
1203
self.addCleanup(state.unlock)
1204
self.assertEqual(expected_entries, list(state._iter_entries()))
1206
def test_add_tree_reference(self):
1207
# make a dirstate and add a tree reference
1208
state = dirstate.DirState.initialize('dirstate')
1210
('', 'subdir', 'subdir-id'),
1211
[('t', 'subtree-123123', 0, False,
1212
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1215
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1216
entry = state._get_entry(0, 'subdir-id', 'subdir')
1217
self.assertEqual(entry, expected_entry)
1222
# now check we can read it back
1224
self.addCleanup(state.unlock)
1226
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1227
self.assertEqual(entry, entry2)
1228
self.assertEqual(entry, expected_entry)
1229
# and lookup by id should work too
1230
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1231
self.assertEqual(entry, expected_entry)
1233
def test_add_forbidden_names(self):
1234
state = dirstate.DirState.initialize('dirstate')
1235
self.addCleanup(state.unlock)
1236
self.assertRaises(errors.BzrError,
1237
state.add, '.', 'ass-id', 'directory', None, None)
1238
self.assertRaises(errors.BzrError,
1239
state.add, '..', 'ass-id', 'directory', None, None)
1242
class TestGetLines(TestCaseWithDirState):
1244
def test_get_line_with_2_rows(self):
1245
state = self.create_dirstate_with_root_and_subdir()
1247
self.assertEqual(['#bazaar dirstate flat format 3\n',
1248
'crc32: 41262208\n',
1252
'\x00\x00a-root-value\x00'
1253
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1254
'\x00subdir\x00subdir-id\x00'
1255
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1256
], state.get_lines())
1260
def test_entry_to_line(self):
1261
state = self.create_dirstate_with_root()
1264
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1265
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1266
state._entry_to_line(state._dirblocks[0][1][0]))
1270
def test_entry_to_line_with_parent(self):
1271
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1272
root_entry = ('', '', 'a-root-value'), [
1273
('d', '', 0, False, packed_stat), # current tree details
1274
# first: a pointer to the current location
1275
('a', 'dirname/basename', 0, False, ''),
1277
state = dirstate.DirState.initialize('dirstate')
1280
'\x00\x00a-root-value\x00'
1281
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1282
'a\x00dirname/basename\x000\x00n\x00',
1283
state._entry_to_line(root_entry))
1287
def test_entry_to_line_with_two_parents_at_different_paths(self):
1288
# / in the tree, at / in one parent and /dirname/basename in the other.
1289
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1290
root_entry = ('', '', 'a-root-value'), [
1291
('d', '', 0, False, packed_stat), # current tree details
1292
('d', '', 0, False, 'rev_id'), # first parent details
1293
# second: a pointer to the current location
1294
('a', 'dirname/basename', 0, False, ''),
1296
state = dirstate.DirState.initialize('dirstate')
1299
'\x00\x00a-root-value\x00'
1300
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1301
'd\x00\x000\x00n\x00rev_id\x00'
1302
'a\x00dirname/basename\x000\x00n\x00',
1303
state._entry_to_line(root_entry))
1307
def test_iter_entries(self):
1308
# we should be able to iterate the dirstate entries from end to end
1309
# this is for get_lines to be easy to read.
1310
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1312
root_entries = [(('', '', 'a-root-value'), [
1313
('d', '', 0, False, packed_stat), # current tree details
1315
dirblocks.append(('', root_entries))
1316
# add two files in the root
1317
subdir_entry = ('', 'subdir', 'subdir-id'), [
1318
('d', '', 0, False, packed_stat), # current tree details
1320
afile_entry = ('', 'afile', 'afile-id'), [
1321
('f', 'sha1value', 34, False, packed_stat), # current tree details
1323
dirblocks.append(('', [subdir_entry, afile_entry]))
1325
file_entry2 = ('subdir', '2file', '2file-id'), [
1326
('f', 'sha1value', 23, False, packed_stat), # current tree details
1328
dirblocks.append(('subdir', [file_entry2]))
1329
state = dirstate.DirState.initialize('dirstate')
1331
state._set_data([], dirblocks)
1332
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1334
self.assertEqual(expected_entries, list(state._iter_entries()))
1339
class TestGetBlockRowIndex(TestCaseWithDirState):
1341
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1342
file_present, state, dirname, basename, tree_index):
1343
self.assertEqual((block_index, row_index, dir_present, file_present),
1344
state._get_block_entry_index(dirname, basename, tree_index))
1346
block = state._dirblocks[block_index]
1347
self.assertEqual(dirname, block[0])
1348
if dir_present and file_present:
1349
row = state._dirblocks[block_index][1][row_index]
1350
self.assertEqual(dirname, row[0][0])
1351
self.assertEqual(basename, row[0][1])
1353
def test_simple_structure(self):
1354
state = self.create_dirstate_with_root_and_subdir()
1355
self.addCleanup(state.unlock)
1356
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1357
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1358
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1359
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1360
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1363
def test_complex_structure_exists(self):
1364
state = self.create_complex_dirstate()
1365
self.addCleanup(state.unlock)
1366
# Make sure we can find everything that exists
1367
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1368
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1369
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1370
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1371
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1372
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1373
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1374
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1375
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1376
'b', 'h\xc3\xa5', 0)
1378
def test_complex_structure_missing(self):
1379
state = self.create_complex_dirstate()
1380
self.addCleanup(state.unlock)
1381
# Make sure things would be inserted in the right locations
1382
# '_' comes before 'a'
1383
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1384
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1385
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1386
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1388
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1389
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1390
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1391
# This would be inserted between a/ and b/
1392
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1394
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1397
class TestGetEntry(TestCaseWithDirState):
1399
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1400
"""Check that the right entry is returned for a request to getEntry."""
1401
entry = state._get_entry(index, path_utf8=path)
1403
self.assertEqual((None, None), entry)
1406
self.assertEqual((dirname, basename, file_id), cur[:3])
1408
def test_simple_structure(self):
1409
state = self.create_dirstate_with_root_and_subdir()
1410
self.addCleanup(state.unlock)
1411
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1412
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1413
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1414
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1415
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1417
def test_complex_structure_exists(self):
1418
state = self.create_complex_dirstate()
1419
self.addCleanup(state.unlock)
1420
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1421
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1422
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1423
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1424
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1425
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1426
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1427
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1428
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1431
def test_complex_structure_missing(self):
1432
state = self.create_complex_dirstate()
1433
self.addCleanup(state.unlock)
1434
self.assertEntryEqual(None, None, None, state, '_', 0)
1435
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1436
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1437
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1439
def test_get_entry_uninitialized(self):
1440
"""Calling get_entry will load data if it needs to"""
1441
state = self.create_dirstate_with_root()
1447
state = dirstate.DirState.on_file('dirstate')
1450
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1451
state._header_state)
1452
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1453
state._dirblock_state)
1454
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1459
class TestIterChildEntries(TestCaseWithDirState):
1461
def create_dirstate_with_two_trees(self):
1462
"""This dirstate contains multiple files and directories.
1472
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1474
Notice that a/e is an empty directory.
1476
There is one parent tree, which has the same shape with the following variations:
1477
b/g in the parent is gone.
1478
b/h in the parent has a different id
1479
b/i is new in the parent
1480
c is renamed to b/j in the parent
1482
:return: The dirstate, still write-locked.
1484
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1485
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1486
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1487
root_entry = ('', '', 'a-root-value'), [
1488
('d', '', 0, False, packed_stat),
1489
('d', '', 0, False, 'parent-revid'),
1491
a_entry = ('', 'a', 'a-dir'), [
1492
('d', '', 0, False, packed_stat),
1493
('d', '', 0, False, 'parent-revid'),
1495
b_entry = ('', 'b', 'b-dir'), [
1496
('d', '', 0, False, packed_stat),
1497
('d', '', 0, False, 'parent-revid'),
1499
c_entry = ('', 'c', 'c-file'), [
1500
('f', null_sha, 10, False, packed_stat),
1501
('r', 'b/j', 0, False, ''),
1503
d_entry = ('', 'd', 'd-file'), [
1504
('f', null_sha, 20, False, packed_stat),
1505
('f', 'd', 20, False, 'parent-revid'),
1507
e_entry = ('a', 'e', 'e-dir'), [
1508
('d', '', 0, False, packed_stat),
1509
('d', '', 0, False, 'parent-revid'),
1511
f_entry = ('a', 'f', 'f-file'), [
1512
('f', null_sha, 30, False, packed_stat),
1513
('f', 'f', 20, False, 'parent-revid'),
1515
g_entry = ('b', 'g', 'g-file'), [
1516
('f', null_sha, 30, False, packed_stat),
1517
NULL_PARENT_DETAILS,
1519
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1520
('f', null_sha, 40, False, packed_stat),
1521
NULL_PARENT_DETAILS,
1523
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1524
NULL_PARENT_DETAILS,
1525
('f', 'h', 20, False, 'parent-revid'),
1527
i_entry = ('b', 'i', 'i-file'), [
1528
NULL_PARENT_DETAILS,
1529
('f', 'h', 20, False, 'parent-revid'),
1531
j_entry = ('b', 'j', 'c-file'), [
1532
('r', 'c', 0, False, ''),
1533
('f', 'j', 20, False, 'parent-revid'),
1536
dirblocks.append(('', [root_entry]))
1537
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1538
dirblocks.append(('a', [e_entry, f_entry]))
1539
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1540
state = dirstate.DirState.initialize('dirstate')
1543
state._set_data(['parent'], dirblocks)
1547
return state, dirblocks
1549
def test_iter_children_b(self):
1550
state, dirblocks = self.create_dirstate_with_two_trees()
1551
self.addCleanup(state.unlock)
1552
expected_result = []
1553
expected_result.append(dirblocks[3][1][2]) # h2
1554
expected_result.append(dirblocks[3][1][3]) # i
1555
expected_result.append(dirblocks[3][1][4]) # j
1556
self.assertEqual(expected_result,
1557
list(state._iter_child_entries(1, 'b')))
1559
def test_iter_child_root(self):
1560
state, dirblocks = self.create_dirstate_with_two_trees()
1561
self.addCleanup(state.unlock)
1562
expected_result = []
1563
expected_result.append(dirblocks[1][1][0]) # a
1564
expected_result.append(dirblocks[1][1][1]) # b
1565
expected_result.append(dirblocks[1][1][3]) # d
1566
expected_result.append(dirblocks[2][1][0]) # e
1567
expected_result.append(dirblocks[2][1][1]) # f
1568
expected_result.append(dirblocks[3][1][2]) # h2
1569
expected_result.append(dirblocks[3][1][3]) # i
1570
expected_result.append(dirblocks[3][1][4]) # j
1571
self.assertEqual(expected_result,
1572
list(state._iter_child_entries(1, '')))
1575
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1576
"""Test that DirState adds entries in the right order."""
1578
def test_add_sorting(self):
1579
"""Add entries in lexicographical order, we get path sorted order.
1581
This tests it to a depth of 4, to make sure we don't just get it right
1582
at a single depth. 'a/a' should come before 'a-a', even though it
1583
doesn't lexicographically.
1585
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1586
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1589
state = dirstate.DirState.initialize('dirstate')
1590
self.addCleanup(state.unlock)
1592
fake_stat = os.stat('dirstate')
1594
d_id = d.replace('/', '_')+'-id'
1595
file_path = d + '/f'
1596
file_id = file_path.replace('/', '_')+'-id'
1597
state.add(d, d_id, 'directory', fake_stat, null_sha)
1598
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1600
expected = ['', '', 'a',
1601
'a/a', 'a/a/a', 'a/a/a/a',
1602
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1604
split = lambda p:p.split('/')
1605
self.assertEqual(sorted(expected, key=split), expected)
1606
dirblock_names = [d[0] for d in state._dirblocks]
1607
self.assertEqual(expected, dirblock_names)
1609
def test_set_parent_trees_correct_order(self):
1610
"""After calling set_parent_trees() we should maintain the order."""
1611
dirs = ['a', 'a-a', 'a/a']
1613
state = dirstate.DirState.initialize('dirstate')
1614
self.addCleanup(state.unlock)
1616
fake_stat = os.stat('dirstate')
1618
d_id = d.replace('/', '_')+'-id'
1619
file_path = d + '/f'
1620
file_id = file_path.replace('/', '_')+'-id'
1621
state.add(d, d_id, 'directory', fake_stat, null_sha)
1622
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1624
expected = ['', '', 'a', 'a/a', 'a-a']
1625
dirblock_names = [d[0] for d in state._dirblocks]
1626
self.assertEqual(expected, dirblock_names)
1628
# *really* cheesy way to just get an empty tree
1629
repo = self.make_repository('repo')
1630
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1631
state.set_parent_trees([('null:', empty_tree)], [])
1633
dirblock_names = [d[0] for d in state._dirblocks]
1634
self.assertEqual(expected, dirblock_names)
1637
class InstrumentedDirState(dirstate.DirState):
1638
"""An DirState with instrumented sha1 functionality."""
1640
def __init__(self, path, sha1_provider):
1641
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1642
self._time_offset = 0
1644
# member is dynamically set in DirState.__init__ to turn on trace
1645
self._sha1_provider = sha1_provider
1646
self._sha1_file = self._sha1_file_and_log
1648
def _sha_cutoff_time(self):
1649
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1650
self._cutoff_time = timestamp + self._time_offset
1652
def _sha1_file_and_log(self, abspath):
1653
self._log.append(('sha1', abspath))
1654
return self._sha1_provider.sha1(abspath)
1656
def _read_link(self, abspath, old_link):
1657
self._log.append(('read_link', abspath, old_link))
1658
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1660
def _lstat(self, abspath, entry):
1661
self._log.append(('lstat', abspath))
1662
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1664
def _is_executable(self, mode, old_executable):
1665
self._log.append(('is_exec', mode, old_executable))
1666
return super(InstrumentedDirState, self)._is_executable(mode,
1669
def adjust_time(self, secs):
1670
"""Move the clock forward or back.
1672
:param secs: The amount to adjust the clock by. Positive values make it
1673
seem as if we are in the future, negative values make it seem like we
1676
self._time_offset += secs
1677
self._cutoff_time = None
1680
class _FakeStat(object):
1681
"""A class with the same attributes as a real stat result."""
1683
def __init__(self, size, mtime, ctime, dev, ino, mode):
1685
self.st_mtime = mtime
1686
self.st_ctime = ctime
1693
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1694
st.st_ino, st.st_mode)
1697
class TestPackStat(tests.TestCaseWithTransport):
1699
def assertPackStat(self, expected, stat_value):
1700
"""Check the packed and serialized form of a stat value."""
1701
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1703
def test_pack_stat_int(self):
1704
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1705
# Make sure that all parameters have an impact on the packed stat.
1706
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1709
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1710
st.st_mtime = 1172758620
1712
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1713
st.st_ctime = 1172758630
1715
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1718
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1719
st.st_ino = 6499540L
1721
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1722
st.st_mode = 0100744
1724
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1726
def test_pack_stat_float(self):
1727
"""On some platforms mtime and ctime are floats.
1729
Make sure we don't get warnings or errors, and that we ignore changes <
1732
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1733
777L, 6499538L, 0100644)
1734
# These should all be the same as the integer counterparts
1735
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1736
st.st_mtime = 1172758620.0
1738
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1739
st.st_ctime = 1172758630.0
1741
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1742
# fractional seconds are discarded, so no change from above
1743
st.st_mtime = 1172758620.453
1744
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1745
st.st_ctime = 1172758630.228
1746
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1749
class TestBisect(TestCaseWithDirState):
1750
"""Test the ability to bisect into the disk format."""
1752
def assertBisect(self, expected_map, map_keys, state, paths):
1753
"""Assert that bisecting for paths returns the right result.
1755
:param expected_map: A map from key => entry value
1756
:param map_keys: The keys to expect for each path
1757
:param state: The DirState object.
1758
:param paths: A list of paths, these will automatically be split into
1759
(dir, name) tuples, and sorted according to how _bisect
1762
result = state._bisect(paths)
1763
# For now, results are just returned in whatever order we read them.
1764
# We could sort by (dir, name, file_id) or something like that, but in
1765
# the end it would still be fairly arbitrary, and we don't want the
1766
# extra overhead if we can avoid it. So sort everything to make sure
1768
self.assertEqual(len(map_keys), len(paths))
1770
for path, keys in zip(paths, map_keys):
1772
# This should not be present in the output
1774
expected[path] = sorted(expected_map[k] for k in keys)
1776
# The returned values are just arranged randomly based on when they
1777
# were read, for testing, make sure it is properly sorted.
1781
self.assertEqual(expected, result)
1783
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1784
"""Assert that bisecting for dirbblocks returns the right result.
1786
:param expected_map: A map from key => expected values
1787
:param map_keys: A nested list of paths we expect to be returned.
1788
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1789
:param state: The DirState object.
1790
:param paths: A list of directories
1792
result = state._bisect_dirblocks(paths)
1793
self.assertEqual(len(map_keys), len(paths))
1795
for path, keys in zip(paths, map_keys):
1797
# This should not be present in the output
1799
expected[path] = sorted(expected_map[k] for k in keys)
1803
self.assertEqual(expected, result)
1805
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1806
"""Assert the return value of a recursive bisection.
1808
:param expected_map: A map from key => entry value
1809
:param map_keys: A list of paths we expect to be returned.
1810
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1811
:param state: The DirState object.
1812
:param paths: A list of files and directories. It will be broken up
1813
into (dir, name) pairs and sorted before calling _bisect_recursive.
1816
for key in map_keys:
1817
entry = expected_map[key]
1818
dir_name_id, trees_info = entry
1819
expected[dir_name_id] = trees_info
1821
result = state._bisect_recursive(paths)
1823
self.assertEqual(expected, result)
1825
def test_bisect_each(self):
1826
"""Find a single record using bisect."""
1827
tree, state, expected = self.create_basic_dirstate()
1829
# Bisect should return the rows for the specified files.
1830
self.assertBisect(expected, [['']], state, [''])
1831
self.assertBisect(expected, [['a']], state, ['a'])
1832
self.assertBisect(expected, [['b']], state, ['b'])
1833
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1834
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1835
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1836
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1837
self.assertBisect(expected, [['f']], state, ['f'])
1839
def test_bisect_multi(self):
1840
"""Bisect can be used to find multiple records at the same time."""
1841
tree, state, expected = self.create_basic_dirstate()
1842
# Bisect should be capable of finding multiple entries at the same time
1843
self.assertBisect(expected, [['a'], ['b'], ['f']],
1844
state, ['a', 'b', 'f'])
1845
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1846
state, ['f', 'b/d', 'b/d/e'])
1847
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1848
state, ['b', 'b-c', 'b/c'])
1850
def test_bisect_one_page(self):
1851
"""Test bisect when there is only 1 page to read"""
1852
tree, state, expected = self.create_basic_dirstate()
1853
state._bisect_page_size = 5000
1854
self.assertBisect(expected,[['']], state, [''])
1855
self.assertBisect(expected,[['a']], state, ['a'])
1856
self.assertBisect(expected,[['b']], state, ['b'])
1857
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1858
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1859
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1860
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1861
self.assertBisect(expected,[['f']], state, ['f'])
1862
self.assertBisect(expected,[['a'], ['b'], ['f']],
1863
state, ['a', 'b', 'f'])
1864
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1865
state, ['b/d', 'b/d/e', 'f'])
1866
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1867
state, ['b', 'b/c', 'b-c'])
1869
def test_bisect_duplicate_paths(self):
1870
"""When bisecting for a path, handle multiple entries."""
1871
tree, state, expected = self.create_duplicated_dirstate()
1873
# Now make sure that both records are properly returned.
1874
self.assertBisect(expected, [['']], state, [''])
1875
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1876
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1877
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1878
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1879
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1881
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1882
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1884
def test_bisect_page_size_too_small(self):
1885
"""If the page size is too small, we will auto increase it."""
1886
tree, state, expected = self.create_basic_dirstate()
1887
state._bisect_page_size = 50
1888
self.assertBisect(expected, [None], state, ['b/e'])
1889
self.assertBisect(expected, [['a']], state, ['a'])
1890
self.assertBisect(expected, [['b']], state, ['b'])
1891
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1892
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1893
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1894
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1895
self.assertBisect(expected, [['f']], state, ['f'])
1897
def test_bisect_missing(self):
1898
"""Test that bisect return None if it cannot find a path."""
1899
tree, state, expected = self.create_basic_dirstate()
1900
self.assertBisect(expected, [None], state, ['foo'])
1901
self.assertBisect(expected, [None], state, ['b/foo'])
1902
self.assertBisect(expected, [None], state, ['bar/foo'])
1903
self.assertBisect(expected, [None], state, ['b-c/foo'])
1905
self.assertBisect(expected, [['a'], None, ['b/d']],
1906
state, ['a', 'foo', 'b/d'])
1908
def test_bisect_rename(self):
1909
"""Check that we find a renamed row."""
1910
tree, state, expected = self.create_renamed_dirstate()
1912
# Search for the pre and post renamed entries
1913
self.assertBisect(expected, [['a']], state, ['a'])
1914
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1915
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1916
self.assertBisect(expected, [['h']], state, ['h'])
1918
# What about b/d/e? shouldn't that also get 2 directory entries?
1919
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1920
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1922
def test_bisect_dirblocks(self):
1923
tree, state, expected = self.create_duplicated_dirstate()
1924
self.assertBisectDirBlocks(expected,
1925
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1927
self.assertBisectDirBlocks(expected,
1928
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1929
self.assertBisectDirBlocks(expected,
1930
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1931
self.assertBisectDirBlocks(expected,
1932
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1933
['b/c', 'b/c2', 'b/d', 'b/d2'],
1934
['b/d/e', 'b/d/e2'],
1935
], state, ['', 'b', 'b/d'])
1937
def test_bisect_dirblocks_missing(self):
1938
tree, state, expected = self.create_basic_dirstate()
1939
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1940
state, ['b/d', 'b/e'])
1941
# Files don't show up in this search
1942
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1943
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1944
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1945
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1946
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1948
def test_bisect_recursive_each(self):
1949
tree, state, expected = self.create_basic_dirstate()
1950
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1951
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1952
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1953
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1954
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1956
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1958
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1962
def test_bisect_recursive_multiple(self):
1963
tree, state, expected = self.create_basic_dirstate()
1964
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1965
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1966
state, ['b/d', 'b/d/e'])
1968
def test_bisect_recursive_missing(self):
1969
tree, state, expected = self.create_basic_dirstate()
1970
self.assertBisectRecursive(expected, [], state, ['d'])
1971
self.assertBisectRecursive(expected, [], state, ['b/e'])
1972
self.assertBisectRecursive(expected, [], state, ['g'])
1973
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1975
def test_bisect_recursive_renamed(self):
1976
tree, state, expected = self.create_renamed_dirstate()
1978
# Looking for either renamed item should find the other
1979
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1980
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1981
# Looking in the containing directory should find the rename target,
1982
# and anything in a subdir of the renamed target.
1983
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1984
'b/d/e', 'b/g', 'h', 'h/e'],
1988
class TestDirstateValidation(TestCaseWithDirState):
1990
def test_validate_correct_dirstate(self):
1991
state = self.create_complex_dirstate()
1994
# and make sure we can also validate with a read lock
2001
def test_dirblock_not_sorted(self):
2002
tree, state, expected = self.create_renamed_dirstate()
2003
state._read_dirblocks_if_needed()
2004
last_dirblock = state._dirblocks[-1]
2005
# we're appending to the dirblock, but this name comes before some of
2006
# the existing names; that's wrong
2007
last_dirblock[1].append(
2008
(('h', 'aaaa', 'a-id'),
2009
[('a', '', 0, False, ''),
2010
('a', '', 0, False, '')]))
2011
e = self.assertRaises(AssertionError,
2013
self.assertContainsRe(str(e), 'not sorted')
2015
def test_dirblock_name_mismatch(self):
2016
tree, state, expected = self.create_renamed_dirstate()
2017
state._read_dirblocks_if_needed()
2018
last_dirblock = state._dirblocks[-1]
2019
# add an entry with the wrong directory name
2020
last_dirblock[1].append(
2022
[('a', '', 0, False, ''),
2023
('a', '', 0, False, '')]))
2024
e = self.assertRaises(AssertionError,
2026
self.assertContainsRe(str(e),
2027
"doesn't match directory name")
2029
def test_dirblock_missing_rename(self):
2030
tree, state, expected = self.create_renamed_dirstate()
2031
state._read_dirblocks_if_needed()
2032
last_dirblock = state._dirblocks[-1]
2033
# make another entry for a-id, without a correct 'r' pointer to
2034
# the real occurrence in the working tree
2035
last_dirblock[1].append(
2036
(('h', 'z', 'a-id'),
2037
[('a', '', 0, False, ''),
2038
('a', '', 0, False, '')]))
2039
e = self.assertRaises(AssertionError,
2041
self.assertContainsRe(str(e),
2042
'file a-id is absent in row')
2045
class TestDirstateTreeReference(TestCaseWithDirState):
2047
def test_reference_revision_is_none(self):
2048
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2049
subtree = self.make_branch_and_tree('tree/subtree',
2050
format='dirstate-with-subtree')
2051
subtree.set_root_id('subtree')
2052
tree.add_reference(subtree)
2054
state = dirstate.DirState.from_tree(tree, 'dirstate')
2055
key = ('', 'subtree', 'subtree')
2056
expected = ('', [(key,
2057
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2060
self.assertEqual(expected, state._find_block(key))
2065
class TestDiscardMergeParents(TestCaseWithDirState):
2067
def test_discard_no_parents(self):
2068
# This should be a no-op
2069
state = self.create_empty_dirstate()
2070
self.addCleanup(state.unlock)
2071
state._discard_merge_parents()
2074
def test_discard_one_parent(self):
2076
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2077
root_entry_direntry = ('', '', 'a-root-value'), [
2078
('d', '', 0, False, packed_stat),
2079
('d', '', 0, False, packed_stat),
2082
dirblocks.append(('', [root_entry_direntry]))
2083
dirblocks.append(('', []))
2085
state = self.create_empty_dirstate()
2086
self.addCleanup(state.unlock)
2087
state._set_data(['parent-id'], dirblocks[:])
2090
state._discard_merge_parents()
2092
self.assertEqual(dirblocks, state._dirblocks)
2094
def test_discard_simple(self):
2096
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2097
root_entry_direntry = ('', '', 'a-root-value'), [
2098
('d', '', 0, False, packed_stat),
2099
('d', '', 0, False, packed_stat),
2100
('d', '', 0, False, packed_stat),
2102
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2103
('d', '', 0, False, packed_stat),
2104
('d', '', 0, False, packed_stat),
2107
dirblocks.append(('', [root_entry_direntry]))
2108
dirblocks.append(('', []))
2110
state = self.create_empty_dirstate()
2111
self.addCleanup(state.unlock)
2112
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2115
# This should strip of the extra column
2116
state._discard_merge_parents()
2118
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2119
self.assertEqual(expected_dirblocks, state._dirblocks)
2121
def test_discard_absent(self):
2122
"""If entries are only in a merge, discard should remove the entries"""
2123
null_stat = dirstate.DirState.NULLSTAT
2124
present_dir = ('d', '', 0, False, null_stat)
2125
present_file = ('f', '', 0, False, null_stat)
2126
absent = dirstate.DirState.NULL_PARENT_DETAILS
2127
root_key = ('', '', 'a-root-value')
2128
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2129
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2130
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2131
('', [(file_in_merged_key,
2132
[absent, absent, present_file]),
2134
[present_file, present_file, present_file]),
2138
state = self.create_empty_dirstate()
2139
self.addCleanup(state.unlock)
2140
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2143
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2144
('', [(file_in_root_key,
2145
[present_file, present_file]),
2148
state._discard_merge_parents()
2150
self.assertEqual(exp_dirblocks, state._dirblocks)
2152
def test_discard_renamed(self):
2153
null_stat = dirstate.DirState.NULLSTAT
2154
present_dir = ('d', '', 0, False, null_stat)
2155
present_file = ('f', '', 0, False, null_stat)
2156
absent = dirstate.DirState.NULL_PARENT_DETAILS
2157
root_key = ('', '', 'a-root-value')
2158
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2159
# Renamed relative to parent
2160
file_rename_s_key = ('', 'file-s', 'b-file-id')
2161
file_rename_t_key = ('', 'file-t', 'b-file-id')
2162
# And one that is renamed between the parents, but absent in this
2163
key_in_1 = ('', 'file-in-1', 'c-file-id')
2164
key_in_2 = ('', 'file-in-2', 'c-file-id')
2167
('', [(root_key, [present_dir, present_dir, present_dir])]),
2169
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2171
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2173
[present_file, present_file, present_file]),
2175
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2177
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2181
('', [(root_key, [present_dir, present_dir])]),
2182
('', [(key_in_1, [absent, present_file]),
2183
(file_in_root_key, [present_file, present_file]),
2184
(file_rename_t_key, [present_file, absent]),
2187
state = self.create_empty_dirstate()
2188
self.addCleanup(state.unlock)
2189
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2192
state._discard_merge_parents()
2194
self.assertEqual(exp_dirblocks, state._dirblocks)
2196
def test_discard_all_subdir(self):
2197
null_stat = dirstate.DirState.NULLSTAT
2198
present_dir = ('d', '', 0, False, null_stat)
2199
present_file = ('f', '', 0, False, null_stat)
2200
absent = dirstate.DirState.NULL_PARENT_DETAILS
2201
root_key = ('', '', 'a-root-value')
2202
subdir_key = ('', 'sub', 'dir-id')
2203
child1_key = ('sub', 'child1', 'child1-id')
2204
child2_key = ('sub', 'child2', 'child2-id')
2205
child3_key = ('sub', 'child3', 'child3-id')
2208
('', [(root_key, [present_dir, present_dir, present_dir])]),
2209
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2210
('sub', [(child1_key, [absent, absent, present_file]),
2211
(child2_key, [absent, absent, present_file]),
2212
(child3_key, [absent, absent, present_file]),
2216
('', [(root_key, [present_dir, present_dir])]),
2217
('', [(subdir_key, [present_dir, present_dir])]),
2220
state = self.create_empty_dirstate()
2221
self.addCleanup(state.unlock)
2222
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2225
state._discard_merge_parents()
2227
self.assertEqual(exp_dirblocks, state._dirblocks)
2230
class Test_InvEntryToDetails(tests.TestCase):
2232
def assertDetails(self, expected, inv_entry):
2233
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2234
self.assertEqual(expected, details)
2235
# details should always allow join() and always be a plain str when
2237
(minikind, fingerprint, size, executable, tree_data) = details
2238
self.assertIsInstance(minikind, str)
2239
self.assertIsInstance(fingerprint, str)
2240
self.assertIsInstance(tree_data, str)
2242
def test_unicode_symlink(self):
2243
inv_entry = inventory.InventoryLink('link-file-id',
2244
u'nam\N{Euro Sign}e',
2246
inv_entry.revision = 'link-revision-id'
2247
target = u'link-targ\N{Euro Sign}t'
2248
inv_entry.symlink_target = target
2249
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2250
'link-revision-id'), inv_entry)
2253
class TestSHA1Provider(tests.TestCaseInTempDir):
2255
def test_sha1provider_is_an_interface(self):
2256
p = dirstate.SHA1Provider()
2257
self.assertRaises(NotImplementedError, p.sha1, "foo")
2258
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2260
def test_defaultsha1provider_sha1(self):
2261
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2262
self.build_tree_contents([('foo', text)])
2263
expected_sha = osutils.sha_string(text)
2264
p = dirstate.DefaultSHA1Provider()
2265
self.assertEqual(expected_sha, p.sha1('foo'))
2267
def test_defaultsha1provider_stat_and_sha1(self):
2268
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2269
self.build_tree_contents([('foo', text)])
2270
expected_sha = osutils.sha_string(text)
2271
p = dirstate.DefaultSHA1Provider()
2272
statvalue, sha1 = p.stat_and_sha1('foo')
2273
self.assertTrue(len(statvalue) >= 10)
2274
self.assertEqual(len(text), statvalue.st_size)
2275
self.assertEqual(expected_sha, sha1)