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.tests import (
38
from bzrlib.tests.scenarios import load_tests_apply_scenarios
43
# general checks for NOT_IN_MEMORY error conditions.
44
# set_path_id on a NOT_IN_MEMORY dirstate
45
# set_path_id unicode support
46
# set_path_id setting id of a path not root
47
# set_path_id setting id when there are parents without the id in the parents
48
# set_path_id setting id when there are parents with the id in the parents
49
# set_path_id setting id when state is not in memory
50
# set_path_id setting id when state is in memory unmodified
51
# set_path_id setting id when state is in memory modified
54
load_tests = load_tests_apply_scenarios
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
60
scenarios = test_osutils.dir_reader_scenarios()
63
_dir_reader_class = None
64
_native_to_unicode = None # Not used yet
67
tests.TestCaseWithTransport.setUp(self)
69
self.overrideAttr(osutils,
70
'_selected_dir_reader', self._dir_reader_class())
72
def create_empty_dirstate(self):
73
"""Return a locked but empty dirstate"""
74
state = dirstate.DirState.initialize('dirstate')
77
def create_dirstate_with_root(self):
78
"""Return a write-locked state with a single root entry."""
79
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
80
root_entry_direntry = ('', '', 'a-root-value'), [
81
('d', '', 0, False, packed_stat),
84
dirblocks.append(('', [root_entry_direntry]))
85
dirblocks.append(('', []))
86
state = self.create_empty_dirstate()
88
state._set_data([], dirblocks)
95
def create_dirstate_with_root_and_subdir(self):
96
"""Return a locked DirState with a root and a subdir"""
97
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
98
subdir_entry = ('', 'subdir', 'subdir-id'), [
99
('d', '', 0, False, packed_stat),
101
state = self.create_dirstate_with_root()
103
dirblocks = list(state._dirblocks)
104
dirblocks[1][1].append(subdir_entry)
105
state._set_data([], dirblocks)
111
def create_complex_dirstate(self):
112
"""This dirstate contains multiple files and directories.
122
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
124
Notice that a/e is an empty directory.
126
:return: The dirstate, still write-locked.
128
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
129
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
130
root_entry = ('', '', 'a-root-value'), [
131
('d', '', 0, False, packed_stat),
133
a_entry = ('', 'a', 'a-dir'), [
134
('d', '', 0, False, packed_stat),
136
b_entry = ('', 'b', 'b-dir'), [
137
('d', '', 0, False, packed_stat),
139
c_entry = ('', 'c', 'c-file'), [
140
('f', null_sha, 10, False, packed_stat),
142
d_entry = ('', 'd', 'd-file'), [
143
('f', null_sha, 20, False, packed_stat),
145
e_entry = ('a', 'e', 'e-dir'), [
146
('d', '', 0, False, packed_stat),
148
f_entry = ('a', 'f', 'f-file'), [
149
('f', null_sha, 30, False, packed_stat),
151
g_entry = ('b', 'g', 'g-file'), [
152
('f', null_sha, 30, False, packed_stat),
154
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
155
('f', null_sha, 40, False, packed_stat),
158
dirblocks.append(('', [root_entry]))
159
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
160
dirblocks.append(('a', [e_entry, f_entry]))
161
dirblocks.append(('b', [g_entry, h_entry]))
162
state = dirstate.DirState.initialize('dirstate')
165
state._set_data([], dirblocks)
171
def check_state_with_reopen(self, expected_result, state):
172
"""Check that state has current state expected_result.
174
This will check the current state, open the file anew and check it
176
This function expects the current state to be locked for writing, and
177
will unlock it before re-opening.
178
This is required because we can't open a lock_read() while something
179
else has a lock_write().
180
write => mutually exclusive lock
183
# The state should already be write locked, since we just had to do
184
# some operation to get here.
185
self.assertTrue(state._lock_token is not None)
187
self.assertEqual(expected_result[0], state.get_parent_ids())
188
# there should be no ghosts in this tree.
189
self.assertEqual([], state.get_ghosts())
190
# there should be one fileid in this tree - the root of the tree.
191
self.assertEqual(expected_result[1], list(state._iter_entries()))
196
state = dirstate.DirState.on_file('dirstate')
199
self.assertEqual(expected_result[1], list(state._iter_entries()))
203
def create_basic_dirstate(self):
204
"""Create a dirstate with a few files and directories.
214
tree = self.make_branch_and_tree('tree')
215
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
216
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
217
self.build_tree(['tree/' + p for p in paths])
218
tree.set_root_id('TREE_ROOT')
219
tree.add([p.rstrip('/') for p in paths], file_ids)
220
tree.commit('initial', rev_id='rev-1')
221
revision_id = 'rev-1'
222
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
223
t = self.get_transport('tree')
224
a_text = t.get_bytes('a')
225
a_sha = osutils.sha_string(a_text)
227
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
228
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
229
c_text = t.get_bytes('b/c')
230
c_sha = osutils.sha_string(c_text)
232
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
233
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
234
e_text = t.get_bytes('b/d/e')
235
e_sha = osutils.sha_string(e_text)
237
b_c_text = t.get_bytes('b-c')
238
b_c_sha = osutils.sha_string(b_c_text)
239
b_c_len = len(b_c_text)
240
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
241
f_text = t.get_bytes('f')
242
f_sha = osutils.sha_string(f_text)
244
null_stat = dirstate.DirState.NULLSTAT
246
'':(('', '', 'TREE_ROOT'), [
247
('d', '', 0, False, null_stat),
248
('d', '', 0, False, revision_id),
250
'a':(('', 'a', 'a-id'), [
251
('f', '', 0, False, null_stat),
252
('f', a_sha, a_len, False, revision_id),
254
'b':(('', 'b', 'b-id'), [
255
('d', '', 0, False, null_stat),
256
('d', '', 0, False, revision_id),
258
'b/c':(('b', 'c', 'c-id'), [
259
('f', '', 0, False, null_stat),
260
('f', c_sha, c_len, False, revision_id),
262
'b/d':(('b', 'd', 'd-id'), [
263
('d', '', 0, False, null_stat),
264
('d', '', 0, False, revision_id),
266
'b/d/e':(('b/d', 'e', 'e-id'), [
267
('f', '', 0, False, null_stat),
268
('f', e_sha, e_len, False, revision_id),
270
'b-c':(('', 'b-c', 'b-c-id'), [
271
('f', '', 0, False, null_stat),
272
('f', b_c_sha, b_c_len, False, revision_id),
274
'f':(('', 'f', 'f-id'), [
275
('f', '', 0, False, null_stat),
276
('f', f_sha, f_len, False, revision_id),
279
state = dirstate.DirState.from_tree(tree, 'dirstate')
284
# Use a different object, to make sure nothing is pre-cached in memory.
285
state = dirstate.DirState.on_file('dirstate')
287
self.addCleanup(state.unlock)
288
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
289
state._dirblock_state)
290
# This is code is only really tested if we actually have to make more
291
# than one read, so set the page size to something smaller.
292
# We want it to contain about 2.2 records, so that we have a couple
293
# records that we can read per attempt
294
state._bisect_page_size = 200
295
return tree, state, expected
297
def create_duplicated_dirstate(self):
298
"""Create a dirstate with a deleted and added entries.
300
This grabs a basic_dirstate, and then removes and re adds every entry
303
tree, state, expected = self.create_basic_dirstate()
304
# Now we will just remove and add every file so we get an extra entry
305
# per entry. Unversion in reverse order so we handle subdirs
306
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
307
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
308
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
310
# Update the expected dictionary.
311
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
312
orig = expected[path]
314
# This record was deleted in the current tree
315
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
317
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
318
# And didn't exist in the basis tree
319
expected[path2] = (new_key, [orig[1][0],
320
dirstate.DirState.NULL_PARENT_DETAILS])
322
# We will replace the 'dirstate' file underneath 'state', but that is
323
# okay as lock as we unlock 'state' first.
326
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
332
# But we need to leave state in a read-lock because we already have
333
# a cleanup scheduled
335
return tree, state, expected
337
def create_renamed_dirstate(self):
338
"""Create a dirstate with a few internal renames.
340
This takes the basic dirstate, and moves the paths around.
342
tree, state, expected = self.create_basic_dirstate()
344
tree.rename_one('a', 'b/g')
346
tree.rename_one('b/d', 'h')
348
old_a = expected['a']
349
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
350
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
351
('r', 'a', 0, False, '')])
352
old_d = expected['b/d']
353
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
354
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
355
('r', 'b/d', 0, False, '')])
357
old_e = expected['b/d/e']
358
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
360
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
361
('r', 'b/d/e', 0, False, '')])
365
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
372
return tree, state, expected
375
class TestTreeToDirState(TestCaseWithDirState):
377
def test_empty_to_dirstate(self):
378
"""We should be able to create a dirstate for an empty tree."""
379
# There are no files on disk and no parents
380
tree = self.make_branch_and_tree('tree')
381
expected_result = ([], [
382
(('', '', tree.get_root_id()), # common details
383
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
385
state = dirstate.DirState.from_tree(tree, 'dirstate')
387
self.check_state_with_reopen(expected_result, state)
389
def test_1_parents_empty_to_dirstate(self):
390
# create a parent by doing a commit
391
tree = self.make_branch_and_tree('tree')
392
rev_id = tree.commit('first post').encode('utf8')
393
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
394
expected_result = ([rev_id], [
395
(('', '', tree.get_root_id()), # common details
396
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
('d', '', 0, False, rev_id), # first parent details
399
state = dirstate.DirState.from_tree(tree, 'dirstate')
400
self.check_state_with_reopen(expected_result, state)
407
def test_2_parents_empty_to_dirstate(self):
408
# create a parent by doing a commit
409
tree = self.make_branch_and_tree('tree')
410
rev_id = tree.commit('first post')
411
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
412
rev_id2 = tree2.commit('second post', allow_pointless=True)
413
tree.merge_from_branch(tree2.branch)
414
expected_result = ([rev_id, rev_id2], [
415
(('', '', tree.get_root_id()), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
417
('d', '', 0, False, rev_id), # first parent details
418
('d', '', 0, False, rev_id), # second parent details
420
state = dirstate.DirState.from_tree(tree, 'dirstate')
421
self.check_state_with_reopen(expected_result, state)
428
def test_empty_unknowns_are_ignored_to_dirstate(self):
429
"""We should be able to create a dirstate for an empty tree."""
430
# There are no files on disk and no parents
431
tree = self.make_branch_and_tree('tree')
432
self.build_tree(['tree/unknown'])
433
expected_result = ([], [
434
(('', '', tree.get_root_id()), # common details
435
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
437
state = dirstate.DirState.from_tree(tree, 'dirstate')
438
self.check_state_with_reopen(expected_result, state)
440
def get_tree_with_a_file(self):
441
tree = self.make_branch_and_tree('tree')
442
self.build_tree(['tree/a file'])
443
tree.add('a file', 'a-file-id')
446
def test_non_empty_no_parents_to_dirstate(self):
447
"""We should be able to create a dirstate for an empty tree."""
448
# There are files on disk and no parents
449
tree = self.get_tree_with_a_file()
450
expected_result = ([], [
451
(('', '', tree.get_root_id()), # common details
452
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
454
(('', 'a file', 'a-file-id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
458
state = dirstate.DirState.from_tree(tree, 'dirstate')
459
self.check_state_with_reopen(expected_result, state)
461
def test_1_parents_not_empty_to_dirstate(self):
462
# create a parent by doing a commit
463
tree = self.get_tree_with_a_file()
464
rev_id = tree.commit('first post').encode('utf8')
465
# change the current content to be different this will alter stat, sha
467
self.build_tree_contents([('tree/a file', 'new content\n')])
468
expected_result = ([rev_id], [
469
(('', '', tree.get_root_id()), # common details
470
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
471
('d', '', 0, False, rev_id), # first parent details
473
(('', 'a file', 'a-file-id'), # common
474
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
475
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
476
rev_id), # first parent
479
state = dirstate.DirState.from_tree(tree, 'dirstate')
480
self.check_state_with_reopen(expected_result, state)
482
def test_2_parents_not_empty_to_dirstate(self):
483
# create a parent by doing a commit
484
tree = self.get_tree_with_a_file()
485
rev_id = tree.commit('first post').encode('utf8')
486
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
487
# change the current content to be different this will alter stat, sha
489
self.build_tree_contents([('tree2/a file', 'merge content\n')])
490
rev_id2 = tree2.commit('second post').encode('utf8')
491
tree.merge_from_branch(tree2.branch)
492
# change the current content to be different this will alter stat, sha
493
# and length again, giving us three distinct values:
494
self.build_tree_contents([('tree/a file', 'new content\n')])
495
expected_result = ([rev_id, rev_id2], [
496
(('', '', tree.get_root_id()), # common details
497
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
498
('d', '', 0, False, rev_id), # first parent details
499
('d', '', 0, False, rev_id), # second parent details
501
(('', 'a file', 'a-file-id'), # common
502
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
503
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
504
rev_id), # first parent
505
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
506
rev_id2), # second parent
509
state = dirstate.DirState.from_tree(tree, 'dirstate')
510
self.check_state_with_reopen(expected_result, state)
512
def test_colliding_fileids(self):
513
# test insertion of parents creating several entries at the same path.
514
# we used to have a bug where they could cause the dirstate to break
515
# its ordering invariants.
516
# create some trees to test from
519
tree = self.make_branch_and_tree('tree%d' % i)
520
self.build_tree(['tree%d/name' % i,])
521
tree.add(['name'], ['file-id%d' % i])
522
revision_id = 'revid-%d' % i
523
tree.commit('message', rev_id=revision_id)
524
parents.append((revision_id,
525
tree.branch.repository.revision_tree(revision_id)))
526
# now fold these trees into a dirstate
527
state = dirstate.DirState.initialize('dirstate')
529
state.set_parent_trees(parents, [])
535
class TestDirStateOnFile(TestCaseWithDirState):
537
def create_updated_dirstate(self):
538
self.build_tree(['a-file'])
539
tree = self.make_branch_and_tree('.')
540
tree.add(['a-file'], ['a-id'])
541
tree.commit('add a-file')
542
# Save and unlock the state, re-open it in readonly mode
543
state = dirstate.DirState.from_tree(tree, 'dirstate')
546
state = dirstate.DirState.on_file('dirstate')
550
def test_construct_with_path(self):
551
tree = self.make_branch_and_tree('tree')
552
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
553
# we want to be able to get the lines of the dirstate that we will
555
lines = state.get_lines()
557
self.build_tree_contents([('dirstate', ''.join(lines))])
559
# no parents, default tree content
560
expected_result = ([], [
561
(('', '', tree.get_root_id()), # common details
562
# current tree details, but new from_tree skips statting, it
563
# uses set_state_from_inventory, and thus depends on the
565
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
568
state = dirstate.DirState.on_file('dirstate')
569
state.lock_write() # check_state_with_reopen will save() and unlock it
570
self.check_state_with_reopen(expected_result, state)
572
def test_can_save_clean_on_file(self):
573
tree = self.make_branch_and_tree('tree')
574
state = dirstate.DirState.from_tree(tree, 'dirstate')
576
# doing a save should work here as there have been no changes.
578
# TODO: stat it and check it hasn't changed; may require waiting
579
# for the state accuracy window.
583
def test_can_save_in_read_lock(self):
584
state = self.create_updated_dirstate()
586
entry = state._get_entry(0, path_utf8='a-file')
587
# The current size should be 0 (default)
588
self.assertEqual(0, entry[1][0][2])
589
# We should have a real entry.
590
self.assertNotEqual((None, None), entry)
591
# Set the cutoff-time into the future, so things look cacheable
592
state._sha_cutoff_time()
593
state._cutoff_time += 10.0
594
st = os.lstat('a-file')
595
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
596
# We updated the current sha1sum because the file is cacheable
597
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
600
# The dirblock has been updated
601
self.assertEqual(st.st_size, entry[1][0][2])
602
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
603
state._dirblock_state)
606
# Now, since we are the only one holding a lock, we should be able
607
# to save and have it written to disk
612
# Re-open the file, and ensure that the state has been updated.
613
state = dirstate.DirState.on_file('dirstate')
616
entry = state._get_entry(0, path_utf8='a-file')
617
self.assertEqual(st.st_size, entry[1][0][2])
621
def test_save_fails_quietly_if_locked(self):
622
"""If dirstate is locked, save will fail without complaining."""
623
state = self.create_updated_dirstate()
625
entry = state._get_entry(0, path_utf8='a-file')
626
# No cached sha1 yet.
627
self.assertEqual('', entry[1][0][1])
628
# Set the cutoff-time into the future, so things look cacheable
629
state._sha_cutoff_time()
630
state._cutoff_time += 10.0
631
st = os.lstat('a-file')
632
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
633
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
635
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
636
state._dirblock_state)
638
# Now, before we try to save, grab another dirstate, and take out a
640
# TODO: jam 20070315 Ideally this would be locked by another
641
# process. To make sure the file is really OS locked.
642
state2 = dirstate.DirState.on_file('dirstate')
645
# This won't actually write anything, because it couldn't grab
646
# a write lock. But it shouldn't raise an error, either.
647
# TODO: jam 20070315 We should probably distinguish between
648
# being dirty because of 'update_entry'. And dirty
649
# because of real modification. So that save() *does*
650
# raise a real error if it fails when we have real
658
# The file on disk should not be modified.
659
state = dirstate.DirState.on_file('dirstate')
662
entry = state._get_entry(0, path_utf8='a-file')
663
self.assertEqual('', entry[1][0][1])
667
def test_save_refuses_if_changes_aborted(self):
668
self.build_tree(['a-file', 'a-dir/'])
669
state = dirstate.DirState.initialize('dirstate')
671
# No stat and no sha1 sum.
672
state.add('a-file', 'a-file-id', 'file', None, '')
677
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
679
('', [(('', '', 'TREE_ROOT'),
680
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
681
('', [(('', 'a-file', 'a-file-id'),
682
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
685
state = dirstate.DirState.on_file('dirstate')
688
state._read_dirblocks_if_needed()
689
self.assertEqual(expected_blocks, state._dirblocks)
691
# Now modify the state, but mark it as inconsistent
692
state.add('a-dir', 'a-dir-id', 'directory', None, '')
693
state._changes_aborted = True
698
state = dirstate.DirState.on_file('dirstate')
701
state._read_dirblocks_if_needed()
702
self.assertEqual(expected_blocks, state._dirblocks)
707
class TestDirStateInitialize(TestCaseWithDirState):
709
def test_initialize(self):
710
expected_result = ([], [
711
(('', '', 'TREE_ROOT'), # common details
712
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
715
state = dirstate.DirState.initialize('dirstate')
717
self.assertIsInstance(state, dirstate.DirState)
718
lines = state.get_lines()
721
# On win32 you can't read from a locked file, even within the same
722
# process. So we have to unlock and release before we check the file
724
self.assertFileEqual(''.join(lines), 'dirstate')
725
state.lock_read() # check_state_with_reopen will unlock
726
self.check_state_with_reopen(expected_result, state)
729
class TestDirStateManipulations(TestCaseWithDirState):
731
def make_minimal_tree(self):
732
tree1 = self.make_branch_and_memory_tree('tree1')
734
self.addCleanup(tree1.unlock)
736
revid1 = tree1.commit('foo')
739
def test_update_minimal_updates_id_index(self):
740
state = self.create_dirstate_with_root_and_subdir()
741
self.addCleanup(state.unlock)
742
id_index = state._get_id_index()
743
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
744
state.add('file-name', 'file-id', 'file', None, '')
745
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
747
state.update_minimal(('', 'new-name', 'file-id'), 'f',
748
path_utf8='new-name')
749
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
751
self.assertEqual([('', 'new-name', 'file-id')],
752
sorted(id_index['file-id']))
755
def test_set_state_from_inventory_no_content_no_parents(self):
756
# setting the current inventory is a slow but important api to support.
757
tree1, revid1 = self.make_minimal_tree()
758
inv = tree1.root_inventory
759
root_id = inv.path2id('')
760
expected_result = [], [
761
(('', '', root_id), [
762
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
763
state = dirstate.DirState.initialize('dirstate')
765
state.set_state_from_inventory(inv)
766
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
768
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
769
state._dirblock_state)
774
# This will unlock it
775
self.check_state_with_reopen(expected_result, state)
777
def test_set_state_from_scratch_no_parents(self):
778
tree1, revid1 = self.make_minimal_tree()
779
inv = tree1.root_inventory
780
root_id = inv.path2id('')
781
expected_result = [], [
782
(('', '', root_id), [
783
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
784
state = dirstate.DirState.initialize('dirstate')
786
state.set_state_from_scratch(inv, [], [])
787
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
789
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
790
state._dirblock_state)
795
# This will unlock it
796
self.check_state_with_reopen(expected_result, state)
798
def test_set_state_from_scratch_identical_parent(self):
799
tree1, revid1 = self.make_minimal_tree()
800
inv = tree1.root_inventory
801
root_id = inv.path2id('')
802
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
803
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
804
parent_entry = ('d', '', 0, False, revid1)
805
expected_result = [revid1], [
806
(('', '', root_id), [d_entry, parent_entry])]
807
state = dirstate.DirState.initialize('dirstate')
809
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
810
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
812
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
813
state._dirblock_state)
818
# This will unlock it
819
self.check_state_with_reopen(expected_result, state)
821
def test_set_state_from_inventory_preserves_hashcache(self):
822
# https://bugs.launchpad.net/bzr/+bug/146176
823
# set_state_from_inventory should preserve the stat and hash value for
824
# workingtree files that are not changed by the inventory.
826
tree = self.make_branch_and_tree('.')
827
# depends on the default format using dirstate...
830
# make a dirstate with some valid hashcache data
831
# file on disk, but that's not needed for this test
832
foo_contents = 'contents of foo'
833
self.build_tree_contents([('foo', foo_contents)])
834
tree.add('foo', 'foo-id')
836
foo_stat = os.stat('foo')
837
foo_packed = dirstate.pack_stat(foo_stat)
838
foo_sha = osutils.sha_string(foo_contents)
839
foo_size = len(foo_contents)
841
# should not be cached yet, because the file's too fresh
843
(('', 'foo', 'foo-id',),
844
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
845
tree._dirstate._get_entry(0, 'foo-id'))
846
# poke in some hashcache information - it wouldn't normally be
847
# stored because it's too fresh
848
tree._dirstate.update_minimal(
849
('', 'foo', 'foo-id'),
850
'f', False, foo_sha, foo_packed, foo_size, 'foo')
851
# now should be cached
853
(('', 'foo', 'foo-id',),
854
[('f', foo_sha, foo_size, False, foo_packed)]),
855
tree._dirstate._get_entry(0, 'foo-id'))
857
# extract the inventory, and add something to it
858
inv = tree._get_root_inventory()
859
# should see the file we poked in...
860
self.assertTrue(inv.has_id('foo-id'))
861
self.assertTrue(inv.has_filename('foo'))
862
inv.add_path('bar', 'file', 'bar-id')
863
tree._dirstate._validate()
864
# this used to cause it to lose its hashcache
865
tree._dirstate.set_state_from_inventory(inv)
866
tree._dirstate._validate()
872
# now check that the state still has the original hashcache value
873
state = tree._dirstate
875
foo_tuple = state._get_entry(0, path_utf8='foo')
877
(('', 'foo', 'foo-id',),
878
[('f', foo_sha, len(foo_contents), False,
879
dirstate.pack_stat(foo_stat))]),
884
def test_set_state_from_inventory_mixed_paths(self):
885
tree1 = self.make_branch_and_tree('tree1')
886
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
887
'tree1/a/b/foo', 'tree1/a-b/bar'])
890
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
891
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
892
tree1.commit('rev1', rev_id='rev1')
893
root_id = tree1.get_root_id()
894
inv = tree1.root_inventory
897
expected_result1 = [('', '', root_id, 'd'),
898
('', 'a', 'a-id', 'd'),
899
('', 'a-b', 'a-b-id', 'd'),
900
('a', 'b', 'b-id', 'd'),
901
('a/b', 'foo', 'foo-id', 'f'),
902
('a-b', 'bar', 'bar-id', 'f'),
904
expected_result2 = [('', '', root_id, 'd'),
905
('', 'a', 'a-id', 'd'),
906
('', 'a-b', 'a-b-id', 'd'),
907
('a-b', 'bar', 'bar-id', 'f'),
909
state = dirstate.DirState.initialize('dirstate')
911
state.set_state_from_inventory(inv)
913
for entry in state._iter_entries():
914
values.append(entry[0] + entry[1][0][:1])
915
self.assertEqual(expected_result1, values)
917
state.set_state_from_inventory(inv)
919
for entry in state._iter_entries():
920
values.append(entry[0] + entry[1][0][:1])
921
self.assertEqual(expected_result2, values)
925
def test_set_path_id_no_parents(self):
926
"""The id of a path can be changed trivally with no parents."""
927
state = dirstate.DirState.initialize('dirstate')
929
# check precondition to be sure the state does change appropriately.
930
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
931
self.assertEqual([root_entry], list(state._iter_entries()))
932
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
933
self.assertEqual(root_entry,
934
state._get_entry(0, fileid_utf8='TREE_ROOT'))
935
self.assertEqual((None, None),
936
state._get_entry(0, fileid_utf8='second-root-id'))
937
state.set_path_id('', 'second-root-id')
938
new_root_entry = (('', '', 'second-root-id'),
939
[('d', '', 0, False, 'x'*32)])
940
expected_rows = [new_root_entry]
941
self.assertEqual(expected_rows, list(state._iter_entries()))
942
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
943
self.assertEqual(new_root_entry,
944
state._get_entry(0, fileid_utf8='second-root-id'))
945
self.assertEqual((None, None),
946
state._get_entry(0, fileid_utf8='TREE_ROOT'))
947
# should work across save too
951
state = dirstate.DirState.on_file('dirstate')
955
self.assertEqual(expected_rows, list(state._iter_entries()))
959
def test_set_path_id_with_parents(self):
960
"""Set the root file id in a dirstate with parents"""
961
mt = self.make_branch_and_tree('mt')
962
# in case the default tree format uses a different root id
963
mt.set_root_id('TREE_ROOT')
964
mt.commit('foo', rev_id='parent-revid')
965
rt = mt.branch.repository.revision_tree('parent-revid')
966
state = dirstate.DirState.initialize('dirstate')
969
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
970
root_entry = (('', '', 'TREE_ROOT'),
971
[('d', '', 0, False, 'x'*32),
972
('d', '', 0, False, 'parent-revid')])
973
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
974
self.assertEqual(root_entry,
975
state._get_entry(0, fileid_utf8='TREE_ROOT'))
976
self.assertEqual((None, None),
977
state._get_entry(0, fileid_utf8='Asecond-root-id'))
978
state.set_path_id('', 'Asecond-root-id')
980
# now see that it is what we expected
981
old_root_entry = (('', '', 'TREE_ROOT'),
982
[('a', '', 0, False, ''),
983
('d', '', 0, False, 'parent-revid')])
984
new_root_entry = (('', '', 'Asecond-root-id'),
985
[('d', '', 0, False, ''),
986
('a', '', 0, False, '')])
987
expected_rows = [new_root_entry, old_root_entry]
989
self.assertEqual(expected_rows, list(state._iter_entries()))
990
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
991
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
992
self.assertEqual((None, None),
993
state._get_entry(0, fileid_utf8='TREE_ROOT'))
994
self.assertEqual(old_root_entry,
995
state._get_entry(1, fileid_utf8='TREE_ROOT'))
996
self.assertEqual(new_root_entry,
997
state._get_entry(0, fileid_utf8='Asecond-root-id'))
998
self.assertEqual((None, None),
999
state._get_entry(1, fileid_utf8='Asecond-root-id'))
1000
# should work across save too
1004
# now flush & check we get the same
1005
state = dirstate.DirState.on_file('dirstate')
1009
self.assertEqual(expected_rows, list(state._iter_entries()))
1012
# now change within an existing file-backed state
1016
state.set_path_id('', 'tree-root-2')
1021
def test_set_parent_trees_no_content(self):
1022
# set_parent_trees is a slow but important api to support.
1023
tree1 = self.make_branch_and_memory_tree('tree1')
1027
revid1 = tree1.commit('foo')
1030
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1031
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1034
revid2 = tree2.commit('foo')
1035
root_id = tree2.get_root_id()
1038
state = dirstate.DirState.initialize('dirstate')
1040
state.set_path_id('', root_id)
1041
state.set_parent_trees(
1042
((revid1, tree1.branch.repository.revision_tree(revid1)),
1043
(revid2, tree2.branch.repository.revision_tree(revid2)),
1044
('ghost-rev', None)),
1046
# check we can reopen and use the dirstate after setting parent
1053
state = dirstate.DirState.on_file('dirstate')
1056
self.assertEqual([revid1, revid2, 'ghost-rev'],
1057
state.get_parent_ids())
1058
# iterating the entire state ensures that the state is parsable.
1059
list(state._iter_entries())
1060
# be sure that it sets not appends - change it
1061
state.set_parent_trees(
1062
((revid1, tree1.branch.repository.revision_tree(revid1)),
1063
('ghost-rev', None)),
1065
# and now put it back.
1066
state.set_parent_trees(
1067
((revid1, tree1.branch.repository.revision_tree(revid1)),
1068
(revid2, tree2.branch.repository.revision_tree(revid2)),
1069
('ghost-rev', tree2.branch.repository.revision_tree(
1070
_mod_revision.NULL_REVISION))),
1072
self.assertEqual([revid1, revid2, 'ghost-rev'],
1073
state.get_parent_ids())
1074
# the ghost should be recorded as such by set_parent_trees.
1075
self.assertEqual(['ghost-rev'], state.get_ghosts())
1077
[(('', '', root_id), [
1078
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1079
('d', '', 0, False, revid1),
1080
('d', '', 0, False, revid1)
1082
list(state._iter_entries()))
1086
def test_set_parent_trees_file_missing_from_tree(self):
1087
# Adding a parent tree may reference files not in the current state.
1088
# they should get listed just once by id, even if they are in two
1090
# set_parent_trees is a slow but important api to support.
1091
tree1 = self.make_branch_and_memory_tree('tree1')
1095
tree1.add(['a file'], ['file-id'], ['file'])
1096
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1097
revid1 = tree1.commit('foo')
1100
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1101
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1104
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1105
revid2 = tree2.commit('foo')
1106
root_id = tree2.get_root_id()
1109
# check the layout in memory
1110
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1111
(('', '', root_id), [
1112
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1113
('d', '', 0, False, revid1.encode('utf8')),
1114
('d', '', 0, False, revid1.encode('utf8'))
1116
(('', 'a file', 'file-id'), [
1117
('a', '', 0, False, ''),
1118
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1119
revid1.encode('utf8')),
1120
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1121
revid2.encode('utf8'))
1124
state = dirstate.DirState.initialize('dirstate')
1126
state.set_path_id('', root_id)
1127
state.set_parent_trees(
1128
((revid1, tree1.branch.repository.revision_tree(revid1)),
1129
(revid2, tree2.branch.repository.revision_tree(revid2)),
1135
# check_state_with_reopen will unlock
1136
self.check_state_with_reopen(expected_result, state)
1138
### add a path via _set_data - so we dont need delta work, just
1139
# raw data in, and ensure that it comes out via get_lines happily.
1141
def test_add_path_to_root_no_parents_all_data(self):
1142
# The most trivial addition of a path is when there are no parents and
1143
# its in the root and all data about the file is supplied
1144
self.build_tree(['a file'])
1145
stat = os.lstat('a file')
1146
# the 1*20 is the sha1 pretend value.
1147
state = dirstate.DirState.initialize('dirstate')
1148
expected_entries = [
1149
(('', '', 'TREE_ROOT'), [
1150
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1152
(('', 'a file', 'a-file-id'), [
1153
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1157
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1158
# having added it, it should be in the output of iter_entries.
1159
self.assertEqual(expected_entries, list(state._iter_entries()))
1160
# saving and reloading should not affect this.
1164
state = dirstate.DirState.on_file('dirstate')
1166
self.addCleanup(state.unlock)
1167
self.assertEqual(expected_entries, list(state._iter_entries()))
1169
def test_add_path_to_unversioned_directory(self):
1170
"""Adding a path to an unversioned directory should error.
1172
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1173
once dirstate is stable and if it is merged with WorkingTree3, consider
1174
removing this copy of the test.
1176
self.build_tree(['unversioned/', 'unversioned/a file'])
1177
state = dirstate.DirState.initialize('dirstate')
1178
self.addCleanup(state.unlock)
1179
self.assertRaises(errors.NotVersionedError, state.add,
1180
'unversioned/a file', 'a-file-id', 'file', None, None)
1182
def test_add_directory_to_root_no_parents_all_data(self):
1183
# The most trivial addition of a dir is when there are no parents and
1184
# its in the root and all data about the file is supplied
1185
self.build_tree(['a dir/'])
1186
stat = os.lstat('a dir')
1187
expected_entries = [
1188
(('', '', 'TREE_ROOT'), [
1189
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1191
(('', 'a dir', 'a dir id'), [
1192
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1195
state = dirstate.DirState.initialize('dirstate')
1197
state.add('a dir', 'a dir id', 'directory', stat, None)
1198
# having added it, it should be in the output of iter_entries.
1199
self.assertEqual(expected_entries, list(state._iter_entries()))
1200
# saving and reloading should not affect this.
1204
state = dirstate.DirState.on_file('dirstate')
1206
self.addCleanup(state.unlock)
1208
self.assertEqual(expected_entries, list(state._iter_entries()))
1210
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1211
# The most trivial addition of a symlink when there are no parents and
1212
# its in the root and all data about the file is supplied
1213
# bzr doesn't support fake symlinks on windows, yet.
1214
self.requireFeature(features.SymlinkFeature)
1215
os.symlink(target, link_name)
1216
stat = os.lstat(link_name)
1217
expected_entries = [
1218
(('', '', 'TREE_ROOT'), [
1219
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1221
(('', link_name.encode('UTF-8'), 'a link id'), [
1222
('l', target.encode('UTF-8'), stat[6],
1223
False, dirstate.pack_stat(stat)), # current tree
1226
state = dirstate.DirState.initialize('dirstate')
1228
state.add(link_name, 'a link id', 'symlink', stat,
1229
target.encode('UTF-8'))
1230
# having added it, it should be in the output of iter_entries.
1231
self.assertEqual(expected_entries, list(state._iter_entries()))
1232
# saving and reloading should not affect this.
1236
state = dirstate.DirState.on_file('dirstate')
1238
self.addCleanup(state.unlock)
1239
self.assertEqual(expected_entries, list(state._iter_entries()))
1241
def test_add_symlink_to_root_no_parents_all_data(self):
1242
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1244
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1245
self.requireFeature(features.UnicodeFilenameFeature)
1246
self._test_add_symlink_to_root_no_parents_all_data(
1247
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1249
def test_add_directory_and_child_no_parents_all_data(self):
1250
# after adding a directory, we should be able to add children to it.
1251
self.build_tree(['a dir/', 'a dir/a file'])
1252
dirstat = os.lstat('a dir')
1253
filestat = os.lstat('a dir/a file')
1254
expected_entries = [
1255
(('', '', 'TREE_ROOT'), [
1256
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1258
(('', 'a dir', 'a dir id'), [
1259
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1261
(('a dir', 'a file', 'a-file-id'), [
1262
('f', '1'*20, 25, False,
1263
dirstate.pack_stat(filestat)), # current tree details
1266
state = dirstate.DirState.initialize('dirstate')
1268
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1269
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1270
# added it, it should be in the output of iter_entries.
1271
self.assertEqual(expected_entries, list(state._iter_entries()))
1272
# saving and reloading should not affect this.
1276
state = dirstate.DirState.on_file('dirstate')
1278
self.addCleanup(state.unlock)
1279
self.assertEqual(expected_entries, list(state._iter_entries()))
1281
def test_add_tree_reference(self):
1282
# make a dirstate and add a tree reference
1283
state = dirstate.DirState.initialize('dirstate')
1285
('', 'subdir', 'subdir-id'),
1286
[('t', 'subtree-123123', 0, False,
1287
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1290
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1291
entry = state._get_entry(0, 'subdir-id', 'subdir')
1292
self.assertEqual(entry, expected_entry)
1297
# now check we can read it back
1299
self.addCleanup(state.unlock)
1301
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1302
self.assertEqual(entry, entry2)
1303
self.assertEqual(entry, expected_entry)
1304
# and lookup by id should work too
1305
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1306
self.assertEqual(entry, expected_entry)
1308
def test_add_forbidden_names(self):
1309
state = dirstate.DirState.initialize('dirstate')
1310
self.addCleanup(state.unlock)
1311
self.assertRaises(errors.BzrError,
1312
state.add, '.', 'ass-id', 'directory', None, None)
1313
self.assertRaises(errors.BzrError,
1314
state.add, '..', 'ass-id', 'directory', None, None)
1316
def test_set_state_with_rename_b_a_bug_395556(self):
1317
# bug 395556 uncovered a bug where the dirstate ends up with a false
1318
# relocation record - in a tree with no parents there should be no
1319
# absent or relocated records. This then leads to further corruption
1320
# when a commit occurs, as the incorrect relocation gathers an
1321
# incorrect absent in tree 1, and future changes go to pot.
1322
tree1 = self.make_branch_and_tree('tree1')
1323
self.build_tree(['tree1/b'])
1326
tree1.add(['b'], ['b-id'])
1327
root_id = tree1.get_root_id()
1328
inv = tree1.root_inventory
1329
state = dirstate.DirState.initialize('dirstate')
1331
# Set the initial state with 'b'
1332
state.set_state_from_inventory(inv)
1333
inv.rename('b-id', root_id, 'a')
1334
# Set the new state with 'a', which currently corrupts.
1335
state.set_state_from_inventory(inv)
1336
expected_result1 = [('', '', root_id, 'd'),
1337
('', 'a', 'b-id', 'f'),
1340
for entry in state._iter_entries():
1341
values.append(entry[0] + entry[1][0][:1])
1342
self.assertEqual(expected_result1, values)
1349
class TestDirStateHashUpdates(TestCaseWithDirState):
1351
def do_update_entry(self, state, path):
1352
entry = state._get_entry(0, path_utf8=path)
1353
stat = os.lstat(path)
1354
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1356
def _read_state_content(self, state):
1357
"""Read the content of the dirstate file.
1359
On Windows when one process locks a file, you can't even open() the
1360
file in another process (to read it). So we go directly to
1361
state._state_file. This should always be the exact disk representation,
1362
so it is reasonable to do so.
1363
DirState also always seeks before reading, so it doesn't matter if we
1364
bump the file pointer.
1366
state._state_file.seek(0)
1367
return state._state_file.read()
1369
def test_worth_saving_limit_avoids_writing(self):
1370
tree = self.make_branch_and_tree('.')
1371
self.build_tree(['c', 'd'])
1373
tree.add(['c', 'd'], ['c-id', 'd-id'])
1374
tree.commit('add c and d')
1375
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1376
worth_saving_limit=2)
1379
self.addCleanup(state.unlock)
1380
state._read_dirblocks_if_needed()
1381
state.adjust_time(+20) # Allow things to be cached
1382
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1383
state._dirblock_state)
1384
content = self._read_state_content(state)
1385
self.do_update_entry(state, 'c')
1386
self.assertEqual(1, len(state._known_hash_changes))
1387
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1388
state._dirblock_state)
1390
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1391
# hash values haven't been written out.
1392
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1393
state._dirblock_state)
1394
self.assertEqual(content, self._read_state_content(state))
1395
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1396
state._dirblock_state)
1397
self.do_update_entry(state, 'd')
1398
self.assertEqual(2, len(state._known_hash_changes))
1400
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1401
state._dirblock_state)
1402
self.assertEqual(0, len(state._known_hash_changes))
1405
class TestGetLines(TestCaseWithDirState):
1407
def test_get_line_with_2_rows(self):
1408
state = self.create_dirstate_with_root_and_subdir()
1410
self.assertEqual(['#bazaar dirstate flat format 3\n',
1411
'crc32: 41262208\n',
1415
'\x00\x00a-root-value\x00'
1416
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1417
'\x00subdir\x00subdir-id\x00'
1418
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1419
], state.get_lines())
1423
def test_entry_to_line(self):
1424
state = self.create_dirstate_with_root()
1427
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1428
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1429
state._entry_to_line(state._dirblocks[0][1][0]))
1433
def test_entry_to_line_with_parent(self):
1434
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1435
root_entry = ('', '', 'a-root-value'), [
1436
('d', '', 0, False, packed_stat), # current tree details
1437
# first: a pointer to the current location
1438
('a', 'dirname/basename', 0, False, ''),
1440
state = dirstate.DirState.initialize('dirstate')
1443
'\x00\x00a-root-value\x00'
1444
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1445
'a\x00dirname/basename\x000\x00n\x00',
1446
state._entry_to_line(root_entry))
1450
def test_entry_to_line_with_two_parents_at_different_paths(self):
1451
# / in the tree, at / in one parent and /dirname/basename in the other.
1452
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1453
root_entry = ('', '', 'a-root-value'), [
1454
('d', '', 0, False, packed_stat), # current tree details
1455
('d', '', 0, False, 'rev_id'), # first parent details
1456
# second: a pointer to the current location
1457
('a', 'dirname/basename', 0, False, ''),
1459
state = dirstate.DirState.initialize('dirstate')
1462
'\x00\x00a-root-value\x00'
1463
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1464
'd\x00\x000\x00n\x00rev_id\x00'
1465
'a\x00dirname/basename\x000\x00n\x00',
1466
state._entry_to_line(root_entry))
1470
def test_iter_entries(self):
1471
# we should be able to iterate the dirstate entries from end to end
1472
# this is for get_lines to be easy to read.
1473
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1475
root_entries = [(('', '', 'a-root-value'), [
1476
('d', '', 0, False, packed_stat), # current tree details
1478
dirblocks.append(('', root_entries))
1479
# add two files in the root
1480
subdir_entry = ('', 'subdir', 'subdir-id'), [
1481
('d', '', 0, False, packed_stat), # current tree details
1483
afile_entry = ('', 'afile', 'afile-id'), [
1484
('f', 'sha1value', 34, False, packed_stat), # current tree details
1486
dirblocks.append(('', [subdir_entry, afile_entry]))
1488
file_entry2 = ('subdir', '2file', '2file-id'), [
1489
('f', 'sha1value', 23, False, packed_stat), # current tree details
1491
dirblocks.append(('subdir', [file_entry2]))
1492
state = dirstate.DirState.initialize('dirstate')
1494
state._set_data([], dirblocks)
1495
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1497
self.assertEqual(expected_entries, list(state._iter_entries()))
1502
class TestGetBlockRowIndex(TestCaseWithDirState):
1504
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1505
file_present, state, dirname, basename, tree_index):
1506
self.assertEqual((block_index, row_index, dir_present, file_present),
1507
state._get_block_entry_index(dirname, basename, tree_index))
1509
block = state._dirblocks[block_index]
1510
self.assertEqual(dirname, block[0])
1511
if dir_present and file_present:
1512
row = state._dirblocks[block_index][1][row_index]
1513
self.assertEqual(dirname, row[0][0])
1514
self.assertEqual(basename, row[0][1])
1516
def test_simple_structure(self):
1517
state = self.create_dirstate_with_root_and_subdir()
1518
self.addCleanup(state.unlock)
1519
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1520
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1521
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1522
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1523
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1526
def test_complex_structure_exists(self):
1527
state = self.create_complex_dirstate()
1528
self.addCleanup(state.unlock)
1529
# Make sure we can find everything that exists
1530
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1531
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1532
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1533
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1534
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1535
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1536
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1537
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1538
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1539
'b', 'h\xc3\xa5', 0)
1541
def test_complex_structure_missing(self):
1542
state = self.create_complex_dirstate()
1543
self.addCleanup(state.unlock)
1544
# Make sure things would be inserted in the right locations
1545
# '_' comes before 'a'
1546
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1547
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1548
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1549
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1551
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1552
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1553
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1554
# This would be inserted between a/ and b/
1555
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1557
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1560
class TestGetEntry(TestCaseWithDirState):
1562
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1563
"""Check that the right entry is returned for a request to getEntry."""
1564
entry = state._get_entry(index, path_utf8=path)
1566
self.assertEqual((None, None), entry)
1569
self.assertEqual((dirname, basename, file_id), cur[:3])
1571
def test_simple_structure(self):
1572
state = self.create_dirstate_with_root_and_subdir()
1573
self.addCleanup(state.unlock)
1574
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1575
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1576
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1577
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1578
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1580
def test_complex_structure_exists(self):
1581
state = self.create_complex_dirstate()
1582
self.addCleanup(state.unlock)
1583
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1584
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1585
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1586
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1587
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1588
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1589
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1590
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1591
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1594
def test_complex_structure_missing(self):
1595
state = self.create_complex_dirstate()
1596
self.addCleanup(state.unlock)
1597
self.assertEntryEqual(None, None, None, state, '_', 0)
1598
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1599
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1600
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1602
def test_get_entry_uninitialized(self):
1603
"""Calling get_entry will load data if it needs to"""
1604
state = self.create_dirstate_with_root()
1610
state = dirstate.DirState.on_file('dirstate')
1613
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1614
state._header_state)
1615
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1616
state._dirblock_state)
1617
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1622
class TestIterChildEntries(TestCaseWithDirState):
1624
def create_dirstate_with_two_trees(self):
1625
"""This dirstate contains multiple files and directories.
1635
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1637
Notice that a/e is an empty directory.
1639
There is one parent tree, which has the same shape with the following variations:
1640
b/g in the parent is gone.
1641
b/h in the parent has a different id
1642
b/i is new in the parent
1643
c is renamed to b/j in the parent
1645
:return: The dirstate, still write-locked.
1647
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1648
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1649
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1650
root_entry = ('', '', 'a-root-value'), [
1651
('d', '', 0, False, packed_stat),
1652
('d', '', 0, False, 'parent-revid'),
1654
a_entry = ('', 'a', 'a-dir'), [
1655
('d', '', 0, False, packed_stat),
1656
('d', '', 0, False, 'parent-revid'),
1658
b_entry = ('', 'b', 'b-dir'), [
1659
('d', '', 0, False, packed_stat),
1660
('d', '', 0, False, 'parent-revid'),
1662
c_entry = ('', 'c', 'c-file'), [
1663
('f', null_sha, 10, False, packed_stat),
1664
('r', 'b/j', 0, False, ''),
1666
d_entry = ('', 'd', 'd-file'), [
1667
('f', null_sha, 20, False, packed_stat),
1668
('f', 'd', 20, False, 'parent-revid'),
1670
e_entry = ('a', 'e', 'e-dir'), [
1671
('d', '', 0, False, packed_stat),
1672
('d', '', 0, False, 'parent-revid'),
1674
f_entry = ('a', 'f', 'f-file'), [
1675
('f', null_sha, 30, False, packed_stat),
1676
('f', 'f', 20, False, 'parent-revid'),
1678
g_entry = ('b', 'g', 'g-file'), [
1679
('f', null_sha, 30, False, packed_stat),
1680
NULL_PARENT_DETAILS,
1682
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1683
('f', null_sha, 40, False, packed_stat),
1684
NULL_PARENT_DETAILS,
1686
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1687
NULL_PARENT_DETAILS,
1688
('f', 'h', 20, False, 'parent-revid'),
1690
i_entry = ('b', 'i', 'i-file'), [
1691
NULL_PARENT_DETAILS,
1692
('f', 'h', 20, False, 'parent-revid'),
1694
j_entry = ('b', 'j', 'c-file'), [
1695
('r', 'c', 0, False, ''),
1696
('f', 'j', 20, False, 'parent-revid'),
1699
dirblocks.append(('', [root_entry]))
1700
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1701
dirblocks.append(('a', [e_entry, f_entry]))
1702
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1703
state = dirstate.DirState.initialize('dirstate')
1706
state._set_data(['parent'], dirblocks)
1710
return state, dirblocks
1712
def test_iter_children_b(self):
1713
state, dirblocks = self.create_dirstate_with_two_trees()
1714
self.addCleanup(state.unlock)
1715
expected_result = []
1716
expected_result.append(dirblocks[3][1][2]) # h2
1717
expected_result.append(dirblocks[3][1][3]) # i
1718
expected_result.append(dirblocks[3][1][4]) # j
1719
self.assertEqual(expected_result,
1720
list(state._iter_child_entries(1, 'b')))
1722
def test_iter_child_root(self):
1723
state, dirblocks = self.create_dirstate_with_two_trees()
1724
self.addCleanup(state.unlock)
1725
expected_result = []
1726
expected_result.append(dirblocks[1][1][0]) # a
1727
expected_result.append(dirblocks[1][1][1]) # b
1728
expected_result.append(dirblocks[1][1][3]) # d
1729
expected_result.append(dirblocks[2][1][0]) # e
1730
expected_result.append(dirblocks[2][1][1]) # f
1731
expected_result.append(dirblocks[3][1][2]) # h2
1732
expected_result.append(dirblocks[3][1][3]) # i
1733
expected_result.append(dirblocks[3][1][4]) # j
1734
self.assertEqual(expected_result,
1735
list(state._iter_child_entries(1, '')))
1738
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1739
"""Test that DirState adds entries in the right order."""
1741
def test_add_sorting(self):
1742
"""Add entries in lexicographical order, we get path sorted order.
1744
This tests it to a depth of 4, to make sure we don't just get it right
1745
at a single depth. 'a/a' should come before 'a-a', even though it
1746
doesn't lexicographically.
1748
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1749
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1752
state = dirstate.DirState.initialize('dirstate')
1753
self.addCleanup(state.unlock)
1755
fake_stat = os.stat('dirstate')
1757
d_id = d.replace('/', '_')+'-id'
1758
file_path = d + '/f'
1759
file_id = file_path.replace('/', '_')+'-id'
1760
state.add(d, d_id, 'directory', fake_stat, null_sha)
1761
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1763
expected = ['', '', 'a',
1764
'a/a', 'a/a/a', 'a/a/a/a',
1765
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1767
split = lambda p:p.split('/')
1768
self.assertEqual(sorted(expected, key=split), expected)
1769
dirblock_names = [d[0] for d in state._dirblocks]
1770
self.assertEqual(expected, dirblock_names)
1772
def test_set_parent_trees_correct_order(self):
1773
"""After calling set_parent_trees() we should maintain the order."""
1774
dirs = ['a', 'a-a', 'a/a']
1776
state = dirstate.DirState.initialize('dirstate')
1777
self.addCleanup(state.unlock)
1779
fake_stat = os.stat('dirstate')
1781
d_id = d.replace('/', '_')+'-id'
1782
file_path = d + '/f'
1783
file_id = file_path.replace('/', '_')+'-id'
1784
state.add(d, d_id, 'directory', fake_stat, null_sha)
1785
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1787
expected = ['', '', 'a', 'a/a', 'a-a']
1788
dirblock_names = [d[0] for d in state._dirblocks]
1789
self.assertEqual(expected, dirblock_names)
1791
# *really* cheesy way to just get an empty tree
1792
repo = self.make_repository('repo')
1793
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1794
state.set_parent_trees([('null:', empty_tree)], [])
1796
dirblock_names = [d[0] for d in state._dirblocks]
1797
self.assertEqual(expected, dirblock_names)
1800
class InstrumentedDirState(dirstate.DirState):
1801
"""An DirState with instrumented sha1 functionality."""
1803
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1804
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1805
worth_saving_limit=worth_saving_limit)
1806
self._time_offset = 0
1808
# member is dynamically set in DirState.__init__ to turn on trace
1809
self._sha1_provider = sha1_provider
1810
self._sha1_file = self._sha1_file_and_log
1812
def _sha_cutoff_time(self):
1813
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1814
self._cutoff_time = timestamp + self._time_offset
1816
def _sha1_file_and_log(self, abspath):
1817
self._log.append(('sha1', abspath))
1818
return self._sha1_provider.sha1(abspath)
1820
def _read_link(self, abspath, old_link):
1821
self._log.append(('read_link', abspath, old_link))
1822
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1824
def _lstat(self, abspath, entry):
1825
self._log.append(('lstat', abspath))
1826
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1828
def _is_executable(self, mode, old_executable):
1829
self._log.append(('is_exec', mode, old_executable))
1830
return super(InstrumentedDirState, self)._is_executable(mode,
1833
def adjust_time(self, secs):
1834
"""Move the clock forward or back.
1836
:param secs: The amount to adjust the clock by. Positive values make it
1837
seem as if we are in the future, negative values make it seem like we
1840
self._time_offset += secs
1841
self._cutoff_time = None
1844
class _FakeStat(object):
1845
"""A class with the same attributes as a real stat result."""
1847
def __init__(self, size, mtime, ctime, dev, ino, mode):
1849
self.st_mtime = mtime
1850
self.st_ctime = ctime
1857
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1858
st.st_ino, st.st_mode)
1861
class TestPackStat(tests.TestCaseWithTransport):
1863
def assertPackStat(self, expected, stat_value):
1864
"""Check the packed and serialized form of a stat value."""
1865
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1867
def test_pack_stat_int(self):
1868
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1869
# Make sure that all parameters have an impact on the packed stat.
1870
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1873
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1874
st.st_mtime = 1172758620
1876
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1877
st.st_ctime = 1172758630
1879
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1882
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1883
st.st_ino = 6499540L
1885
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1886
st.st_mode = 0100744
1888
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1890
def test_pack_stat_float(self):
1891
"""On some platforms mtime and ctime are floats.
1893
Make sure we don't get warnings or errors, and that we ignore changes <
1896
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1897
777L, 6499538L, 0100644)
1898
# These should all be the same as the integer counterparts
1899
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1900
st.st_mtime = 1172758620.0
1902
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1903
st.st_ctime = 1172758630.0
1905
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1906
# fractional seconds are discarded, so no change from above
1907
st.st_mtime = 1172758620.453
1908
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1909
st.st_ctime = 1172758630.228
1910
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1913
class TestBisect(TestCaseWithDirState):
1914
"""Test the ability to bisect into the disk format."""
1916
def assertBisect(self, expected_map, map_keys, state, paths):
1917
"""Assert that bisecting for paths returns the right result.
1919
:param expected_map: A map from key => entry value
1920
:param map_keys: The keys to expect for each path
1921
:param state: The DirState object.
1922
:param paths: A list of paths, these will automatically be split into
1923
(dir, name) tuples, and sorted according to how _bisect
1926
result = state._bisect(paths)
1927
# For now, results are just returned in whatever order we read them.
1928
# We could sort by (dir, name, file_id) or something like that, but in
1929
# the end it would still be fairly arbitrary, and we don't want the
1930
# extra overhead if we can avoid it. So sort everything to make sure
1932
self.assertEqual(len(map_keys), len(paths))
1934
for path, keys in zip(paths, map_keys):
1936
# This should not be present in the output
1938
expected[path] = sorted(expected_map[k] for k in keys)
1940
# The returned values are just arranged randomly based on when they
1941
# were read, for testing, make sure it is properly sorted.
1945
self.assertEqual(expected, result)
1947
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1948
"""Assert that bisecting for dirbblocks returns the right result.
1950
:param expected_map: A map from key => expected values
1951
:param map_keys: A nested list of paths we expect to be returned.
1952
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1953
:param state: The DirState object.
1954
:param paths: A list of directories
1956
result = state._bisect_dirblocks(paths)
1957
self.assertEqual(len(map_keys), len(paths))
1959
for path, keys in zip(paths, map_keys):
1961
# This should not be present in the output
1963
expected[path] = sorted(expected_map[k] for k in keys)
1967
self.assertEqual(expected, result)
1969
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1970
"""Assert the return value of a recursive bisection.
1972
:param expected_map: A map from key => entry value
1973
:param map_keys: A list of paths we expect to be returned.
1974
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1975
:param state: The DirState object.
1976
:param paths: A list of files and directories. It will be broken up
1977
into (dir, name) pairs and sorted before calling _bisect_recursive.
1980
for key in map_keys:
1981
entry = expected_map[key]
1982
dir_name_id, trees_info = entry
1983
expected[dir_name_id] = trees_info
1985
result = state._bisect_recursive(paths)
1987
self.assertEqual(expected, result)
1989
def test_bisect_each(self):
1990
"""Find a single record using bisect."""
1991
tree, state, expected = self.create_basic_dirstate()
1993
# Bisect should return the rows for the specified files.
1994
self.assertBisect(expected, [['']], state, [''])
1995
self.assertBisect(expected, [['a']], state, ['a'])
1996
self.assertBisect(expected, [['b']], state, ['b'])
1997
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1998
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1999
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2000
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2001
self.assertBisect(expected, [['f']], state, ['f'])
2003
def test_bisect_multi(self):
2004
"""Bisect can be used to find multiple records at the same time."""
2005
tree, state, expected = self.create_basic_dirstate()
2006
# Bisect should be capable of finding multiple entries at the same time
2007
self.assertBisect(expected, [['a'], ['b'], ['f']],
2008
state, ['a', 'b', 'f'])
2009
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2010
state, ['f', 'b/d', 'b/d/e'])
2011
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2012
state, ['b', 'b-c', 'b/c'])
2014
def test_bisect_one_page(self):
2015
"""Test bisect when there is only 1 page to read"""
2016
tree, state, expected = self.create_basic_dirstate()
2017
state._bisect_page_size = 5000
2018
self.assertBisect(expected,[['']], state, [''])
2019
self.assertBisect(expected,[['a']], state, ['a'])
2020
self.assertBisect(expected,[['b']], state, ['b'])
2021
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2022
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2023
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2024
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2025
self.assertBisect(expected,[['f']], state, ['f'])
2026
self.assertBisect(expected,[['a'], ['b'], ['f']],
2027
state, ['a', 'b', 'f'])
2028
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2029
state, ['b/d', 'b/d/e', 'f'])
2030
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2031
state, ['b', 'b/c', 'b-c'])
2033
def test_bisect_duplicate_paths(self):
2034
"""When bisecting for a path, handle multiple entries."""
2035
tree, state, expected = self.create_duplicated_dirstate()
2037
# Now make sure that both records are properly returned.
2038
self.assertBisect(expected, [['']], state, [''])
2039
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2040
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2041
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2042
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2043
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2045
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2046
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2048
def test_bisect_page_size_too_small(self):
2049
"""If the page size is too small, we will auto increase it."""
2050
tree, state, expected = self.create_basic_dirstate()
2051
state._bisect_page_size = 50
2052
self.assertBisect(expected, [None], state, ['b/e'])
2053
self.assertBisect(expected, [['a']], state, ['a'])
2054
self.assertBisect(expected, [['b']], state, ['b'])
2055
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2056
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2057
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2058
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2059
self.assertBisect(expected, [['f']], state, ['f'])
2061
def test_bisect_missing(self):
2062
"""Test that bisect return None if it cannot find a path."""
2063
tree, state, expected = self.create_basic_dirstate()
2064
self.assertBisect(expected, [None], state, ['foo'])
2065
self.assertBisect(expected, [None], state, ['b/foo'])
2066
self.assertBisect(expected, [None], state, ['bar/foo'])
2067
self.assertBisect(expected, [None], state, ['b-c/foo'])
2069
self.assertBisect(expected, [['a'], None, ['b/d']],
2070
state, ['a', 'foo', 'b/d'])
2072
def test_bisect_rename(self):
2073
"""Check that we find a renamed row."""
2074
tree, state, expected = self.create_renamed_dirstate()
2076
# Search for the pre and post renamed entries
2077
self.assertBisect(expected, [['a']], state, ['a'])
2078
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2079
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2080
self.assertBisect(expected, [['h']], state, ['h'])
2082
# What about b/d/e? shouldn't that also get 2 directory entries?
2083
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2084
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2086
def test_bisect_dirblocks(self):
2087
tree, state, expected = self.create_duplicated_dirstate()
2088
self.assertBisectDirBlocks(expected,
2089
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2091
self.assertBisectDirBlocks(expected,
2092
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2093
self.assertBisectDirBlocks(expected,
2094
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2095
self.assertBisectDirBlocks(expected,
2096
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2097
['b/c', 'b/c2', 'b/d', 'b/d2'],
2098
['b/d/e', 'b/d/e2'],
2099
], state, ['', 'b', 'b/d'])
2101
def test_bisect_dirblocks_missing(self):
2102
tree, state, expected = self.create_basic_dirstate()
2103
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2104
state, ['b/d', 'b/e'])
2105
# Files don't show up in this search
2106
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2107
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2108
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2109
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2110
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2112
def test_bisect_recursive_each(self):
2113
tree, state, expected = self.create_basic_dirstate()
2114
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2115
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2116
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2117
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2118
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2120
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2122
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2126
def test_bisect_recursive_multiple(self):
2127
tree, state, expected = self.create_basic_dirstate()
2128
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2129
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2130
state, ['b/d', 'b/d/e'])
2132
def test_bisect_recursive_missing(self):
2133
tree, state, expected = self.create_basic_dirstate()
2134
self.assertBisectRecursive(expected, [], state, ['d'])
2135
self.assertBisectRecursive(expected, [], state, ['b/e'])
2136
self.assertBisectRecursive(expected, [], state, ['g'])
2137
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2139
def test_bisect_recursive_renamed(self):
2140
tree, state, expected = self.create_renamed_dirstate()
2142
# Looking for either renamed item should find the other
2143
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2144
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2145
# Looking in the containing directory should find the rename target,
2146
# and anything in a subdir of the renamed target.
2147
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2148
'b/d/e', 'b/g', 'h', 'h/e'],
2152
class TestDirstateValidation(TestCaseWithDirState):
2154
def test_validate_correct_dirstate(self):
2155
state = self.create_complex_dirstate()
2158
# and make sure we can also validate with a read lock
2165
def test_dirblock_not_sorted(self):
2166
tree, state, expected = self.create_renamed_dirstate()
2167
state._read_dirblocks_if_needed()
2168
last_dirblock = state._dirblocks[-1]
2169
# we're appending to the dirblock, but this name comes before some of
2170
# the existing names; that's wrong
2171
last_dirblock[1].append(
2172
(('h', 'aaaa', 'a-id'),
2173
[('a', '', 0, False, ''),
2174
('a', '', 0, False, '')]))
2175
e = self.assertRaises(AssertionError,
2177
self.assertContainsRe(str(e), 'not sorted')
2179
def test_dirblock_name_mismatch(self):
2180
tree, state, expected = self.create_renamed_dirstate()
2181
state._read_dirblocks_if_needed()
2182
last_dirblock = state._dirblocks[-1]
2183
# add an entry with the wrong directory name
2184
last_dirblock[1].append(
2186
[('a', '', 0, False, ''),
2187
('a', '', 0, False, '')]))
2188
e = self.assertRaises(AssertionError,
2190
self.assertContainsRe(str(e),
2191
"doesn't match directory name")
2193
def test_dirblock_missing_rename(self):
2194
tree, state, expected = self.create_renamed_dirstate()
2195
state._read_dirblocks_if_needed()
2196
last_dirblock = state._dirblocks[-1]
2197
# make another entry for a-id, without a correct 'r' pointer to
2198
# the real occurrence in the working tree
2199
last_dirblock[1].append(
2200
(('h', 'z', 'a-id'),
2201
[('a', '', 0, False, ''),
2202
('a', '', 0, False, '')]))
2203
e = self.assertRaises(AssertionError,
2205
self.assertContainsRe(str(e),
2206
'file a-id is absent in row')
2209
class TestDirstateTreeReference(TestCaseWithDirState):
2211
def test_reference_revision_is_none(self):
2212
tree = self.make_branch_and_tree('tree', format='development-subtree')
2213
subtree = self.make_branch_and_tree('tree/subtree',
2214
format='development-subtree')
2215
subtree.set_root_id('subtree')
2216
tree.add_reference(subtree)
2218
state = dirstate.DirState.from_tree(tree, 'dirstate')
2219
key = ('', 'subtree', 'subtree')
2220
expected = ('', [(key,
2221
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2224
self.assertEqual(expected, state._find_block(key))
2229
class TestDiscardMergeParents(TestCaseWithDirState):
2231
def test_discard_no_parents(self):
2232
# This should be a no-op
2233
state = self.create_empty_dirstate()
2234
self.addCleanup(state.unlock)
2235
state._discard_merge_parents()
2238
def test_discard_one_parent(self):
2240
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2241
root_entry_direntry = ('', '', 'a-root-value'), [
2242
('d', '', 0, False, packed_stat),
2243
('d', '', 0, False, packed_stat),
2246
dirblocks.append(('', [root_entry_direntry]))
2247
dirblocks.append(('', []))
2249
state = self.create_empty_dirstate()
2250
self.addCleanup(state.unlock)
2251
state._set_data(['parent-id'], dirblocks[:])
2254
state._discard_merge_parents()
2256
self.assertEqual(dirblocks, state._dirblocks)
2258
def test_discard_simple(self):
2260
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2261
root_entry_direntry = ('', '', 'a-root-value'), [
2262
('d', '', 0, False, packed_stat),
2263
('d', '', 0, False, packed_stat),
2264
('d', '', 0, False, packed_stat),
2266
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2267
('d', '', 0, False, packed_stat),
2268
('d', '', 0, False, packed_stat),
2271
dirblocks.append(('', [root_entry_direntry]))
2272
dirblocks.append(('', []))
2274
state = self.create_empty_dirstate()
2275
self.addCleanup(state.unlock)
2276
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2279
# This should strip of the extra column
2280
state._discard_merge_parents()
2282
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2283
self.assertEqual(expected_dirblocks, state._dirblocks)
2285
def test_discard_absent(self):
2286
"""If entries are only in a merge, discard should remove the entries"""
2287
null_stat = dirstate.DirState.NULLSTAT
2288
present_dir = ('d', '', 0, False, null_stat)
2289
present_file = ('f', '', 0, False, null_stat)
2290
absent = dirstate.DirState.NULL_PARENT_DETAILS
2291
root_key = ('', '', 'a-root-value')
2292
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2293
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2294
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2295
('', [(file_in_merged_key,
2296
[absent, absent, present_file]),
2298
[present_file, present_file, present_file]),
2302
state = self.create_empty_dirstate()
2303
self.addCleanup(state.unlock)
2304
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2307
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2308
('', [(file_in_root_key,
2309
[present_file, present_file]),
2312
state._discard_merge_parents()
2314
self.assertEqual(exp_dirblocks, state._dirblocks)
2316
def test_discard_renamed(self):
2317
null_stat = dirstate.DirState.NULLSTAT
2318
present_dir = ('d', '', 0, False, null_stat)
2319
present_file = ('f', '', 0, False, null_stat)
2320
absent = dirstate.DirState.NULL_PARENT_DETAILS
2321
root_key = ('', '', 'a-root-value')
2322
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2323
# Renamed relative to parent
2324
file_rename_s_key = ('', 'file-s', 'b-file-id')
2325
file_rename_t_key = ('', 'file-t', 'b-file-id')
2326
# And one that is renamed between the parents, but absent in this
2327
key_in_1 = ('', 'file-in-1', 'c-file-id')
2328
key_in_2 = ('', 'file-in-2', 'c-file-id')
2331
('', [(root_key, [present_dir, present_dir, present_dir])]),
2333
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2335
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2337
[present_file, present_file, present_file]),
2339
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2341
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2345
('', [(root_key, [present_dir, present_dir])]),
2346
('', [(key_in_1, [absent, present_file]),
2347
(file_in_root_key, [present_file, present_file]),
2348
(file_rename_t_key, [present_file, absent]),
2351
state = self.create_empty_dirstate()
2352
self.addCleanup(state.unlock)
2353
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2356
state._discard_merge_parents()
2358
self.assertEqual(exp_dirblocks, state._dirblocks)
2360
def test_discard_all_subdir(self):
2361
null_stat = dirstate.DirState.NULLSTAT
2362
present_dir = ('d', '', 0, False, null_stat)
2363
present_file = ('f', '', 0, False, null_stat)
2364
absent = dirstate.DirState.NULL_PARENT_DETAILS
2365
root_key = ('', '', 'a-root-value')
2366
subdir_key = ('', 'sub', 'dir-id')
2367
child1_key = ('sub', 'child1', 'child1-id')
2368
child2_key = ('sub', 'child2', 'child2-id')
2369
child3_key = ('sub', 'child3', 'child3-id')
2372
('', [(root_key, [present_dir, present_dir, present_dir])]),
2373
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2374
('sub', [(child1_key, [absent, absent, present_file]),
2375
(child2_key, [absent, absent, present_file]),
2376
(child3_key, [absent, absent, present_file]),
2380
('', [(root_key, [present_dir, present_dir])]),
2381
('', [(subdir_key, [present_dir, present_dir])]),
2384
state = self.create_empty_dirstate()
2385
self.addCleanup(state.unlock)
2386
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2389
state._discard_merge_parents()
2391
self.assertEqual(exp_dirblocks, state._dirblocks)
2394
class Test_InvEntryToDetails(tests.TestCase):
2396
def assertDetails(self, expected, inv_entry):
2397
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2398
self.assertEqual(expected, details)
2399
# details should always allow join() and always be a plain str when
2401
(minikind, fingerprint, size, executable, tree_data) = details
2402
self.assertIsInstance(minikind, str)
2403
self.assertIsInstance(fingerprint, str)
2404
self.assertIsInstance(tree_data, str)
2406
def test_unicode_symlink(self):
2407
inv_entry = inventory.InventoryLink('link-file-id',
2408
u'nam\N{Euro Sign}e',
2410
inv_entry.revision = 'link-revision-id'
2411
target = u'link-targ\N{Euro Sign}t'
2412
inv_entry.symlink_target = target
2413
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2414
'link-revision-id'), inv_entry)
2417
class TestSHA1Provider(tests.TestCaseInTempDir):
2419
def test_sha1provider_is_an_interface(self):
2420
p = dirstate.SHA1Provider()
2421
self.assertRaises(NotImplementedError, p.sha1, "foo")
2422
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2424
def test_defaultsha1provider_sha1(self):
2425
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2426
self.build_tree_contents([('foo', text)])
2427
expected_sha = osutils.sha_string(text)
2428
p = dirstate.DefaultSHA1Provider()
2429
self.assertEqual(expected_sha, p.sha1('foo'))
2431
def test_defaultsha1provider_stat_and_sha1(self):
2432
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2433
self.build_tree_contents([('foo', text)])
2434
expected_sha = osutils.sha_string(text)
2435
p = dirstate.DefaultSHA1Provider()
2436
statvalue, sha1 = p.stat_and_sha1('foo')
2437
self.assertTrue(len(statvalue) >= 10)
2438
self.assertEqual(len(text), statvalue.st_size)
2439
self.assertEqual(expected_sha, sha1)
2442
class _Repo(object):
2443
"""A minimal api to get InventoryRevisionTree to work."""
2446
default_format = controldir.format_registry.make_bzrdir('default')
2447
self._format = default_format.repository_format
2449
def lock_read(self):
2456
class TestUpdateBasisByDelta(tests.TestCase):
2458
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2459
if path.endswith('/'):
2464
dirname, basename = osutils.split(path)
2466
dir_id = dir_ids[dirname]
2468
dir_id = osutils.basename(dirname) + '-id'
2470
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2471
dir_ids[path] = file_id
2473
ie = inventory.InventoryFile(file_id, basename, dir_id)
2476
ie.revision = rev_id
2479
def create_tree_from_shape(self, rev_id, shape):
2480
dir_ids = {'': 'root-id'}
2481
inv = inventory.Inventory('root-id', rev_id)
2482
for path, file_id in shape:
2484
# Replace the root entry
2485
del inv._byid[inv.root.file_id]
2486
inv.root.file_id = file_id
2487
inv._byid[file_id] = inv.root
2488
dir_ids[''] = file_id
2490
inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
2491
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2493
def create_empty_dirstate(self):
2494
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2495
self.addCleanup(os.remove, path)
2497
state = dirstate.DirState.initialize(path)
2498
self.addCleanup(state.unlock)
2501
def create_inv_delta(self, delta, rev_id):
2502
"""Translate a 'delta shape' into an actual InventoryDelta"""
2503
dir_ids = {'': 'root-id'}
2505
for old_path, new_path, file_id in delta:
2506
if old_path is not None and old_path.endswith('/'):
2507
# Don't have to actually do anything for this, because only
2508
# new_path creates InventoryEntries
2509
old_path = old_path[:-1]
2510
if new_path is None: # Delete
2511
inv_delta.append((old_path, None, file_id, None))
2513
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2514
inv_delta.append((old_path, new_path, file_id, ie))
2517
def assertUpdate(self, active, basis, target):
2518
"""Assert that update_basis_by_delta works how we want.
2520
Set up a DirState object with active_shape for tree 0, basis_shape for
2521
tree 1. Then apply the delta from basis_shape to target_shape,
2522
and assert that the DirState is still valid, and that its stored
2523
content matches the target_shape.
2525
active_tree = self.create_tree_from_shape('active', active)
2526
basis_tree = self.create_tree_from_shape('basis', basis)
2527
target_tree = self.create_tree_from_shape('target', target)
2528
state = self.create_empty_dirstate()
2529
state.set_state_from_scratch(active_tree.root_inventory,
2530
[('basis', basis_tree)], [])
2531
delta = target_tree.root_inventory._make_delta(
2532
basis_tree.root_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.root_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.root_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')])