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
load_tests = load_tests_apply_scenarios
60
class TestCaseWithDirState(tests.TestCaseWithTransport):
61
"""Helper functions for creating DirState objects with various content."""
63
scenarios = test_osutils.dir_reader_scenarios()
66
_dir_reader_class = None
67
_native_to_unicode = None # Not used yet
70
super(TestCaseWithDirState, self).setUp()
71
self.overrideAttr(osutils,
72
'_selected_dir_reader', self._dir_reader_class())
74
def create_empty_dirstate(self):
75
"""Return a locked but empty dirstate"""
76
state = dirstate.DirState.initialize('dirstate')
79
def create_dirstate_with_root(self):
80
"""Return a write-locked state with a single root entry."""
81
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
82
root_entry_direntry = ('', '', 'a-root-value'), [
83
('d', '', 0, False, packed_stat),
86
dirblocks.append(('', [root_entry_direntry]))
87
dirblocks.append(('', []))
88
state = self.create_empty_dirstate()
90
state._set_data([], dirblocks)
97
def create_dirstate_with_root_and_subdir(self):
98
"""Return a locked DirState with a root and a subdir"""
99
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
100
subdir_entry = ('', 'subdir', 'subdir-id'), [
101
('d', '', 0, False, packed_stat),
103
state = self.create_dirstate_with_root()
105
dirblocks = list(state._dirblocks)
106
dirblocks[1][1].append(subdir_entry)
107
state._set_data([], dirblocks)
113
def create_complex_dirstate(self):
114
"""This dirstate contains multiple files and directories.
124
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
126
Notice that a/e is an empty directory.
128
:return: The dirstate, still write-locked.
130
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
131
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
132
root_entry = ('', '', 'a-root-value'), [
133
('d', '', 0, False, packed_stat),
135
a_entry = ('', 'a', 'a-dir'), [
136
('d', '', 0, False, packed_stat),
138
b_entry = ('', 'b', 'b-dir'), [
139
('d', '', 0, False, packed_stat),
141
c_entry = ('', 'c', 'c-file'), [
142
('f', null_sha, 10, False, packed_stat),
144
d_entry = ('', 'd', 'd-file'), [
145
('f', null_sha, 20, False, packed_stat),
147
e_entry = ('a', 'e', 'e-dir'), [
148
('d', '', 0, False, packed_stat),
150
f_entry = ('a', 'f', 'f-file'), [
151
('f', null_sha, 30, False, packed_stat),
153
g_entry = ('b', 'g', 'g-file'), [
154
('f', null_sha, 30, False, packed_stat),
156
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
157
('f', null_sha, 40, False, packed_stat),
160
dirblocks.append(('', [root_entry]))
161
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
162
dirblocks.append(('a', [e_entry, f_entry]))
163
dirblocks.append(('b', [g_entry, h_entry]))
164
state = dirstate.DirState.initialize('dirstate')
167
state._set_data([], dirblocks)
173
def check_state_with_reopen(self, expected_result, state):
174
"""Check that state has current state expected_result.
176
This will check the current state, open the file anew and check it
178
This function expects the current state to be locked for writing, and
179
will unlock it before re-opening.
180
This is required because we can't open a lock_read() while something
181
else has a lock_write().
182
write => mutually exclusive lock
185
# The state should already be write locked, since we just had to do
186
# some operation to get here.
187
self.assertTrue(state._lock_token is not None)
189
self.assertEqual(expected_result[0], state.get_parent_ids())
190
# there should be no ghosts in this tree.
191
self.assertEqual([], state.get_ghosts())
192
# there should be one fileid in this tree - the root of the tree.
193
self.assertEqual(expected_result[1], list(state._iter_entries()))
198
state = dirstate.DirState.on_file('dirstate')
201
self.assertEqual(expected_result[1], list(state._iter_entries()))
205
def create_basic_dirstate(self):
206
"""Create a dirstate with a few files and directories.
216
tree = self.make_branch_and_tree('tree')
217
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
218
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
219
self.build_tree(['tree/' + p for p in paths])
220
tree.set_root_id('TREE_ROOT')
221
tree.add([p.rstrip('/') for p in paths], file_ids)
222
tree.commit('initial', rev_id='rev-1')
223
revision_id = 'rev-1'
224
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
225
t = self.get_transport('tree')
226
a_text = t.get_bytes('a')
227
a_sha = osutils.sha_string(a_text)
229
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
230
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
231
c_text = t.get_bytes('b/c')
232
c_sha = osutils.sha_string(c_text)
234
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
235
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
236
e_text = t.get_bytes('b/d/e')
237
e_sha = osutils.sha_string(e_text)
239
b_c_text = t.get_bytes('b-c')
240
b_c_sha = osutils.sha_string(b_c_text)
241
b_c_len = len(b_c_text)
242
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
243
f_text = t.get_bytes('f')
244
f_sha = osutils.sha_string(f_text)
246
null_stat = dirstate.DirState.NULLSTAT
248
'':(('', '', 'TREE_ROOT'), [
249
('d', '', 0, False, null_stat),
250
('d', '', 0, False, revision_id),
252
'a':(('', 'a', 'a-id'), [
253
('f', '', 0, False, null_stat),
254
('f', a_sha, a_len, False, revision_id),
256
'b':(('', 'b', 'b-id'), [
257
('d', '', 0, False, null_stat),
258
('d', '', 0, False, revision_id),
260
'b/c':(('b', 'c', 'c-id'), [
261
('f', '', 0, False, null_stat),
262
('f', c_sha, c_len, False, revision_id),
264
'b/d':(('b', 'd', 'd-id'), [
265
('d', '', 0, False, null_stat),
266
('d', '', 0, False, revision_id),
268
'b/d/e':(('b/d', 'e', 'e-id'), [
269
('f', '', 0, False, null_stat),
270
('f', e_sha, e_len, False, revision_id),
272
'b-c':(('', 'b-c', 'b-c-id'), [
273
('f', '', 0, False, null_stat),
274
('f', b_c_sha, b_c_len, False, revision_id),
276
'f':(('', 'f', 'f-id'), [
277
('f', '', 0, False, null_stat),
278
('f', f_sha, f_len, False, revision_id),
281
state = dirstate.DirState.from_tree(tree, 'dirstate')
286
# Use a different object, to make sure nothing is pre-cached in memory.
287
state = dirstate.DirState.on_file('dirstate')
289
self.addCleanup(state.unlock)
290
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
291
state._dirblock_state)
292
# This is code is only really tested if we actually have to make more
293
# than one read, so set the page size to something smaller.
294
# We want it to contain about 2.2 records, so that we have a couple
295
# records that we can read per attempt
296
state._bisect_page_size = 200
297
return tree, state, expected
299
def create_duplicated_dirstate(self):
300
"""Create a dirstate with a deleted and added entries.
302
This grabs a basic_dirstate, and then removes and re adds every entry
305
tree, state, expected = self.create_basic_dirstate()
306
# Now we will just remove and add every file so we get an extra entry
307
# per entry. Unversion in reverse order so we handle subdirs
308
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
309
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
310
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
312
# Update the expected dictionary.
313
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
314
orig = expected[path]
316
# This record was deleted in the current tree
317
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
319
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
320
# And didn't exist in the basis tree
321
expected[path2] = (new_key, [orig[1][0],
322
dirstate.DirState.NULL_PARENT_DETAILS])
324
# We will replace the 'dirstate' file underneath 'state', but that is
325
# okay as lock as we unlock 'state' first.
328
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
334
# But we need to leave state in a read-lock because we already have
335
# a cleanup scheduled
337
return tree, state, expected
339
def create_renamed_dirstate(self):
340
"""Create a dirstate with a few internal renames.
342
This takes the basic dirstate, and moves the paths around.
344
tree, state, expected = self.create_basic_dirstate()
346
tree.rename_one('a', 'b/g')
348
tree.rename_one('b/d', 'h')
350
old_a = expected['a']
351
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
352
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
353
('r', 'a', 0, False, '')])
354
old_d = expected['b/d']
355
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
356
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
357
('r', 'b/d', 0, False, '')])
359
old_e = expected['b/d/e']
360
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
362
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
363
('r', 'b/d/e', 0, False, '')])
367
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
374
return tree, state, expected
377
class TestTreeToDirState(TestCaseWithDirState):
379
def test_empty_to_dirstate(self):
380
"""We should be able to create a dirstate for an empty tree."""
381
# There are no files on disk and no parents
382
tree = self.make_branch_and_tree('tree')
383
expected_result = ([], [
384
(('', '', tree.get_root_id()), # common details
385
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
387
state = dirstate.DirState.from_tree(tree, 'dirstate')
389
self.check_state_with_reopen(expected_result, state)
391
def test_1_parents_empty_to_dirstate(self):
392
# create a parent by doing a commit
393
tree = self.make_branch_and_tree('tree')
394
rev_id = tree.commit('first post').encode('utf8')
395
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
396
expected_result = ([rev_id], [
397
(('', '', tree.get_root_id()), # common details
398
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
399
('d', '', 0, False, rev_id), # first parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_2_parents_empty_to_dirstate(self):
410
# create a parent by doing a commit
411
tree = self.make_branch_and_tree('tree')
412
rev_id = tree.commit('first post')
413
tree2 = tree.controldir.sprout('tree2').open_workingtree()
414
rev_id2 = tree2.commit('second post', allow_pointless=True)
415
tree.merge_from_branch(tree2.branch)
416
expected_result = ([rev_id, rev_id2], [
417
(('', '', tree.get_root_id()), # common details
418
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
419
('d', '', 0, False, rev_id), # first parent details
420
('d', '', 0, False, rev_id), # second parent details
422
state = dirstate.DirState.from_tree(tree, 'dirstate')
423
self.check_state_with_reopen(expected_result, state)
430
def test_empty_unknowns_are_ignored_to_dirstate(self):
431
"""We should be able to create a dirstate for an empty tree."""
432
# There are no files on disk and no parents
433
tree = self.make_branch_and_tree('tree')
434
self.build_tree(['tree/unknown'])
435
expected_result = ([], [
436
(('', '', tree.get_root_id()), # common details
437
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def get_tree_with_a_file(self):
443
tree = self.make_branch_and_tree('tree')
444
self.build_tree(['tree/a file'])
445
tree.add('a file', 'a-file-id')
448
def test_non_empty_no_parents_to_dirstate(self):
449
"""We should be able to create a dirstate for an empty tree."""
450
# There are files on disk and no parents
451
tree = self.get_tree_with_a_file()
452
expected_result = ([], [
453
(('', '', tree.get_root_id()), # common details
454
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
456
(('', 'a file', 'a-file-id'), # common
457
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_1_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
# change the current content to be different this will alter stat, sha
469
self.build_tree_contents([('tree/a file', 'new content\n')])
470
expected_result = ([rev_id], [
471
(('', '', tree.get_root_id()), # common details
472
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
473
('d', '', 0, False, rev_id), # first parent details
475
(('', 'a file', 'a-file-id'), # common
476
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
477
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
478
rev_id), # first parent
481
state = dirstate.DirState.from_tree(tree, 'dirstate')
482
self.check_state_with_reopen(expected_result, state)
484
def test_2_parents_not_empty_to_dirstate(self):
485
# create a parent by doing a commit
486
tree = self.get_tree_with_a_file()
487
rev_id = tree.commit('first post').encode('utf8')
488
tree2 = tree.controldir.sprout('tree2').open_workingtree()
489
# change the current content to be different this will alter stat, sha
491
self.build_tree_contents([('tree2/a file', 'merge content\n')])
492
rev_id2 = tree2.commit('second post').encode('utf8')
493
tree.merge_from_branch(tree2.branch)
494
# change the current content to be different this will alter stat, sha
495
# and length again, giving us three distinct values:
496
self.build_tree_contents([('tree/a file', 'new content\n')])
497
expected_result = ([rev_id, rev_id2], [
498
(('', '', tree.get_root_id()), # common details
499
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
500
('d', '', 0, False, rev_id), # first parent details
501
('d', '', 0, False, rev_id), # second parent details
503
(('', 'a file', 'a-file-id'), # common
504
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
505
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
506
rev_id), # first parent
507
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
508
rev_id2), # second parent
511
state = dirstate.DirState.from_tree(tree, 'dirstate')
512
self.check_state_with_reopen(expected_result, state)
514
def test_colliding_fileids(self):
515
# test insertion of parents creating several entries at the same path.
516
# we used to have a bug where they could cause the dirstate to break
517
# its ordering invariants.
518
# create some trees to test from
521
tree = self.make_branch_and_tree('tree%d' % i)
522
self.build_tree(['tree%d/name' % i,])
523
tree.add(['name'], ['file-id%d' % i])
524
revision_id = 'revid-%d' % i
525
tree.commit('message', rev_id=revision_id)
526
parents.append((revision_id,
527
tree.branch.repository.revision_tree(revision_id)))
528
# now fold these trees into a dirstate
529
state = dirstate.DirState.initialize('dirstate')
531
state.set_parent_trees(parents, [])
537
class TestDirStateOnFile(TestCaseWithDirState):
539
def create_updated_dirstate(self):
540
self.build_tree(['a-file'])
541
tree = self.make_branch_and_tree('.')
542
tree.add(['a-file'], ['a-id'])
543
tree.commit('add a-file')
544
# Save and unlock the state, re-open it in readonly mode
545
state = dirstate.DirState.from_tree(tree, 'dirstate')
548
state = dirstate.DirState.on_file('dirstate')
552
def test_construct_with_path(self):
553
tree = self.make_branch_and_tree('tree')
554
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
555
# we want to be able to get the lines of the dirstate that we will
557
lines = state.get_lines()
559
self.build_tree_contents([('dirstate', ''.join(lines))])
561
# no parents, default tree content
562
expected_result = ([], [
563
(('', '', tree.get_root_id()), # common details
564
# current tree details, but new from_tree skips statting, it
565
# uses set_state_from_inventory, and thus depends on the
567
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
570
state = dirstate.DirState.on_file('dirstate')
571
state.lock_write() # check_state_with_reopen will save() and unlock it
572
self.check_state_with_reopen(expected_result, state)
574
def test_can_save_clean_on_file(self):
575
tree = self.make_branch_and_tree('tree')
576
state = dirstate.DirState.from_tree(tree, 'dirstate')
578
# doing a save should work here as there have been no changes.
580
# TODO: stat it and check it hasn't changed; may require waiting
581
# for the state accuracy window.
585
def test_can_save_in_read_lock(self):
586
state = self.create_updated_dirstate()
588
entry = state._get_entry(0, path_utf8='a-file')
589
# The current size should be 0 (default)
590
self.assertEqual(0, entry[1][0][2])
591
# We should have a real entry.
592
self.assertNotEqual((None, None), entry)
593
# Set the cutoff-time into the future, so things look cacheable
594
state._sha_cutoff_time()
595
state._cutoff_time += 10.0
596
st = os.lstat('a-file')
597
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
598
# We updated the current sha1sum because the file is cacheable
599
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
602
# The dirblock has been updated
603
self.assertEqual(st.st_size, entry[1][0][2])
604
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
605
state._dirblock_state)
608
# Now, since we are the only one holding a lock, we should be able
609
# to save and have it written to disk
614
# Re-open the file, and ensure that the state has been updated.
615
state = dirstate.DirState.on_file('dirstate')
618
entry = state._get_entry(0, path_utf8='a-file')
619
self.assertEqual(st.st_size, entry[1][0][2])
623
def test_save_fails_quietly_if_locked(self):
624
"""If dirstate is locked, save will fail without complaining."""
625
state = self.create_updated_dirstate()
627
entry = state._get_entry(0, path_utf8='a-file')
628
# No cached sha1 yet.
629
self.assertEqual('', entry[1][0][1])
630
# Set the cutoff-time into the future, so things look cacheable
631
state._sha_cutoff_time()
632
state._cutoff_time += 10.0
633
st = os.lstat('a-file')
634
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
635
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
637
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
638
state._dirblock_state)
640
# Now, before we try to save, grab another dirstate, and take out a
642
# TODO: jam 20070315 Ideally this would be locked by another
643
# process. To make sure the file is really OS locked.
644
state2 = dirstate.DirState.on_file('dirstate')
647
# This won't actually write anything, because it couldn't grab
648
# a write lock. But it shouldn't raise an error, either.
649
# TODO: jam 20070315 We should probably distinguish between
650
# being dirty because of 'update_entry'. And dirty
651
# because of real modification. So that save() *does*
652
# raise a real error if it fails when we have real
660
# The file on disk should not be modified.
661
state = dirstate.DirState.on_file('dirstate')
664
entry = state._get_entry(0, path_utf8='a-file')
665
self.assertEqual('', entry[1][0][1])
669
def test_save_refuses_if_changes_aborted(self):
670
self.build_tree(['a-file', 'a-dir/'])
671
state = dirstate.DirState.initialize('dirstate')
673
# No stat and no sha1 sum.
674
state.add('a-file', 'a-file-id', 'file', None, '')
679
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
681
('', [(('', '', 'TREE_ROOT'),
682
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
683
('', [(('', 'a-file', 'a-file-id'),
684
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
687
state = dirstate.DirState.on_file('dirstate')
690
state._read_dirblocks_if_needed()
691
self.assertEqual(expected_blocks, state._dirblocks)
693
# Now modify the state, but mark it as inconsistent
694
state.add('a-dir', 'a-dir-id', 'directory', None, '')
695
state._changes_aborted = True
700
state = dirstate.DirState.on_file('dirstate')
703
state._read_dirblocks_if_needed()
704
self.assertEqual(expected_blocks, state._dirblocks)
709
class TestDirStateInitialize(TestCaseWithDirState):
711
def test_initialize(self):
712
expected_result = ([], [
713
(('', '', 'TREE_ROOT'), # common details
714
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
717
state = dirstate.DirState.initialize('dirstate')
719
self.assertIsInstance(state, dirstate.DirState)
720
lines = state.get_lines()
723
# On win32 you can't read from a locked file, even within the same
724
# process. So we have to unlock and release before we check the file
726
self.assertFileEqual(''.join(lines), 'dirstate')
727
state.lock_read() # check_state_with_reopen will unlock
728
self.check_state_with_reopen(expected_result, state)
731
class TestDirStateManipulations(TestCaseWithDirState):
733
def make_minimal_tree(self):
734
tree1 = self.make_branch_and_memory_tree('tree1')
736
self.addCleanup(tree1.unlock)
738
revid1 = tree1.commit('foo')
741
def test_update_minimal_updates_id_index(self):
742
state = self.create_dirstate_with_root_and_subdir()
743
self.addCleanup(state.unlock)
744
id_index = state._get_id_index()
745
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
746
state.add('file-name', 'file-id', 'file', None, '')
747
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
749
state.update_minimal(('', 'new-name', 'file-id'), 'f',
750
path_utf8='new-name')
751
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
753
self.assertEqual([('', 'new-name', 'file-id')],
754
sorted(id_index['file-id']))
757
def test_set_state_from_inventory_no_content_no_parents(self):
758
# setting the current inventory is a slow but important api to support.
759
tree1, revid1 = self.make_minimal_tree()
760
inv = tree1.root_inventory
761
root_id = inv.path2id('')
762
expected_result = [], [
763
(('', '', root_id), [
764
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
765
state = dirstate.DirState.initialize('dirstate')
767
state.set_state_from_inventory(inv)
768
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
770
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
771
state._dirblock_state)
776
# This will unlock it
777
self.check_state_with_reopen(expected_result, state)
779
def test_set_state_from_scratch_no_parents(self):
780
tree1, revid1 = self.make_minimal_tree()
781
inv = tree1.root_inventory
782
root_id = inv.path2id('')
783
expected_result = [], [
784
(('', '', root_id), [
785
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
786
state = dirstate.DirState.initialize('dirstate')
788
state.set_state_from_scratch(inv, [], [])
789
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
791
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
792
state._dirblock_state)
797
# This will unlock it
798
self.check_state_with_reopen(expected_result, state)
800
def test_set_state_from_scratch_identical_parent(self):
801
tree1, revid1 = self.make_minimal_tree()
802
inv = tree1.root_inventory
803
root_id = inv.path2id('')
804
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
805
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
806
parent_entry = ('d', '', 0, False, revid1)
807
expected_result = [revid1], [
808
(('', '', root_id), [d_entry, parent_entry])]
809
state = dirstate.DirState.initialize('dirstate')
811
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
812
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
814
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
815
state._dirblock_state)
820
# This will unlock it
821
self.check_state_with_reopen(expected_result, state)
823
def test_set_state_from_inventory_preserves_hashcache(self):
824
# https://bugs.launchpad.net/bzr/+bug/146176
825
# set_state_from_inventory should preserve the stat and hash value for
826
# workingtree files that are not changed by the inventory.
828
tree = self.make_branch_and_tree('.')
829
# depends on the default format using dirstate...
832
# make a dirstate with some valid hashcache data
833
# file on disk, but that's not needed for this test
834
foo_contents = 'contents of foo'
835
self.build_tree_contents([('foo', foo_contents)])
836
tree.add('foo', 'foo-id')
838
foo_stat = os.stat('foo')
839
foo_packed = dirstate.pack_stat(foo_stat)
840
foo_sha = osutils.sha_string(foo_contents)
841
foo_size = len(foo_contents)
843
# should not be cached yet, because the file's too fresh
845
(('', 'foo', 'foo-id',),
846
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
847
tree._dirstate._get_entry(0, 'foo-id'))
848
# poke in some hashcache information - it wouldn't normally be
849
# stored because it's too fresh
850
tree._dirstate.update_minimal(
851
('', 'foo', 'foo-id'),
852
'f', False, foo_sha, foo_packed, foo_size, 'foo')
853
# now should be cached
855
(('', 'foo', 'foo-id',),
856
[('f', foo_sha, foo_size, False, foo_packed)]),
857
tree._dirstate._get_entry(0, 'foo-id'))
859
# extract the inventory, and add something to it
860
inv = tree._get_root_inventory()
861
# should see the file we poked in...
862
self.assertTrue(inv.has_id('foo-id'))
863
self.assertTrue(inv.has_filename('foo'))
864
inv.add_path('bar', 'file', 'bar-id')
865
tree._dirstate._validate()
866
# this used to cause it to lose its hashcache
867
tree._dirstate.set_state_from_inventory(inv)
868
tree._dirstate._validate()
874
# now check that the state still has the original hashcache value
875
state = tree._dirstate
877
foo_tuple = state._get_entry(0, path_utf8='foo')
879
(('', 'foo', 'foo-id',),
880
[('f', foo_sha, len(foo_contents), False,
881
dirstate.pack_stat(foo_stat))]),
886
def test_set_state_from_inventory_mixed_paths(self):
887
tree1 = self.make_branch_and_tree('tree1')
888
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
889
'tree1/a/b/foo', 'tree1/a-b/bar'])
892
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
893
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
894
tree1.commit('rev1', rev_id='rev1')
895
root_id = tree1.get_root_id()
896
inv = tree1.root_inventory
899
expected_result1 = [('', '', root_id, 'd'),
900
('', 'a', 'a-id', 'd'),
901
('', 'a-b', 'a-b-id', 'd'),
902
('a', 'b', 'b-id', 'd'),
903
('a/b', 'foo', 'foo-id', 'f'),
904
('a-b', 'bar', 'bar-id', 'f'),
906
expected_result2 = [('', '', root_id, 'd'),
907
('', 'a', 'a-id', 'd'),
908
('', 'a-b', 'a-b-id', 'd'),
909
('a-b', 'bar', 'bar-id', 'f'),
911
state = dirstate.DirState.initialize('dirstate')
913
state.set_state_from_inventory(inv)
915
for entry in state._iter_entries():
916
values.append(entry[0] + entry[1][0][:1])
917
self.assertEqual(expected_result1, values)
919
state.set_state_from_inventory(inv)
921
for entry in state._iter_entries():
922
values.append(entry[0] + entry[1][0][:1])
923
self.assertEqual(expected_result2, values)
927
def test_set_path_id_no_parents(self):
928
"""The id of a path can be changed trivally with no parents."""
929
state = dirstate.DirState.initialize('dirstate')
931
# check precondition to be sure the state does change appropriately.
932
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
933
self.assertEqual([root_entry], list(state._iter_entries()))
934
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
935
self.assertEqual(root_entry,
936
state._get_entry(0, fileid_utf8='TREE_ROOT'))
937
self.assertEqual((None, None),
938
state._get_entry(0, fileid_utf8='second-root-id'))
939
state.set_path_id('', 'second-root-id')
940
new_root_entry = (('', '', 'second-root-id'),
941
[('d', '', 0, False, 'x'*32)])
942
expected_rows = [new_root_entry]
943
self.assertEqual(expected_rows, list(state._iter_entries()))
944
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
945
self.assertEqual(new_root_entry,
946
state._get_entry(0, fileid_utf8='second-root-id'))
947
self.assertEqual((None, None),
948
state._get_entry(0, fileid_utf8='TREE_ROOT'))
949
# should work across save too
953
state = dirstate.DirState.on_file('dirstate')
957
self.assertEqual(expected_rows, list(state._iter_entries()))
961
def test_set_path_id_with_parents(self):
962
"""Set the root file id in a dirstate with parents"""
963
mt = self.make_branch_and_tree('mt')
964
# in case the default tree format uses a different root id
965
mt.set_root_id('TREE_ROOT')
966
mt.commit('foo', rev_id='parent-revid')
967
rt = mt.branch.repository.revision_tree('parent-revid')
968
state = dirstate.DirState.initialize('dirstate')
971
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
972
root_entry = (('', '', 'TREE_ROOT'),
973
[('d', '', 0, False, 'x'*32),
974
('d', '', 0, False, 'parent-revid')])
975
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
976
self.assertEqual(root_entry,
977
state._get_entry(0, fileid_utf8='TREE_ROOT'))
978
self.assertEqual((None, None),
979
state._get_entry(0, fileid_utf8='Asecond-root-id'))
980
state.set_path_id('', 'Asecond-root-id')
982
# now see that it is what we expected
983
old_root_entry = (('', '', 'TREE_ROOT'),
984
[('a', '', 0, False, ''),
985
('d', '', 0, False, 'parent-revid')])
986
new_root_entry = (('', '', 'Asecond-root-id'),
987
[('d', '', 0, False, ''),
988
('a', '', 0, False, '')])
989
expected_rows = [new_root_entry, old_root_entry]
991
self.assertEqual(expected_rows, list(state._iter_entries()))
992
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
993
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
994
self.assertEqual((None, None),
995
state._get_entry(0, fileid_utf8='TREE_ROOT'))
996
self.assertEqual(old_root_entry,
997
state._get_entry(1, fileid_utf8='TREE_ROOT'))
998
self.assertEqual(new_root_entry,
999
state._get_entry(0, fileid_utf8='Asecond-root-id'))
1000
self.assertEqual((None, None),
1001
state._get_entry(1, fileid_utf8='Asecond-root-id'))
1002
# should work across save too
1006
# now flush & check we get the same
1007
state = dirstate.DirState.on_file('dirstate')
1011
self.assertEqual(expected_rows, list(state._iter_entries()))
1014
# now change within an existing file-backed state
1018
state.set_path_id('', 'tree-root-2')
1023
def test_set_parent_trees_no_content(self):
1024
# set_parent_trees is a slow but important api to support.
1025
tree1 = self.make_branch_and_memory_tree('tree1')
1029
revid1 = tree1.commit('foo')
1032
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1033
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1036
revid2 = tree2.commit('foo')
1037
root_id = tree2.get_root_id()
1040
state = dirstate.DirState.initialize('dirstate')
1042
state.set_path_id('', root_id)
1043
state.set_parent_trees(
1044
((revid1, tree1.branch.repository.revision_tree(revid1)),
1045
(revid2, tree2.branch.repository.revision_tree(revid2)),
1046
('ghost-rev', None)),
1048
# check we can reopen and use the dirstate after setting parent
1055
state = dirstate.DirState.on_file('dirstate')
1058
self.assertEqual([revid1, revid2, 'ghost-rev'],
1059
state.get_parent_ids())
1060
# iterating the entire state ensures that the state is parsable.
1061
list(state._iter_entries())
1062
# be sure that it sets not appends - change it
1063
state.set_parent_trees(
1064
((revid1, tree1.branch.repository.revision_tree(revid1)),
1065
('ghost-rev', None)),
1067
# and now put it back.
1068
state.set_parent_trees(
1069
((revid1, tree1.branch.repository.revision_tree(revid1)),
1070
(revid2, tree2.branch.repository.revision_tree(revid2)),
1071
('ghost-rev', tree2.branch.repository.revision_tree(
1072
_mod_revision.NULL_REVISION))),
1074
self.assertEqual([revid1, revid2, 'ghost-rev'],
1075
state.get_parent_ids())
1076
# the ghost should be recorded as such by set_parent_trees.
1077
self.assertEqual(['ghost-rev'], state.get_ghosts())
1079
[(('', '', root_id), [
1080
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1081
('d', '', 0, False, revid1),
1082
('d', '', 0, False, revid1)
1084
list(state._iter_entries()))
1088
def test_set_parent_trees_file_missing_from_tree(self):
1089
# Adding a parent tree may reference files not in the current state.
1090
# they should get listed just once by id, even if they are in two
1092
# set_parent_trees is a slow but important api to support.
1093
tree1 = self.make_branch_and_memory_tree('tree1')
1097
tree1.add(['a file'], ['file-id'], ['file'])
1098
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1099
revid1 = tree1.commit('foo')
1102
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1103
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1106
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1107
revid2 = tree2.commit('foo')
1108
root_id = tree2.get_root_id()
1111
# check the layout in memory
1112
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1113
(('', '', root_id), [
1114
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1115
('d', '', 0, False, revid1.encode('utf8')),
1116
('d', '', 0, False, revid1.encode('utf8'))
1118
(('', 'a file', 'file-id'), [
1119
('a', '', 0, False, ''),
1120
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1121
revid1.encode('utf8')),
1122
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1123
revid2.encode('utf8'))
1126
state = dirstate.DirState.initialize('dirstate')
1128
state.set_path_id('', root_id)
1129
state.set_parent_trees(
1130
((revid1, tree1.branch.repository.revision_tree(revid1)),
1131
(revid2, tree2.branch.repository.revision_tree(revid2)),
1137
# check_state_with_reopen will unlock
1138
self.check_state_with_reopen(expected_result, state)
1140
### add a path via _set_data - so we dont need delta work, just
1141
# raw data in, and ensure that it comes out via get_lines happily.
1143
def test_add_path_to_root_no_parents_all_data(self):
1144
# The most trivial addition of a path is when there are no parents and
1145
# its in the root and all data about the file is supplied
1146
self.build_tree(['a file'])
1147
stat = os.lstat('a file')
1148
# the 1*20 is the sha1 pretend value.
1149
state = dirstate.DirState.initialize('dirstate')
1150
expected_entries = [
1151
(('', '', 'TREE_ROOT'), [
1152
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1154
(('', 'a file', 'a-file-id'), [
1155
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1159
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1160
# having added it, it should be in the output of iter_entries.
1161
self.assertEqual(expected_entries, list(state._iter_entries()))
1162
# saving and reloading should not affect this.
1166
state = dirstate.DirState.on_file('dirstate')
1168
self.addCleanup(state.unlock)
1169
self.assertEqual(expected_entries, list(state._iter_entries()))
1171
def test_add_path_to_unversioned_directory(self):
1172
"""Adding a path to an unversioned directory should error.
1174
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1175
once dirstate is stable and if it is merged with WorkingTree3, consider
1176
removing this copy of the test.
1178
self.build_tree(['unversioned/', 'unversioned/a file'])
1179
state = dirstate.DirState.initialize('dirstate')
1180
self.addCleanup(state.unlock)
1181
self.assertRaises(errors.NotVersionedError, state.add,
1182
'unversioned/a file', 'a-file-id', 'file', None, None)
1184
def test_add_directory_to_root_no_parents_all_data(self):
1185
# The most trivial addition of a dir is when there are no parents and
1186
# its in the root and all data about the file is supplied
1187
self.build_tree(['a dir/'])
1188
stat = os.lstat('a dir')
1189
expected_entries = [
1190
(('', '', 'TREE_ROOT'), [
1191
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1193
(('', 'a dir', 'a dir id'), [
1194
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1197
state = dirstate.DirState.initialize('dirstate')
1199
state.add('a dir', 'a dir id', 'directory', stat, None)
1200
# having added it, it should be in the output of iter_entries.
1201
self.assertEqual(expected_entries, list(state._iter_entries()))
1202
# saving and reloading should not affect this.
1206
state = dirstate.DirState.on_file('dirstate')
1208
self.addCleanup(state.unlock)
1210
self.assertEqual(expected_entries, list(state._iter_entries()))
1212
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1213
# The most trivial addition of a symlink when there are no parents and
1214
# its in the root and all data about the file is supplied
1215
# bzr doesn't support fake symlinks on windows, yet.
1216
self.requireFeature(features.SymlinkFeature)
1217
os.symlink(target, link_name)
1218
stat = os.lstat(link_name)
1219
expected_entries = [
1220
(('', '', 'TREE_ROOT'), [
1221
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1223
(('', link_name.encode('UTF-8'), 'a link id'), [
1224
('l', target.encode('UTF-8'), stat[6],
1225
False, dirstate.pack_stat(stat)), # current tree
1228
state = dirstate.DirState.initialize('dirstate')
1230
state.add(link_name, 'a link id', 'symlink', stat,
1231
target.encode('UTF-8'))
1232
# having added it, it should be in the output of iter_entries.
1233
self.assertEqual(expected_entries, list(state._iter_entries()))
1234
# saving and reloading should not affect this.
1238
state = dirstate.DirState.on_file('dirstate')
1240
self.addCleanup(state.unlock)
1241
self.assertEqual(expected_entries, list(state._iter_entries()))
1243
def test_add_symlink_to_root_no_parents_all_data(self):
1244
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1246
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1247
self.requireFeature(features.UnicodeFilenameFeature)
1248
self._test_add_symlink_to_root_no_parents_all_data(
1249
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1251
def test_add_directory_and_child_no_parents_all_data(self):
1252
# after adding a directory, we should be able to add children to it.
1253
self.build_tree(['a dir/', 'a dir/a file'])
1254
dirstat = os.lstat('a dir')
1255
filestat = os.lstat('a dir/a file')
1256
expected_entries = [
1257
(('', '', 'TREE_ROOT'), [
1258
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1260
(('', 'a dir', 'a dir id'), [
1261
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1263
(('a dir', 'a file', 'a-file-id'), [
1264
('f', '1'*20, 25, False,
1265
dirstate.pack_stat(filestat)), # current tree details
1268
state = dirstate.DirState.initialize('dirstate')
1270
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1271
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1272
# added it, it should be in the output of iter_entries.
1273
self.assertEqual(expected_entries, list(state._iter_entries()))
1274
# saving and reloading should not affect this.
1278
state = dirstate.DirState.on_file('dirstate')
1280
self.addCleanup(state.unlock)
1281
self.assertEqual(expected_entries, list(state._iter_entries()))
1283
def test_add_tree_reference(self):
1284
# make a dirstate and add a tree reference
1285
state = dirstate.DirState.initialize('dirstate')
1287
('', 'subdir', 'subdir-id'),
1288
[('t', 'subtree-123123', 0, False,
1289
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1292
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1293
entry = state._get_entry(0, 'subdir-id', 'subdir')
1294
self.assertEqual(entry, expected_entry)
1299
# now check we can read it back
1301
self.addCleanup(state.unlock)
1303
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1304
self.assertEqual(entry, entry2)
1305
self.assertEqual(entry, expected_entry)
1306
# and lookup by id should work too
1307
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1308
self.assertEqual(entry, expected_entry)
1310
def test_add_forbidden_names(self):
1311
state = dirstate.DirState.initialize('dirstate')
1312
self.addCleanup(state.unlock)
1313
self.assertRaises(errors.BzrError,
1314
state.add, '.', 'ass-id', 'directory', None, None)
1315
self.assertRaises(errors.BzrError,
1316
state.add, '..', 'ass-id', 'directory', None, None)
1318
def test_set_state_with_rename_b_a_bug_395556(self):
1319
# bug 395556 uncovered a bug where the dirstate ends up with a false
1320
# relocation record - in a tree with no parents there should be no
1321
# absent or relocated records. This then leads to further corruption
1322
# when a commit occurs, as the incorrect relocation gathers an
1323
# incorrect absent in tree 1, and future changes go to pot.
1324
tree1 = self.make_branch_and_tree('tree1')
1325
self.build_tree(['tree1/b'])
1328
tree1.add(['b'], ['b-id'])
1329
root_id = tree1.get_root_id()
1330
inv = tree1.root_inventory
1331
state = dirstate.DirState.initialize('dirstate')
1333
# Set the initial state with 'b'
1334
state.set_state_from_inventory(inv)
1335
inv.rename('b-id', root_id, 'a')
1336
# Set the new state with 'a', which currently corrupts.
1337
state.set_state_from_inventory(inv)
1338
expected_result1 = [('', '', root_id, 'd'),
1339
('', 'a', 'b-id', 'f'),
1342
for entry in state._iter_entries():
1343
values.append(entry[0] + entry[1][0][:1])
1344
self.assertEqual(expected_result1, values)
1351
class TestDirStateHashUpdates(TestCaseWithDirState):
1353
def do_update_entry(self, state, path):
1354
entry = state._get_entry(0, path_utf8=path)
1355
stat = os.lstat(path)
1356
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1358
def _read_state_content(self, state):
1359
"""Read the content of the dirstate file.
1361
On Windows when one process locks a file, you can't even open() the
1362
file in another process (to read it). So we go directly to
1363
state._state_file. This should always be the exact disk representation,
1364
so it is reasonable to do so.
1365
DirState also always seeks before reading, so it doesn't matter if we
1366
bump the file pointer.
1368
state._state_file.seek(0)
1369
return state._state_file.read()
1371
def test_worth_saving_limit_avoids_writing(self):
1372
tree = self.make_branch_and_tree('.')
1373
self.build_tree(['c', 'd'])
1375
tree.add(['c', 'd'], ['c-id', 'd-id'])
1376
tree.commit('add c and d')
1377
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1378
worth_saving_limit=2)
1381
self.addCleanup(state.unlock)
1382
state._read_dirblocks_if_needed()
1383
state.adjust_time(+20) # Allow things to be cached
1384
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1385
state._dirblock_state)
1386
content = self._read_state_content(state)
1387
self.do_update_entry(state, 'c')
1388
self.assertEqual(1, len(state._known_hash_changes))
1389
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1390
state._dirblock_state)
1392
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1393
# hash values haven't been written out.
1394
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1395
state._dirblock_state)
1396
self.assertEqual(content, self._read_state_content(state))
1397
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1398
state._dirblock_state)
1399
self.do_update_entry(state, 'd')
1400
self.assertEqual(2, len(state._known_hash_changes))
1402
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1403
state._dirblock_state)
1404
self.assertEqual(0, len(state._known_hash_changes))
1407
class TestGetLines(TestCaseWithDirState):
1409
def test_get_line_with_2_rows(self):
1410
state = self.create_dirstate_with_root_and_subdir()
1412
self.assertEqual(['#bazaar dirstate flat format 3\n',
1413
'crc32: 41262208\n',
1417
'\x00\x00a-root-value\x00'
1418
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1419
'\x00subdir\x00subdir-id\x00'
1420
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1421
], state.get_lines())
1425
def test_entry_to_line(self):
1426
state = self.create_dirstate_with_root()
1429
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1430
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1431
state._entry_to_line(state._dirblocks[0][1][0]))
1435
def test_entry_to_line_with_parent(self):
1436
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1437
root_entry = ('', '', 'a-root-value'), [
1438
('d', '', 0, False, packed_stat), # current tree details
1439
# first: a pointer to the current location
1440
('a', 'dirname/basename', 0, False, ''),
1442
state = dirstate.DirState.initialize('dirstate')
1445
'\x00\x00a-root-value\x00'
1446
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1447
'a\x00dirname/basename\x000\x00n\x00',
1448
state._entry_to_line(root_entry))
1452
def test_entry_to_line_with_two_parents_at_different_paths(self):
1453
# / in the tree, at / in one parent and /dirname/basename in the other.
1454
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1455
root_entry = ('', '', 'a-root-value'), [
1456
('d', '', 0, False, packed_stat), # current tree details
1457
('d', '', 0, False, 'rev_id'), # first parent details
1458
# second: a pointer to the current location
1459
('a', 'dirname/basename', 0, False, ''),
1461
state = dirstate.DirState.initialize('dirstate')
1464
'\x00\x00a-root-value\x00'
1465
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1466
'd\x00\x000\x00n\x00rev_id\x00'
1467
'a\x00dirname/basename\x000\x00n\x00',
1468
state._entry_to_line(root_entry))
1472
def test_iter_entries(self):
1473
# we should be able to iterate the dirstate entries from end to end
1474
# this is for get_lines to be easy to read.
1475
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1477
root_entries = [(('', '', 'a-root-value'), [
1478
('d', '', 0, False, packed_stat), # current tree details
1480
dirblocks.append(('', root_entries))
1481
# add two files in the root
1482
subdir_entry = ('', 'subdir', 'subdir-id'), [
1483
('d', '', 0, False, packed_stat), # current tree details
1485
afile_entry = ('', 'afile', 'afile-id'), [
1486
('f', 'sha1value', 34, False, packed_stat), # current tree details
1488
dirblocks.append(('', [subdir_entry, afile_entry]))
1490
file_entry2 = ('subdir', '2file', '2file-id'), [
1491
('f', 'sha1value', 23, False, packed_stat), # current tree details
1493
dirblocks.append(('subdir', [file_entry2]))
1494
state = dirstate.DirState.initialize('dirstate')
1496
state._set_data([], dirblocks)
1497
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1499
self.assertEqual(expected_entries, list(state._iter_entries()))
1504
class TestGetBlockRowIndex(TestCaseWithDirState):
1506
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1507
file_present, state, dirname, basename, tree_index):
1508
self.assertEqual((block_index, row_index, dir_present, file_present),
1509
state._get_block_entry_index(dirname, basename, tree_index))
1511
block = state._dirblocks[block_index]
1512
self.assertEqual(dirname, block[0])
1513
if dir_present and file_present:
1514
row = state._dirblocks[block_index][1][row_index]
1515
self.assertEqual(dirname, row[0][0])
1516
self.assertEqual(basename, row[0][1])
1518
def test_simple_structure(self):
1519
state = self.create_dirstate_with_root_and_subdir()
1520
self.addCleanup(state.unlock)
1521
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1522
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1523
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1524
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1525
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1528
def test_complex_structure_exists(self):
1529
state = self.create_complex_dirstate()
1530
self.addCleanup(state.unlock)
1531
# Make sure we can find everything that exists
1532
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1533
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1534
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1535
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1536
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1537
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1538
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1539
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1540
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1541
'b', 'h\xc3\xa5', 0)
1543
def test_complex_structure_missing(self):
1544
state = self.create_complex_dirstate()
1545
self.addCleanup(state.unlock)
1546
# Make sure things would be inserted in the right locations
1547
# '_' comes before 'a'
1548
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1549
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1550
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1551
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1553
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1554
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1555
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1556
# This would be inserted between a/ and b/
1557
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1559
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1562
class TestGetEntry(TestCaseWithDirState):
1564
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1565
"""Check that the right entry is returned for a request to getEntry."""
1566
entry = state._get_entry(index, path_utf8=path)
1568
self.assertEqual((None, None), entry)
1571
self.assertEqual((dirname, basename, file_id), cur[:3])
1573
def test_simple_structure(self):
1574
state = self.create_dirstate_with_root_and_subdir()
1575
self.addCleanup(state.unlock)
1576
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1577
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1578
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1579
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1580
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1582
def test_complex_structure_exists(self):
1583
state = self.create_complex_dirstate()
1584
self.addCleanup(state.unlock)
1585
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1586
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1587
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1588
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1589
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1590
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1591
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1592
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1593
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1596
def test_complex_structure_missing(self):
1597
state = self.create_complex_dirstate()
1598
self.addCleanup(state.unlock)
1599
self.assertEntryEqual(None, None, None, state, '_', 0)
1600
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1601
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1602
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1604
def test_get_entry_uninitialized(self):
1605
"""Calling get_entry will load data if it needs to"""
1606
state = self.create_dirstate_with_root()
1612
state = dirstate.DirState.on_file('dirstate')
1615
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1616
state._header_state)
1617
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1618
state._dirblock_state)
1619
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1624
class TestIterChildEntries(TestCaseWithDirState):
1626
def create_dirstate_with_two_trees(self):
1627
"""This dirstate contains multiple files and directories.
1637
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1639
Notice that a/e is an empty directory.
1641
There is one parent tree, which has the same shape with the following variations:
1642
b/g in the parent is gone.
1643
b/h in the parent has a different id
1644
b/i is new in the parent
1645
c is renamed to b/j in the parent
1647
:return: The dirstate, still write-locked.
1649
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1650
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1651
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1652
root_entry = ('', '', 'a-root-value'), [
1653
('d', '', 0, False, packed_stat),
1654
('d', '', 0, False, 'parent-revid'),
1656
a_entry = ('', 'a', 'a-dir'), [
1657
('d', '', 0, False, packed_stat),
1658
('d', '', 0, False, 'parent-revid'),
1660
b_entry = ('', 'b', 'b-dir'), [
1661
('d', '', 0, False, packed_stat),
1662
('d', '', 0, False, 'parent-revid'),
1664
c_entry = ('', 'c', 'c-file'), [
1665
('f', null_sha, 10, False, packed_stat),
1666
('r', 'b/j', 0, False, ''),
1668
d_entry = ('', 'd', 'd-file'), [
1669
('f', null_sha, 20, False, packed_stat),
1670
('f', 'd', 20, False, 'parent-revid'),
1672
e_entry = ('a', 'e', 'e-dir'), [
1673
('d', '', 0, False, packed_stat),
1674
('d', '', 0, False, 'parent-revid'),
1676
f_entry = ('a', 'f', 'f-file'), [
1677
('f', null_sha, 30, False, packed_stat),
1678
('f', 'f', 20, False, 'parent-revid'),
1680
g_entry = ('b', 'g', 'g-file'), [
1681
('f', null_sha, 30, False, packed_stat),
1682
NULL_PARENT_DETAILS,
1684
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1685
('f', null_sha, 40, False, packed_stat),
1686
NULL_PARENT_DETAILS,
1688
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1689
NULL_PARENT_DETAILS,
1690
('f', 'h', 20, False, 'parent-revid'),
1692
i_entry = ('b', 'i', 'i-file'), [
1693
NULL_PARENT_DETAILS,
1694
('f', 'h', 20, False, 'parent-revid'),
1696
j_entry = ('b', 'j', 'c-file'), [
1697
('r', 'c', 0, False, ''),
1698
('f', 'j', 20, False, 'parent-revid'),
1701
dirblocks.append(('', [root_entry]))
1702
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1703
dirblocks.append(('a', [e_entry, f_entry]))
1704
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1705
state = dirstate.DirState.initialize('dirstate')
1708
state._set_data(['parent'], dirblocks)
1712
return state, dirblocks
1714
def test_iter_children_b(self):
1715
state, dirblocks = self.create_dirstate_with_two_trees()
1716
self.addCleanup(state.unlock)
1717
expected_result = []
1718
expected_result.append(dirblocks[3][1][2]) # h2
1719
expected_result.append(dirblocks[3][1][3]) # i
1720
expected_result.append(dirblocks[3][1][4]) # j
1721
self.assertEqual(expected_result,
1722
list(state._iter_child_entries(1, 'b')))
1724
def test_iter_child_root(self):
1725
state, dirblocks = self.create_dirstate_with_two_trees()
1726
self.addCleanup(state.unlock)
1727
expected_result = []
1728
expected_result.append(dirblocks[1][1][0]) # a
1729
expected_result.append(dirblocks[1][1][1]) # b
1730
expected_result.append(dirblocks[1][1][3]) # d
1731
expected_result.append(dirblocks[2][1][0]) # e
1732
expected_result.append(dirblocks[2][1][1]) # f
1733
expected_result.append(dirblocks[3][1][2]) # h2
1734
expected_result.append(dirblocks[3][1][3]) # i
1735
expected_result.append(dirblocks[3][1][4]) # j
1736
self.assertEqual(expected_result,
1737
list(state._iter_child_entries(1, '')))
1740
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1741
"""Test that DirState adds entries in the right order."""
1743
def test_add_sorting(self):
1744
"""Add entries in lexicographical order, we get path sorted order.
1746
This tests it to a depth of 4, to make sure we don't just get it right
1747
at a single depth. 'a/a' should come before 'a-a', even though it
1748
doesn't lexicographically.
1750
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1751
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1754
state = dirstate.DirState.initialize('dirstate')
1755
self.addCleanup(state.unlock)
1757
fake_stat = os.stat('dirstate')
1759
d_id = d.replace('/', '_')+'-id'
1760
file_path = d + '/f'
1761
file_id = file_path.replace('/', '_')+'-id'
1762
state.add(d, d_id, 'directory', fake_stat, null_sha)
1763
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1765
expected = ['', '', 'a',
1766
'a/a', 'a/a/a', 'a/a/a/a',
1767
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1769
split = lambda p:p.split('/')
1770
self.assertEqual(sorted(expected, key=split), expected)
1771
dirblock_names = [d[0] for d in state._dirblocks]
1772
self.assertEqual(expected, dirblock_names)
1774
def test_set_parent_trees_correct_order(self):
1775
"""After calling set_parent_trees() we should maintain the order."""
1776
dirs = ['a', 'a-a', 'a/a']
1778
state = dirstate.DirState.initialize('dirstate')
1779
self.addCleanup(state.unlock)
1781
fake_stat = os.stat('dirstate')
1783
d_id = d.replace('/', '_')+'-id'
1784
file_path = d + '/f'
1785
file_id = file_path.replace('/', '_')+'-id'
1786
state.add(d, d_id, 'directory', fake_stat, null_sha)
1787
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1789
expected = ['', '', 'a', 'a/a', 'a-a']
1790
dirblock_names = [d[0] for d in state._dirblocks]
1791
self.assertEqual(expected, dirblock_names)
1793
# *really* cheesy way to just get an empty tree
1794
repo = self.make_repository('repo')
1795
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1796
state.set_parent_trees([('null:', empty_tree)], [])
1798
dirblock_names = [d[0] for d in state._dirblocks]
1799
self.assertEqual(expected, dirblock_names)
1802
class InstrumentedDirState(dirstate.DirState):
1803
"""An DirState with instrumented sha1 functionality."""
1805
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1806
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1807
worth_saving_limit=worth_saving_limit)
1808
self._time_offset = 0
1810
# member is dynamically set in DirState.__init__ to turn on trace
1811
self._sha1_provider = sha1_provider
1812
self._sha1_file = self._sha1_file_and_log
1814
def _sha_cutoff_time(self):
1815
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1816
self._cutoff_time = timestamp + self._time_offset
1818
def _sha1_file_and_log(self, abspath):
1819
self._log.append(('sha1', abspath))
1820
return self._sha1_provider.sha1(abspath)
1822
def _read_link(self, abspath, old_link):
1823
self._log.append(('read_link', abspath, old_link))
1824
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1826
def _lstat(self, abspath, entry):
1827
self._log.append(('lstat', abspath))
1828
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1830
def _is_executable(self, mode, old_executable):
1831
self._log.append(('is_exec', mode, old_executable))
1832
return super(InstrumentedDirState, self)._is_executable(mode,
1835
def adjust_time(self, secs):
1836
"""Move the clock forward or back.
1838
:param secs: The amount to adjust the clock by. Positive values make it
1839
seem as if we are in the future, negative values make it seem like we
1842
self._time_offset += secs
1843
self._cutoff_time = None
1846
class _FakeStat(object):
1847
"""A class with the same attributes as a real stat result."""
1849
def __init__(self, size, mtime, ctime, dev, ino, mode):
1851
self.st_mtime = mtime
1852
self.st_ctime = ctime
1859
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1860
st.st_ino, st.st_mode)
1863
class TestPackStat(tests.TestCaseWithTransport):
1865
def assertPackStat(self, expected, stat_value):
1866
"""Check the packed and serialized form of a stat value."""
1867
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1869
def test_pack_stat_int(self):
1870
st = _FakeStat(6859, 1172758614, 1172758617, 777, 6499538, 0o100644)
1871
# Make sure that all parameters have an impact on the packed stat.
1872
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1875
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1876
st.st_mtime = 1172758620
1878
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1879
st.st_ctime = 1172758630
1881
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1884
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1887
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1888
st.st_mode = 0o100744
1890
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1892
def test_pack_stat_float(self):
1893
"""On some platforms mtime and ctime are floats.
1895
Make sure we don't get warnings or errors, and that we ignore changes <
1898
st = _FakeStat(7000, 1172758614.0, 1172758617.0,
1899
777, 6499538, 0o100644)
1900
# These should all be the same as the integer counterparts
1901
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1902
st.st_mtime = 1172758620.0
1904
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1905
st.st_ctime = 1172758630.0
1907
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1908
# fractional seconds are discarded, so no change from above
1909
st.st_mtime = 1172758620.453
1910
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1911
st.st_ctime = 1172758630.228
1912
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1915
class TestBisect(TestCaseWithDirState):
1916
"""Test the ability to bisect into the disk format."""
1918
def assertBisect(self, expected_map, map_keys, state, paths):
1919
"""Assert that bisecting for paths returns the right result.
1921
:param expected_map: A map from key => entry value
1922
:param map_keys: The keys to expect for each path
1923
:param state: The DirState object.
1924
:param paths: A list of paths, these will automatically be split into
1925
(dir, name) tuples, and sorted according to how _bisect
1928
result = state._bisect(paths)
1929
# For now, results are just returned in whatever order we read them.
1930
# We could sort by (dir, name, file_id) or something like that, but in
1931
# the end it would still be fairly arbitrary, and we don't want the
1932
# extra overhead if we can avoid it. So sort everything to make sure
1934
self.assertEqual(len(map_keys), len(paths))
1936
for path, keys in zip(paths, map_keys):
1938
# This should not be present in the output
1940
expected[path] = sorted(expected_map[k] for k in keys)
1942
# The returned values are just arranged randomly based on when they
1943
# were read, for testing, make sure it is properly sorted.
1947
self.assertEqual(expected, result)
1949
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1950
"""Assert that bisecting for dirbblocks returns the right result.
1952
:param expected_map: A map from key => expected values
1953
:param map_keys: A nested list of paths we expect to be returned.
1954
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1955
:param state: The DirState object.
1956
:param paths: A list of directories
1958
result = state._bisect_dirblocks(paths)
1959
self.assertEqual(len(map_keys), len(paths))
1961
for path, keys in zip(paths, map_keys):
1963
# This should not be present in the output
1965
expected[path] = sorted(expected_map[k] for k in keys)
1969
self.assertEqual(expected, result)
1971
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1972
"""Assert the return value of a recursive bisection.
1974
:param expected_map: A map from key => entry value
1975
:param map_keys: A list of paths we expect to be returned.
1976
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1977
:param state: The DirState object.
1978
:param paths: A list of files and directories. It will be broken up
1979
into (dir, name) pairs and sorted before calling _bisect_recursive.
1982
for key in map_keys:
1983
entry = expected_map[key]
1984
dir_name_id, trees_info = entry
1985
expected[dir_name_id] = trees_info
1987
result = state._bisect_recursive(paths)
1989
self.assertEqual(expected, result)
1991
def test_bisect_each(self):
1992
"""Find a single record using bisect."""
1993
tree, state, expected = self.create_basic_dirstate()
1995
# Bisect should return the rows for the specified files.
1996
self.assertBisect(expected, [['']], state, [''])
1997
self.assertBisect(expected, [['a']], state, ['a'])
1998
self.assertBisect(expected, [['b']], state, ['b'])
1999
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2000
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2001
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2002
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2003
self.assertBisect(expected, [['f']], state, ['f'])
2005
def test_bisect_multi(self):
2006
"""Bisect can be used to find multiple records at the same time."""
2007
tree, state, expected = self.create_basic_dirstate()
2008
# Bisect should be capable of finding multiple entries at the same time
2009
self.assertBisect(expected, [['a'], ['b'], ['f']],
2010
state, ['a', 'b', 'f'])
2011
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2012
state, ['f', 'b/d', 'b/d/e'])
2013
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2014
state, ['b', 'b-c', 'b/c'])
2016
def test_bisect_one_page(self):
2017
"""Test bisect when there is only 1 page to read"""
2018
tree, state, expected = self.create_basic_dirstate()
2019
state._bisect_page_size = 5000
2020
self.assertBisect(expected,[['']], state, [''])
2021
self.assertBisect(expected,[['a']], state, ['a'])
2022
self.assertBisect(expected,[['b']], state, ['b'])
2023
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2024
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2025
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2026
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2027
self.assertBisect(expected,[['f']], state, ['f'])
2028
self.assertBisect(expected,[['a'], ['b'], ['f']],
2029
state, ['a', 'b', 'f'])
2030
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2031
state, ['b/d', 'b/d/e', 'f'])
2032
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2033
state, ['b', 'b/c', 'b-c'])
2035
def test_bisect_duplicate_paths(self):
2036
"""When bisecting for a path, handle multiple entries."""
2037
tree, state, expected = self.create_duplicated_dirstate()
2039
# Now make sure that both records are properly returned.
2040
self.assertBisect(expected, [['']], state, [''])
2041
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2042
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2043
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2044
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2045
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2047
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2048
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2050
def test_bisect_page_size_too_small(self):
2051
"""If the page size is too small, we will auto increase it."""
2052
tree, state, expected = self.create_basic_dirstate()
2053
state._bisect_page_size = 50
2054
self.assertBisect(expected, [None], state, ['b/e'])
2055
self.assertBisect(expected, [['a']], state, ['a'])
2056
self.assertBisect(expected, [['b']], state, ['b'])
2057
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2058
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2059
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2060
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2061
self.assertBisect(expected, [['f']], state, ['f'])
2063
def test_bisect_missing(self):
2064
"""Test that bisect return None if it cannot find a path."""
2065
tree, state, expected = self.create_basic_dirstate()
2066
self.assertBisect(expected, [None], state, ['foo'])
2067
self.assertBisect(expected, [None], state, ['b/foo'])
2068
self.assertBisect(expected, [None], state, ['bar/foo'])
2069
self.assertBisect(expected, [None], state, ['b-c/foo'])
2071
self.assertBisect(expected, [['a'], None, ['b/d']],
2072
state, ['a', 'foo', 'b/d'])
2074
def test_bisect_rename(self):
2075
"""Check that we find a renamed row."""
2076
tree, state, expected = self.create_renamed_dirstate()
2078
# Search for the pre and post renamed entries
2079
self.assertBisect(expected, [['a']], state, ['a'])
2080
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2081
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2082
self.assertBisect(expected, [['h']], state, ['h'])
2084
# What about b/d/e? shouldn't that also get 2 directory entries?
2085
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2086
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2088
def test_bisect_dirblocks(self):
2089
tree, state, expected = self.create_duplicated_dirstate()
2090
self.assertBisectDirBlocks(expected,
2091
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2093
self.assertBisectDirBlocks(expected,
2094
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2095
self.assertBisectDirBlocks(expected,
2096
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2097
self.assertBisectDirBlocks(expected,
2098
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2099
['b/c', 'b/c2', 'b/d', 'b/d2'],
2100
['b/d/e', 'b/d/e2'],
2101
], state, ['', 'b', 'b/d'])
2103
def test_bisect_dirblocks_missing(self):
2104
tree, state, expected = self.create_basic_dirstate()
2105
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2106
state, ['b/d', 'b/e'])
2107
# Files don't show up in this search
2108
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2109
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2110
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2111
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2112
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2114
def test_bisect_recursive_each(self):
2115
tree, state, expected = self.create_basic_dirstate()
2116
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2117
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2118
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2119
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2120
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2122
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2124
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2128
def test_bisect_recursive_multiple(self):
2129
tree, state, expected = self.create_basic_dirstate()
2130
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2131
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2132
state, ['b/d', 'b/d/e'])
2134
def test_bisect_recursive_missing(self):
2135
tree, state, expected = self.create_basic_dirstate()
2136
self.assertBisectRecursive(expected, [], state, ['d'])
2137
self.assertBisectRecursive(expected, [], state, ['b/e'])
2138
self.assertBisectRecursive(expected, [], state, ['g'])
2139
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2141
def test_bisect_recursive_renamed(self):
2142
tree, state, expected = self.create_renamed_dirstate()
2144
# Looking for either renamed item should find the other
2145
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2146
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2147
# Looking in the containing directory should find the rename target,
2148
# and anything in a subdir of the renamed target.
2149
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2150
'b/d/e', 'b/g', 'h', 'h/e'],
2154
class TestDirstateValidation(TestCaseWithDirState):
2156
def test_validate_correct_dirstate(self):
2157
state = self.create_complex_dirstate()
2160
# and make sure we can also validate with a read lock
2167
def test_dirblock_not_sorted(self):
2168
tree, state, expected = self.create_renamed_dirstate()
2169
state._read_dirblocks_if_needed()
2170
last_dirblock = state._dirblocks[-1]
2171
# we're appending to the dirblock, but this name comes before some of
2172
# the existing names; that's wrong
2173
last_dirblock[1].append(
2174
(('h', 'aaaa', 'a-id'),
2175
[('a', '', 0, False, ''),
2176
('a', '', 0, False, '')]))
2177
e = self.assertRaises(AssertionError,
2179
self.assertContainsRe(str(e), 'not sorted')
2181
def test_dirblock_name_mismatch(self):
2182
tree, state, expected = self.create_renamed_dirstate()
2183
state._read_dirblocks_if_needed()
2184
last_dirblock = state._dirblocks[-1]
2185
# add an entry with the wrong directory name
2186
last_dirblock[1].append(
2188
[('a', '', 0, False, ''),
2189
('a', '', 0, False, '')]))
2190
e = self.assertRaises(AssertionError,
2192
self.assertContainsRe(str(e),
2193
"doesn't match directory name")
2195
def test_dirblock_missing_rename(self):
2196
tree, state, expected = self.create_renamed_dirstate()
2197
state._read_dirblocks_if_needed()
2198
last_dirblock = state._dirblocks[-1]
2199
# make another entry for a-id, without a correct 'r' pointer to
2200
# the real occurrence in the working tree
2201
last_dirblock[1].append(
2202
(('h', 'z', 'a-id'),
2203
[('a', '', 0, False, ''),
2204
('a', '', 0, False, '')]))
2205
e = self.assertRaises(AssertionError,
2207
self.assertContainsRe(str(e),
2208
'file a-id is absent in row')
2211
class TestDirstateTreeReference(TestCaseWithDirState):
2213
def test_reference_revision_is_none(self):
2214
tree = self.make_branch_and_tree('tree', format='development-subtree')
2215
subtree = self.make_branch_and_tree('tree/subtree',
2216
format='development-subtree')
2217
subtree.set_root_id('subtree')
2218
tree.add_reference(subtree)
2220
state = dirstate.DirState.from_tree(tree, 'dirstate')
2221
key = ('', 'subtree', 'subtree')
2222
expected = ('', [(key,
2223
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2226
self.assertEqual(expected, state._find_block(key))
2231
class TestDiscardMergeParents(TestCaseWithDirState):
2233
def test_discard_no_parents(self):
2234
# This should be a no-op
2235
state = self.create_empty_dirstate()
2236
self.addCleanup(state.unlock)
2237
state._discard_merge_parents()
2240
def test_discard_one_parent(self):
2242
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2243
root_entry_direntry = ('', '', 'a-root-value'), [
2244
('d', '', 0, False, packed_stat),
2245
('d', '', 0, False, packed_stat),
2248
dirblocks.append(('', [root_entry_direntry]))
2249
dirblocks.append(('', []))
2251
state = self.create_empty_dirstate()
2252
self.addCleanup(state.unlock)
2253
state._set_data(['parent-id'], dirblocks[:])
2256
state._discard_merge_parents()
2258
self.assertEqual(dirblocks, state._dirblocks)
2260
def test_discard_simple(self):
2262
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2263
root_entry_direntry = ('', '', 'a-root-value'), [
2264
('d', '', 0, False, packed_stat),
2265
('d', '', 0, False, packed_stat),
2266
('d', '', 0, False, packed_stat),
2268
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2269
('d', '', 0, False, packed_stat),
2270
('d', '', 0, False, packed_stat),
2273
dirblocks.append(('', [root_entry_direntry]))
2274
dirblocks.append(('', []))
2276
state = self.create_empty_dirstate()
2277
self.addCleanup(state.unlock)
2278
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2281
# This should strip of the extra column
2282
state._discard_merge_parents()
2284
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2285
self.assertEqual(expected_dirblocks, state._dirblocks)
2287
def test_discard_absent(self):
2288
"""If entries are only in a merge, discard should remove the entries"""
2289
null_stat = dirstate.DirState.NULLSTAT
2290
present_dir = ('d', '', 0, False, null_stat)
2291
present_file = ('f', '', 0, False, null_stat)
2292
absent = dirstate.DirState.NULL_PARENT_DETAILS
2293
root_key = ('', '', 'a-root-value')
2294
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2295
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2296
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2297
('', [(file_in_merged_key,
2298
[absent, absent, present_file]),
2300
[present_file, present_file, present_file]),
2304
state = self.create_empty_dirstate()
2305
self.addCleanup(state.unlock)
2306
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2309
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2310
('', [(file_in_root_key,
2311
[present_file, present_file]),
2314
state._discard_merge_parents()
2316
self.assertEqual(exp_dirblocks, state._dirblocks)
2318
def test_discard_renamed(self):
2319
null_stat = dirstate.DirState.NULLSTAT
2320
present_dir = ('d', '', 0, False, null_stat)
2321
present_file = ('f', '', 0, False, null_stat)
2322
absent = dirstate.DirState.NULL_PARENT_DETAILS
2323
root_key = ('', '', 'a-root-value')
2324
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2325
# Renamed relative to parent
2326
file_rename_s_key = ('', 'file-s', 'b-file-id')
2327
file_rename_t_key = ('', 'file-t', 'b-file-id')
2328
# And one that is renamed between the parents, but absent in this
2329
key_in_1 = ('', 'file-in-1', 'c-file-id')
2330
key_in_2 = ('', 'file-in-2', 'c-file-id')
2333
('', [(root_key, [present_dir, present_dir, present_dir])]),
2335
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2337
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2339
[present_file, present_file, present_file]),
2341
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2343
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2347
('', [(root_key, [present_dir, present_dir])]),
2348
('', [(key_in_1, [absent, present_file]),
2349
(file_in_root_key, [present_file, present_file]),
2350
(file_rename_t_key, [present_file, absent]),
2353
state = self.create_empty_dirstate()
2354
self.addCleanup(state.unlock)
2355
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2358
state._discard_merge_parents()
2360
self.assertEqual(exp_dirblocks, state._dirblocks)
2362
def test_discard_all_subdir(self):
2363
null_stat = dirstate.DirState.NULLSTAT
2364
present_dir = ('d', '', 0, False, null_stat)
2365
present_file = ('f', '', 0, False, null_stat)
2366
absent = dirstate.DirState.NULL_PARENT_DETAILS
2367
root_key = ('', '', 'a-root-value')
2368
subdir_key = ('', 'sub', 'dir-id')
2369
child1_key = ('sub', 'child1', 'child1-id')
2370
child2_key = ('sub', 'child2', 'child2-id')
2371
child3_key = ('sub', 'child3', 'child3-id')
2374
('', [(root_key, [present_dir, present_dir, present_dir])]),
2375
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2376
('sub', [(child1_key, [absent, absent, present_file]),
2377
(child2_key, [absent, absent, present_file]),
2378
(child3_key, [absent, absent, present_file]),
2382
('', [(root_key, [present_dir, present_dir])]),
2383
('', [(subdir_key, [present_dir, present_dir])]),
2386
state = self.create_empty_dirstate()
2387
self.addCleanup(state.unlock)
2388
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2391
state._discard_merge_parents()
2393
self.assertEqual(exp_dirblocks, state._dirblocks)
2396
class Test_InvEntryToDetails(tests.TestCase):
2398
def assertDetails(self, expected, inv_entry):
2399
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2400
self.assertEqual(expected, details)
2401
# details should always allow join() and always be a plain str when
2403
(minikind, fingerprint, size, executable, tree_data) = details
2404
self.assertIsInstance(minikind, str)
2405
self.assertIsInstance(fingerprint, str)
2406
self.assertIsInstance(tree_data, str)
2408
def test_unicode_symlink(self):
2409
inv_entry = inventory.InventoryLink('link-file-id',
2410
u'nam\N{Euro Sign}e',
2412
inv_entry.revision = 'link-revision-id'
2413
target = u'link-targ\N{Euro Sign}t'
2414
inv_entry.symlink_target = target
2415
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2416
'link-revision-id'), inv_entry)
2419
class TestSHA1Provider(tests.TestCaseInTempDir):
2421
def test_sha1provider_is_an_interface(self):
2422
p = dirstate.SHA1Provider()
2423
self.assertRaises(NotImplementedError, p.sha1, "foo")
2424
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2426
def test_defaultsha1provider_sha1(self):
2427
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2428
self.build_tree_contents([('foo', text)])
2429
expected_sha = osutils.sha_string(text)
2430
p = dirstate.DefaultSHA1Provider()
2431
self.assertEqual(expected_sha, p.sha1('foo'))
2433
def test_defaultsha1provider_stat_and_sha1(self):
2434
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2435
self.build_tree_contents([('foo', text)])
2436
expected_sha = osutils.sha_string(text)
2437
p = dirstate.DefaultSHA1Provider()
2438
statvalue, sha1 = p.stat_and_sha1('foo')
2439
self.assertTrue(len(statvalue) >= 10)
2440
self.assertEqual(len(text), statvalue.st_size)
2441
self.assertEqual(expected_sha, sha1)
2444
class _Repo(object):
2445
"""A minimal api to get InventoryRevisionTree to work."""
2448
default_format = controldir.format_registry.make_controldir('default')
2449
self._format = default_format.repository_format
2451
def lock_read(self):
2458
class TestUpdateBasisByDelta(tests.TestCase):
2460
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2461
if path.endswith('/'):
2466
dirname, basename = osutils.split(path)
2468
dir_id = dir_ids[dirname]
2470
dir_id = osutils.basename(dirname) + '-id'
2472
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2473
dir_ids[path] = file_id
2475
ie = inventory.InventoryFile(file_id, basename, dir_id)
2478
ie.revision = rev_id
2481
def create_tree_from_shape(self, rev_id, shape):
2482
dir_ids = {'': 'root-id'}
2483
inv = inventory.Inventory('root-id', rev_id)
2486
path, file_id = info
2489
path, file_id, ie_rev_id = info
2491
# Replace the root entry
2492
del inv._byid[inv.root.file_id]
2493
inv.root.file_id = file_id
2494
inv._byid[file_id] = inv.root
2495
dir_ids[''] = file_id
2497
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2498
return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
2500
def create_empty_dirstate(self):
2501
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2502
self.addCleanup(os.remove, path)
2504
state = dirstate.DirState.initialize(path)
2505
self.addCleanup(state.unlock)
2508
def create_inv_delta(self, delta, rev_id):
2509
"""Translate a 'delta shape' into an actual InventoryDelta"""
2510
dir_ids = {'': 'root-id'}
2512
for old_path, new_path, file_id in delta:
2513
if old_path is not None and old_path.endswith('/'):
2514
# Don't have to actually do anything for this, because only
2515
# new_path creates InventoryEntries
2516
old_path = old_path[:-1]
2517
if new_path is None: # Delete
2518
inv_delta.append((old_path, None, file_id, None))
2520
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2521
inv_delta.append((old_path, new_path, file_id, ie))
2524
def assertUpdate(self, active, basis, target):
2525
"""Assert that update_basis_by_delta works how we want.
2527
Set up a DirState object with active_shape for tree 0, basis_shape for
2528
tree 1. Then apply the delta from basis_shape to target_shape,
2529
and assert that the DirState is still valid, and that its stored
2530
content matches the target_shape.
2532
active_tree = self.create_tree_from_shape('active', active)
2533
basis_tree = self.create_tree_from_shape('basis', basis)
2534
target_tree = self.create_tree_from_shape('target', target)
2535
state = self.create_empty_dirstate()
2536
state.set_state_from_scratch(active_tree.root_inventory,
2537
[('basis', basis_tree)], [])
2538
delta = target_tree.root_inventory._make_delta(
2539
basis_tree.root_inventory)
2540
state.update_basis_by_delta(delta, 'target')
2542
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2544
# The target now that delta has been applied should match the
2546
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2547
# And the dirblock state should be identical to the state if we created
2549
state2 = self.create_empty_dirstate()
2550
state2.set_state_from_scratch(active_tree.root_inventory,
2551
[('target', target_tree)], [])
2552
self.assertEqual(state2._dirblocks, state._dirblocks)
2555
def assertBadDelta(self, active, basis, delta):
2556
"""Test that we raise InconsistentDelta when appropriate.
2558
:param active: The active tree shape
2559
:param basis: The basis tree shape
2560
:param delta: A description of the delta to apply. Similar to the form
2561
for regular inventory deltas, but omitting the InventoryEntry.
2562
So adding a file is: (None, 'path', 'file-id')
2563
Adding a directory is: (None, 'path/', 'dir-id')
2564
Renaming a dir is: ('old/', 'new/', 'dir-id')
2567
active_tree = self.create_tree_from_shape('active', active)
2568
basis_tree = self.create_tree_from_shape('basis', basis)
2569
inv_delta = self.create_inv_delta(delta, 'target')
2570
state = self.create_empty_dirstate()
2571
state.set_state_from_scratch(active_tree.root_inventory,
2572
[('basis', basis_tree)], [])
2573
self.assertRaises(errors.InconsistentDelta,
2574
state.update_basis_by_delta, inv_delta, 'target')
2576
## state.update_basis_by_delta(inv_delta, 'target')
2577
## except errors.InconsistentDelta, e:
2578
## import pdb; pdb.set_trace()
2580
## import pdb; pdb.set_trace()
2581
self.assertTrue(state._changes_aborted)
2583
def test_remove_file_matching_active_state(self):
2584
state = self.assertUpdate(
2586
basis =[('file', 'file-id')],
2590
def test_remove_file_present_in_active_state(self):
2591
state = self.assertUpdate(
2592
active=[('file', 'file-id')],
2593
basis =[('file', 'file-id')],
2597
def test_remove_file_present_elsewhere_in_active_state(self):
2598
state = self.assertUpdate(
2599
active=[('other-file', 'file-id')],
2600
basis =[('file', 'file-id')],
2604
def test_remove_file_active_state_has_diff_file(self):
2605
state = self.assertUpdate(
2606
active=[('file', 'file-id-2')],
2607
basis =[('file', 'file-id')],
2611
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2612
state = self.assertUpdate(
2613
active=[('file', 'file-id-2'),
2614
('other-file', 'file-id')],
2615
basis =[('file', 'file-id')],
2619
def test_add_file_matching_active_state(self):
2620
state = self.assertUpdate(
2621
active=[('file', 'file-id')],
2623
target=[('file', 'file-id')],
2626
def test_add_file_in_empty_dir_not_matching_active_state(self):
2627
state = self.assertUpdate(
2629
basis=[('dir/', 'dir-id')],
2630
target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
2633
def test_add_file_missing_in_active_state(self):
2634
state = self.assertUpdate(
2637
target=[('file', 'file-id')],
2640
def test_add_file_elsewhere_in_active_state(self):
2641
state = self.assertUpdate(
2642
active=[('other-file', 'file-id')],
2644
target=[('file', 'file-id')],
2647
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2648
state = self.assertUpdate(
2649
active=[('other-file', 'file-id'),
2650
('file', 'file-id-2')],
2652
target=[('file', 'file-id')],
2655
def test_rename_file_matching_active_state(self):
2656
state = self.assertUpdate(
2657
active=[('other-file', 'file-id')],
2658
basis =[('file', 'file-id')],
2659
target=[('other-file', 'file-id')],
2662
def test_rename_file_missing_in_active_state(self):
2663
state = self.assertUpdate(
2665
basis =[('file', 'file-id')],
2666
target=[('other-file', 'file-id')],
2669
def test_rename_file_present_elsewhere_in_active_state(self):
2670
state = self.assertUpdate(
2671
active=[('third', 'file-id')],
2672
basis =[('file', 'file-id')],
2673
target=[('other-file', 'file-id')],
2676
def test_rename_file_active_state_has_diff_source_file(self):
2677
state = self.assertUpdate(
2678
active=[('file', 'file-id-2')],
2679
basis =[('file', 'file-id')],
2680
target=[('other-file', 'file-id')],
2683
def test_rename_file_active_state_has_diff_target_file(self):
2684
state = self.assertUpdate(
2685
active=[('other-file', 'file-id-2')],
2686
basis =[('file', 'file-id')],
2687
target=[('other-file', 'file-id')],
2690
def test_rename_file_active_has_swapped_files(self):
2691
state = self.assertUpdate(
2692
active=[('file', 'file-id'),
2693
('other-file', 'file-id-2')],
2694
basis= [('file', 'file-id'),
2695
('other-file', 'file-id-2')],
2696
target=[('file', 'file-id-2'),
2697
('other-file', 'file-id')])
2699
def test_rename_file_basis_has_swapped_files(self):
2700
state = self.assertUpdate(
2701
active=[('file', 'file-id'),
2702
('other-file', 'file-id-2')],
2703
basis= [('file', 'file-id-2'),
2704
('other-file', 'file-id')],
2705
target=[('file', 'file-id'),
2706
('other-file', 'file-id-2')])
2708
def test_rename_directory_with_contents(self):
2709
state = self.assertUpdate( # active matches basis
2710
active=[('dir1/', 'dir-id'),
2711
('dir1/file', 'file-id')],
2712
basis= [('dir1/', 'dir-id'),
2713
('dir1/file', 'file-id')],
2714
target=[('dir2/', 'dir-id'),
2715
('dir2/file', 'file-id')])
2716
state = self.assertUpdate( # active matches target
2717
active=[('dir2/', 'dir-id'),
2718
('dir2/file', 'file-id')],
2719
basis= [('dir1/', 'dir-id'),
2720
('dir1/file', 'file-id')],
2721
target=[('dir2/', 'dir-id'),
2722
('dir2/file', 'file-id')])
2723
state = self.assertUpdate( # active empty
2725
basis= [('dir1/', 'dir-id'),
2726
('dir1/file', 'file-id')],
2727
target=[('dir2/', 'dir-id'),
2728
('dir2/file', 'file-id')])
2729
state = self.assertUpdate( # active present at other location
2730
active=[('dir3/', 'dir-id'),
2731
('dir3/file', 'file-id')],
2732
basis= [('dir1/', 'dir-id'),
2733
('dir1/file', 'file-id')],
2734
target=[('dir2/', 'dir-id'),
2735
('dir2/file', 'file-id')])
2736
state = self.assertUpdate( # active has different ids
2737
active=[('dir1/', 'dir1-id'),
2738
('dir1/file', 'file1-id'),
2739
('dir2/', 'dir2-id'),
2740
('dir2/file', 'file2-id')],
2741
basis= [('dir1/', 'dir-id'),
2742
('dir1/file', 'file-id')],
2743
target=[('dir2/', 'dir-id'),
2744
('dir2/file', 'file-id')])
2746
def test_invalid_file_not_present(self):
2747
state = self.assertBadDelta(
2748
active=[('file', 'file-id')],
2749
basis= [('file', 'file-id')],
2750
delta=[('other-file', 'file', 'file-id')])
2752
def test_invalid_new_id_same_path(self):
2753
# The bad entry comes after
2754
state = self.assertBadDelta(
2755
active=[('file', 'file-id')],
2756
basis= [('file', 'file-id')],
2757
delta=[(None, 'file', 'file-id-2')])
2758
# The bad entry comes first
2759
state = self.assertBadDelta(
2760
active=[('file', 'file-id-2')],
2761
basis=[('file', 'file-id-2')],
2762
delta=[(None, 'file', 'file-id')])
2764
def test_invalid_existing_id(self):
2765
state = self.assertBadDelta(
2766
active=[('file', 'file-id')],
2767
basis= [('file', 'file-id')],
2768
delta=[(None, 'file', 'file-id')])
2770
def test_invalid_parent_missing(self):
2771
state = self.assertBadDelta(
2774
delta=[(None, 'path/path2', 'file-id')])
2775
# Note: we force the active tree to have the directory, by knowing how
2776
# path_to_ie handles entries with missing parents
2777
state = self.assertBadDelta(
2778
active=[('path/', 'path-id')],
2780
delta=[(None, 'path/path2', 'file-id')])
2781
state = self.assertBadDelta(
2782
active=[('path/', 'path-id'),
2783
('path/path2', 'file-id')],
2785
delta=[(None, 'path/path2', 'file-id')])
2787
def test_renamed_dir_same_path(self):
2788
# We replace the parent directory, with another parent dir. But the C
2789
# file doesn't look like it has been moved.
2790
state = self.assertUpdate(# Same as basis
2791
active=[('dir/', 'A-id'),
2793
basis= [('dir/', 'A-id'),
2795
target=[('dir/', 'C-id'),
2797
state = self.assertUpdate(# Same as target
2798
active=[('dir/', 'C-id'),
2800
basis= [('dir/', 'A-id'),
2802
target=[('dir/', 'C-id'),
2804
state = self.assertUpdate(# empty active
2806
basis= [('dir/', 'A-id'),
2808
target=[('dir/', 'C-id'),
2810
state = self.assertUpdate(# different active
2811
active=[('dir/', 'D-id'),
2813
basis= [('dir/', 'A-id'),
2815
target=[('dir/', 'C-id'),
2818
def test_parent_child_swap(self):
2819
state = self.assertUpdate(# Same as basis
2820
active=[('A/', 'A-id'),
2823
basis= [('A/', 'A-id'),
2826
target=[('A/', 'B-id'),
2829
state = self.assertUpdate(# Same as target
2830
active=[('A/', 'B-id'),
2833
basis= [('A/', 'A-id'),
2836
target=[('A/', 'B-id'),
2839
state = self.assertUpdate(# empty active
2841
basis= [('A/', 'A-id'),
2844
target=[('A/', 'B-id'),
2847
state = self.assertUpdate(# different active
2848
active=[('D/', 'A-id'),
2851
basis= [('A/', 'A-id'),
2854
target=[('A/', 'B-id'),
2858
def test_change_root_id(self):
2859
state = self.assertUpdate( # same as basis
2860
active=[('', 'root-id'),
2861
('file', 'file-id')],
2862
basis= [('', 'root-id'),
2863
('file', 'file-id')],
2864
target=[('', 'target-root-id'),
2865
('file', 'file-id')])
2866
state = self.assertUpdate( # same as target
2867
active=[('', 'target-root-id'),
2868
('file', 'file-id')],
2869
basis= [('', 'root-id'),
2870
('file', 'file-id')],
2871
target=[('', 'target-root-id'),
2872
('file', 'root-id')])
2873
state = self.assertUpdate( # all different
2874
active=[('', 'active-root-id'),
2875
('file', 'file-id')],
2876
basis= [('', 'root-id'),
2877
('file', 'file-id')],
2878
target=[('', 'target-root-id'),
2879
('file', 'root-id')])
2881
def test_change_file_absent_in_active(self):
2882
state = self.assertUpdate(
2884
basis= [('file', 'file-id')],
2885
target=[('file', 'file-id')])
2887
def test_invalid_changed_file(self):
2888
state = self.assertBadDelta( # Not present in basis
2889
active=[('file', 'file-id')],
2891
delta=[('file', 'file', 'file-id')])
2892
state = self.assertBadDelta( # present at another location in basis
2893
active=[('file', 'file-id')],
2894
basis= [('other-file', 'file-id')],
2895
delta=[('file', 'file', 'file-id')])