1
# Copyright (C) 2006-2011 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."""
27
revision as _mod_revision,
41
from .scenarios import load_tests_apply_scenarios
46
# general checks for NOT_IN_MEMORY error conditions.
47
# set_path_id on a NOT_IN_MEMORY dirstate
48
# set_path_id unicode support
49
# set_path_id setting id of a path not root
50
# set_path_id setting id when there are parents without the id in the parents
51
# set_path_id setting id when there are parents with the id in the parents
52
# set_path_id setting id when state is not in memory
53
# set_path_id setting id when state is in memory unmodified
54
# set_path_id setting id when state is in memory modified
57
class TestErrors(tests.TestCase):
59
def test_dirstate_corrupt(self):
60
error = dirstate.DirstateCorrupt('.bzr/checkout/dirstate',
61
'trailing garbage: "x"')
62
self.assertEqualDiff("The dirstate file (.bzr/checkout/dirstate)"
63
" appears to be corrupt: trailing garbage: \"x\"",
67
load_tests = load_tests_apply_scenarios
70
class TestCaseWithDirState(tests.TestCaseWithTransport):
71
"""Helper functions for creating DirState objects with various content."""
73
scenarios = test_osutils.dir_reader_scenarios()
76
_dir_reader_class = None
77
_native_to_unicode = None # Not used yet
80
super(TestCaseWithDirState, self).setUp()
81
self.overrideAttr(osutils,
82
'_selected_dir_reader', self._dir_reader_class())
84
def create_empty_dirstate(self):
85
"""Return a locked but empty dirstate"""
86
state = dirstate.DirState.initialize('dirstate')
89
def create_dirstate_with_root(self):
90
"""Return a write-locked state with a single root entry."""
91
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
92
root_entry_direntry = ('', '', 'a-root-value'), [
93
('d', '', 0, False, packed_stat),
96
dirblocks.append(('', [root_entry_direntry]))
97
dirblocks.append(('', []))
98
state = self.create_empty_dirstate()
100
state._set_data([], dirblocks)
107
def create_dirstate_with_root_and_subdir(self):
108
"""Return a locked DirState with a root and a subdir"""
109
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
subdir_entry = ('', 'subdir', 'subdir-id'), [
111
('d', '', 0, False, packed_stat),
113
state = self.create_dirstate_with_root()
115
dirblocks = list(state._dirblocks)
116
dirblocks[1][1].append(subdir_entry)
117
state._set_data([], dirblocks)
123
def create_complex_dirstate(self):
124
"""This dirstate contains multiple files and directories.
134
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
136
Notice that a/e is an empty directory.
138
:return: The dirstate, still write-locked.
140
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
141
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
142
root_entry = ('', '', 'a-root-value'), [
143
('d', '', 0, False, packed_stat),
145
a_entry = ('', 'a', 'a-dir'), [
146
('d', '', 0, False, packed_stat),
148
b_entry = ('', 'b', 'b-dir'), [
149
('d', '', 0, False, packed_stat),
151
c_entry = ('', 'c', 'c-file'), [
152
('f', null_sha, 10, False, packed_stat),
154
d_entry = ('', 'd', 'd-file'), [
155
('f', null_sha, 20, False, packed_stat),
157
e_entry = ('a', 'e', 'e-dir'), [
158
('d', '', 0, False, packed_stat),
160
f_entry = ('a', 'f', 'f-file'), [
161
('f', null_sha, 30, False, packed_stat),
163
g_entry = ('b', 'g', 'g-file'), [
164
('f', null_sha, 30, False, packed_stat),
166
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
167
('f', null_sha, 40, False, packed_stat),
170
dirblocks.append(('', [root_entry]))
171
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
172
dirblocks.append(('a', [e_entry, f_entry]))
173
dirblocks.append(('b', [g_entry, h_entry]))
174
state = dirstate.DirState.initialize('dirstate')
177
state._set_data([], dirblocks)
183
def check_state_with_reopen(self, expected_result, state):
184
"""Check that state has current state expected_result.
186
This will check the current state, open the file anew and check it
188
This function expects the current state to be locked for writing, and
189
will unlock it before re-opening.
190
This is required because we can't open a lock_read() while something
191
else has a lock_write().
192
write => mutually exclusive lock
195
# The state should already be write locked, since we just had to do
196
# some operation to get here.
197
self.assertTrue(state._lock_token is not None)
199
self.assertEqual(expected_result[0], state.get_parent_ids())
200
# there should be no ghosts in this tree.
201
self.assertEqual([], state.get_ghosts())
202
# there should be one fileid in this tree - the root of the tree.
203
self.assertEqual(expected_result[1], list(state._iter_entries()))
208
state = dirstate.DirState.on_file('dirstate')
211
self.assertEqual(expected_result[1], list(state._iter_entries()))
215
def create_basic_dirstate(self):
216
"""Create a dirstate with a few files and directories.
226
tree = self.make_branch_and_tree('tree')
227
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
228
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
229
self.build_tree(['tree/' + p for p in paths])
230
tree.set_root_id('TREE_ROOT')
231
tree.add([p.rstrip('/') for p in paths], file_ids)
232
tree.commit('initial', rev_id='rev-1')
233
revision_id = 'rev-1'
234
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
235
t = self.get_transport('tree')
236
a_text = t.get_bytes('a')
237
a_sha = osutils.sha_string(a_text)
239
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
240
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
241
c_text = t.get_bytes('b/c')
242
c_sha = osutils.sha_string(c_text)
244
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
245
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
246
e_text = t.get_bytes('b/d/e')
247
e_sha = osutils.sha_string(e_text)
249
b_c_text = t.get_bytes('b-c')
250
b_c_sha = osutils.sha_string(b_c_text)
251
b_c_len = len(b_c_text)
252
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
253
f_text = t.get_bytes('f')
254
f_sha = osutils.sha_string(f_text)
256
null_stat = dirstate.DirState.NULLSTAT
258
'':(('', '', 'TREE_ROOT'), [
259
('d', '', 0, False, null_stat),
260
('d', '', 0, False, revision_id),
262
'a':(('', 'a', 'a-id'), [
263
('f', '', 0, False, null_stat),
264
('f', a_sha, a_len, False, revision_id),
266
'b':(('', 'b', 'b-id'), [
267
('d', '', 0, False, null_stat),
268
('d', '', 0, False, revision_id),
270
'b/c':(('b', 'c', 'c-id'), [
271
('f', '', 0, False, null_stat),
272
('f', c_sha, c_len, False, revision_id),
274
'b/d':(('b', 'd', 'd-id'), [
275
('d', '', 0, False, null_stat),
276
('d', '', 0, False, revision_id),
278
'b/d/e':(('b/d', 'e', 'e-id'), [
279
('f', '', 0, False, null_stat),
280
('f', e_sha, e_len, False, revision_id),
282
'b-c':(('', 'b-c', 'b-c-id'), [
283
('f', '', 0, False, null_stat),
284
('f', b_c_sha, b_c_len, False, revision_id),
286
'f':(('', 'f', 'f-id'), [
287
('f', '', 0, False, null_stat),
288
('f', f_sha, f_len, False, revision_id),
291
state = dirstate.DirState.from_tree(tree, 'dirstate')
296
# Use a different object, to make sure nothing is pre-cached in memory.
297
state = dirstate.DirState.on_file('dirstate')
299
self.addCleanup(state.unlock)
300
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
301
state._dirblock_state)
302
# This is code is only really tested if we actually have to make more
303
# than one read, so set the page size to something smaller.
304
# We want it to contain about 2.2 records, so that we have a couple
305
# records that we can read per attempt
306
state._bisect_page_size = 200
307
return tree, state, expected
309
def create_duplicated_dirstate(self):
310
"""Create a dirstate with a deleted and added entries.
312
This grabs a basic_dirstate, and then removes and re adds every entry
315
tree, state, expected = self.create_basic_dirstate()
316
# Now we will just remove and add every file so we get an extra entry
317
# per entry. Unversion in reverse order so we handle subdirs
318
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
319
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
320
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
322
# Update the expected dictionary.
323
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
324
orig = expected[path]
326
# This record was deleted in the current tree
327
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
329
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
330
# And didn't exist in the basis tree
331
expected[path2] = (new_key, [orig[1][0],
332
dirstate.DirState.NULL_PARENT_DETAILS])
334
# We will replace the 'dirstate' file underneath 'state', but that is
335
# okay as lock as we unlock 'state' first.
338
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
344
# But we need to leave state in a read-lock because we already have
345
# a cleanup scheduled
347
return tree, state, expected
349
def create_renamed_dirstate(self):
350
"""Create a dirstate with a few internal renames.
352
This takes the basic dirstate, and moves the paths around.
354
tree, state, expected = self.create_basic_dirstate()
356
tree.rename_one('a', 'b/g')
358
tree.rename_one('b/d', 'h')
360
old_a = expected['a']
361
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
362
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
363
('r', 'a', 0, False, '')])
364
old_d = expected['b/d']
365
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
366
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
367
('r', 'b/d', 0, False, '')])
369
old_e = expected['b/d/e']
370
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
372
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
373
('r', 'b/d/e', 0, False, '')])
377
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
384
return tree, state, expected
387
class TestTreeToDirState(TestCaseWithDirState):
389
def test_empty_to_dirstate(self):
390
"""We should be able to create a dirstate for an empty tree."""
391
# There are no files on disk and no parents
392
tree = self.make_branch_and_tree('tree')
393
expected_result = ([], [
394
(('', '', tree.get_root_id()), # common details
395
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
state = dirstate.DirState.from_tree(tree, 'dirstate')
399
self.check_state_with_reopen(expected_result, state)
401
def test_1_parents_empty_to_dirstate(self):
402
# create a parent by doing a commit
403
tree = self.make_branch_and_tree('tree')
404
rev_id = tree.commit('first post').encode('utf8')
405
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
406
expected_result = ([rev_id], [
407
(('', '', tree.get_root_id()), # common details
408
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
409
('d', '', 0, False, rev_id), # first parent details
411
state = dirstate.DirState.from_tree(tree, 'dirstate')
412
self.check_state_with_reopen(expected_result, state)
419
def test_2_parents_empty_to_dirstate(self):
420
# create a parent by doing a commit
421
tree = self.make_branch_and_tree('tree')
422
rev_id = tree.commit('first post')
423
tree2 = tree.controldir.sprout('tree2').open_workingtree()
424
rev_id2 = tree2.commit('second post', allow_pointless=True)
425
tree.merge_from_branch(tree2.branch)
426
expected_result = ([rev_id, rev_id2], [
427
(('', '', tree.get_root_id()), # common details
428
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
429
('d', '', 0, False, rev_id), # first parent details
430
('d', '', 0, False, rev_id), # second parent details
432
state = dirstate.DirState.from_tree(tree, 'dirstate')
433
self.check_state_with_reopen(expected_result, state)
440
def test_empty_unknowns_are_ignored_to_dirstate(self):
441
"""We should be able to create a dirstate for an empty tree."""
442
# There are no files on disk and no parents
443
tree = self.make_branch_and_tree('tree')
444
self.build_tree(['tree/unknown'])
445
expected_result = ([], [
446
(('', '', tree.get_root_id()), # common details
447
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
449
state = dirstate.DirState.from_tree(tree, 'dirstate')
450
self.check_state_with_reopen(expected_result, state)
452
def get_tree_with_a_file(self):
453
tree = self.make_branch_and_tree('tree')
454
self.build_tree(['tree/a file'])
455
tree.add('a file', 'a-file-id')
458
def test_non_empty_no_parents_to_dirstate(self):
459
"""We should be able to create a dirstate for an empty tree."""
460
# There are files on disk and no parents
461
tree = self.get_tree_with_a_file()
462
expected_result = ([], [
463
(('', '', tree.get_root_id()), # common details
464
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
466
(('', 'a file', 'a-file-id'), # common
467
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
470
state = dirstate.DirState.from_tree(tree, 'dirstate')
471
self.check_state_with_reopen(expected_result, state)
473
def test_1_parents_not_empty_to_dirstate(self):
474
# create a parent by doing a commit
475
tree = self.get_tree_with_a_file()
476
rev_id = tree.commit('first post').encode('utf8')
477
# change the current content to be different this will alter stat, sha
479
self.build_tree_contents([('tree/a file', 'new content\n')])
480
expected_result = ([rev_id], [
481
(('', '', tree.get_root_id()), # common details
482
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
483
('d', '', 0, False, rev_id), # first parent details
485
(('', 'a file', 'a-file-id'), # common
486
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
487
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
488
rev_id), # first parent
491
state = dirstate.DirState.from_tree(tree, 'dirstate')
492
self.check_state_with_reopen(expected_result, state)
494
def test_2_parents_not_empty_to_dirstate(self):
495
# create a parent by doing a commit
496
tree = self.get_tree_with_a_file()
497
rev_id = tree.commit('first post').encode('utf8')
498
tree2 = tree.controldir.sprout('tree2').open_workingtree()
499
# change the current content to be different this will alter stat, sha
501
self.build_tree_contents([('tree2/a file', 'merge content\n')])
502
rev_id2 = tree2.commit('second post').encode('utf8')
503
tree.merge_from_branch(tree2.branch)
504
# change the current content to be different this will alter stat, sha
505
# and length again, giving us three distinct values:
506
self.build_tree_contents([('tree/a file', 'new content\n')])
507
expected_result = ([rev_id, rev_id2], [
508
(('', '', tree.get_root_id()), # common details
509
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
510
('d', '', 0, False, rev_id), # first parent details
511
('d', '', 0, False, rev_id), # second parent details
513
(('', 'a file', 'a-file-id'), # common
514
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
515
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
516
rev_id), # first parent
517
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
518
rev_id2), # second parent
521
state = dirstate.DirState.from_tree(tree, 'dirstate')
522
self.check_state_with_reopen(expected_result, state)
524
def test_colliding_fileids(self):
525
# test insertion of parents creating several entries at the same path.
526
# we used to have a bug where they could cause the dirstate to break
527
# its ordering invariants.
528
# create some trees to test from
531
tree = self.make_branch_and_tree('tree%d' % i)
532
self.build_tree(['tree%d/name' % i,])
533
tree.add(['name'], ['file-id%d' % i])
534
revision_id = 'revid-%d' % i
535
tree.commit('message', rev_id=revision_id)
536
parents.append((revision_id,
537
tree.branch.repository.revision_tree(revision_id)))
538
# now fold these trees into a dirstate
539
state = dirstate.DirState.initialize('dirstate')
541
state.set_parent_trees(parents, [])
547
class TestDirStateOnFile(TestCaseWithDirState):
549
def create_updated_dirstate(self):
550
self.build_tree(['a-file'])
551
tree = self.make_branch_and_tree('.')
552
tree.add(['a-file'], ['a-id'])
553
tree.commit('add a-file')
554
# Save and unlock the state, re-open it in readonly mode
555
state = dirstate.DirState.from_tree(tree, 'dirstate')
558
state = dirstate.DirState.on_file('dirstate')
562
def test_construct_with_path(self):
563
tree = self.make_branch_and_tree('tree')
564
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
565
# we want to be able to get the lines of the dirstate that we will
567
lines = state.get_lines()
569
self.build_tree_contents([('dirstate', ''.join(lines))])
571
# no parents, default tree content
572
expected_result = ([], [
573
(('', '', tree.get_root_id()), # common details
574
# current tree details, but new from_tree skips statting, it
575
# uses set_state_from_inventory, and thus depends on the
577
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
580
state = dirstate.DirState.on_file('dirstate')
581
state.lock_write() # check_state_with_reopen will save() and unlock it
582
self.check_state_with_reopen(expected_result, state)
584
def test_can_save_clean_on_file(self):
585
tree = self.make_branch_and_tree('tree')
586
state = dirstate.DirState.from_tree(tree, 'dirstate')
588
# doing a save should work here as there have been no changes.
590
# TODO: stat it and check it hasn't changed; may require waiting
591
# for the state accuracy window.
595
def test_can_save_in_read_lock(self):
596
state = self.create_updated_dirstate()
598
entry = state._get_entry(0, path_utf8='a-file')
599
# The current size should be 0 (default)
600
self.assertEqual(0, entry[1][0][2])
601
# We should have a real entry.
602
self.assertNotEqual((None, None), entry)
603
# Set the cutoff-time into the future, so things look cacheable
604
state._sha_cutoff_time()
605
state._cutoff_time += 10.0
606
st = os.lstat('a-file')
607
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
608
# We updated the current sha1sum because the file is cacheable
609
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
612
# The dirblock has been updated
613
self.assertEqual(st.st_size, entry[1][0][2])
614
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
615
state._dirblock_state)
618
# Now, since we are the only one holding a lock, we should be able
619
# to save and have it written to disk
624
# Re-open the file, and ensure that the state has been updated.
625
state = dirstate.DirState.on_file('dirstate')
628
entry = state._get_entry(0, path_utf8='a-file')
629
self.assertEqual(st.st_size, entry[1][0][2])
633
def test_save_fails_quietly_if_locked(self):
634
"""If dirstate is locked, save will fail without complaining."""
635
state = self.create_updated_dirstate()
637
entry = state._get_entry(0, path_utf8='a-file')
638
# No cached sha1 yet.
639
self.assertEqual('', entry[1][0][1])
640
# Set the cutoff-time into the future, so things look cacheable
641
state._sha_cutoff_time()
642
state._cutoff_time += 10.0
643
st = os.lstat('a-file')
644
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
645
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
647
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
648
state._dirblock_state)
650
# Now, before we try to save, grab another dirstate, and take out a
652
# TODO: jam 20070315 Ideally this would be locked by another
653
# process. To make sure the file is really OS locked.
654
state2 = dirstate.DirState.on_file('dirstate')
657
# This won't actually write anything, because it couldn't grab
658
# a write lock. But it shouldn't raise an error, either.
659
# TODO: jam 20070315 We should probably distinguish between
660
# being dirty because of 'update_entry'. And dirty
661
# because of real modification. So that save() *does*
662
# raise a real error if it fails when we have real
670
# The file on disk should not be modified.
671
state = dirstate.DirState.on_file('dirstate')
674
entry = state._get_entry(0, path_utf8='a-file')
675
self.assertEqual('', entry[1][0][1])
679
def test_save_refuses_if_changes_aborted(self):
680
self.build_tree(['a-file', 'a-dir/'])
681
state = dirstate.DirState.initialize('dirstate')
683
# No stat and no sha1 sum.
684
state.add('a-file', 'a-file-id', 'file', None, '')
689
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
691
('', [(('', '', 'TREE_ROOT'),
692
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
693
('', [(('', 'a-file', 'a-file-id'),
694
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
697
state = dirstate.DirState.on_file('dirstate')
700
state._read_dirblocks_if_needed()
701
self.assertEqual(expected_blocks, state._dirblocks)
703
# Now modify the state, but mark it as inconsistent
704
state.add('a-dir', 'a-dir-id', 'directory', None, '')
705
state._changes_aborted = True
710
state = dirstate.DirState.on_file('dirstate')
713
state._read_dirblocks_if_needed()
714
self.assertEqual(expected_blocks, state._dirblocks)
719
class TestDirStateInitialize(TestCaseWithDirState):
721
def test_initialize(self):
722
expected_result = ([], [
723
(('', '', 'TREE_ROOT'), # common details
724
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
727
state = dirstate.DirState.initialize('dirstate')
729
self.assertIsInstance(state, dirstate.DirState)
730
lines = state.get_lines()
733
# On win32 you can't read from a locked file, even within the same
734
# process. So we have to unlock and release before we check the file
736
self.assertFileEqual(''.join(lines), 'dirstate')
737
state.lock_read() # check_state_with_reopen will unlock
738
self.check_state_with_reopen(expected_result, state)
741
class TestDirStateManipulations(TestCaseWithDirState):
743
def make_minimal_tree(self):
744
tree1 = self.make_branch_and_memory_tree('tree1')
746
self.addCleanup(tree1.unlock)
748
revid1 = tree1.commit('foo')
751
def test_update_minimal_updates_id_index(self):
752
state = self.create_dirstate_with_root_and_subdir()
753
self.addCleanup(state.unlock)
754
id_index = state._get_id_index()
755
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
756
state.add('file-name', 'file-id', 'file', None, '')
757
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
759
state.update_minimal(('', 'new-name', 'file-id'), 'f',
760
path_utf8='new-name')
761
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
763
self.assertEqual([('', 'new-name', 'file-id')],
764
sorted(id_index['file-id']))
767
def test_set_state_from_inventory_no_content_no_parents(self):
768
# setting the current inventory is a slow but important api to support.
769
tree1, revid1 = self.make_minimal_tree()
770
inv = tree1.root_inventory
771
root_id = inv.path2id('')
772
expected_result = [], [
773
(('', '', root_id), [
774
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
775
state = dirstate.DirState.initialize('dirstate')
777
state.set_state_from_inventory(inv)
778
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
780
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
781
state._dirblock_state)
786
# This will unlock it
787
self.check_state_with_reopen(expected_result, state)
789
def test_set_state_from_scratch_no_parents(self):
790
tree1, revid1 = self.make_minimal_tree()
791
inv = tree1.root_inventory
792
root_id = inv.path2id('')
793
expected_result = [], [
794
(('', '', root_id), [
795
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
796
state = dirstate.DirState.initialize('dirstate')
798
state.set_state_from_scratch(inv, [], [])
799
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
801
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
802
state._dirblock_state)
807
# This will unlock it
808
self.check_state_with_reopen(expected_result, state)
810
def test_set_state_from_scratch_identical_parent(self):
811
tree1, revid1 = self.make_minimal_tree()
812
inv = tree1.root_inventory
813
root_id = inv.path2id('')
814
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
815
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
816
parent_entry = ('d', '', 0, False, revid1)
817
expected_result = [revid1], [
818
(('', '', root_id), [d_entry, parent_entry])]
819
state = dirstate.DirState.initialize('dirstate')
821
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
822
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
824
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
825
state._dirblock_state)
830
# This will unlock it
831
self.check_state_with_reopen(expected_result, state)
833
def test_set_state_from_inventory_preserves_hashcache(self):
834
# https://bugs.launchpad.net/bzr/+bug/146176
835
# set_state_from_inventory should preserve the stat and hash value for
836
# workingtree files that are not changed by the inventory.
838
tree = self.make_branch_and_tree('.')
839
# depends on the default format using dirstate...
842
# make a dirstate with some valid hashcache data
843
# file on disk, but that's not needed for this test
844
foo_contents = 'contents of foo'
845
self.build_tree_contents([('foo', foo_contents)])
846
tree.add('foo', 'foo-id')
848
foo_stat = os.stat('foo')
849
foo_packed = dirstate.pack_stat(foo_stat)
850
foo_sha = osutils.sha_string(foo_contents)
851
foo_size = len(foo_contents)
853
# should not be cached yet, because the file's too fresh
855
(('', 'foo', 'foo-id',),
856
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
857
tree._dirstate._get_entry(0, 'foo-id'))
858
# poke in some hashcache information - it wouldn't normally be
859
# stored because it's too fresh
860
tree._dirstate.update_minimal(
861
('', 'foo', 'foo-id'),
862
'f', False, foo_sha, foo_packed, foo_size, 'foo')
863
# now should be cached
865
(('', 'foo', 'foo-id',),
866
[('f', foo_sha, foo_size, False, foo_packed)]),
867
tree._dirstate._get_entry(0, 'foo-id'))
869
# extract the inventory, and add something to it
870
inv = tree._get_root_inventory()
871
# should see the file we poked in...
872
self.assertTrue(inv.has_id('foo-id'))
873
self.assertTrue(inv.has_filename('foo'))
874
inv.add_path('bar', 'file', 'bar-id')
875
tree._dirstate._validate()
876
# this used to cause it to lose its hashcache
877
tree._dirstate.set_state_from_inventory(inv)
878
tree._dirstate._validate()
884
# now check that the state still has the original hashcache value
885
state = tree._dirstate
887
foo_tuple = state._get_entry(0, path_utf8='foo')
889
(('', 'foo', 'foo-id',),
890
[('f', foo_sha, len(foo_contents), False,
891
dirstate.pack_stat(foo_stat))]),
896
def test_set_state_from_inventory_mixed_paths(self):
897
tree1 = self.make_branch_and_tree('tree1')
898
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
899
'tree1/a/b/foo', 'tree1/a-b/bar'])
902
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
903
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
904
tree1.commit('rev1', rev_id='rev1')
905
root_id = tree1.get_root_id()
906
inv = tree1.root_inventory
909
expected_result1 = [('', '', root_id, 'd'),
910
('', 'a', 'a-id', 'd'),
911
('', 'a-b', 'a-b-id', 'd'),
912
('a', 'b', 'b-id', 'd'),
913
('a/b', 'foo', 'foo-id', 'f'),
914
('a-b', 'bar', 'bar-id', 'f'),
916
expected_result2 = [('', '', root_id, 'd'),
917
('', 'a', 'a-id', 'd'),
918
('', 'a-b', 'a-b-id', 'd'),
919
('a-b', 'bar', 'bar-id', 'f'),
921
state = dirstate.DirState.initialize('dirstate')
923
state.set_state_from_inventory(inv)
925
for entry in state._iter_entries():
926
values.append(entry[0] + entry[1][0][:1])
927
self.assertEqual(expected_result1, values)
929
state.set_state_from_inventory(inv)
931
for entry in state._iter_entries():
932
values.append(entry[0] + entry[1][0][:1])
933
self.assertEqual(expected_result2, values)
937
def test_set_path_id_no_parents(self):
938
"""The id of a path can be changed trivally with no parents."""
939
state = dirstate.DirState.initialize('dirstate')
941
# check precondition to be sure the state does change appropriately.
942
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
943
self.assertEqual([root_entry], list(state._iter_entries()))
944
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
945
self.assertEqual(root_entry,
946
state._get_entry(0, fileid_utf8='TREE_ROOT'))
947
self.assertEqual((None, None),
948
state._get_entry(0, fileid_utf8='second-root-id'))
949
state.set_path_id('', 'second-root-id')
950
new_root_entry = (('', '', 'second-root-id'),
951
[('d', '', 0, False, 'x'*32)])
952
expected_rows = [new_root_entry]
953
self.assertEqual(expected_rows, list(state._iter_entries()))
954
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
955
self.assertEqual(new_root_entry,
956
state._get_entry(0, fileid_utf8='second-root-id'))
957
self.assertEqual((None, None),
958
state._get_entry(0, fileid_utf8='TREE_ROOT'))
959
# should work across save too
963
state = dirstate.DirState.on_file('dirstate')
967
self.assertEqual(expected_rows, list(state._iter_entries()))
971
def test_set_path_id_with_parents(self):
972
"""Set the root file id in a dirstate with parents"""
973
mt = self.make_branch_and_tree('mt')
974
# in case the default tree format uses a different root id
975
mt.set_root_id('TREE_ROOT')
976
mt.commit('foo', rev_id='parent-revid')
977
rt = mt.branch.repository.revision_tree('parent-revid')
978
state = dirstate.DirState.initialize('dirstate')
981
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
982
root_entry = (('', '', 'TREE_ROOT'),
983
[('d', '', 0, False, 'x'*32),
984
('d', '', 0, False, 'parent-revid')])
985
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
986
self.assertEqual(root_entry,
987
state._get_entry(0, fileid_utf8='TREE_ROOT'))
988
self.assertEqual((None, None),
989
state._get_entry(0, fileid_utf8='Asecond-root-id'))
990
state.set_path_id('', 'Asecond-root-id')
992
# now see that it is what we expected
993
old_root_entry = (('', '', 'TREE_ROOT'),
994
[('a', '', 0, False, ''),
995
('d', '', 0, False, 'parent-revid')])
996
new_root_entry = (('', '', 'Asecond-root-id'),
997
[('d', '', 0, False, ''),
998
('a', '', 0, False, '')])
999
expected_rows = [new_root_entry, old_root_entry]
1001
self.assertEqual(expected_rows, list(state._iter_entries()))
1002
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
1003
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
1004
self.assertEqual((None, None),
1005
state._get_entry(0, fileid_utf8='TREE_ROOT'))
1006
self.assertEqual(old_root_entry,
1007
state._get_entry(1, fileid_utf8='TREE_ROOT'))
1008
self.assertEqual(new_root_entry,
1009
state._get_entry(0, fileid_utf8='Asecond-root-id'))
1010
self.assertEqual((None, None),
1011
state._get_entry(1, fileid_utf8='Asecond-root-id'))
1012
# should work across save too
1016
# now flush & check we get the same
1017
state = dirstate.DirState.on_file('dirstate')
1021
self.assertEqual(expected_rows, list(state._iter_entries()))
1024
# now change within an existing file-backed state
1028
state.set_path_id('', 'tree-root-2')
1033
def test_set_parent_trees_no_content(self):
1034
# set_parent_trees is a slow but important api to support.
1035
tree1 = self.make_branch_and_memory_tree('tree1')
1039
revid1 = tree1.commit('foo')
1042
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1043
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1046
revid2 = tree2.commit('foo')
1047
root_id = tree2.get_root_id()
1050
state = dirstate.DirState.initialize('dirstate')
1052
state.set_path_id('', root_id)
1053
state.set_parent_trees(
1054
((revid1, tree1.branch.repository.revision_tree(revid1)),
1055
(revid2, tree2.branch.repository.revision_tree(revid2)),
1056
('ghost-rev', None)),
1058
# check we can reopen and use the dirstate after setting parent
1065
state = dirstate.DirState.on_file('dirstate')
1068
self.assertEqual([revid1, revid2, 'ghost-rev'],
1069
state.get_parent_ids())
1070
# iterating the entire state ensures that the state is parsable.
1071
list(state._iter_entries())
1072
# be sure that it sets not appends - change it
1073
state.set_parent_trees(
1074
((revid1, tree1.branch.repository.revision_tree(revid1)),
1075
('ghost-rev', None)),
1077
# and now put it back.
1078
state.set_parent_trees(
1079
((revid1, tree1.branch.repository.revision_tree(revid1)),
1080
(revid2, tree2.branch.repository.revision_tree(revid2)),
1081
('ghost-rev', tree2.branch.repository.revision_tree(
1082
_mod_revision.NULL_REVISION))),
1084
self.assertEqual([revid1, revid2, 'ghost-rev'],
1085
state.get_parent_ids())
1086
# the ghost should be recorded as such by set_parent_trees.
1087
self.assertEqual(['ghost-rev'], state.get_ghosts())
1089
[(('', '', root_id), [
1090
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1091
('d', '', 0, False, revid1),
1092
('d', '', 0, False, revid1)
1094
list(state._iter_entries()))
1098
def test_set_parent_trees_file_missing_from_tree(self):
1099
# Adding a parent tree may reference files not in the current state.
1100
# they should get listed just once by id, even if they are in two
1102
# set_parent_trees is a slow but important api to support.
1103
tree1 = self.make_branch_and_memory_tree('tree1')
1107
tree1.add(['a file'], ['file-id'], ['file'])
1108
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1109
revid1 = tree1.commit('foo')
1112
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1113
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1116
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1117
revid2 = tree2.commit('foo')
1118
root_id = tree2.get_root_id()
1121
# check the layout in memory
1122
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1123
(('', '', root_id), [
1124
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1125
('d', '', 0, False, revid1.encode('utf8')),
1126
('d', '', 0, False, revid1.encode('utf8'))
1128
(('', 'a file', 'file-id'), [
1129
('a', '', 0, False, ''),
1130
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1131
revid1.encode('utf8')),
1132
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1133
revid2.encode('utf8'))
1136
state = dirstate.DirState.initialize('dirstate')
1138
state.set_path_id('', root_id)
1139
state.set_parent_trees(
1140
((revid1, tree1.branch.repository.revision_tree(revid1)),
1141
(revid2, tree2.branch.repository.revision_tree(revid2)),
1147
# check_state_with_reopen will unlock
1148
self.check_state_with_reopen(expected_result, state)
1150
### add a path via _set_data - so we dont need delta work, just
1151
# raw data in, and ensure that it comes out via get_lines happily.
1153
def test_add_path_to_root_no_parents_all_data(self):
1154
# The most trivial addition of a path is when there are no parents and
1155
# its in the root and all data about the file is supplied
1156
self.build_tree(['a file'])
1157
stat = os.lstat('a file')
1158
# the 1*20 is the sha1 pretend value.
1159
state = dirstate.DirState.initialize('dirstate')
1160
expected_entries = [
1161
(('', '', 'TREE_ROOT'), [
1162
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1164
(('', 'a file', 'a-file-id'), [
1165
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1169
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1170
# having added it, it should be in the output of iter_entries.
1171
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
# saving and reloading should not affect this.
1176
state = dirstate.DirState.on_file('dirstate')
1178
self.addCleanup(state.unlock)
1179
self.assertEqual(expected_entries, list(state._iter_entries()))
1181
def test_add_path_to_unversioned_directory(self):
1182
"""Adding a path to an unversioned directory should error.
1184
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1185
once dirstate is stable and if it is merged with WorkingTree3, consider
1186
removing this copy of the test.
1188
self.build_tree(['unversioned/', 'unversioned/a file'])
1189
state = dirstate.DirState.initialize('dirstate')
1190
self.addCleanup(state.unlock)
1191
self.assertRaises(errors.NotVersionedError, state.add,
1192
'unversioned/a file', 'a-file-id', 'file', None, None)
1194
def test_add_directory_to_root_no_parents_all_data(self):
1195
# The most trivial addition of a dir is when there are no parents and
1196
# its in the root and all data about the file is supplied
1197
self.build_tree(['a dir/'])
1198
stat = os.lstat('a dir')
1199
expected_entries = [
1200
(('', '', 'TREE_ROOT'), [
1201
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1203
(('', 'a dir', 'a dir id'), [
1204
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1207
state = dirstate.DirState.initialize('dirstate')
1209
state.add('a dir', 'a dir id', 'directory', stat, None)
1210
# having added it, it should be in the output of iter_entries.
1211
self.assertEqual(expected_entries, list(state._iter_entries()))
1212
# saving and reloading should not affect this.
1216
state = dirstate.DirState.on_file('dirstate')
1218
self.addCleanup(state.unlock)
1220
self.assertEqual(expected_entries, list(state._iter_entries()))
1222
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1223
# The most trivial addition of a symlink when there are no parents and
1224
# its in the root and all data about the file is supplied
1225
# bzr doesn't support fake symlinks on windows, yet.
1226
self.requireFeature(features.SymlinkFeature)
1227
os.symlink(target, link_name)
1228
stat = os.lstat(link_name)
1229
expected_entries = [
1230
(('', '', 'TREE_ROOT'), [
1231
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1233
(('', link_name.encode('UTF-8'), 'a link id'), [
1234
('l', target.encode('UTF-8'), stat[6],
1235
False, dirstate.pack_stat(stat)), # current tree
1238
state = dirstate.DirState.initialize('dirstate')
1240
state.add(link_name, 'a link id', 'symlink', stat,
1241
target.encode('UTF-8'))
1242
# having added it, it should be in the output of iter_entries.
1243
self.assertEqual(expected_entries, list(state._iter_entries()))
1244
# saving and reloading should not affect this.
1248
state = dirstate.DirState.on_file('dirstate')
1250
self.addCleanup(state.unlock)
1251
self.assertEqual(expected_entries, list(state._iter_entries()))
1253
def test_add_symlink_to_root_no_parents_all_data(self):
1254
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1256
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1257
self.requireFeature(features.UnicodeFilenameFeature)
1258
self._test_add_symlink_to_root_no_parents_all_data(
1259
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1261
def test_add_directory_and_child_no_parents_all_data(self):
1262
# after adding a directory, we should be able to add children to it.
1263
self.build_tree(['a dir/', 'a dir/a file'])
1264
dirstat = os.lstat('a dir')
1265
filestat = os.lstat('a dir/a file')
1266
expected_entries = [
1267
(('', '', 'TREE_ROOT'), [
1268
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1270
(('', 'a dir', 'a dir id'), [
1271
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1273
(('a dir', 'a file', 'a-file-id'), [
1274
('f', '1'*20, 25, False,
1275
dirstate.pack_stat(filestat)), # current tree details
1278
state = dirstate.DirState.initialize('dirstate')
1280
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1281
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1282
# added it, it should be in the output of iter_entries.
1283
self.assertEqual(expected_entries, list(state._iter_entries()))
1284
# saving and reloading should not affect this.
1288
state = dirstate.DirState.on_file('dirstate')
1290
self.addCleanup(state.unlock)
1291
self.assertEqual(expected_entries, list(state._iter_entries()))
1293
def test_add_tree_reference(self):
1294
# make a dirstate and add a tree reference
1295
state = dirstate.DirState.initialize('dirstate')
1297
('', 'subdir', 'subdir-id'),
1298
[('t', 'subtree-123123', 0, False,
1299
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1302
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1303
entry = state._get_entry(0, 'subdir-id', 'subdir')
1304
self.assertEqual(entry, expected_entry)
1309
# now check we can read it back
1311
self.addCleanup(state.unlock)
1313
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1314
self.assertEqual(entry, entry2)
1315
self.assertEqual(entry, expected_entry)
1316
# and lookup by id should work too
1317
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1318
self.assertEqual(entry, expected_entry)
1320
def test_add_forbidden_names(self):
1321
state = dirstate.DirState.initialize('dirstate')
1322
self.addCleanup(state.unlock)
1323
self.assertRaises(errors.BzrError,
1324
state.add, '.', 'ass-id', 'directory', None, None)
1325
self.assertRaises(errors.BzrError,
1326
state.add, '..', 'ass-id', 'directory', None, None)
1328
def test_set_state_with_rename_b_a_bug_395556(self):
1329
# bug 395556 uncovered a bug where the dirstate ends up with a false
1330
# relocation record - in a tree with no parents there should be no
1331
# absent or relocated records. This then leads to further corruption
1332
# when a commit occurs, as the incorrect relocation gathers an
1333
# incorrect absent in tree 1, and future changes go to pot.
1334
tree1 = self.make_branch_and_tree('tree1')
1335
self.build_tree(['tree1/b'])
1338
tree1.add(['b'], ['b-id'])
1339
root_id = tree1.get_root_id()
1340
inv = tree1.root_inventory
1341
state = dirstate.DirState.initialize('dirstate')
1343
# Set the initial state with 'b'
1344
state.set_state_from_inventory(inv)
1345
inv.rename('b-id', root_id, 'a')
1346
# Set the new state with 'a', which currently corrupts.
1347
state.set_state_from_inventory(inv)
1348
expected_result1 = [('', '', root_id, 'd'),
1349
('', 'a', 'b-id', 'f'),
1352
for entry in state._iter_entries():
1353
values.append(entry[0] + entry[1][0][:1])
1354
self.assertEqual(expected_result1, values)
1361
class TestDirStateHashUpdates(TestCaseWithDirState):
1363
def do_update_entry(self, state, path):
1364
entry = state._get_entry(0, path_utf8=path)
1365
stat = os.lstat(path)
1366
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1368
def _read_state_content(self, state):
1369
"""Read the content of the dirstate file.
1371
On Windows when one process locks a file, you can't even open() the
1372
file in another process (to read it). So we go directly to
1373
state._state_file. This should always be the exact disk representation,
1374
so it is reasonable to do so.
1375
DirState also always seeks before reading, so it doesn't matter if we
1376
bump the file pointer.
1378
state._state_file.seek(0)
1379
return state._state_file.read()
1381
def test_worth_saving_limit_avoids_writing(self):
1382
tree = self.make_branch_and_tree('.')
1383
self.build_tree(['c', 'd'])
1385
tree.add(['c', 'd'], ['c-id', 'd-id'])
1386
tree.commit('add c and d')
1387
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1388
worth_saving_limit=2)
1391
self.addCleanup(state.unlock)
1392
state._read_dirblocks_if_needed()
1393
state.adjust_time(+20) # Allow things to be cached
1394
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1395
state._dirblock_state)
1396
content = self._read_state_content(state)
1397
self.do_update_entry(state, 'c')
1398
self.assertEqual(1, len(state._known_hash_changes))
1399
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1400
state._dirblock_state)
1402
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1403
# hash values haven't been written out.
1404
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1405
state._dirblock_state)
1406
self.assertEqual(content, self._read_state_content(state))
1407
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1408
state._dirblock_state)
1409
self.do_update_entry(state, 'd')
1410
self.assertEqual(2, len(state._known_hash_changes))
1412
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1413
state._dirblock_state)
1414
self.assertEqual(0, len(state._known_hash_changes))
1417
class TestGetLines(TestCaseWithDirState):
1419
def test_get_line_with_2_rows(self):
1420
state = self.create_dirstate_with_root_and_subdir()
1422
self.assertEqual(['#bazaar dirstate flat format 3\n',
1423
'crc32: 41262208\n',
1427
'\x00\x00a-root-value\x00'
1428
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1429
'\x00subdir\x00subdir-id\x00'
1430
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1431
], state.get_lines())
1435
def test_entry_to_line(self):
1436
state = self.create_dirstate_with_root()
1439
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1440
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1441
state._entry_to_line(state._dirblocks[0][1][0]))
1445
def test_entry_to_line_with_parent(self):
1446
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1447
root_entry = ('', '', 'a-root-value'), [
1448
('d', '', 0, False, packed_stat), # current tree details
1449
# first: a pointer to the current location
1450
('a', 'dirname/basename', 0, False, ''),
1452
state = dirstate.DirState.initialize('dirstate')
1455
'\x00\x00a-root-value\x00'
1456
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1457
'a\x00dirname/basename\x000\x00n\x00',
1458
state._entry_to_line(root_entry))
1462
def test_entry_to_line_with_two_parents_at_different_paths(self):
1463
# / in the tree, at / in one parent and /dirname/basename in the other.
1464
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1465
root_entry = ('', '', 'a-root-value'), [
1466
('d', '', 0, False, packed_stat), # current tree details
1467
('d', '', 0, False, 'rev_id'), # first parent details
1468
# second: a pointer to the current location
1469
('a', 'dirname/basename', 0, False, ''),
1471
state = dirstate.DirState.initialize('dirstate')
1474
'\x00\x00a-root-value\x00'
1475
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1476
'd\x00\x000\x00n\x00rev_id\x00'
1477
'a\x00dirname/basename\x000\x00n\x00',
1478
state._entry_to_line(root_entry))
1482
def test_iter_entries(self):
1483
# we should be able to iterate the dirstate entries from end to end
1484
# this is for get_lines to be easy to read.
1485
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1487
root_entries = [(('', '', 'a-root-value'), [
1488
('d', '', 0, False, packed_stat), # current tree details
1490
dirblocks.append(('', root_entries))
1491
# add two files in the root
1492
subdir_entry = ('', 'subdir', 'subdir-id'), [
1493
('d', '', 0, False, packed_stat), # current tree details
1495
afile_entry = ('', 'afile', 'afile-id'), [
1496
('f', 'sha1value', 34, False, packed_stat), # current tree details
1498
dirblocks.append(('', [subdir_entry, afile_entry]))
1500
file_entry2 = ('subdir', '2file', '2file-id'), [
1501
('f', 'sha1value', 23, False, packed_stat), # current tree details
1503
dirblocks.append(('subdir', [file_entry2]))
1504
state = dirstate.DirState.initialize('dirstate')
1506
state._set_data([], dirblocks)
1507
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1509
self.assertEqual(expected_entries, list(state._iter_entries()))
1514
class TestGetBlockRowIndex(TestCaseWithDirState):
1516
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1517
file_present, state, dirname, basename, tree_index):
1518
self.assertEqual((block_index, row_index, dir_present, file_present),
1519
state._get_block_entry_index(dirname, basename, tree_index))
1521
block = state._dirblocks[block_index]
1522
self.assertEqual(dirname, block[0])
1523
if dir_present and file_present:
1524
row = state._dirblocks[block_index][1][row_index]
1525
self.assertEqual(dirname, row[0][0])
1526
self.assertEqual(basename, row[0][1])
1528
def test_simple_structure(self):
1529
state = self.create_dirstate_with_root_and_subdir()
1530
self.addCleanup(state.unlock)
1531
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1532
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1533
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1534
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1535
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1538
def test_complex_structure_exists(self):
1539
state = self.create_complex_dirstate()
1540
self.addCleanup(state.unlock)
1541
# Make sure we can find everything that exists
1542
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1543
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1544
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1545
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1546
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1547
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1548
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1549
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1550
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1551
'b', 'h\xc3\xa5', 0)
1553
def test_complex_structure_missing(self):
1554
state = self.create_complex_dirstate()
1555
self.addCleanup(state.unlock)
1556
# Make sure things would be inserted in the right locations
1557
# '_' comes before 'a'
1558
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1559
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1560
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1561
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1563
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1564
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1565
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1566
# This would be inserted between a/ and b/
1567
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1569
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1572
class TestGetEntry(TestCaseWithDirState):
1574
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1575
"""Check that the right entry is returned for a request to getEntry."""
1576
entry = state._get_entry(index, path_utf8=path)
1578
self.assertEqual((None, None), entry)
1581
self.assertEqual((dirname, basename, file_id), cur[:3])
1583
def test_simple_structure(self):
1584
state = self.create_dirstate_with_root_and_subdir()
1585
self.addCleanup(state.unlock)
1586
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1587
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1588
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1589
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1590
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1592
def test_complex_structure_exists(self):
1593
state = self.create_complex_dirstate()
1594
self.addCleanup(state.unlock)
1595
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1596
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1597
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1598
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1599
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1600
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1601
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1602
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1603
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1606
def test_complex_structure_missing(self):
1607
state = self.create_complex_dirstate()
1608
self.addCleanup(state.unlock)
1609
self.assertEntryEqual(None, None, None, state, '_', 0)
1610
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1611
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1612
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1614
def test_get_entry_uninitialized(self):
1615
"""Calling get_entry will load data if it needs to"""
1616
state = self.create_dirstate_with_root()
1622
state = dirstate.DirState.on_file('dirstate')
1625
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1626
state._header_state)
1627
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1628
state._dirblock_state)
1629
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1634
class TestIterChildEntries(TestCaseWithDirState):
1636
def create_dirstate_with_two_trees(self):
1637
"""This dirstate contains multiple files and directories.
1647
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1649
Notice that a/e is an empty directory.
1651
There is one parent tree, which has the same shape with the following variations:
1652
b/g in the parent is gone.
1653
b/h in the parent has a different id
1654
b/i is new in the parent
1655
c is renamed to b/j in the parent
1657
:return: The dirstate, still write-locked.
1659
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1660
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1661
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1662
root_entry = ('', '', 'a-root-value'), [
1663
('d', '', 0, False, packed_stat),
1664
('d', '', 0, False, 'parent-revid'),
1666
a_entry = ('', 'a', 'a-dir'), [
1667
('d', '', 0, False, packed_stat),
1668
('d', '', 0, False, 'parent-revid'),
1670
b_entry = ('', 'b', 'b-dir'), [
1671
('d', '', 0, False, packed_stat),
1672
('d', '', 0, False, 'parent-revid'),
1674
c_entry = ('', 'c', 'c-file'), [
1675
('f', null_sha, 10, False, packed_stat),
1676
('r', 'b/j', 0, False, ''),
1678
d_entry = ('', 'd', 'd-file'), [
1679
('f', null_sha, 20, False, packed_stat),
1680
('f', 'd', 20, False, 'parent-revid'),
1682
e_entry = ('a', 'e', 'e-dir'), [
1683
('d', '', 0, False, packed_stat),
1684
('d', '', 0, False, 'parent-revid'),
1686
f_entry = ('a', 'f', 'f-file'), [
1687
('f', null_sha, 30, False, packed_stat),
1688
('f', 'f', 20, False, 'parent-revid'),
1690
g_entry = ('b', 'g', 'g-file'), [
1691
('f', null_sha, 30, False, packed_stat),
1692
NULL_PARENT_DETAILS,
1694
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1695
('f', null_sha, 40, False, packed_stat),
1696
NULL_PARENT_DETAILS,
1698
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1699
NULL_PARENT_DETAILS,
1700
('f', 'h', 20, False, 'parent-revid'),
1702
i_entry = ('b', 'i', 'i-file'), [
1703
NULL_PARENT_DETAILS,
1704
('f', 'h', 20, False, 'parent-revid'),
1706
j_entry = ('b', 'j', 'c-file'), [
1707
('r', 'c', 0, False, ''),
1708
('f', 'j', 20, False, 'parent-revid'),
1711
dirblocks.append(('', [root_entry]))
1712
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1713
dirblocks.append(('a', [e_entry, f_entry]))
1714
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1715
state = dirstate.DirState.initialize('dirstate')
1718
state._set_data(['parent'], dirblocks)
1722
return state, dirblocks
1724
def test_iter_children_b(self):
1725
state, dirblocks = self.create_dirstate_with_two_trees()
1726
self.addCleanup(state.unlock)
1727
expected_result = []
1728
expected_result.append(dirblocks[3][1][2]) # h2
1729
expected_result.append(dirblocks[3][1][3]) # i
1730
expected_result.append(dirblocks[3][1][4]) # j
1731
self.assertEqual(expected_result,
1732
list(state._iter_child_entries(1, 'b')))
1734
def test_iter_child_root(self):
1735
state, dirblocks = self.create_dirstate_with_two_trees()
1736
self.addCleanup(state.unlock)
1737
expected_result = []
1738
expected_result.append(dirblocks[1][1][0]) # a
1739
expected_result.append(dirblocks[1][1][1]) # b
1740
expected_result.append(dirblocks[1][1][3]) # d
1741
expected_result.append(dirblocks[2][1][0]) # e
1742
expected_result.append(dirblocks[2][1][1]) # f
1743
expected_result.append(dirblocks[3][1][2]) # h2
1744
expected_result.append(dirblocks[3][1][3]) # i
1745
expected_result.append(dirblocks[3][1][4]) # j
1746
self.assertEqual(expected_result,
1747
list(state._iter_child_entries(1, '')))
1750
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1751
"""Test that DirState adds entries in the right order."""
1753
def test_add_sorting(self):
1754
"""Add entries in lexicographical order, we get path sorted order.
1756
This tests it to a depth of 4, to make sure we don't just get it right
1757
at a single depth. 'a/a' should come before 'a-a', even though it
1758
doesn't lexicographically.
1760
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1761
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1764
state = dirstate.DirState.initialize('dirstate')
1765
self.addCleanup(state.unlock)
1767
fake_stat = os.stat('dirstate')
1769
d_id = d.replace('/', '_')+'-id'
1770
file_path = d + '/f'
1771
file_id = file_path.replace('/', '_')+'-id'
1772
state.add(d, d_id, 'directory', fake_stat, null_sha)
1773
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1775
expected = ['', '', 'a',
1776
'a/a', 'a/a/a', 'a/a/a/a',
1777
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1779
split = lambda p:p.split('/')
1780
self.assertEqual(sorted(expected, key=split), expected)
1781
dirblock_names = [d[0] for d in state._dirblocks]
1782
self.assertEqual(expected, dirblock_names)
1784
def test_set_parent_trees_correct_order(self):
1785
"""After calling set_parent_trees() we should maintain the order."""
1786
dirs = ['a', 'a-a', 'a/a']
1788
state = dirstate.DirState.initialize('dirstate')
1789
self.addCleanup(state.unlock)
1791
fake_stat = os.stat('dirstate')
1793
d_id = d.replace('/', '_')+'-id'
1794
file_path = d + '/f'
1795
file_id = file_path.replace('/', '_')+'-id'
1796
state.add(d, d_id, 'directory', fake_stat, null_sha)
1797
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1799
expected = ['', '', 'a', 'a/a', 'a-a']
1800
dirblock_names = [d[0] for d in state._dirblocks]
1801
self.assertEqual(expected, dirblock_names)
1803
# *really* cheesy way to just get an empty tree
1804
repo = self.make_repository('repo')
1805
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1806
state.set_parent_trees([('null:', empty_tree)], [])
1808
dirblock_names = [d[0] for d in state._dirblocks]
1809
self.assertEqual(expected, dirblock_names)
1812
class InstrumentedDirState(dirstate.DirState):
1813
"""An DirState with instrumented sha1 functionality."""
1815
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1816
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1817
worth_saving_limit=worth_saving_limit)
1818
self._time_offset = 0
1820
# member is dynamically set in DirState.__init__ to turn on trace
1821
self._sha1_provider = sha1_provider
1822
self._sha1_file = self._sha1_file_and_log
1824
def _sha_cutoff_time(self):
1825
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1826
self._cutoff_time = timestamp + self._time_offset
1828
def _sha1_file_and_log(self, abspath):
1829
self._log.append(('sha1', abspath))
1830
return self._sha1_provider.sha1(abspath)
1832
def _read_link(self, abspath, old_link):
1833
self._log.append(('read_link', abspath, old_link))
1834
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1836
def _lstat(self, abspath, entry):
1837
self._log.append(('lstat', abspath))
1838
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1840
def _is_executable(self, mode, old_executable):
1841
self._log.append(('is_exec', mode, old_executable))
1842
return super(InstrumentedDirState, self)._is_executable(mode,
1845
def adjust_time(self, secs):
1846
"""Move the clock forward or back.
1848
:param secs: The amount to adjust the clock by. Positive values make it
1849
seem as if we are in the future, negative values make it seem like we
1852
self._time_offset += secs
1853
self._cutoff_time = None
1856
class _FakeStat(object):
1857
"""A class with the same attributes as a real stat result."""
1859
def __init__(self, size, mtime, ctime, dev, ino, mode):
1861
self.st_mtime = mtime
1862
self.st_ctime = ctime
1869
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1870
st.st_ino, st.st_mode)
1873
class TestPackStat(tests.TestCaseWithTransport):
1875
def assertPackStat(self, expected, stat_value):
1876
"""Check the packed and serialized form of a stat value."""
1877
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1879
def test_pack_stat_int(self):
1880
st = _FakeStat(6859, 1172758614, 1172758617, 777, 6499538, 0o100644)
1881
# Make sure that all parameters have an impact on the packed stat.
1882
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1885
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1886
st.st_mtime = 1172758620
1888
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1889
st.st_ctime = 1172758630
1891
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1894
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1897
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1898
st.st_mode = 0o100744
1900
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1902
def test_pack_stat_float(self):
1903
"""On some platforms mtime and ctime are floats.
1905
Make sure we don't get warnings or errors, and that we ignore changes <
1908
st = _FakeStat(7000, 1172758614.0, 1172758617.0,
1909
777, 6499538, 0o100644)
1910
# These should all be the same as the integer counterparts
1911
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1912
st.st_mtime = 1172758620.0
1914
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1915
st.st_ctime = 1172758630.0
1917
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1918
# fractional seconds are discarded, so no change from above
1919
st.st_mtime = 1172758620.453
1920
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1921
st.st_ctime = 1172758630.228
1922
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1925
class TestBisect(TestCaseWithDirState):
1926
"""Test the ability to bisect into the disk format."""
1928
def assertBisect(self, expected_map, map_keys, state, paths):
1929
"""Assert that bisecting for paths returns the right result.
1931
:param expected_map: A map from key => entry value
1932
:param map_keys: The keys to expect for each path
1933
:param state: The DirState object.
1934
:param paths: A list of paths, these will automatically be split into
1935
(dir, name) tuples, and sorted according to how _bisect
1938
result = state._bisect(paths)
1939
# For now, results are just returned in whatever order we read them.
1940
# We could sort by (dir, name, file_id) or something like that, but in
1941
# the end it would still be fairly arbitrary, and we don't want the
1942
# extra overhead if we can avoid it. So sort everything to make sure
1944
self.assertEqual(len(map_keys), len(paths))
1946
for path, keys in zip(paths, map_keys):
1948
# This should not be present in the output
1950
expected[path] = sorted(expected_map[k] for k in keys)
1952
# The returned values are just arranged randomly based on when they
1953
# were read, for testing, make sure it is properly sorted.
1957
self.assertEqual(expected, result)
1959
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1960
"""Assert that bisecting for dirbblocks returns the right result.
1962
:param expected_map: A map from key => expected values
1963
:param map_keys: A nested list of paths we expect to be returned.
1964
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1965
:param state: The DirState object.
1966
:param paths: A list of directories
1968
result = state._bisect_dirblocks(paths)
1969
self.assertEqual(len(map_keys), len(paths))
1971
for path, keys in zip(paths, map_keys):
1973
# This should not be present in the output
1975
expected[path] = sorted(expected_map[k] for k in keys)
1979
self.assertEqual(expected, result)
1981
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1982
"""Assert the return value of a recursive bisection.
1984
:param expected_map: A map from key => entry value
1985
:param map_keys: A list of paths we expect to be returned.
1986
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1987
:param state: The DirState object.
1988
:param paths: A list of files and directories. It will be broken up
1989
into (dir, name) pairs and sorted before calling _bisect_recursive.
1992
for key in map_keys:
1993
entry = expected_map[key]
1994
dir_name_id, trees_info = entry
1995
expected[dir_name_id] = trees_info
1997
result = state._bisect_recursive(paths)
1999
self.assertEqual(expected, result)
2001
def test_bisect_each(self):
2002
"""Find a single record using bisect."""
2003
tree, state, expected = self.create_basic_dirstate()
2005
# Bisect should return the rows for the specified files.
2006
self.assertBisect(expected, [['']], state, [''])
2007
self.assertBisect(expected, [['a']], state, ['a'])
2008
self.assertBisect(expected, [['b']], state, ['b'])
2009
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2010
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2011
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2012
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2013
self.assertBisect(expected, [['f']], state, ['f'])
2015
def test_bisect_multi(self):
2016
"""Bisect can be used to find multiple records at the same time."""
2017
tree, state, expected = self.create_basic_dirstate()
2018
# Bisect should be capable of finding multiple entries at the same time
2019
self.assertBisect(expected, [['a'], ['b'], ['f']],
2020
state, ['a', 'b', 'f'])
2021
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2022
state, ['f', 'b/d', 'b/d/e'])
2023
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2024
state, ['b', 'b-c', 'b/c'])
2026
def test_bisect_one_page(self):
2027
"""Test bisect when there is only 1 page to read"""
2028
tree, state, expected = self.create_basic_dirstate()
2029
state._bisect_page_size = 5000
2030
self.assertBisect(expected,[['']], state, [''])
2031
self.assertBisect(expected,[['a']], state, ['a'])
2032
self.assertBisect(expected,[['b']], state, ['b'])
2033
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2034
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2035
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2036
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2037
self.assertBisect(expected,[['f']], state, ['f'])
2038
self.assertBisect(expected,[['a'], ['b'], ['f']],
2039
state, ['a', 'b', 'f'])
2040
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2041
state, ['b/d', 'b/d/e', 'f'])
2042
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2043
state, ['b', 'b/c', 'b-c'])
2045
def test_bisect_duplicate_paths(self):
2046
"""When bisecting for a path, handle multiple entries."""
2047
tree, state, expected = self.create_duplicated_dirstate()
2049
# Now make sure that both records are properly returned.
2050
self.assertBisect(expected, [['']], state, [''])
2051
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2052
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2053
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2054
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2055
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2057
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2058
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2060
def test_bisect_page_size_too_small(self):
2061
"""If the page size is too small, we will auto increase it."""
2062
tree, state, expected = self.create_basic_dirstate()
2063
state._bisect_page_size = 50
2064
self.assertBisect(expected, [None], state, ['b/e'])
2065
self.assertBisect(expected, [['a']], state, ['a'])
2066
self.assertBisect(expected, [['b']], state, ['b'])
2067
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2068
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2069
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2070
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2071
self.assertBisect(expected, [['f']], state, ['f'])
2073
def test_bisect_missing(self):
2074
"""Test that bisect return None if it cannot find a path."""
2075
tree, state, expected = self.create_basic_dirstate()
2076
self.assertBisect(expected, [None], state, ['foo'])
2077
self.assertBisect(expected, [None], state, ['b/foo'])
2078
self.assertBisect(expected, [None], state, ['bar/foo'])
2079
self.assertBisect(expected, [None], state, ['b-c/foo'])
2081
self.assertBisect(expected, [['a'], None, ['b/d']],
2082
state, ['a', 'foo', 'b/d'])
2084
def test_bisect_rename(self):
2085
"""Check that we find a renamed row."""
2086
tree, state, expected = self.create_renamed_dirstate()
2088
# Search for the pre and post renamed entries
2089
self.assertBisect(expected, [['a']], state, ['a'])
2090
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2091
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2092
self.assertBisect(expected, [['h']], state, ['h'])
2094
# What about b/d/e? shouldn't that also get 2 directory entries?
2095
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2096
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2098
def test_bisect_dirblocks(self):
2099
tree, state, expected = self.create_duplicated_dirstate()
2100
self.assertBisectDirBlocks(expected,
2101
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2103
self.assertBisectDirBlocks(expected,
2104
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2105
self.assertBisectDirBlocks(expected,
2106
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2107
self.assertBisectDirBlocks(expected,
2108
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2109
['b/c', 'b/c2', 'b/d', 'b/d2'],
2110
['b/d/e', 'b/d/e2'],
2111
], state, ['', 'b', 'b/d'])
2113
def test_bisect_dirblocks_missing(self):
2114
tree, state, expected = self.create_basic_dirstate()
2115
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2116
state, ['b/d', 'b/e'])
2117
# Files don't show up in this search
2118
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2119
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2120
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2121
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2122
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2124
def test_bisect_recursive_each(self):
2125
tree, state, expected = self.create_basic_dirstate()
2126
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2127
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2128
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2129
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2130
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2132
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2134
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2138
def test_bisect_recursive_multiple(self):
2139
tree, state, expected = self.create_basic_dirstate()
2140
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2141
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2142
state, ['b/d', 'b/d/e'])
2144
def test_bisect_recursive_missing(self):
2145
tree, state, expected = self.create_basic_dirstate()
2146
self.assertBisectRecursive(expected, [], state, ['d'])
2147
self.assertBisectRecursive(expected, [], state, ['b/e'])
2148
self.assertBisectRecursive(expected, [], state, ['g'])
2149
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2151
def test_bisect_recursive_renamed(self):
2152
tree, state, expected = self.create_renamed_dirstate()
2154
# Looking for either renamed item should find the other
2155
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2156
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2157
# Looking in the containing directory should find the rename target,
2158
# and anything in a subdir of the renamed target.
2159
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2160
'b/d/e', 'b/g', 'h', 'h/e'],
2164
class TestDirstateValidation(TestCaseWithDirState):
2166
def test_validate_correct_dirstate(self):
2167
state = self.create_complex_dirstate()
2170
# and make sure we can also validate with a read lock
2177
def test_dirblock_not_sorted(self):
2178
tree, state, expected = self.create_renamed_dirstate()
2179
state._read_dirblocks_if_needed()
2180
last_dirblock = state._dirblocks[-1]
2181
# we're appending to the dirblock, but this name comes before some of
2182
# the existing names; that's wrong
2183
last_dirblock[1].append(
2184
(('h', 'aaaa', 'a-id'),
2185
[('a', '', 0, False, ''),
2186
('a', '', 0, False, '')]))
2187
e = self.assertRaises(AssertionError,
2189
self.assertContainsRe(str(e), 'not sorted')
2191
def test_dirblock_name_mismatch(self):
2192
tree, state, expected = self.create_renamed_dirstate()
2193
state._read_dirblocks_if_needed()
2194
last_dirblock = state._dirblocks[-1]
2195
# add an entry with the wrong directory name
2196
last_dirblock[1].append(
2198
[('a', '', 0, False, ''),
2199
('a', '', 0, False, '')]))
2200
e = self.assertRaises(AssertionError,
2202
self.assertContainsRe(str(e),
2203
"doesn't match directory name")
2205
def test_dirblock_missing_rename(self):
2206
tree, state, expected = self.create_renamed_dirstate()
2207
state._read_dirblocks_if_needed()
2208
last_dirblock = state._dirblocks[-1]
2209
# make another entry for a-id, without a correct 'r' pointer to
2210
# the real occurrence in the working tree
2211
last_dirblock[1].append(
2212
(('h', 'z', 'a-id'),
2213
[('a', '', 0, False, ''),
2214
('a', '', 0, False, '')]))
2215
e = self.assertRaises(AssertionError,
2217
self.assertContainsRe(str(e),
2218
'file a-id is absent in row')
2221
class TestDirstateTreeReference(TestCaseWithDirState):
2223
def test_reference_revision_is_none(self):
2224
tree = self.make_branch_and_tree('tree', format='development-subtree')
2225
subtree = self.make_branch_and_tree('tree/subtree',
2226
format='development-subtree')
2227
subtree.set_root_id('subtree')
2228
tree.add_reference(subtree)
2230
state = dirstate.DirState.from_tree(tree, 'dirstate')
2231
key = ('', 'subtree', 'subtree')
2232
expected = ('', [(key,
2233
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2236
self.assertEqual(expected, state._find_block(key))
2241
class TestDiscardMergeParents(TestCaseWithDirState):
2243
def test_discard_no_parents(self):
2244
# This should be a no-op
2245
state = self.create_empty_dirstate()
2246
self.addCleanup(state.unlock)
2247
state._discard_merge_parents()
2250
def test_discard_one_parent(self):
2252
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2253
root_entry_direntry = ('', '', 'a-root-value'), [
2254
('d', '', 0, False, packed_stat),
2255
('d', '', 0, False, packed_stat),
2258
dirblocks.append(('', [root_entry_direntry]))
2259
dirblocks.append(('', []))
2261
state = self.create_empty_dirstate()
2262
self.addCleanup(state.unlock)
2263
state._set_data(['parent-id'], dirblocks[:])
2266
state._discard_merge_parents()
2268
self.assertEqual(dirblocks, state._dirblocks)
2270
def test_discard_simple(self):
2272
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2273
root_entry_direntry = ('', '', 'a-root-value'), [
2274
('d', '', 0, False, packed_stat),
2275
('d', '', 0, False, packed_stat),
2276
('d', '', 0, False, packed_stat),
2278
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2279
('d', '', 0, False, packed_stat),
2280
('d', '', 0, False, packed_stat),
2283
dirblocks.append(('', [root_entry_direntry]))
2284
dirblocks.append(('', []))
2286
state = self.create_empty_dirstate()
2287
self.addCleanup(state.unlock)
2288
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2291
# This should strip of the extra column
2292
state._discard_merge_parents()
2294
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2295
self.assertEqual(expected_dirblocks, state._dirblocks)
2297
def test_discard_absent(self):
2298
"""If entries are only in a merge, discard should remove the entries"""
2299
null_stat = dirstate.DirState.NULLSTAT
2300
present_dir = ('d', '', 0, False, null_stat)
2301
present_file = ('f', '', 0, False, null_stat)
2302
absent = dirstate.DirState.NULL_PARENT_DETAILS
2303
root_key = ('', '', 'a-root-value')
2304
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2305
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2306
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2307
('', [(file_in_merged_key,
2308
[absent, absent, present_file]),
2310
[present_file, present_file, present_file]),
2314
state = self.create_empty_dirstate()
2315
self.addCleanup(state.unlock)
2316
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2319
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2320
('', [(file_in_root_key,
2321
[present_file, present_file]),
2324
state._discard_merge_parents()
2326
self.assertEqual(exp_dirblocks, state._dirblocks)
2328
def test_discard_renamed(self):
2329
null_stat = dirstate.DirState.NULLSTAT
2330
present_dir = ('d', '', 0, False, null_stat)
2331
present_file = ('f', '', 0, False, null_stat)
2332
absent = dirstate.DirState.NULL_PARENT_DETAILS
2333
root_key = ('', '', 'a-root-value')
2334
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2335
# Renamed relative to parent
2336
file_rename_s_key = ('', 'file-s', 'b-file-id')
2337
file_rename_t_key = ('', 'file-t', 'b-file-id')
2338
# And one that is renamed between the parents, but absent in this
2339
key_in_1 = ('', 'file-in-1', 'c-file-id')
2340
key_in_2 = ('', 'file-in-2', 'c-file-id')
2343
('', [(root_key, [present_dir, present_dir, present_dir])]),
2345
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2347
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2349
[present_file, present_file, present_file]),
2351
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2353
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2357
('', [(root_key, [present_dir, present_dir])]),
2358
('', [(key_in_1, [absent, present_file]),
2359
(file_in_root_key, [present_file, present_file]),
2360
(file_rename_t_key, [present_file, absent]),
2363
state = self.create_empty_dirstate()
2364
self.addCleanup(state.unlock)
2365
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2368
state._discard_merge_parents()
2370
self.assertEqual(exp_dirblocks, state._dirblocks)
2372
def test_discard_all_subdir(self):
2373
null_stat = dirstate.DirState.NULLSTAT
2374
present_dir = ('d', '', 0, False, null_stat)
2375
present_file = ('f', '', 0, False, null_stat)
2376
absent = dirstate.DirState.NULL_PARENT_DETAILS
2377
root_key = ('', '', 'a-root-value')
2378
subdir_key = ('', 'sub', 'dir-id')
2379
child1_key = ('sub', 'child1', 'child1-id')
2380
child2_key = ('sub', 'child2', 'child2-id')
2381
child3_key = ('sub', 'child3', 'child3-id')
2384
('', [(root_key, [present_dir, present_dir, present_dir])]),
2385
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2386
('sub', [(child1_key, [absent, absent, present_file]),
2387
(child2_key, [absent, absent, present_file]),
2388
(child3_key, [absent, absent, present_file]),
2392
('', [(root_key, [present_dir, present_dir])]),
2393
('', [(subdir_key, [present_dir, present_dir])]),
2396
state = self.create_empty_dirstate()
2397
self.addCleanup(state.unlock)
2398
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2401
state._discard_merge_parents()
2403
self.assertEqual(exp_dirblocks, state._dirblocks)
2406
class Test_InvEntryToDetails(tests.TestCase):
2408
def assertDetails(self, expected, inv_entry):
2409
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2410
self.assertEqual(expected, details)
2411
# details should always allow join() and always be a plain str when
2413
(minikind, fingerprint, size, executable, tree_data) = details
2414
self.assertIsInstance(minikind, str)
2415
self.assertIsInstance(fingerprint, str)
2416
self.assertIsInstance(tree_data, str)
2418
def test_unicode_symlink(self):
2419
inv_entry = inventory.InventoryLink('link-file-id',
2420
u'nam\N{Euro Sign}e',
2422
inv_entry.revision = 'link-revision-id'
2423
target = u'link-targ\N{Euro Sign}t'
2424
inv_entry.symlink_target = target
2425
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2426
'link-revision-id'), inv_entry)
2429
class TestSHA1Provider(tests.TestCaseInTempDir):
2431
def test_sha1provider_is_an_interface(self):
2432
p = dirstate.SHA1Provider()
2433
self.assertRaises(NotImplementedError, p.sha1, "foo")
2434
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2436
def test_defaultsha1provider_sha1(self):
2437
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2438
self.build_tree_contents([('foo', text)])
2439
expected_sha = osutils.sha_string(text)
2440
p = dirstate.DefaultSHA1Provider()
2441
self.assertEqual(expected_sha, p.sha1('foo'))
2443
def test_defaultsha1provider_stat_and_sha1(self):
2444
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2445
self.build_tree_contents([('foo', text)])
2446
expected_sha = osutils.sha_string(text)
2447
p = dirstate.DefaultSHA1Provider()
2448
statvalue, sha1 = p.stat_and_sha1('foo')
2449
self.assertTrue(len(statvalue) >= 10)
2450
self.assertEqual(len(text), statvalue.st_size)
2451
self.assertEqual(expected_sha, sha1)
2454
class _Repo(object):
2455
"""A minimal api to get InventoryRevisionTree to work."""
2458
default_format = controldir.format_registry.make_controldir('default')
2459
self._format = default_format.repository_format
2461
def lock_read(self):
2468
class TestUpdateBasisByDelta(tests.TestCase):
2470
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2471
if path.endswith('/'):
2476
dirname, basename = osutils.split(path)
2478
dir_id = dir_ids[dirname]
2480
dir_id = osutils.basename(dirname) + '-id'
2482
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2483
dir_ids[path] = file_id
2485
ie = inventory.InventoryFile(file_id, basename, dir_id)
2488
ie.revision = rev_id
2491
def create_tree_from_shape(self, rev_id, shape):
2492
dir_ids = {'': 'root-id'}
2493
inv = inventory.Inventory('root-id', rev_id)
2496
path, file_id = info
2499
path, file_id, ie_rev_id = info
2501
# Replace the root entry
2502
del inv._byid[inv.root.file_id]
2503
inv.root.file_id = file_id
2504
inv._byid[file_id] = inv.root
2505
dir_ids[''] = file_id
2507
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2508
return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
2510
def create_empty_dirstate(self):
2511
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2512
self.addCleanup(os.remove, path)
2514
state = dirstate.DirState.initialize(path)
2515
self.addCleanup(state.unlock)
2518
def create_inv_delta(self, delta, rev_id):
2519
"""Translate a 'delta shape' into an actual InventoryDelta"""
2520
dir_ids = {'': 'root-id'}
2522
for old_path, new_path, file_id in delta:
2523
if old_path is not None and old_path.endswith('/'):
2524
# Don't have to actually do anything for this, because only
2525
# new_path creates InventoryEntries
2526
old_path = old_path[:-1]
2527
if new_path is None: # Delete
2528
inv_delta.append((old_path, None, file_id, None))
2530
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2531
inv_delta.append((old_path, new_path, file_id, ie))
2534
def assertUpdate(self, active, basis, target):
2535
"""Assert that update_basis_by_delta works how we want.
2537
Set up a DirState object with active_shape for tree 0, basis_shape for
2538
tree 1. Then apply the delta from basis_shape to target_shape,
2539
and assert that the DirState is still valid, and that its stored
2540
content matches the target_shape.
2542
active_tree = self.create_tree_from_shape('active', active)
2543
basis_tree = self.create_tree_from_shape('basis', basis)
2544
target_tree = self.create_tree_from_shape('target', target)
2545
state = self.create_empty_dirstate()
2546
state.set_state_from_scratch(active_tree.root_inventory,
2547
[('basis', basis_tree)], [])
2548
delta = target_tree.root_inventory._make_delta(
2549
basis_tree.root_inventory)
2550
state.update_basis_by_delta(delta, 'target')
2552
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2554
# The target now that delta has been applied should match the
2556
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2557
# And the dirblock state should be identical to the state if we created
2559
state2 = self.create_empty_dirstate()
2560
state2.set_state_from_scratch(active_tree.root_inventory,
2561
[('target', target_tree)], [])
2562
self.assertEqual(state2._dirblocks, state._dirblocks)
2565
def assertBadDelta(self, active, basis, delta):
2566
"""Test that we raise InconsistentDelta when appropriate.
2568
:param active: The active tree shape
2569
:param basis: The basis tree shape
2570
:param delta: A description of the delta to apply. Similar to the form
2571
for regular inventory deltas, but omitting the InventoryEntry.
2572
So adding a file is: (None, 'path', 'file-id')
2573
Adding a directory is: (None, 'path/', 'dir-id')
2574
Renaming a dir is: ('old/', 'new/', 'dir-id')
2577
active_tree = self.create_tree_from_shape('active', active)
2578
basis_tree = self.create_tree_from_shape('basis', basis)
2579
inv_delta = self.create_inv_delta(delta, 'target')
2580
state = self.create_empty_dirstate()
2581
state.set_state_from_scratch(active_tree.root_inventory,
2582
[('basis', basis_tree)], [])
2583
self.assertRaises(errors.InconsistentDelta,
2584
state.update_basis_by_delta, inv_delta, 'target')
2586
## state.update_basis_by_delta(inv_delta, 'target')
2587
## except errors.InconsistentDelta, e:
2588
## import pdb; pdb.set_trace()
2590
## import pdb; pdb.set_trace()
2591
self.assertTrue(state._changes_aborted)
2593
def test_remove_file_matching_active_state(self):
2594
state = self.assertUpdate(
2596
basis =[('file', 'file-id')],
2600
def test_remove_file_present_in_active_state(self):
2601
state = self.assertUpdate(
2602
active=[('file', 'file-id')],
2603
basis =[('file', 'file-id')],
2607
def test_remove_file_present_elsewhere_in_active_state(self):
2608
state = self.assertUpdate(
2609
active=[('other-file', 'file-id')],
2610
basis =[('file', 'file-id')],
2614
def test_remove_file_active_state_has_diff_file(self):
2615
state = self.assertUpdate(
2616
active=[('file', 'file-id-2')],
2617
basis =[('file', 'file-id')],
2621
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2622
state = self.assertUpdate(
2623
active=[('file', 'file-id-2'),
2624
('other-file', 'file-id')],
2625
basis =[('file', 'file-id')],
2629
def test_add_file_matching_active_state(self):
2630
state = self.assertUpdate(
2631
active=[('file', 'file-id')],
2633
target=[('file', 'file-id')],
2636
def test_add_file_in_empty_dir_not_matching_active_state(self):
2637
state = self.assertUpdate(
2639
basis=[('dir/', 'dir-id')],
2640
target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
2643
def test_add_file_missing_in_active_state(self):
2644
state = self.assertUpdate(
2647
target=[('file', 'file-id')],
2650
def test_add_file_elsewhere_in_active_state(self):
2651
state = self.assertUpdate(
2652
active=[('other-file', 'file-id')],
2654
target=[('file', 'file-id')],
2657
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2658
state = self.assertUpdate(
2659
active=[('other-file', 'file-id'),
2660
('file', 'file-id-2')],
2662
target=[('file', 'file-id')],
2665
def test_rename_file_matching_active_state(self):
2666
state = self.assertUpdate(
2667
active=[('other-file', 'file-id')],
2668
basis =[('file', 'file-id')],
2669
target=[('other-file', 'file-id')],
2672
def test_rename_file_missing_in_active_state(self):
2673
state = self.assertUpdate(
2675
basis =[('file', 'file-id')],
2676
target=[('other-file', 'file-id')],
2679
def test_rename_file_present_elsewhere_in_active_state(self):
2680
state = self.assertUpdate(
2681
active=[('third', 'file-id')],
2682
basis =[('file', 'file-id')],
2683
target=[('other-file', 'file-id')],
2686
def test_rename_file_active_state_has_diff_source_file(self):
2687
state = self.assertUpdate(
2688
active=[('file', 'file-id-2')],
2689
basis =[('file', 'file-id')],
2690
target=[('other-file', 'file-id')],
2693
def test_rename_file_active_state_has_diff_target_file(self):
2694
state = self.assertUpdate(
2695
active=[('other-file', 'file-id-2')],
2696
basis =[('file', 'file-id')],
2697
target=[('other-file', 'file-id')],
2700
def test_rename_file_active_has_swapped_files(self):
2701
state = self.assertUpdate(
2702
active=[('file', 'file-id'),
2703
('other-file', 'file-id-2')],
2704
basis= [('file', 'file-id'),
2705
('other-file', 'file-id-2')],
2706
target=[('file', 'file-id-2'),
2707
('other-file', 'file-id')])
2709
def test_rename_file_basis_has_swapped_files(self):
2710
state = self.assertUpdate(
2711
active=[('file', 'file-id'),
2712
('other-file', 'file-id-2')],
2713
basis= [('file', 'file-id-2'),
2714
('other-file', 'file-id')],
2715
target=[('file', 'file-id'),
2716
('other-file', 'file-id-2')])
2718
def test_rename_directory_with_contents(self):
2719
state = self.assertUpdate( # active matches basis
2720
active=[('dir1/', 'dir-id'),
2721
('dir1/file', 'file-id')],
2722
basis= [('dir1/', 'dir-id'),
2723
('dir1/file', 'file-id')],
2724
target=[('dir2/', 'dir-id'),
2725
('dir2/file', 'file-id')])
2726
state = self.assertUpdate( # active matches target
2727
active=[('dir2/', 'dir-id'),
2728
('dir2/file', 'file-id')],
2729
basis= [('dir1/', 'dir-id'),
2730
('dir1/file', 'file-id')],
2731
target=[('dir2/', 'dir-id'),
2732
('dir2/file', 'file-id')])
2733
state = self.assertUpdate( # active empty
2735
basis= [('dir1/', 'dir-id'),
2736
('dir1/file', 'file-id')],
2737
target=[('dir2/', 'dir-id'),
2738
('dir2/file', 'file-id')])
2739
state = self.assertUpdate( # active present at other location
2740
active=[('dir3/', 'dir-id'),
2741
('dir3/file', 'file-id')],
2742
basis= [('dir1/', 'dir-id'),
2743
('dir1/file', 'file-id')],
2744
target=[('dir2/', 'dir-id'),
2745
('dir2/file', 'file-id')])
2746
state = self.assertUpdate( # active has different ids
2747
active=[('dir1/', 'dir1-id'),
2748
('dir1/file', 'file1-id'),
2749
('dir2/', 'dir2-id'),
2750
('dir2/file', 'file2-id')],
2751
basis= [('dir1/', 'dir-id'),
2752
('dir1/file', 'file-id')],
2753
target=[('dir2/', 'dir-id'),
2754
('dir2/file', 'file-id')])
2756
def test_invalid_file_not_present(self):
2757
state = self.assertBadDelta(
2758
active=[('file', 'file-id')],
2759
basis= [('file', 'file-id')],
2760
delta=[('other-file', 'file', 'file-id')])
2762
def test_invalid_new_id_same_path(self):
2763
# The bad entry comes after
2764
state = self.assertBadDelta(
2765
active=[('file', 'file-id')],
2766
basis= [('file', 'file-id')],
2767
delta=[(None, 'file', 'file-id-2')])
2768
# The bad entry comes first
2769
state = self.assertBadDelta(
2770
active=[('file', 'file-id-2')],
2771
basis=[('file', 'file-id-2')],
2772
delta=[(None, 'file', 'file-id')])
2774
def test_invalid_existing_id(self):
2775
state = self.assertBadDelta(
2776
active=[('file', 'file-id')],
2777
basis= [('file', 'file-id')],
2778
delta=[(None, 'file', 'file-id')])
2780
def test_invalid_parent_missing(self):
2781
state = self.assertBadDelta(
2784
delta=[(None, 'path/path2', 'file-id')])
2785
# Note: we force the active tree to have the directory, by knowing how
2786
# path_to_ie handles entries with missing parents
2787
state = self.assertBadDelta(
2788
active=[('path/', 'path-id')],
2790
delta=[(None, 'path/path2', 'file-id')])
2791
state = self.assertBadDelta(
2792
active=[('path/', 'path-id'),
2793
('path/path2', 'file-id')],
2795
delta=[(None, 'path/path2', 'file-id')])
2797
def test_renamed_dir_same_path(self):
2798
# We replace the parent directory, with another parent dir. But the C
2799
# file doesn't look like it has been moved.
2800
state = self.assertUpdate(# Same as basis
2801
active=[('dir/', 'A-id'),
2803
basis= [('dir/', 'A-id'),
2805
target=[('dir/', 'C-id'),
2807
state = self.assertUpdate(# Same as target
2808
active=[('dir/', 'C-id'),
2810
basis= [('dir/', 'A-id'),
2812
target=[('dir/', 'C-id'),
2814
state = self.assertUpdate(# empty active
2816
basis= [('dir/', 'A-id'),
2818
target=[('dir/', 'C-id'),
2820
state = self.assertUpdate(# different active
2821
active=[('dir/', 'D-id'),
2823
basis= [('dir/', 'A-id'),
2825
target=[('dir/', 'C-id'),
2828
def test_parent_child_swap(self):
2829
state = self.assertUpdate(# Same as basis
2830
active=[('A/', 'A-id'),
2833
basis= [('A/', 'A-id'),
2836
target=[('A/', 'B-id'),
2839
state = self.assertUpdate(# Same as target
2840
active=[('A/', 'B-id'),
2843
basis= [('A/', 'A-id'),
2846
target=[('A/', 'B-id'),
2849
state = self.assertUpdate(# empty active
2851
basis= [('A/', 'A-id'),
2854
target=[('A/', 'B-id'),
2857
state = self.assertUpdate(# different active
2858
active=[('D/', 'A-id'),
2861
basis= [('A/', 'A-id'),
2864
target=[('A/', 'B-id'),
2868
def test_change_root_id(self):
2869
state = self.assertUpdate( # same as basis
2870
active=[('', 'root-id'),
2871
('file', 'file-id')],
2872
basis= [('', 'root-id'),
2873
('file', 'file-id')],
2874
target=[('', 'target-root-id'),
2875
('file', 'file-id')])
2876
state = self.assertUpdate( # same as target
2877
active=[('', 'target-root-id'),
2878
('file', 'file-id')],
2879
basis= [('', 'root-id'),
2880
('file', 'file-id')],
2881
target=[('', 'target-root-id'),
2882
('file', 'root-id')])
2883
state = self.assertUpdate( # all different
2884
active=[('', 'active-root-id'),
2885
('file', 'file-id')],
2886
basis= [('', 'root-id'),
2887
('file', 'file-id')],
2888
target=[('', 'target-root-id'),
2889
('file', 'root-id')])
2891
def test_change_file_absent_in_active(self):
2892
state = self.assertUpdate(
2894
basis= [('file', 'file-id')],
2895
target=[('file', 'file-id')])
2897
def test_invalid_changed_file(self):
2898
state = self.assertBadDelta( # Not present in basis
2899
active=[('file', 'file-id')],
2901
delta=[('file', 'file', 'file-id')])
2902
state = self.assertBadDelta( # present at another location in basis
2903
active=[('file', 'file-id')],
2904
basis= [('other-file', 'file-id')],
2905
delta=[('file', 'file', 'file-id')])