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."""
29
revision as _mod_revision,
34
from bzrlib.transport import memory
35
from bzrlib.tests import (
39
from bzrlib.tests.scenarios import load_tests_apply_scenarios
44
# general checks for NOT_IN_MEMORY error conditions.
45
# set_path_id on a NOT_IN_MEMORY dirstate
46
# set_path_id unicode support
47
# set_path_id setting id of a path not root
48
# set_path_id setting id when there are parents without the id in the parents
49
# set_path_id setting id when there are parents with the id in the parents
50
# set_path_id setting id when state is not in memory
51
# set_path_id setting id when state is in memory unmodified
52
# set_path_id setting id when state is in memory modified
55
load_tests = load_tests_apply_scenarios
58
class TestCaseWithDirState(tests.TestCaseWithTransport):
59
"""Helper functions for creating DirState objects with various content."""
61
scenarios = test_osutils.dir_reader_scenarios()
64
_dir_reader_class = None
65
_native_to_unicode = None # Not used yet
68
tests.TestCaseWithTransport.setUp(self)
70
self.overrideAttr(osutils,
71
'_selected_dir_reader', self._dir_reader_class())
73
def create_empty_dirstate(self):
74
"""Return a locked but empty dirstate"""
75
state = dirstate.DirState.initialize('dirstate')
78
def create_dirstate_with_root(self):
79
"""Return a write-locked state with a single root entry."""
80
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
81
root_entry_direntry = ('', '', 'a-root-value'), [
82
('d', '', 0, False, packed_stat),
85
dirblocks.append(('', [root_entry_direntry]))
86
dirblocks.append(('', []))
87
state = self.create_empty_dirstate()
89
state._set_data([], dirblocks)
96
def create_dirstate_with_root_and_subdir(self):
97
"""Return a locked DirState with a root and a subdir"""
98
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
99
subdir_entry = ('', 'subdir', 'subdir-id'), [
100
('d', '', 0, False, packed_stat),
102
state = self.create_dirstate_with_root()
104
dirblocks = list(state._dirblocks)
105
dirblocks[1][1].append(subdir_entry)
106
state._set_data([], dirblocks)
112
def create_complex_dirstate(self):
113
"""This dirstate contains multiple files and directories.
123
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
125
Notice that a/e is an empty directory.
127
:return: The dirstate, still write-locked.
129
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
130
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
131
root_entry = ('', '', 'a-root-value'), [
132
('d', '', 0, False, packed_stat),
134
a_entry = ('', 'a', 'a-dir'), [
135
('d', '', 0, False, packed_stat),
137
b_entry = ('', 'b', 'b-dir'), [
138
('d', '', 0, False, packed_stat),
140
c_entry = ('', 'c', 'c-file'), [
141
('f', null_sha, 10, False, packed_stat),
143
d_entry = ('', 'd', 'd-file'), [
144
('f', null_sha, 20, False, packed_stat),
146
e_entry = ('a', 'e', 'e-dir'), [
147
('d', '', 0, False, packed_stat),
149
f_entry = ('a', 'f', 'f-file'), [
150
('f', null_sha, 30, False, packed_stat),
152
g_entry = ('b', 'g', 'g-file'), [
153
('f', null_sha, 30, False, packed_stat),
155
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
156
('f', null_sha, 40, False, packed_stat),
159
dirblocks.append(('', [root_entry]))
160
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
161
dirblocks.append(('a', [e_entry, f_entry]))
162
dirblocks.append(('b', [g_entry, h_entry]))
163
state = dirstate.DirState.initialize('dirstate')
166
state._set_data([], dirblocks)
172
def check_state_with_reopen(self, expected_result, state):
173
"""Check that state has current state expected_result.
175
This will check the current state, open the file anew and check it
177
This function expects the current state to be locked for writing, and
178
will unlock it before re-opening.
179
This is required because we can't open a lock_read() while something
180
else has a lock_write().
181
write => mutually exclusive lock
184
# The state should already be write locked, since we just had to do
185
# some operation to get here.
186
self.assertTrue(state._lock_token is not None)
188
self.assertEqual(expected_result[0], state.get_parent_ids())
189
# there should be no ghosts in this tree.
190
self.assertEqual([], state.get_ghosts())
191
# there should be one fileid in this tree - the root of the tree.
192
self.assertEqual(expected_result[1], list(state._iter_entries()))
197
state = dirstate.DirState.on_file('dirstate')
200
self.assertEqual(expected_result[1], list(state._iter_entries()))
204
def create_basic_dirstate(self):
205
"""Create a dirstate with a few files and directories.
215
tree = self.make_branch_and_tree('tree')
216
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
217
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
218
self.build_tree(['tree/' + p for p in paths])
219
tree.set_root_id('TREE_ROOT')
220
tree.add([p.rstrip('/') for p in paths], file_ids)
221
tree.commit('initial', rev_id='rev-1')
222
revision_id = 'rev-1'
223
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
224
t = self.get_transport('tree')
225
a_text = t.get_bytes('a')
226
a_sha = osutils.sha_string(a_text)
228
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
229
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
230
c_text = t.get_bytes('b/c')
231
c_sha = osutils.sha_string(c_text)
233
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
234
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
235
e_text = t.get_bytes('b/d/e')
236
e_sha = osutils.sha_string(e_text)
238
b_c_text = t.get_bytes('b-c')
239
b_c_sha = osutils.sha_string(b_c_text)
240
b_c_len = len(b_c_text)
241
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
242
f_text = t.get_bytes('f')
243
f_sha = osutils.sha_string(f_text)
245
null_stat = dirstate.DirState.NULLSTAT
247
'':(('', '', 'TREE_ROOT'), [
248
('d', '', 0, False, null_stat),
249
('d', '', 0, False, revision_id),
251
'a':(('', 'a', 'a-id'), [
252
('f', '', 0, False, null_stat),
253
('f', a_sha, a_len, False, revision_id),
255
'b':(('', 'b', 'b-id'), [
256
('d', '', 0, False, null_stat),
257
('d', '', 0, False, revision_id),
259
'b/c':(('b', 'c', 'c-id'), [
260
('f', '', 0, False, null_stat),
261
('f', c_sha, c_len, False, revision_id),
263
'b/d':(('b', 'd', 'd-id'), [
264
('d', '', 0, False, null_stat),
265
('d', '', 0, False, revision_id),
267
'b/d/e':(('b/d', 'e', 'e-id'), [
268
('f', '', 0, False, null_stat),
269
('f', e_sha, e_len, False, revision_id),
271
'b-c':(('', 'b-c', 'b-c-id'), [
272
('f', '', 0, False, null_stat),
273
('f', b_c_sha, b_c_len, False, revision_id),
275
'f':(('', 'f', 'f-id'), [
276
('f', '', 0, False, null_stat),
277
('f', f_sha, f_len, False, revision_id),
280
state = dirstate.DirState.from_tree(tree, 'dirstate')
285
# Use a different object, to make sure nothing is pre-cached in memory.
286
state = dirstate.DirState.on_file('dirstate')
288
self.addCleanup(state.unlock)
289
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
290
state._dirblock_state)
291
# This is code is only really tested if we actually have to make more
292
# than one read, so set the page size to something smaller.
293
# We want it to contain about 2.2 records, so that we have a couple
294
# records that we can read per attempt
295
state._bisect_page_size = 200
296
return tree, state, expected
298
def create_duplicated_dirstate(self):
299
"""Create a dirstate with a deleted and added entries.
301
This grabs a basic_dirstate, and then removes and re adds every entry
304
tree, state, expected = self.create_basic_dirstate()
305
# Now we will just remove and add every file so we get an extra entry
306
# per entry. Unversion in reverse order so we handle subdirs
307
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
308
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
309
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
311
# Update the expected dictionary.
312
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
313
orig = expected[path]
315
# This record was deleted in the current tree
316
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
318
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
319
# And didn't exist in the basis tree
320
expected[path2] = (new_key, [orig[1][0],
321
dirstate.DirState.NULL_PARENT_DETAILS])
323
# We will replace the 'dirstate' file underneath 'state', but that is
324
# okay as lock as we unlock 'state' first.
327
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
333
# But we need to leave state in a read-lock because we already have
334
# a cleanup scheduled
336
return tree, state, expected
338
def create_renamed_dirstate(self):
339
"""Create a dirstate with a few internal renames.
341
This takes the basic dirstate, and moves the paths around.
343
tree, state, expected = self.create_basic_dirstate()
345
tree.rename_one('a', 'b/g')
347
tree.rename_one('b/d', 'h')
349
old_a = expected['a']
350
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
351
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
352
('r', 'a', 0, False, '')])
353
old_d = expected['b/d']
354
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
355
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
356
('r', 'b/d', 0, False, '')])
358
old_e = expected['b/d/e']
359
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
361
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
362
('r', 'b/d/e', 0, False, '')])
366
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
373
return tree, state, expected
376
class TestTreeToDirState(TestCaseWithDirState):
378
def test_empty_to_dirstate(self):
379
"""We should be able to create a dirstate for an empty tree."""
380
# There are no files on disk and no parents
381
tree = self.make_branch_and_tree('tree')
382
expected_result = ([], [
383
(('', '', tree.get_root_id()), # common details
384
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
386
state = dirstate.DirState.from_tree(tree, 'dirstate')
388
self.check_state_with_reopen(expected_result, state)
390
def test_1_parents_empty_to_dirstate(self):
391
# create a parent by doing a commit
392
tree = self.make_branch_and_tree('tree')
393
rev_id = tree.commit('first post').encode('utf8')
394
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
395
expected_result = ([rev_id], [
396
(('', '', tree.get_root_id()), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
400
state = dirstate.DirState.from_tree(tree, 'dirstate')
401
self.check_state_with_reopen(expected_result, state)
408
def test_2_parents_empty_to_dirstate(self):
409
# create a parent by doing a commit
410
tree = self.make_branch_and_tree('tree')
411
rev_id = tree.commit('first post')
412
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
413
rev_id2 = tree2.commit('second post', allow_pointless=True)
414
tree.merge_from_branch(tree2.branch)
415
expected_result = ([rev_id, rev_id2], [
416
(('', '', tree.get_root_id()), # common details
417
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
('d', '', 0, False, rev_id), # first parent details
419
('d', '', 0, False, rev_id), # second parent details
421
state = dirstate.DirState.from_tree(tree, 'dirstate')
422
self.check_state_with_reopen(expected_result, state)
429
def test_empty_unknowns_are_ignored_to_dirstate(self):
430
"""We should be able to create a dirstate for an empty tree."""
431
# There are no files on disk and no parents
432
tree = self.make_branch_and_tree('tree')
433
self.build_tree(['tree/unknown'])
434
expected_result = ([], [
435
(('', '', tree.get_root_id()), # common details
436
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
438
state = dirstate.DirState.from_tree(tree, 'dirstate')
439
self.check_state_with_reopen(expected_result, state)
441
def get_tree_with_a_file(self):
442
tree = self.make_branch_and_tree('tree')
443
self.build_tree(['tree/a file'])
444
tree.add('a file', 'a-file-id')
447
def test_non_empty_no_parents_to_dirstate(self):
448
"""We should be able to create a dirstate for an empty tree."""
449
# There are files on disk and no parents
450
tree = self.get_tree_with_a_file()
451
expected_result = ([], [
452
(('', '', tree.get_root_id()), # common details
453
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
455
(('', 'a file', 'a-file-id'), # common
456
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
459
state = dirstate.DirState.from_tree(tree, 'dirstate')
460
self.check_state_with_reopen(expected_result, state)
462
def test_1_parents_not_empty_to_dirstate(self):
463
# create a parent by doing a commit
464
tree = self.get_tree_with_a_file()
465
rev_id = tree.commit('first post').encode('utf8')
466
# change the current content to be different this will alter stat, sha
468
self.build_tree_contents([('tree/a file', 'new content\n')])
469
expected_result = ([rev_id], [
470
(('', '', tree.get_root_id()), # common details
471
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
472
('d', '', 0, False, rev_id), # first parent details
474
(('', 'a file', 'a-file-id'), # common
475
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
476
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
477
rev_id), # first parent
480
state = dirstate.DirState.from_tree(tree, 'dirstate')
481
self.check_state_with_reopen(expected_result, state)
483
def test_2_parents_not_empty_to_dirstate(self):
484
# create a parent by doing a commit
485
tree = self.get_tree_with_a_file()
486
rev_id = tree.commit('first post').encode('utf8')
487
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
488
# change the current content to be different this will alter stat, sha
490
self.build_tree_contents([('tree2/a file', 'merge content\n')])
491
rev_id2 = tree2.commit('second post').encode('utf8')
492
tree.merge_from_branch(tree2.branch)
493
# change the current content to be different this will alter stat, sha
494
# and length again, giving us three distinct values:
495
self.build_tree_contents([('tree/a file', 'new content\n')])
496
expected_result = ([rev_id, rev_id2], [
497
(('', '', tree.get_root_id()), # common details
498
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
499
('d', '', 0, False, rev_id), # first parent details
500
('d', '', 0, False, rev_id), # second parent details
502
(('', 'a file', 'a-file-id'), # common
503
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
504
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
505
rev_id), # first parent
506
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
507
rev_id2), # second parent
510
state = dirstate.DirState.from_tree(tree, 'dirstate')
511
self.check_state_with_reopen(expected_result, state)
513
def test_colliding_fileids(self):
514
# test insertion of parents creating several entries at the same path.
515
# we used to have a bug where they could cause the dirstate to break
516
# its ordering invariants.
517
# create some trees to test from
520
tree = self.make_branch_and_tree('tree%d' % i)
521
self.build_tree(['tree%d/name' % i,])
522
tree.add(['name'], ['file-id%d' % i])
523
revision_id = 'revid-%d' % i
524
tree.commit('message', rev_id=revision_id)
525
parents.append((revision_id,
526
tree.branch.repository.revision_tree(revision_id)))
527
# now fold these trees into a dirstate
528
state = dirstate.DirState.initialize('dirstate')
530
state.set_parent_trees(parents, [])
536
class TestDirStateOnFile(TestCaseWithDirState):
538
def create_updated_dirstate(self):
539
self.build_tree(['a-file'])
540
tree = self.make_branch_and_tree('.')
541
tree.add(['a-file'], ['a-id'])
542
tree.commit('add a-file')
543
# Save and unlock the state, re-open it in readonly mode
544
state = dirstate.DirState.from_tree(tree, 'dirstate')
547
state = dirstate.DirState.on_file('dirstate')
551
def test_construct_with_path(self):
552
tree = self.make_branch_and_tree('tree')
553
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
554
# we want to be able to get the lines of the dirstate that we will
556
lines = state.get_lines()
558
self.build_tree_contents([('dirstate', ''.join(lines))])
560
# no parents, default tree content
561
expected_result = ([], [
562
(('', '', tree.get_root_id()), # common details
563
# current tree details, but new from_tree skips statting, it
564
# uses set_state_from_inventory, and thus depends on the
566
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
569
state = dirstate.DirState.on_file('dirstate')
570
state.lock_write() # check_state_with_reopen will save() and unlock it
571
self.check_state_with_reopen(expected_result, state)
573
def test_can_save_clean_on_file(self):
574
tree = self.make_branch_and_tree('tree')
575
state = dirstate.DirState.from_tree(tree, 'dirstate')
577
# doing a save should work here as there have been no changes.
579
# TODO: stat it and check it hasn't changed; may require waiting
580
# for the state accuracy window.
584
def test_can_save_in_read_lock(self):
585
state = self.create_updated_dirstate()
587
entry = state._get_entry(0, path_utf8='a-file')
588
# The current size should be 0 (default)
589
self.assertEqual(0, entry[1][0][2])
590
# We should have a real entry.
591
self.assertNotEqual((None, None), entry)
592
# Set the cutoff-time into the future, so things look cacheable
593
state._sha_cutoff_time()
594
state._cutoff_time += 10.0
595
st = os.lstat('a-file')
596
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
597
# We updated the current sha1sum because the file is cacheable
598
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
601
# The dirblock has been updated
602
self.assertEqual(st.st_size, entry[1][0][2])
603
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
604
state._dirblock_state)
607
# Now, since we are the only one holding a lock, we should be able
608
# to save and have it written to disk
613
# Re-open the file, and ensure that the state has been updated.
614
state = dirstate.DirState.on_file('dirstate')
617
entry = state._get_entry(0, path_utf8='a-file')
618
self.assertEqual(st.st_size, entry[1][0][2])
622
def test_save_fails_quietly_if_locked(self):
623
"""If dirstate is locked, save will fail without complaining."""
624
state = self.create_updated_dirstate()
626
entry = state._get_entry(0, path_utf8='a-file')
627
# No cached sha1 yet.
628
self.assertEqual('', entry[1][0][1])
629
# Set the cutoff-time into the future, so things look cacheable
630
state._sha_cutoff_time()
631
state._cutoff_time += 10.0
632
st = os.lstat('a-file')
633
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
634
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
636
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
637
state._dirblock_state)
639
# Now, before we try to save, grab another dirstate, and take out a
641
# TODO: jam 20070315 Ideally this would be locked by another
642
# process. To make sure the file is really OS locked.
643
state2 = dirstate.DirState.on_file('dirstate')
646
# This won't actually write anything, because it couldn't grab
647
# a write lock. But it shouldn't raise an error, either.
648
# TODO: jam 20070315 We should probably distinguish between
649
# being dirty because of 'update_entry'. And dirty
650
# because of real modification. So that save() *does*
651
# raise a real error if it fails when we have real
659
# The file on disk should not be modified.
660
state = dirstate.DirState.on_file('dirstate')
663
entry = state._get_entry(0, path_utf8='a-file')
664
self.assertEqual('', entry[1][0][1])
668
def test_save_refuses_if_changes_aborted(self):
669
self.build_tree(['a-file', 'a-dir/'])
670
state = dirstate.DirState.initialize('dirstate')
672
# No stat and no sha1 sum.
673
state.add('a-file', 'a-file-id', 'file', None, '')
678
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
680
('', [(('', '', 'TREE_ROOT'),
681
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
682
('', [(('', 'a-file', 'a-file-id'),
683
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
686
state = dirstate.DirState.on_file('dirstate')
689
state._read_dirblocks_if_needed()
690
self.assertEqual(expected_blocks, state._dirblocks)
692
# Now modify the state, but mark it as inconsistent
693
state.add('a-dir', 'a-dir-id', 'directory', None, '')
694
state._changes_aborted = True
699
state = dirstate.DirState.on_file('dirstate')
702
state._read_dirblocks_if_needed()
703
self.assertEqual(expected_blocks, state._dirblocks)
708
class TestDirStateInitialize(TestCaseWithDirState):
710
def test_initialize(self):
711
expected_result = ([], [
712
(('', '', 'TREE_ROOT'), # common details
713
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
716
state = dirstate.DirState.initialize('dirstate')
718
self.assertIsInstance(state, dirstate.DirState)
719
lines = state.get_lines()
722
# On win32 you can't read from a locked file, even within the same
723
# process. So we have to unlock and release before we check the file
725
self.assertFileEqual(''.join(lines), 'dirstate')
726
state.lock_read() # check_state_with_reopen will unlock
727
self.check_state_with_reopen(expected_result, state)
730
class TestDirStateManipulations(TestCaseWithDirState):
732
def make_minimal_tree(self):
733
tree1 = self.make_branch_and_memory_tree('tree1')
735
self.addCleanup(tree1.unlock)
737
revid1 = tree1.commit('foo')
740
def test_update_minimal_updates_id_index(self):
741
state = self.create_dirstate_with_root_and_subdir()
742
self.addCleanup(state.unlock)
743
id_index = state._get_id_index()
744
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
745
state.add('file-name', 'file-id', 'file', None, '')
746
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
748
state.update_minimal(('', 'new-name', 'file-id'), 'f',
749
path_utf8='new-name')
750
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
752
self.assertEqual([('', 'new-name', 'file-id')],
753
sorted(id_index['file-id']))
756
def test_set_state_from_inventory_no_content_no_parents(self):
757
# setting the current inventory is a slow but important api to support.
758
tree1, revid1 = self.make_minimal_tree()
759
inv = tree1.inventory
760
root_id = inv.path2id('')
761
expected_result = [], [
762
(('', '', root_id), [
763
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
764
state = dirstate.DirState.initialize('dirstate')
766
state.set_state_from_inventory(inv)
767
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
769
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
770
state._dirblock_state)
775
# This will unlock it
776
self.check_state_with_reopen(expected_result, state)
778
def test_set_state_from_scratch_no_parents(self):
779
tree1, revid1 = self.make_minimal_tree()
780
inv = tree1.inventory
781
root_id = inv.path2id('')
782
expected_result = [], [
783
(('', '', root_id), [
784
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
785
state = dirstate.DirState.initialize('dirstate')
787
state.set_state_from_scratch(inv, [], [])
788
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
790
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
791
state._dirblock_state)
796
# This will unlock it
797
self.check_state_with_reopen(expected_result, state)
799
def test_set_state_from_scratch_identical_parent(self):
800
tree1, revid1 = self.make_minimal_tree()
801
inv = tree1.inventory
802
root_id = inv.path2id('')
803
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
804
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
805
parent_entry = ('d', '', 0, False, revid1)
806
expected_result = [revid1], [
807
(('', '', root_id), [d_entry, parent_entry])]
808
state = dirstate.DirState.initialize('dirstate')
810
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
811
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
813
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
814
state._dirblock_state)
819
# This will unlock it
820
self.check_state_with_reopen(expected_result, state)
822
def test_set_state_from_inventory_preserves_hashcache(self):
823
# https://bugs.launchpad.net/bzr/+bug/146176
824
# set_state_from_inventory should preserve the stat and hash value for
825
# workingtree files that are not changed by the inventory.
827
tree = self.make_branch_and_tree('.')
828
# depends on the default format using dirstate...
831
# make a dirstate with some valid hashcache data
832
# file on disk, but that's not needed for this test
833
foo_contents = 'contents of foo'
834
self.build_tree_contents([('foo', foo_contents)])
835
tree.add('foo', 'foo-id')
837
foo_stat = os.stat('foo')
838
foo_packed = dirstate.pack_stat(foo_stat)
839
foo_sha = osutils.sha_string(foo_contents)
840
foo_size = len(foo_contents)
842
# should not be cached yet, because the file's too fresh
844
(('', 'foo', 'foo-id',),
845
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
846
tree._dirstate._get_entry(0, 'foo-id'))
847
# poke in some hashcache information - it wouldn't normally be
848
# stored because it's too fresh
849
tree._dirstate.update_minimal(
850
('', 'foo', 'foo-id'),
851
'f', False, foo_sha, foo_packed, foo_size, 'foo')
852
# now should be cached
854
(('', 'foo', 'foo-id',),
855
[('f', foo_sha, foo_size, False, foo_packed)]),
856
tree._dirstate._get_entry(0, 'foo-id'))
858
# extract the inventory, and add something to it
859
inv = tree._get_inventory()
860
# should see the file we poked in...
861
self.assertTrue(inv.has_id('foo-id'))
862
self.assertTrue(inv.has_filename('foo'))
863
inv.add_path('bar', 'file', 'bar-id')
864
tree._dirstate._validate()
865
# this used to cause it to lose its hashcache
866
tree._dirstate.set_state_from_inventory(inv)
867
tree._dirstate._validate()
873
# now check that the state still has the original hashcache value
874
state = tree._dirstate
876
foo_tuple = state._get_entry(0, path_utf8='foo')
878
(('', 'foo', 'foo-id',),
879
[('f', foo_sha, len(foo_contents), False,
880
dirstate.pack_stat(foo_stat))]),
885
def test_set_state_from_inventory_mixed_paths(self):
886
tree1 = self.make_branch_and_tree('tree1')
887
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
888
'tree1/a/b/foo', 'tree1/a-b/bar'])
891
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
892
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
893
tree1.commit('rev1', rev_id='rev1')
894
root_id = tree1.get_root_id()
895
inv = tree1.inventory
898
expected_result1 = [('', '', root_id, 'd'),
899
('', 'a', 'a-id', 'd'),
900
('', 'a-b', 'a-b-id', 'd'),
901
('a', 'b', 'b-id', 'd'),
902
('a/b', 'foo', 'foo-id', 'f'),
903
('a-b', 'bar', 'bar-id', 'f'),
905
expected_result2 = [('', '', root_id, 'd'),
906
('', 'a', 'a-id', 'd'),
907
('', 'a-b', 'a-b-id', 'd'),
908
('a-b', 'bar', 'bar-id', 'f'),
910
state = dirstate.DirState.initialize('dirstate')
912
state.set_state_from_inventory(inv)
914
for entry in state._iter_entries():
915
values.append(entry[0] + entry[1][0][:1])
916
self.assertEqual(expected_result1, values)
918
state.set_state_from_inventory(inv)
920
for entry in state._iter_entries():
921
values.append(entry[0] + entry[1][0][:1])
922
self.assertEqual(expected_result2, values)
926
def test_set_path_id_no_parents(self):
927
"""The id of a path can be changed trivally with no parents."""
928
state = dirstate.DirState.initialize('dirstate')
930
# check precondition to be sure the state does change appropriately.
931
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
932
self.assertEqual([root_entry], list(state._iter_entries()))
933
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
934
self.assertEqual(root_entry,
935
state._get_entry(0, fileid_utf8='TREE_ROOT'))
936
self.assertEqual((None, None),
937
state._get_entry(0, fileid_utf8='second-root-id'))
938
state.set_path_id('', 'second-root-id')
939
new_root_entry = (('', '', 'second-root-id'),
940
[('d', '', 0, False, 'x'*32)])
941
expected_rows = [new_root_entry]
942
self.assertEqual(expected_rows, list(state._iter_entries()))
943
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
944
self.assertEqual(new_root_entry,
945
state._get_entry(0, fileid_utf8='second-root-id'))
946
self.assertEqual((None, None),
947
state._get_entry(0, fileid_utf8='TREE_ROOT'))
948
# should work across save too
952
state = dirstate.DirState.on_file('dirstate')
956
self.assertEqual(expected_rows, list(state._iter_entries()))
960
def test_set_path_id_with_parents(self):
961
"""Set the root file id in a dirstate with parents"""
962
mt = self.make_branch_and_tree('mt')
963
# in case the default tree format uses a different root id
964
mt.set_root_id('TREE_ROOT')
965
mt.commit('foo', rev_id='parent-revid')
966
rt = mt.branch.repository.revision_tree('parent-revid')
967
state = dirstate.DirState.initialize('dirstate')
970
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
971
root_entry = (('', '', 'TREE_ROOT'),
972
[('d', '', 0, False, 'x'*32),
973
('d', '', 0, False, 'parent-revid')])
974
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
975
self.assertEqual(root_entry,
976
state._get_entry(0, fileid_utf8='TREE_ROOT'))
977
self.assertEqual((None, None),
978
state._get_entry(0, fileid_utf8='Asecond-root-id'))
979
state.set_path_id('', 'Asecond-root-id')
981
# now see that it is what we expected
982
old_root_entry = (('', '', 'TREE_ROOT'),
983
[('a', '', 0, False, ''),
984
('d', '', 0, False, 'parent-revid')])
985
new_root_entry = (('', '', 'Asecond-root-id'),
986
[('d', '', 0, False, ''),
987
('a', '', 0, False, '')])
988
expected_rows = [new_root_entry, old_root_entry]
990
self.assertEqual(expected_rows, list(state._iter_entries()))
991
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
992
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
993
self.assertEqual((None, None),
994
state._get_entry(0, fileid_utf8='TREE_ROOT'))
995
self.assertEqual(old_root_entry,
996
state._get_entry(1, fileid_utf8='TREE_ROOT'))
997
self.assertEqual(new_root_entry,
998
state._get_entry(0, fileid_utf8='Asecond-root-id'))
999
self.assertEqual((None, None),
1000
state._get_entry(1, fileid_utf8='Asecond-root-id'))
1001
# should work across save too
1005
# now flush & check we get the same
1006
state = dirstate.DirState.on_file('dirstate')
1010
self.assertEqual(expected_rows, list(state._iter_entries()))
1013
# now change within an existing file-backed state
1017
state.set_path_id('', 'tree-root-2')
1022
def test_set_parent_trees_no_content(self):
1023
# set_parent_trees is a slow but important api to support.
1024
tree1 = self.make_branch_and_memory_tree('tree1')
1028
revid1 = tree1.commit('foo')
1031
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1032
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1035
revid2 = tree2.commit('foo')
1036
root_id = tree2.get_root_id()
1039
state = dirstate.DirState.initialize('dirstate')
1041
state.set_path_id('', root_id)
1042
state.set_parent_trees(
1043
((revid1, tree1.branch.repository.revision_tree(revid1)),
1044
(revid2, tree2.branch.repository.revision_tree(revid2)),
1045
('ghost-rev', None)),
1047
# check we can reopen and use the dirstate after setting parent
1054
state = dirstate.DirState.on_file('dirstate')
1057
self.assertEqual([revid1, revid2, 'ghost-rev'],
1058
state.get_parent_ids())
1059
# iterating the entire state ensures that the state is parsable.
1060
list(state._iter_entries())
1061
# be sure that it sets not appends - change it
1062
state.set_parent_trees(
1063
((revid1, tree1.branch.repository.revision_tree(revid1)),
1064
('ghost-rev', None)),
1066
# and now put it back.
1067
state.set_parent_trees(
1068
((revid1, tree1.branch.repository.revision_tree(revid1)),
1069
(revid2, tree2.branch.repository.revision_tree(revid2)),
1070
('ghost-rev', tree2.branch.repository.revision_tree(
1071
_mod_revision.NULL_REVISION))),
1073
self.assertEqual([revid1, revid2, 'ghost-rev'],
1074
state.get_parent_ids())
1075
# the ghost should be recorded as such by set_parent_trees.
1076
self.assertEqual(['ghost-rev'], state.get_ghosts())
1078
[(('', '', root_id), [
1079
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1080
('d', '', 0, False, revid1),
1081
('d', '', 0, False, revid1)
1083
list(state._iter_entries()))
1087
def test_set_parent_trees_file_missing_from_tree(self):
1088
# Adding a parent tree may reference files not in the current state.
1089
# they should get listed just once by id, even if they are in two
1091
# set_parent_trees is a slow but important api to support.
1092
tree1 = self.make_branch_and_memory_tree('tree1')
1096
tree1.add(['a file'], ['file-id'], ['file'])
1097
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1098
revid1 = tree1.commit('foo')
1101
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1102
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1105
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1106
revid2 = tree2.commit('foo')
1107
root_id = tree2.get_root_id()
1110
# check the layout in memory
1111
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1112
(('', '', root_id), [
1113
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1114
('d', '', 0, False, revid1.encode('utf8')),
1115
('d', '', 0, False, revid1.encode('utf8'))
1117
(('', 'a file', 'file-id'), [
1118
('a', '', 0, False, ''),
1119
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1120
revid1.encode('utf8')),
1121
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1122
revid2.encode('utf8'))
1125
state = dirstate.DirState.initialize('dirstate')
1127
state.set_path_id('', root_id)
1128
state.set_parent_trees(
1129
((revid1, tree1.branch.repository.revision_tree(revid1)),
1130
(revid2, tree2.branch.repository.revision_tree(revid2)),
1136
# check_state_with_reopen will unlock
1137
self.check_state_with_reopen(expected_result, state)
1139
### add a path via _set_data - so we dont need delta work, just
1140
# raw data in, and ensure that it comes out via get_lines happily.
1142
def test_add_path_to_root_no_parents_all_data(self):
1143
# The most trivial addition of a path is when there are no parents and
1144
# its in the root and all data about the file is supplied
1145
self.build_tree(['a file'])
1146
stat = os.lstat('a file')
1147
# the 1*20 is the sha1 pretend value.
1148
state = dirstate.DirState.initialize('dirstate')
1149
expected_entries = [
1150
(('', '', 'TREE_ROOT'), [
1151
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1153
(('', 'a file', 'a-file-id'), [
1154
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1158
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1159
# having added it, it should be in the output of iter_entries.
1160
self.assertEqual(expected_entries, list(state._iter_entries()))
1161
# saving and reloading should not affect this.
1165
state = dirstate.DirState.on_file('dirstate')
1167
self.addCleanup(state.unlock)
1168
self.assertEqual(expected_entries, list(state._iter_entries()))
1170
def test_add_path_to_unversioned_directory(self):
1171
"""Adding a path to an unversioned directory should error.
1173
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1174
once dirstate is stable and if it is merged with WorkingTree3, consider
1175
removing this copy of the test.
1177
self.build_tree(['unversioned/', 'unversioned/a file'])
1178
state = dirstate.DirState.initialize('dirstate')
1179
self.addCleanup(state.unlock)
1180
self.assertRaises(errors.NotVersionedError, state.add,
1181
'unversioned/a file', 'a-file-id', 'file', None, None)
1183
def test_add_directory_to_root_no_parents_all_data(self):
1184
# The most trivial addition of a dir is when there are no parents and
1185
# its in the root and all data about the file is supplied
1186
self.build_tree(['a dir/'])
1187
stat = os.lstat('a dir')
1188
expected_entries = [
1189
(('', '', 'TREE_ROOT'), [
1190
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1192
(('', 'a dir', 'a dir id'), [
1193
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1196
state = dirstate.DirState.initialize('dirstate')
1198
state.add('a dir', 'a dir id', 'directory', stat, None)
1199
# having added it, it should be in the output of iter_entries.
1200
self.assertEqual(expected_entries, list(state._iter_entries()))
1201
# saving and reloading should not affect this.
1205
state = dirstate.DirState.on_file('dirstate')
1207
self.addCleanup(state.unlock)
1209
self.assertEqual(expected_entries, list(state._iter_entries()))
1211
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1212
# The most trivial addition of a symlink when there are no parents and
1213
# its in the root and all data about the file is supplied
1214
# bzr doesn't support fake symlinks on windows, yet.
1215
self.requireFeature(features.SymlinkFeature)
1216
os.symlink(target, link_name)
1217
stat = os.lstat(link_name)
1218
expected_entries = [
1219
(('', '', 'TREE_ROOT'), [
1220
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1222
(('', link_name.encode('UTF-8'), 'a link id'), [
1223
('l', target.encode('UTF-8'), stat[6],
1224
False, dirstate.pack_stat(stat)), # current tree
1227
state = dirstate.DirState.initialize('dirstate')
1229
state.add(link_name, 'a link id', 'symlink', stat,
1230
target.encode('UTF-8'))
1231
# having added it, it should be in the output of iter_entries.
1232
self.assertEqual(expected_entries, list(state._iter_entries()))
1233
# saving and reloading should not affect this.
1237
state = dirstate.DirState.on_file('dirstate')
1239
self.addCleanup(state.unlock)
1240
self.assertEqual(expected_entries, list(state._iter_entries()))
1242
def test_add_symlink_to_root_no_parents_all_data(self):
1243
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1245
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1246
self.requireFeature(features.UnicodeFilenameFeature)
1247
self._test_add_symlink_to_root_no_parents_all_data(
1248
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1250
def test_add_directory_and_child_no_parents_all_data(self):
1251
# after adding a directory, we should be able to add children to it.
1252
self.build_tree(['a dir/', 'a dir/a file'])
1253
dirstat = os.lstat('a dir')
1254
filestat = os.lstat('a dir/a file')
1255
expected_entries = [
1256
(('', '', 'TREE_ROOT'), [
1257
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1259
(('', 'a dir', 'a dir id'), [
1260
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1262
(('a dir', 'a file', 'a-file-id'), [
1263
('f', '1'*20, 25, False,
1264
dirstate.pack_stat(filestat)), # current tree details
1267
state = dirstate.DirState.initialize('dirstate')
1269
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1270
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1271
# added it, it should be in the output of iter_entries.
1272
self.assertEqual(expected_entries, list(state._iter_entries()))
1273
# saving and reloading should not affect this.
1277
state = dirstate.DirState.on_file('dirstate')
1279
self.addCleanup(state.unlock)
1280
self.assertEqual(expected_entries, list(state._iter_entries()))
1282
def test_add_tree_reference(self):
1283
# make a dirstate and add a tree reference
1284
state = dirstate.DirState.initialize('dirstate')
1286
('', 'subdir', 'subdir-id'),
1287
[('t', 'subtree-123123', 0, False,
1288
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1291
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1292
entry = state._get_entry(0, 'subdir-id', 'subdir')
1293
self.assertEqual(entry, expected_entry)
1298
# now check we can read it back
1300
self.addCleanup(state.unlock)
1302
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1303
self.assertEqual(entry, entry2)
1304
self.assertEqual(entry, expected_entry)
1305
# and lookup by id should work too
1306
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1307
self.assertEqual(entry, expected_entry)
1309
def test_add_forbidden_names(self):
1310
state = dirstate.DirState.initialize('dirstate')
1311
self.addCleanup(state.unlock)
1312
self.assertRaises(errors.BzrError,
1313
state.add, '.', 'ass-id', 'directory', None, None)
1314
self.assertRaises(errors.BzrError,
1315
state.add, '..', 'ass-id', 'directory', None, None)
1317
def test_set_state_with_rename_b_a_bug_395556(self):
1318
# bug 395556 uncovered a bug where the dirstate ends up with a false
1319
# relocation record - in a tree with no parents there should be no
1320
# absent or relocated records. This then leads to further corruption
1321
# when a commit occurs, as the incorrect relocation gathers an
1322
# incorrect absent in tree 1, and future changes go to pot.
1323
tree1 = self.make_branch_and_tree('tree1')
1324
self.build_tree(['tree1/b'])
1327
tree1.add(['b'], ['b-id'])
1328
root_id = tree1.get_root_id()
1329
inv = tree1.inventory
1330
state = dirstate.DirState.initialize('dirstate')
1332
# Set the initial state with 'b'
1333
state.set_state_from_inventory(inv)
1334
inv.rename('b-id', root_id, 'a')
1335
# Set the new state with 'a', which currently corrupts.
1336
state.set_state_from_inventory(inv)
1337
expected_result1 = [('', '', root_id, 'd'),
1338
('', 'a', 'b-id', 'f'),
1341
for entry in state._iter_entries():
1342
values.append(entry[0] + entry[1][0][:1])
1343
self.assertEqual(expected_result1, values)
1350
class TestDirStateHashUpdates(TestCaseWithDirState):
1352
def do_update_entry(self, state, path):
1353
entry = state._get_entry(0, path_utf8=path)
1354
stat = os.lstat(path)
1355
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1357
def _read_state_content(self, state):
1358
"""Read the content of the dirstate file.
1360
On Windows when one process locks a file, you can't even open() the
1361
file in another process (to read it). So we go directly to
1362
state._state_file. This should always be the exact disk representation,
1363
so it is reasonable to do so.
1364
DirState also always seeks before reading, so it doesn't matter if we
1365
bump the file pointer.
1367
state._state_file.seek(0)
1368
return state._state_file.read()
1370
def test_worth_saving_limit_avoids_writing(self):
1371
tree = self.make_branch_and_tree('.')
1372
self.build_tree(['c', 'd'])
1374
tree.add(['c', 'd'], ['c-id', 'd-id'])
1375
tree.commit('add c and d')
1376
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1377
worth_saving_limit=2)
1380
self.addCleanup(state.unlock)
1381
state._read_dirblocks_if_needed()
1382
state.adjust_time(+20) # Allow things to be cached
1383
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1384
state._dirblock_state)
1385
content = self._read_state_content(state)
1386
self.do_update_entry(state, 'c')
1387
self.assertEqual(1, len(state._known_hash_changes))
1388
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1389
state._dirblock_state)
1391
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1392
# hash values haven't been written out.
1393
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1394
state._dirblock_state)
1395
self.assertEqual(content, self._read_state_content(state))
1396
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1397
state._dirblock_state)
1398
self.do_update_entry(state, 'd')
1399
self.assertEqual(2, len(state._known_hash_changes))
1401
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1402
state._dirblock_state)
1403
self.assertEqual(0, len(state._known_hash_changes))
1406
class TestGetLines(TestCaseWithDirState):
1408
def test_get_line_with_2_rows(self):
1409
state = self.create_dirstate_with_root_and_subdir()
1411
self.assertEqual(['#bazaar dirstate flat format 3\n',
1412
'crc32: 41262208\n',
1416
'\x00\x00a-root-value\x00'
1417
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1418
'\x00subdir\x00subdir-id\x00'
1419
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1420
], state.get_lines())
1424
def test_entry_to_line(self):
1425
state = self.create_dirstate_with_root()
1428
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1429
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1430
state._entry_to_line(state._dirblocks[0][1][0]))
1434
def test_entry_to_line_with_parent(self):
1435
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1436
root_entry = ('', '', 'a-root-value'), [
1437
('d', '', 0, False, packed_stat), # current tree details
1438
# first: a pointer to the current location
1439
('a', 'dirname/basename', 0, False, ''),
1441
state = dirstate.DirState.initialize('dirstate')
1444
'\x00\x00a-root-value\x00'
1445
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1446
'a\x00dirname/basename\x000\x00n\x00',
1447
state._entry_to_line(root_entry))
1451
def test_entry_to_line_with_two_parents_at_different_paths(self):
1452
# / in the tree, at / in one parent and /dirname/basename in the other.
1453
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1454
root_entry = ('', '', 'a-root-value'), [
1455
('d', '', 0, False, packed_stat), # current tree details
1456
('d', '', 0, False, 'rev_id'), # first parent details
1457
# second: a pointer to the current location
1458
('a', 'dirname/basename', 0, False, ''),
1460
state = dirstate.DirState.initialize('dirstate')
1463
'\x00\x00a-root-value\x00'
1464
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1465
'd\x00\x000\x00n\x00rev_id\x00'
1466
'a\x00dirname/basename\x000\x00n\x00',
1467
state._entry_to_line(root_entry))
1471
def test_iter_entries(self):
1472
# we should be able to iterate the dirstate entries from end to end
1473
# this is for get_lines to be easy to read.
1474
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1476
root_entries = [(('', '', 'a-root-value'), [
1477
('d', '', 0, False, packed_stat), # current tree details
1479
dirblocks.append(('', root_entries))
1480
# add two files in the root
1481
subdir_entry = ('', 'subdir', 'subdir-id'), [
1482
('d', '', 0, False, packed_stat), # current tree details
1484
afile_entry = ('', 'afile', 'afile-id'), [
1485
('f', 'sha1value', 34, False, packed_stat), # current tree details
1487
dirblocks.append(('', [subdir_entry, afile_entry]))
1489
file_entry2 = ('subdir', '2file', '2file-id'), [
1490
('f', 'sha1value', 23, False, packed_stat), # current tree details
1492
dirblocks.append(('subdir', [file_entry2]))
1493
state = dirstate.DirState.initialize('dirstate')
1495
state._set_data([], dirblocks)
1496
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1498
self.assertEqual(expected_entries, list(state._iter_entries()))
1503
class TestGetBlockRowIndex(TestCaseWithDirState):
1505
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1506
file_present, state, dirname, basename, tree_index):
1507
self.assertEqual((block_index, row_index, dir_present, file_present),
1508
state._get_block_entry_index(dirname, basename, tree_index))
1510
block = state._dirblocks[block_index]
1511
self.assertEqual(dirname, block[0])
1512
if dir_present and file_present:
1513
row = state._dirblocks[block_index][1][row_index]
1514
self.assertEqual(dirname, row[0][0])
1515
self.assertEqual(basename, row[0][1])
1517
def test_simple_structure(self):
1518
state = self.create_dirstate_with_root_and_subdir()
1519
self.addCleanup(state.unlock)
1520
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1521
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1522
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1523
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1524
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1527
def test_complex_structure_exists(self):
1528
state = self.create_complex_dirstate()
1529
self.addCleanup(state.unlock)
1530
# Make sure we can find everything that exists
1531
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1532
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1533
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1534
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1535
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1536
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1537
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1538
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1539
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1540
'b', 'h\xc3\xa5', 0)
1542
def test_complex_structure_missing(self):
1543
state = self.create_complex_dirstate()
1544
self.addCleanup(state.unlock)
1545
# Make sure things would be inserted in the right locations
1546
# '_' comes before 'a'
1547
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1548
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1549
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1550
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1552
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1553
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1554
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1555
# This would be inserted between a/ and b/
1556
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1558
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1561
class TestGetEntry(TestCaseWithDirState):
1563
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1564
"""Check that the right entry is returned for a request to getEntry."""
1565
entry = state._get_entry(index, path_utf8=path)
1567
self.assertEqual((None, None), entry)
1570
self.assertEqual((dirname, basename, file_id), cur[:3])
1572
def test_simple_structure(self):
1573
state = self.create_dirstate_with_root_and_subdir()
1574
self.addCleanup(state.unlock)
1575
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1576
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1577
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1578
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1579
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1581
def test_complex_structure_exists(self):
1582
state = self.create_complex_dirstate()
1583
self.addCleanup(state.unlock)
1584
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1585
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1586
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1587
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1588
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1589
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1590
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1591
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1592
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1595
def test_complex_structure_missing(self):
1596
state = self.create_complex_dirstate()
1597
self.addCleanup(state.unlock)
1598
self.assertEntryEqual(None, None, None, state, '_', 0)
1599
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1600
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1601
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1603
def test_get_entry_uninitialized(self):
1604
"""Calling get_entry will load data if it needs to"""
1605
state = self.create_dirstate_with_root()
1611
state = dirstate.DirState.on_file('dirstate')
1614
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1615
state._header_state)
1616
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1617
state._dirblock_state)
1618
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1623
class TestIterChildEntries(TestCaseWithDirState):
1625
def create_dirstate_with_two_trees(self):
1626
"""This dirstate contains multiple files and directories.
1636
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1638
Notice that a/e is an empty directory.
1640
There is one parent tree, which has the same shape with the following variations:
1641
b/g in the parent is gone.
1642
b/h in the parent has a different id
1643
b/i is new in the parent
1644
c is renamed to b/j in the parent
1646
:return: The dirstate, still write-locked.
1648
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1649
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1650
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1651
root_entry = ('', '', 'a-root-value'), [
1652
('d', '', 0, False, packed_stat),
1653
('d', '', 0, False, 'parent-revid'),
1655
a_entry = ('', 'a', 'a-dir'), [
1656
('d', '', 0, False, packed_stat),
1657
('d', '', 0, False, 'parent-revid'),
1659
b_entry = ('', 'b', 'b-dir'), [
1660
('d', '', 0, False, packed_stat),
1661
('d', '', 0, False, 'parent-revid'),
1663
c_entry = ('', 'c', 'c-file'), [
1664
('f', null_sha, 10, False, packed_stat),
1665
('r', 'b/j', 0, False, ''),
1667
d_entry = ('', 'd', 'd-file'), [
1668
('f', null_sha, 20, False, packed_stat),
1669
('f', 'd', 20, False, 'parent-revid'),
1671
e_entry = ('a', 'e', 'e-dir'), [
1672
('d', '', 0, False, packed_stat),
1673
('d', '', 0, False, 'parent-revid'),
1675
f_entry = ('a', 'f', 'f-file'), [
1676
('f', null_sha, 30, False, packed_stat),
1677
('f', 'f', 20, False, 'parent-revid'),
1679
g_entry = ('b', 'g', 'g-file'), [
1680
('f', null_sha, 30, False, packed_stat),
1681
NULL_PARENT_DETAILS,
1683
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1684
('f', null_sha, 40, False, packed_stat),
1685
NULL_PARENT_DETAILS,
1687
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1688
NULL_PARENT_DETAILS,
1689
('f', 'h', 20, False, 'parent-revid'),
1691
i_entry = ('b', 'i', 'i-file'), [
1692
NULL_PARENT_DETAILS,
1693
('f', 'h', 20, False, 'parent-revid'),
1695
j_entry = ('b', 'j', 'c-file'), [
1696
('r', 'c', 0, False, ''),
1697
('f', 'j', 20, False, 'parent-revid'),
1700
dirblocks.append(('', [root_entry]))
1701
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1702
dirblocks.append(('a', [e_entry, f_entry]))
1703
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1704
state = dirstate.DirState.initialize('dirstate')
1707
state._set_data(['parent'], dirblocks)
1711
return state, dirblocks
1713
def test_iter_children_b(self):
1714
state, dirblocks = self.create_dirstate_with_two_trees()
1715
self.addCleanup(state.unlock)
1716
expected_result = []
1717
expected_result.append(dirblocks[3][1][2]) # h2
1718
expected_result.append(dirblocks[3][1][3]) # i
1719
expected_result.append(dirblocks[3][1][4]) # j
1720
self.assertEqual(expected_result,
1721
list(state._iter_child_entries(1, 'b')))
1723
def test_iter_child_root(self):
1724
state, dirblocks = self.create_dirstate_with_two_trees()
1725
self.addCleanup(state.unlock)
1726
expected_result = []
1727
expected_result.append(dirblocks[1][1][0]) # a
1728
expected_result.append(dirblocks[1][1][1]) # b
1729
expected_result.append(dirblocks[1][1][3]) # d
1730
expected_result.append(dirblocks[2][1][0]) # e
1731
expected_result.append(dirblocks[2][1][1]) # f
1732
expected_result.append(dirblocks[3][1][2]) # h2
1733
expected_result.append(dirblocks[3][1][3]) # i
1734
expected_result.append(dirblocks[3][1][4]) # j
1735
self.assertEqual(expected_result,
1736
list(state._iter_child_entries(1, '')))
1739
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1740
"""Test that DirState adds entries in the right order."""
1742
def test_add_sorting(self):
1743
"""Add entries in lexicographical order, we get path sorted order.
1745
This tests it to a depth of 4, to make sure we don't just get it right
1746
at a single depth. 'a/a' should come before 'a-a', even though it
1747
doesn't lexicographically.
1749
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1750
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1753
state = dirstate.DirState.initialize('dirstate')
1754
self.addCleanup(state.unlock)
1756
fake_stat = os.stat('dirstate')
1758
d_id = d.replace('/', '_')+'-id'
1759
file_path = d + '/f'
1760
file_id = file_path.replace('/', '_')+'-id'
1761
state.add(d, d_id, 'directory', fake_stat, null_sha)
1762
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1764
expected = ['', '', 'a',
1765
'a/a', 'a/a/a', 'a/a/a/a',
1766
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1768
split = lambda p:p.split('/')
1769
self.assertEqual(sorted(expected, key=split), expected)
1770
dirblock_names = [d[0] for d in state._dirblocks]
1771
self.assertEqual(expected, dirblock_names)
1773
def test_set_parent_trees_correct_order(self):
1774
"""After calling set_parent_trees() we should maintain the order."""
1775
dirs = ['a', 'a-a', 'a/a']
1777
state = dirstate.DirState.initialize('dirstate')
1778
self.addCleanup(state.unlock)
1780
fake_stat = os.stat('dirstate')
1782
d_id = d.replace('/', '_')+'-id'
1783
file_path = d + '/f'
1784
file_id = file_path.replace('/', '_')+'-id'
1785
state.add(d, d_id, 'directory', fake_stat, null_sha)
1786
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1788
expected = ['', '', 'a', 'a/a', 'a-a']
1789
dirblock_names = [d[0] for d in state._dirblocks]
1790
self.assertEqual(expected, dirblock_names)
1792
# *really* cheesy way to just get an empty tree
1793
repo = self.make_repository('repo')
1794
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1795
state.set_parent_trees([('null:', empty_tree)], [])
1797
dirblock_names = [d[0] for d in state._dirblocks]
1798
self.assertEqual(expected, dirblock_names)
1801
class InstrumentedDirState(dirstate.DirState):
1802
"""An DirState with instrumented sha1 functionality."""
1804
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1805
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1806
worth_saving_limit=worth_saving_limit)
1807
self._time_offset = 0
1809
# member is dynamically set in DirState.__init__ to turn on trace
1810
self._sha1_provider = sha1_provider
1811
self._sha1_file = self._sha1_file_and_log
1813
def _sha_cutoff_time(self):
1814
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1815
self._cutoff_time = timestamp + self._time_offset
1817
def _sha1_file_and_log(self, abspath):
1818
self._log.append(('sha1', abspath))
1819
return self._sha1_provider.sha1(abspath)
1821
def _read_link(self, abspath, old_link):
1822
self._log.append(('read_link', abspath, old_link))
1823
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1825
def _lstat(self, abspath, entry):
1826
self._log.append(('lstat', abspath))
1827
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1829
def _is_executable(self, mode, old_executable):
1830
self._log.append(('is_exec', mode, old_executable))
1831
return super(InstrumentedDirState, self)._is_executable(mode,
1834
def adjust_time(self, secs):
1835
"""Move the clock forward or back.
1837
:param secs: The amount to adjust the clock by. Positive values make it
1838
seem as if we are in the future, negative values make it seem like we
1841
self._time_offset += secs
1842
self._cutoff_time = None
1845
class _FakeStat(object):
1846
"""A class with the same attributes as a real stat result."""
1848
def __init__(self, size, mtime, ctime, dev, ino, mode):
1850
self.st_mtime = mtime
1851
self.st_ctime = ctime
1858
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1859
st.st_ino, st.st_mode)
1862
class TestPackStat(tests.TestCaseWithTransport):
1864
def assertPackStat(self, expected, stat_value):
1865
"""Check the packed and serialized form of a stat value."""
1866
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1868
def test_pack_stat_int(self):
1869
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1870
# Make sure that all parameters have an impact on the packed stat.
1871
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1874
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1875
st.st_mtime = 1172758620
1877
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1878
st.st_ctime = 1172758630
1880
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1883
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1884
st.st_ino = 6499540L
1886
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1887
st.st_mode = 0100744
1889
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1891
def test_pack_stat_float(self):
1892
"""On some platforms mtime and ctime are floats.
1894
Make sure we don't get warnings or errors, and that we ignore changes <
1897
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1898
777L, 6499538L, 0100644)
1899
# These should all be the same as the integer counterparts
1900
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1901
st.st_mtime = 1172758620.0
1903
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1904
st.st_ctime = 1172758630.0
1906
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1907
# fractional seconds are discarded, so no change from above
1908
st.st_mtime = 1172758620.453
1909
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1910
st.st_ctime = 1172758630.228
1911
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1914
class TestBisect(TestCaseWithDirState):
1915
"""Test the ability to bisect into the disk format."""
1917
def assertBisect(self, expected_map, map_keys, state, paths):
1918
"""Assert that bisecting for paths returns the right result.
1920
:param expected_map: A map from key => entry value
1921
:param map_keys: The keys to expect for each path
1922
:param state: The DirState object.
1923
:param paths: A list of paths, these will automatically be split into
1924
(dir, name) tuples, and sorted according to how _bisect
1927
result = state._bisect(paths)
1928
# For now, results are just returned in whatever order we read them.
1929
# We could sort by (dir, name, file_id) or something like that, but in
1930
# the end it would still be fairly arbitrary, and we don't want the
1931
# extra overhead if we can avoid it. So sort everything to make sure
1933
self.assertEqual(len(map_keys), len(paths))
1935
for path, keys in zip(paths, map_keys):
1937
# This should not be present in the output
1939
expected[path] = sorted(expected_map[k] for k in keys)
1941
# The returned values are just arranged randomly based on when they
1942
# were read, for testing, make sure it is properly sorted.
1946
self.assertEqual(expected, result)
1948
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1949
"""Assert that bisecting for dirbblocks returns the right result.
1951
:param expected_map: A map from key => expected values
1952
:param map_keys: A nested list of paths we expect to be returned.
1953
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1954
:param state: The DirState object.
1955
:param paths: A list of directories
1957
result = state._bisect_dirblocks(paths)
1958
self.assertEqual(len(map_keys), len(paths))
1960
for path, keys in zip(paths, map_keys):
1962
# This should not be present in the output
1964
expected[path] = sorted(expected_map[k] for k in keys)
1968
self.assertEqual(expected, result)
1970
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1971
"""Assert the return value of a recursive bisection.
1973
:param expected_map: A map from key => entry value
1974
:param map_keys: A list of paths we expect to be returned.
1975
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1976
:param state: The DirState object.
1977
:param paths: A list of files and directories. It will be broken up
1978
into (dir, name) pairs and sorted before calling _bisect_recursive.
1981
for key in map_keys:
1982
entry = expected_map[key]
1983
dir_name_id, trees_info = entry
1984
expected[dir_name_id] = trees_info
1986
result = state._bisect_recursive(paths)
1988
self.assertEqual(expected, result)
1990
def test_bisect_each(self):
1991
"""Find a single record using bisect."""
1992
tree, state, expected = self.create_basic_dirstate()
1994
# Bisect should return the rows for the specified files.
1995
self.assertBisect(expected, [['']], state, [''])
1996
self.assertBisect(expected, [['a']], state, ['a'])
1997
self.assertBisect(expected, [['b']], state, ['b'])
1998
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1999
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2000
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2001
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2002
self.assertBisect(expected, [['f']], state, ['f'])
2004
def test_bisect_multi(self):
2005
"""Bisect can be used to find multiple records at the same time."""
2006
tree, state, expected = self.create_basic_dirstate()
2007
# Bisect should be capable of finding multiple entries at the same time
2008
self.assertBisect(expected, [['a'], ['b'], ['f']],
2009
state, ['a', 'b', 'f'])
2010
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2011
state, ['f', 'b/d', 'b/d/e'])
2012
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2013
state, ['b', 'b-c', 'b/c'])
2015
def test_bisect_one_page(self):
2016
"""Test bisect when there is only 1 page to read"""
2017
tree, state, expected = self.create_basic_dirstate()
2018
state._bisect_page_size = 5000
2019
self.assertBisect(expected,[['']], state, [''])
2020
self.assertBisect(expected,[['a']], state, ['a'])
2021
self.assertBisect(expected,[['b']], state, ['b'])
2022
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2023
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2024
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2025
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2026
self.assertBisect(expected,[['f']], state, ['f'])
2027
self.assertBisect(expected,[['a'], ['b'], ['f']],
2028
state, ['a', 'b', 'f'])
2029
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2030
state, ['b/d', 'b/d/e', 'f'])
2031
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2032
state, ['b', 'b/c', 'b-c'])
2034
def test_bisect_duplicate_paths(self):
2035
"""When bisecting for a path, handle multiple entries."""
2036
tree, state, expected = self.create_duplicated_dirstate()
2038
# Now make sure that both records are properly returned.
2039
self.assertBisect(expected, [['']], state, [''])
2040
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2041
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2042
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2043
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2044
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2046
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2047
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2049
def test_bisect_page_size_too_small(self):
2050
"""If the page size is too small, we will auto increase it."""
2051
tree, state, expected = self.create_basic_dirstate()
2052
state._bisect_page_size = 50
2053
self.assertBisect(expected, [None], state, ['b/e'])
2054
self.assertBisect(expected, [['a']], state, ['a'])
2055
self.assertBisect(expected, [['b']], state, ['b'])
2056
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2057
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2058
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2059
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2060
self.assertBisect(expected, [['f']], state, ['f'])
2062
def test_bisect_missing(self):
2063
"""Test that bisect return None if it cannot find a path."""
2064
tree, state, expected = self.create_basic_dirstate()
2065
self.assertBisect(expected, [None], state, ['foo'])
2066
self.assertBisect(expected, [None], state, ['b/foo'])
2067
self.assertBisect(expected, [None], state, ['bar/foo'])
2068
self.assertBisect(expected, [None], state, ['b-c/foo'])
2070
self.assertBisect(expected, [['a'], None, ['b/d']],
2071
state, ['a', 'foo', 'b/d'])
2073
def test_bisect_rename(self):
2074
"""Check that we find a renamed row."""
2075
tree, state, expected = self.create_renamed_dirstate()
2077
# Search for the pre and post renamed entries
2078
self.assertBisect(expected, [['a']], state, ['a'])
2079
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2080
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2081
self.assertBisect(expected, [['h']], state, ['h'])
2083
# What about b/d/e? shouldn't that also get 2 directory entries?
2084
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2085
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2087
def test_bisect_dirblocks(self):
2088
tree, state, expected = self.create_duplicated_dirstate()
2089
self.assertBisectDirBlocks(expected,
2090
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2092
self.assertBisectDirBlocks(expected,
2093
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2094
self.assertBisectDirBlocks(expected,
2095
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2096
self.assertBisectDirBlocks(expected,
2097
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2098
['b/c', 'b/c2', 'b/d', 'b/d2'],
2099
['b/d/e', 'b/d/e2'],
2100
], state, ['', 'b', 'b/d'])
2102
def test_bisect_dirblocks_missing(self):
2103
tree, state, expected = self.create_basic_dirstate()
2104
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2105
state, ['b/d', 'b/e'])
2106
# Files don't show up in this search
2107
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2108
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2109
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2110
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2111
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2113
def test_bisect_recursive_each(self):
2114
tree, state, expected = self.create_basic_dirstate()
2115
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2116
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2117
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2118
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2119
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2121
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2123
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2127
def test_bisect_recursive_multiple(self):
2128
tree, state, expected = self.create_basic_dirstate()
2129
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2130
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2131
state, ['b/d', 'b/d/e'])
2133
def test_bisect_recursive_missing(self):
2134
tree, state, expected = self.create_basic_dirstate()
2135
self.assertBisectRecursive(expected, [], state, ['d'])
2136
self.assertBisectRecursive(expected, [], state, ['b/e'])
2137
self.assertBisectRecursive(expected, [], state, ['g'])
2138
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2140
def test_bisect_recursive_renamed(self):
2141
tree, state, expected = self.create_renamed_dirstate()
2143
# Looking for either renamed item should find the other
2144
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2145
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2146
# Looking in the containing directory should find the rename target,
2147
# and anything in a subdir of the renamed target.
2148
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2149
'b/d/e', 'b/g', 'h', 'h/e'],
2153
class TestDirstateValidation(TestCaseWithDirState):
2155
def test_validate_correct_dirstate(self):
2156
state = self.create_complex_dirstate()
2159
# and make sure we can also validate with a read lock
2166
def test_dirblock_not_sorted(self):
2167
tree, state, expected = self.create_renamed_dirstate()
2168
state._read_dirblocks_if_needed()
2169
last_dirblock = state._dirblocks[-1]
2170
# we're appending to the dirblock, but this name comes before some of
2171
# the existing names; that's wrong
2172
last_dirblock[1].append(
2173
(('h', 'aaaa', 'a-id'),
2174
[('a', '', 0, False, ''),
2175
('a', '', 0, False, '')]))
2176
e = self.assertRaises(AssertionError,
2178
self.assertContainsRe(str(e), 'not sorted')
2180
def test_dirblock_name_mismatch(self):
2181
tree, state, expected = self.create_renamed_dirstate()
2182
state._read_dirblocks_if_needed()
2183
last_dirblock = state._dirblocks[-1]
2184
# add an entry with the wrong directory name
2185
last_dirblock[1].append(
2187
[('a', '', 0, False, ''),
2188
('a', '', 0, False, '')]))
2189
e = self.assertRaises(AssertionError,
2191
self.assertContainsRe(str(e),
2192
"doesn't match directory name")
2194
def test_dirblock_missing_rename(self):
2195
tree, state, expected = self.create_renamed_dirstate()
2196
state._read_dirblocks_if_needed()
2197
last_dirblock = state._dirblocks[-1]
2198
# make another entry for a-id, without a correct 'r' pointer to
2199
# the real occurrence in the working tree
2200
last_dirblock[1].append(
2201
(('h', 'z', 'a-id'),
2202
[('a', '', 0, False, ''),
2203
('a', '', 0, False, '')]))
2204
e = self.assertRaises(AssertionError,
2206
self.assertContainsRe(str(e),
2207
'file a-id is absent in row')
2210
class TestDirstateTreeReference(TestCaseWithDirState):
2212
def test_reference_revision_is_none(self):
2213
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2214
subtree = self.make_branch_and_tree('tree/subtree',
2215
format='dirstate-with-subtree')
2216
subtree.set_root_id('subtree')
2217
tree.add_reference(subtree)
2219
state = dirstate.DirState.from_tree(tree, 'dirstate')
2220
key = ('', 'subtree', 'subtree')
2221
expected = ('', [(key,
2222
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2225
self.assertEqual(expected, state._find_block(key))
2230
class TestDiscardMergeParents(TestCaseWithDirState):
2232
def test_discard_no_parents(self):
2233
# This should be a no-op
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._discard_merge_parents()
2239
def test_discard_one_parent(self):
2241
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2242
root_entry_direntry = ('', '', 'a-root-value'), [
2243
('d', '', 0, False, packed_stat),
2244
('d', '', 0, False, packed_stat),
2247
dirblocks.append(('', [root_entry_direntry]))
2248
dirblocks.append(('', []))
2250
state = self.create_empty_dirstate()
2251
self.addCleanup(state.unlock)
2252
state._set_data(['parent-id'], dirblocks[:])
2255
state._discard_merge_parents()
2257
self.assertEqual(dirblocks, state._dirblocks)
2259
def test_discard_simple(self):
2261
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2262
root_entry_direntry = ('', '', 'a-root-value'), [
2263
('d', '', 0, False, packed_stat),
2264
('d', '', 0, False, packed_stat),
2265
('d', '', 0, False, packed_stat),
2267
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2268
('d', '', 0, False, packed_stat),
2269
('d', '', 0, False, packed_stat),
2272
dirblocks.append(('', [root_entry_direntry]))
2273
dirblocks.append(('', []))
2275
state = self.create_empty_dirstate()
2276
self.addCleanup(state.unlock)
2277
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2280
# This should strip of the extra column
2281
state._discard_merge_parents()
2283
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2284
self.assertEqual(expected_dirblocks, state._dirblocks)
2286
def test_discard_absent(self):
2287
"""If entries are only in a merge, discard should remove the entries"""
2288
null_stat = dirstate.DirState.NULLSTAT
2289
present_dir = ('d', '', 0, False, null_stat)
2290
present_file = ('f', '', 0, False, null_stat)
2291
absent = dirstate.DirState.NULL_PARENT_DETAILS
2292
root_key = ('', '', 'a-root-value')
2293
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2294
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2295
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2296
('', [(file_in_merged_key,
2297
[absent, absent, present_file]),
2299
[present_file, present_file, present_file]),
2303
state = self.create_empty_dirstate()
2304
self.addCleanup(state.unlock)
2305
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2308
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2309
('', [(file_in_root_key,
2310
[present_file, present_file]),
2313
state._discard_merge_parents()
2315
self.assertEqual(exp_dirblocks, state._dirblocks)
2317
def test_discard_renamed(self):
2318
null_stat = dirstate.DirState.NULLSTAT
2319
present_dir = ('d', '', 0, False, null_stat)
2320
present_file = ('f', '', 0, False, null_stat)
2321
absent = dirstate.DirState.NULL_PARENT_DETAILS
2322
root_key = ('', '', 'a-root-value')
2323
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2324
# Renamed relative to parent
2325
file_rename_s_key = ('', 'file-s', 'b-file-id')
2326
file_rename_t_key = ('', 'file-t', 'b-file-id')
2327
# And one that is renamed between the parents, but absent in this
2328
key_in_1 = ('', 'file-in-1', 'c-file-id')
2329
key_in_2 = ('', 'file-in-2', 'c-file-id')
2332
('', [(root_key, [present_dir, present_dir, present_dir])]),
2334
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2336
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2338
[present_file, present_file, present_file]),
2340
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2342
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2346
('', [(root_key, [present_dir, present_dir])]),
2347
('', [(key_in_1, [absent, present_file]),
2348
(file_in_root_key, [present_file, present_file]),
2349
(file_rename_t_key, [present_file, absent]),
2352
state = self.create_empty_dirstate()
2353
self.addCleanup(state.unlock)
2354
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2357
state._discard_merge_parents()
2359
self.assertEqual(exp_dirblocks, state._dirblocks)
2361
def test_discard_all_subdir(self):
2362
null_stat = dirstate.DirState.NULLSTAT
2363
present_dir = ('d', '', 0, False, null_stat)
2364
present_file = ('f', '', 0, False, null_stat)
2365
absent = dirstate.DirState.NULL_PARENT_DETAILS
2366
root_key = ('', '', 'a-root-value')
2367
subdir_key = ('', 'sub', 'dir-id')
2368
child1_key = ('sub', 'child1', 'child1-id')
2369
child2_key = ('sub', 'child2', 'child2-id')
2370
child3_key = ('sub', 'child3', 'child3-id')
2373
('', [(root_key, [present_dir, present_dir, present_dir])]),
2374
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2375
('sub', [(child1_key, [absent, absent, present_file]),
2376
(child2_key, [absent, absent, present_file]),
2377
(child3_key, [absent, absent, present_file]),
2381
('', [(root_key, [present_dir, present_dir])]),
2382
('', [(subdir_key, [present_dir, present_dir])]),
2385
state = self.create_empty_dirstate()
2386
self.addCleanup(state.unlock)
2387
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2390
state._discard_merge_parents()
2392
self.assertEqual(exp_dirblocks, state._dirblocks)
2395
class Test_InvEntryToDetails(tests.TestCase):
2397
def assertDetails(self, expected, inv_entry):
2398
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2399
self.assertEqual(expected, details)
2400
# details should always allow join() and always be a plain str when
2402
(minikind, fingerprint, size, executable, tree_data) = details
2403
self.assertIsInstance(minikind, str)
2404
self.assertIsInstance(fingerprint, str)
2405
self.assertIsInstance(tree_data, str)
2407
def test_unicode_symlink(self):
2408
inv_entry = inventory.InventoryLink('link-file-id',
2409
u'nam\N{Euro Sign}e',
2411
inv_entry.revision = 'link-revision-id'
2412
target = u'link-targ\N{Euro Sign}t'
2413
inv_entry.symlink_target = target
2414
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2415
'link-revision-id'), inv_entry)
2418
class TestSHA1Provider(tests.TestCaseInTempDir):
2420
def test_sha1provider_is_an_interface(self):
2421
p = dirstate.SHA1Provider()
2422
self.assertRaises(NotImplementedError, p.sha1, "foo")
2423
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2425
def test_defaultsha1provider_sha1(self):
2426
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2427
self.build_tree_contents([('foo', text)])
2428
expected_sha = osutils.sha_string(text)
2429
p = dirstate.DefaultSHA1Provider()
2430
self.assertEqual(expected_sha, p.sha1('foo'))
2432
def test_defaultsha1provider_stat_and_sha1(self):
2433
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2434
self.build_tree_contents([('foo', text)])
2435
expected_sha = osutils.sha_string(text)
2436
p = dirstate.DefaultSHA1Provider()
2437
statvalue, sha1 = p.stat_and_sha1('foo')
2438
self.assertTrue(len(statvalue) >= 10)
2439
self.assertEqual(len(text), statvalue.st_size)
2440
self.assertEqual(expected_sha, sha1)
2443
class _Repo(object):
2444
"""A minimal api to get InventoryRevisionTree to work."""
2447
default_format = bzrdir.format_registry.make_bzrdir('default')
2448
self._format = default_format.repository_format
2450
def lock_read(self):
2457
class TestUpdateBasisByDelta(tests.TestCase):
2459
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2460
if path.endswith('/'):
2465
dirname, basename = osutils.split(path)
2467
dir_id = dir_ids[dirname]
2469
dir_id = osutils.basename(dirname) + '-id'
2471
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2472
dir_ids[path] = file_id
2474
ie = inventory.InventoryFile(file_id, basename, dir_id)
2477
ie.revision = rev_id
2480
def create_tree_from_shape(self, rev_id, shape):
2481
dir_ids = {'': 'root-id'}
2482
inv = inventory.Inventory('root-id', rev_id)
2483
for path, file_id in shape:
2485
# Replace the root entry
2486
del inv._byid[inv.root.file_id]
2487
inv.root.file_id = file_id
2488
inv._byid[file_id] = inv.root
2489
dir_ids[''] = file_id
2491
inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
2492
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2494
def create_empty_dirstate(self):
2495
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2496
self.addCleanup(os.remove, path)
2498
state = dirstate.DirState.initialize(path)
2499
self.addCleanup(state.unlock)
2502
def create_inv_delta(self, delta, rev_id):
2503
"""Translate a 'delta shape' into an actual InventoryDelta"""
2504
dir_ids = {'': 'root-id'}
2506
for old_path, new_path, file_id in delta:
2507
if old_path is not None and old_path.endswith('/'):
2508
# Don't have to actually do anything for this, because only
2509
# new_path creates InventoryEntries
2510
old_path = old_path[:-1]
2511
if new_path is None: # Delete
2512
inv_delta.append((old_path, None, file_id, None))
2514
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2515
inv_delta.append((old_path, new_path, file_id, ie))
2518
def assertUpdate(self, active, basis, target):
2519
"""Assert that update_basis_by_delta works how we want.
2521
Set up a DirState object with active_shape for tree 0, basis_shape for
2522
tree 1. Then apply the delta from basis_shape to target_shape,
2523
and assert that the DirState is still valid, and that its stored
2524
content matches the target_shape.
2526
active_tree = self.create_tree_from_shape('active', active)
2527
basis_tree = self.create_tree_from_shape('basis', basis)
2528
target_tree = self.create_tree_from_shape('target', target)
2529
state = self.create_empty_dirstate()
2530
state.set_state_from_scratch(active_tree.inventory,
2531
[('basis', basis_tree)], [])
2532
delta = target_tree.inventory._make_delta(basis_tree.inventory)
2533
state.update_basis_by_delta(delta, 'target')
2535
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2537
# The target now that delta has been applied should match the
2539
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2540
# And the dirblock state should be identical to the state if we created
2542
state2 = self.create_empty_dirstate()
2543
state2.set_state_from_scratch(active_tree.inventory,
2544
[('target', target_tree)], [])
2545
self.assertEqual(state2._dirblocks, state._dirblocks)
2548
def assertBadDelta(self, active, basis, delta):
2549
"""Test that we raise InconsistentDelta when appropriate.
2551
:param active: The active tree shape
2552
:param basis: The basis tree shape
2553
:param delta: A description of the delta to apply. Similar to the form
2554
for regular inventory deltas, but omitting the InventoryEntry.
2555
So adding a file is: (None, 'path', 'file-id')
2556
Adding a directory is: (None, 'path/', 'dir-id')
2557
Renaming a dir is: ('old/', 'new/', 'dir-id')
2560
active_tree = self.create_tree_from_shape('active', active)
2561
basis_tree = self.create_tree_from_shape('basis', basis)
2562
inv_delta = self.create_inv_delta(delta, 'target')
2563
state = self.create_empty_dirstate()
2564
state.set_state_from_scratch(active_tree.inventory,
2565
[('basis', basis_tree)], [])
2566
self.assertRaises(errors.InconsistentDelta,
2567
state.update_basis_by_delta, inv_delta, 'target')
2569
## state.update_basis_by_delta(inv_delta, 'target')
2570
## except errors.InconsistentDelta, e:
2571
## import pdb; pdb.set_trace()
2573
## import pdb; pdb.set_trace()
2574
self.assertTrue(state._changes_aborted)
2576
def test_remove_file_matching_active_state(self):
2577
state = self.assertUpdate(
2579
basis =[('file', 'file-id')],
2583
def test_remove_file_present_in_active_state(self):
2584
state = self.assertUpdate(
2585
active=[('file', 'file-id')],
2586
basis =[('file', 'file-id')],
2590
def test_remove_file_present_elsewhere_in_active_state(self):
2591
state = self.assertUpdate(
2592
active=[('other-file', 'file-id')],
2593
basis =[('file', 'file-id')],
2597
def test_remove_file_active_state_has_diff_file(self):
2598
state = self.assertUpdate(
2599
active=[('file', 'file-id-2')],
2600
basis =[('file', 'file-id')],
2604
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2605
state = self.assertUpdate(
2606
active=[('file', 'file-id-2'),
2607
('other-file', 'file-id')],
2608
basis =[('file', 'file-id')],
2612
def test_add_file_matching_active_state(self):
2613
state = self.assertUpdate(
2614
active=[('file', 'file-id')],
2616
target=[('file', 'file-id')],
2619
def test_add_file_missing_in_active_state(self):
2620
state = self.assertUpdate(
2623
target=[('file', 'file-id')],
2626
def test_add_file_elsewhere_in_active_state(self):
2627
state = self.assertUpdate(
2628
active=[('other-file', 'file-id')],
2630
target=[('file', 'file-id')],
2633
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2634
state = self.assertUpdate(
2635
active=[('other-file', 'file-id'),
2636
('file', 'file-id-2')],
2638
target=[('file', 'file-id')],
2641
def test_rename_file_matching_active_state(self):
2642
state = self.assertUpdate(
2643
active=[('other-file', 'file-id')],
2644
basis =[('file', 'file-id')],
2645
target=[('other-file', 'file-id')],
2648
def test_rename_file_missing_in_active_state(self):
2649
state = self.assertUpdate(
2651
basis =[('file', 'file-id')],
2652
target=[('other-file', 'file-id')],
2655
def test_rename_file_present_elsewhere_in_active_state(self):
2656
state = self.assertUpdate(
2657
active=[('third', 'file-id')],
2658
basis =[('file', 'file-id')],
2659
target=[('other-file', 'file-id')],
2662
def test_rename_file_active_state_has_diff_source_file(self):
2663
state = self.assertUpdate(
2664
active=[('file', 'file-id-2')],
2665
basis =[('file', 'file-id')],
2666
target=[('other-file', 'file-id')],
2669
def test_rename_file_active_state_has_diff_target_file(self):
2670
state = self.assertUpdate(
2671
active=[('other-file', 'file-id-2')],
2672
basis =[('file', 'file-id')],
2673
target=[('other-file', 'file-id')],
2676
def test_rename_file_active_has_swapped_files(self):
2677
state = self.assertUpdate(
2678
active=[('file', 'file-id'),
2679
('other-file', 'file-id-2')],
2680
basis= [('file', 'file-id'),
2681
('other-file', 'file-id-2')],
2682
target=[('file', 'file-id-2'),
2683
('other-file', 'file-id')])
2685
def test_rename_file_basis_has_swapped_files(self):
2686
state = self.assertUpdate(
2687
active=[('file', 'file-id'),
2688
('other-file', 'file-id-2')],
2689
basis= [('file', 'file-id-2'),
2690
('other-file', 'file-id')],
2691
target=[('file', 'file-id'),
2692
('other-file', 'file-id-2')])
2694
def test_rename_directory_with_contents(self):
2695
state = self.assertUpdate( # active matches basis
2696
active=[('dir1/', 'dir-id'),
2697
('dir1/file', 'file-id')],
2698
basis= [('dir1/', 'dir-id'),
2699
('dir1/file', 'file-id')],
2700
target=[('dir2/', 'dir-id'),
2701
('dir2/file', 'file-id')])
2702
state = self.assertUpdate( # active matches target
2703
active=[('dir2/', 'dir-id'),
2704
('dir2/file', 'file-id')],
2705
basis= [('dir1/', 'dir-id'),
2706
('dir1/file', 'file-id')],
2707
target=[('dir2/', 'dir-id'),
2708
('dir2/file', 'file-id')])
2709
state = self.assertUpdate( # active empty
2711
basis= [('dir1/', 'dir-id'),
2712
('dir1/file', 'file-id')],
2713
target=[('dir2/', 'dir-id'),
2714
('dir2/file', 'file-id')])
2715
state = self.assertUpdate( # active present at other location
2716
active=[('dir3/', 'dir-id'),
2717
('dir3/file', 'file-id')],
2718
basis= [('dir1/', 'dir-id'),
2719
('dir1/file', 'file-id')],
2720
target=[('dir2/', 'dir-id'),
2721
('dir2/file', 'file-id')])
2722
state = self.assertUpdate( # active has different ids
2723
active=[('dir1/', 'dir1-id'),
2724
('dir1/file', 'file1-id'),
2725
('dir2/', 'dir2-id'),
2726
('dir2/file', 'file2-id')],
2727
basis= [('dir1/', 'dir-id'),
2728
('dir1/file', 'file-id')],
2729
target=[('dir2/', 'dir-id'),
2730
('dir2/file', 'file-id')])
2732
def test_invalid_file_not_present(self):
2733
state = self.assertBadDelta(
2734
active=[('file', 'file-id')],
2735
basis= [('file', 'file-id')],
2736
delta=[('other-file', 'file', 'file-id')])
2738
def test_invalid_new_id_same_path(self):
2739
# The bad entry comes after
2740
state = self.assertBadDelta(
2741
active=[('file', 'file-id')],
2742
basis= [('file', 'file-id')],
2743
delta=[(None, 'file', 'file-id-2')])
2744
# The bad entry comes first
2745
state = self.assertBadDelta(
2746
active=[('file', 'file-id-2')],
2747
basis=[('file', 'file-id-2')],
2748
delta=[(None, 'file', 'file-id')])
2750
def test_invalid_existing_id(self):
2751
state = self.assertBadDelta(
2752
active=[('file', 'file-id')],
2753
basis= [('file', 'file-id')],
2754
delta=[(None, 'file', 'file-id')])
2756
def test_invalid_parent_missing(self):
2757
state = self.assertBadDelta(
2760
delta=[(None, 'path/path2', 'file-id')])
2761
# Note: we force the active tree to have the directory, by knowing how
2762
# path_to_ie handles entries with missing parents
2763
state = self.assertBadDelta(
2764
active=[('path/', 'path-id')],
2766
delta=[(None, 'path/path2', 'file-id')])
2767
state = self.assertBadDelta(
2768
active=[('path/', 'path-id'),
2769
('path/path2', 'file-id')],
2771
delta=[(None, 'path/path2', 'file-id')])
2773
def test_renamed_dir_same_path(self):
2774
# We replace the parent directory, with another parent dir. But the C
2775
# file doesn't look like it has been moved.
2776
state = self.assertUpdate(# Same as basis
2777
active=[('dir/', 'A-id'),
2779
basis= [('dir/', 'A-id'),
2781
target=[('dir/', 'C-id'),
2783
state = self.assertUpdate(# Same as target
2784
active=[('dir/', 'C-id'),
2786
basis= [('dir/', 'A-id'),
2788
target=[('dir/', 'C-id'),
2790
state = self.assertUpdate(# empty active
2792
basis= [('dir/', 'A-id'),
2794
target=[('dir/', 'C-id'),
2796
state = self.assertUpdate(# different active
2797
active=[('dir/', 'D-id'),
2799
basis= [('dir/', 'A-id'),
2801
target=[('dir/', 'C-id'),
2804
def test_parent_child_swap(self):
2805
state = self.assertUpdate(# Same as basis
2806
active=[('A/', 'A-id'),
2809
basis= [('A/', 'A-id'),
2812
target=[('A/', 'B-id'),
2815
state = self.assertUpdate(# Same as target
2816
active=[('A/', 'B-id'),
2819
basis= [('A/', 'A-id'),
2822
target=[('A/', 'B-id'),
2825
state = self.assertUpdate(# empty active
2827
basis= [('A/', 'A-id'),
2830
target=[('A/', 'B-id'),
2833
state = self.assertUpdate(# different active
2834
active=[('D/', 'A-id'),
2837
basis= [('A/', 'A-id'),
2840
target=[('A/', 'B-id'),
2844
def test_change_root_id(self):
2845
state = self.assertUpdate( # same as basis
2846
active=[('', 'root-id'),
2847
('file', 'file-id')],
2848
basis= [('', 'root-id'),
2849
('file', 'file-id')],
2850
target=[('', 'target-root-id'),
2851
('file', 'file-id')])
2852
state = self.assertUpdate( # same as target
2853
active=[('', 'target-root-id'),
2854
('file', 'file-id')],
2855
basis= [('', 'root-id'),
2856
('file', 'file-id')],
2857
target=[('', 'target-root-id'),
2858
('file', 'root-id')])
2859
state = self.assertUpdate( # all different
2860
active=[('', 'active-root-id'),
2861
('file', 'file-id')],
2862
basis= [('', 'root-id'),
2863
('file', 'file-id')],
2864
target=[('', 'target-root-id'),
2865
('file', 'root-id')])
2867
def test_change_file_absent_in_active(self):
2868
state = self.assertUpdate(
2870
basis= [('file', 'file-id')],
2871
target=[('file', 'file-id')])
2873
def test_invalid_changed_file(self):
2874
state = self.assertBadDelta( # Not present in basis
2875
active=[('file', 'file-id')],
2877
delta=[('file', 'file', 'file-id')])
2878
state = self.assertBadDelta( # present at another location in basis
2879
active=[('file', 'file-id')],
2880
basis= [('other-file', 'file-id')],
2881
delta=[('file', 'file', 'file-id')])