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
super(TestCaseWithDirState, self).setUp()
68
self.overrideAttr(osutils,
69
'_selected_dir_reader', self._dir_reader_class())
71
def create_empty_dirstate(self):
72
"""Return a locked but empty dirstate"""
73
state = dirstate.DirState.initialize('dirstate')
76
def create_dirstate_with_root(self):
77
"""Return a write-locked state with a single root entry."""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
root_entry_direntry = ('', '', 'a-root-value'), [
80
('d', '', 0, False, packed_stat),
83
dirblocks.append(('', [root_entry_direntry]))
84
dirblocks.append(('', []))
85
state = self.create_empty_dirstate()
87
state._set_data([], dirblocks)
94
def create_dirstate_with_root_and_subdir(self):
95
"""Return a locked DirState with a root and a subdir"""
96
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
97
subdir_entry = ('', 'subdir', 'subdir-id'), [
98
('d', '', 0, False, packed_stat),
100
state = self.create_dirstate_with_root()
102
dirblocks = list(state._dirblocks)
103
dirblocks[1][1].append(subdir_entry)
104
state._set_data([], dirblocks)
110
def create_complex_dirstate(self):
111
"""This dirstate contains multiple files and directories.
121
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
123
Notice that a/e is an empty directory.
125
:return: The dirstate, still write-locked.
127
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
128
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
129
root_entry = ('', '', 'a-root-value'), [
130
('d', '', 0, False, packed_stat),
132
a_entry = ('', 'a', 'a-dir'), [
133
('d', '', 0, False, packed_stat),
135
b_entry = ('', 'b', 'b-dir'), [
136
('d', '', 0, False, packed_stat),
138
c_entry = ('', 'c', 'c-file'), [
139
('f', null_sha, 10, False, packed_stat),
141
d_entry = ('', 'd', 'd-file'), [
142
('f', null_sha, 20, False, packed_stat),
144
e_entry = ('a', 'e', 'e-dir'), [
145
('d', '', 0, False, packed_stat),
147
f_entry = ('a', 'f', 'f-file'), [
148
('f', null_sha, 30, False, packed_stat),
150
g_entry = ('b', 'g', 'g-file'), [
151
('f', null_sha, 30, False, packed_stat),
153
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
154
('f', null_sha, 40, False, packed_stat),
157
dirblocks.append(('', [root_entry]))
158
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
159
dirblocks.append(('a', [e_entry, f_entry]))
160
dirblocks.append(('b', [g_entry, h_entry]))
161
state = dirstate.DirState.initialize('dirstate')
164
state._set_data([], dirblocks)
170
def check_state_with_reopen(self, expected_result, state):
171
"""Check that state has current state expected_result.
173
This will check the current state, open the file anew and check it
175
This function expects the current state to be locked for writing, and
176
will unlock it before re-opening.
177
This is required because we can't open a lock_read() while something
178
else has a lock_write().
179
write => mutually exclusive lock
182
# The state should already be write locked, since we just had to do
183
# some operation to get here.
184
self.assertTrue(state._lock_token is not None)
186
self.assertEqual(expected_result[0], state.get_parent_ids())
187
# there should be no ghosts in this tree.
188
self.assertEqual([], state.get_ghosts())
189
# there should be one fileid in this tree - the root of the tree.
190
self.assertEqual(expected_result[1], list(state._iter_entries()))
195
state = dirstate.DirState.on_file('dirstate')
198
self.assertEqual(expected_result[1], list(state._iter_entries()))
202
def create_basic_dirstate(self):
203
"""Create a dirstate with a few files and directories.
213
tree = self.make_branch_and_tree('tree')
214
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
215
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
216
self.build_tree(['tree/' + p for p in paths])
217
tree.set_root_id('TREE_ROOT')
218
tree.add([p.rstrip('/') for p in paths], file_ids)
219
tree.commit('initial', rev_id='rev-1')
220
revision_id = 'rev-1'
221
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
222
t = self.get_transport('tree')
223
a_text = t.get_bytes('a')
224
a_sha = osutils.sha_string(a_text)
226
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
227
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
228
c_text = t.get_bytes('b/c')
229
c_sha = osutils.sha_string(c_text)
231
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
232
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
233
e_text = t.get_bytes('b/d/e')
234
e_sha = osutils.sha_string(e_text)
236
b_c_text = t.get_bytes('b-c')
237
b_c_sha = osutils.sha_string(b_c_text)
238
b_c_len = len(b_c_text)
239
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
240
f_text = t.get_bytes('f')
241
f_sha = osutils.sha_string(f_text)
243
null_stat = dirstate.DirState.NULLSTAT
245
'':(('', '', 'TREE_ROOT'), [
246
('d', '', 0, False, null_stat),
247
('d', '', 0, False, revision_id),
249
'a':(('', 'a', 'a-id'), [
250
('f', '', 0, False, null_stat),
251
('f', a_sha, a_len, False, revision_id),
253
'b':(('', 'b', 'b-id'), [
254
('d', '', 0, False, null_stat),
255
('d', '', 0, False, revision_id),
257
'b/c':(('b', 'c', 'c-id'), [
258
('f', '', 0, False, null_stat),
259
('f', c_sha, c_len, False, revision_id),
261
'b/d':(('b', 'd', 'd-id'), [
262
('d', '', 0, False, null_stat),
263
('d', '', 0, False, revision_id),
265
'b/d/e':(('b/d', 'e', 'e-id'), [
266
('f', '', 0, False, null_stat),
267
('f', e_sha, e_len, False, revision_id),
269
'b-c':(('', 'b-c', 'b-c-id'), [
270
('f', '', 0, False, null_stat),
271
('f', b_c_sha, b_c_len, False, revision_id),
273
'f':(('', 'f', 'f-id'), [
274
('f', '', 0, False, null_stat),
275
('f', f_sha, f_len, False, revision_id),
278
state = dirstate.DirState.from_tree(tree, 'dirstate')
283
# Use a different object, to make sure nothing is pre-cached in memory.
284
state = dirstate.DirState.on_file('dirstate')
286
self.addCleanup(state.unlock)
287
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
288
state._dirblock_state)
289
# This is code is only really tested if we actually have to make more
290
# than one read, so set the page size to something smaller.
291
# We want it to contain about 2.2 records, so that we have a couple
292
# records that we can read per attempt
293
state._bisect_page_size = 200
294
return tree, state, expected
296
def create_duplicated_dirstate(self):
297
"""Create a dirstate with a deleted and added entries.
299
This grabs a basic_dirstate, and then removes and re adds every entry
302
tree, state, expected = self.create_basic_dirstate()
303
# Now we will just remove and add every file so we get an extra entry
304
# per entry. Unversion in reverse order so we handle subdirs
305
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
306
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
307
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
309
# Update the expected dictionary.
310
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
311
orig = expected[path]
313
# This record was deleted in the current tree
314
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
316
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
317
# And didn't exist in the basis tree
318
expected[path2] = (new_key, [orig[1][0],
319
dirstate.DirState.NULL_PARENT_DETAILS])
321
# We will replace the 'dirstate' file underneath 'state', but that is
322
# okay as lock as we unlock 'state' first.
325
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
331
# But we need to leave state in a read-lock because we already have
332
# a cleanup scheduled
334
return tree, state, expected
336
def create_renamed_dirstate(self):
337
"""Create a dirstate with a few internal renames.
339
This takes the basic dirstate, and moves the paths around.
341
tree, state, expected = self.create_basic_dirstate()
343
tree.rename_one('a', 'b/g')
345
tree.rename_one('b/d', 'h')
347
old_a = expected['a']
348
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
349
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
350
('r', 'a', 0, False, '')])
351
old_d = expected['b/d']
352
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
353
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
354
('r', 'b/d', 0, False, '')])
356
old_e = expected['b/d/e']
357
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
359
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
360
('r', 'b/d/e', 0, False, '')])
364
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
371
return tree, state, expected
374
class TestTreeToDirState(TestCaseWithDirState):
376
def test_empty_to_dirstate(self):
377
"""We should be able to create a dirstate for an empty tree."""
378
# There are no files on disk and no parents
379
tree = self.make_branch_and_tree('tree')
380
expected_result = ([], [
381
(('', '', tree.get_root_id()), # common details
382
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
384
state = dirstate.DirState.from_tree(tree, 'dirstate')
386
self.check_state_with_reopen(expected_result, state)
388
def test_1_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post').encode('utf8')
392
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
393
expected_result = ([rev_id], [
394
(('', '', tree.get_root_id()), # common details
395
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
396
('d', '', 0, False, rev_id), # first parent details
398
state = dirstate.DirState.from_tree(tree, 'dirstate')
399
self.check_state_with_reopen(expected_result, state)
406
def test_2_parents_empty_to_dirstate(self):
407
# create a parent by doing a commit
408
tree = self.make_branch_and_tree('tree')
409
rev_id = tree.commit('first post')
410
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
411
rev_id2 = tree2.commit('second post', allow_pointless=True)
412
tree.merge_from_branch(tree2.branch)
413
expected_result = ([rev_id, rev_id2], [
414
(('', '', tree.get_root_id()), # common details
415
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
416
('d', '', 0, False, rev_id), # first parent details
417
('d', '', 0, False, rev_id), # second parent details
419
state = dirstate.DirState.from_tree(tree, 'dirstate')
420
self.check_state_with_reopen(expected_result, state)
427
def test_empty_unknowns_are_ignored_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are no files on disk and no parents
430
tree = self.make_branch_and_tree('tree')
431
self.build_tree(['tree/unknown'])
432
expected_result = ([], [
433
(('', '', tree.get_root_id()), # common details
434
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
436
state = dirstate.DirState.from_tree(tree, 'dirstate')
437
self.check_state_with_reopen(expected_result, state)
439
def get_tree_with_a_file(self):
440
tree = self.make_branch_and_tree('tree')
441
self.build_tree(['tree/a file'])
442
tree.add('a file', 'a-file-id')
445
def test_non_empty_no_parents_to_dirstate(self):
446
"""We should be able to create a dirstate for an empty tree."""
447
# There are files on disk and no parents
448
tree = self.get_tree_with_a_file()
449
expected_result = ([], [
450
(('', '', tree.get_root_id()), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
453
(('', 'a file', 'a-file-id'), # common
454
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
457
state = dirstate.DirState.from_tree(tree, 'dirstate')
458
self.check_state_with_reopen(expected_result, state)
460
def test_1_parents_not_empty_to_dirstate(self):
461
# create a parent by doing a commit
462
tree = self.get_tree_with_a_file()
463
rev_id = tree.commit('first post').encode('utf8')
464
# change the current content to be different this will alter stat, sha
466
self.build_tree_contents([('tree/a file', 'new content\n')])
467
expected_result = ([rev_id], [
468
(('', '', tree.get_root_id()), # common details
469
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
470
('d', '', 0, False, rev_id), # first parent details
472
(('', 'a file', 'a-file-id'), # common
473
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
474
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
475
rev_id), # first parent
478
state = dirstate.DirState.from_tree(tree, 'dirstate')
479
self.check_state_with_reopen(expected_result, state)
481
def test_2_parents_not_empty_to_dirstate(self):
482
# create a parent by doing a commit
483
tree = self.get_tree_with_a_file()
484
rev_id = tree.commit('first post').encode('utf8')
485
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
486
# change the current content to be different this will alter stat, sha
488
self.build_tree_contents([('tree2/a file', 'merge content\n')])
489
rev_id2 = tree2.commit('second post').encode('utf8')
490
tree.merge_from_branch(tree2.branch)
491
# change the current content to be different this will alter stat, sha
492
# and length again, giving us three distinct values:
493
self.build_tree_contents([('tree/a file', 'new content\n')])
494
expected_result = ([rev_id, rev_id2], [
495
(('', '', tree.get_root_id()), # common details
496
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
497
('d', '', 0, False, rev_id), # first parent details
498
('d', '', 0, False, rev_id), # second parent details
500
(('', 'a file', 'a-file-id'), # common
501
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
502
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
503
rev_id), # first parent
504
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
505
rev_id2), # second parent
508
state = dirstate.DirState.from_tree(tree, 'dirstate')
509
self.check_state_with_reopen(expected_result, state)
511
def test_colliding_fileids(self):
512
# test insertion of parents creating several entries at the same path.
513
# we used to have a bug where they could cause the dirstate to break
514
# its ordering invariants.
515
# create some trees to test from
518
tree = self.make_branch_and_tree('tree%d' % i)
519
self.build_tree(['tree%d/name' % i,])
520
tree.add(['name'], ['file-id%d' % i])
521
revision_id = 'revid-%d' % i
522
tree.commit('message', rev_id=revision_id)
523
parents.append((revision_id,
524
tree.branch.repository.revision_tree(revision_id)))
525
# now fold these trees into a dirstate
526
state = dirstate.DirState.initialize('dirstate')
528
state.set_parent_trees(parents, [])
534
class TestDirStateOnFile(TestCaseWithDirState):
536
def create_updated_dirstate(self):
537
self.build_tree(['a-file'])
538
tree = self.make_branch_and_tree('.')
539
tree.add(['a-file'], ['a-id'])
540
tree.commit('add a-file')
541
# Save and unlock the state, re-open it in readonly mode
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
545
state = dirstate.DirState.on_file('dirstate')
549
def test_construct_with_path(self):
550
tree = self.make_branch_and_tree('tree')
551
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
552
# we want to be able to get the lines of the dirstate that we will
554
lines = state.get_lines()
556
self.build_tree_contents([('dirstate', ''.join(lines))])
558
# no parents, default tree content
559
expected_result = ([], [
560
(('', '', tree.get_root_id()), # common details
561
# current tree details, but new from_tree skips statting, it
562
# uses set_state_from_inventory, and thus depends on the
564
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
567
state = dirstate.DirState.on_file('dirstate')
568
state.lock_write() # check_state_with_reopen will save() and unlock it
569
self.check_state_with_reopen(expected_result, state)
571
def test_can_save_clean_on_file(self):
572
tree = self.make_branch_and_tree('tree')
573
state = dirstate.DirState.from_tree(tree, 'dirstate')
575
# doing a save should work here as there have been no changes.
577
# TODO: stat it and check it hasn't changed; may require waiting
578
# for the state accuracy window.
582
def test_can_save_in_read_lock(self):
583
state = self.create_updated_dirstate()
585
entry = state._get_entry(0, path_utf8='a-file')
586
# The current size should be 0 (default)
587
self.assertEqual(0, entry[1][0][2])
588
# We should have a real entry.
589
self.assertNotEqual((None, None), entry)
590
# Set the cutoff-time into the future, so things look cacheable
591
state._sha_cutoff_time()
592
state._cutoff_time += 10.0
593
st = os.lstat('a-file')
594
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
595
# We updated the current sha1sum because the file is cacheable
596
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
599
# The dirblock has been updated
600
self.assertEqual(st.st_size, entry[1][0][2])
601
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
602
state._dirblock_state)
605
# Now, since we are the only one holding a lock, we should be able
606
# to save and have it written to disk
611
# Re-open the file, and ensure that the state has been updated.
612
state = dirstate.DirState.on_file('dirstate')
615
entry = state._get_entry(0, path_utf8='a-file')
616
self.assertEqual(st.st_size, entry[1][0][2])
620
def test_save_fails_quietly_if_locked(self):
621
"""If dirstate is locked, save will fail without complaining."""
622
state = self.create_updated_dirstate()
624
entry = state._get_entry(0, path_utf8='a-file')
625
# No cached sha1 yet.
626
self.assertEqual('', entry[1][0][1])
627
# Set the cutoff-time into the future, so things look cacheable
628
state._sha_cutoff_time()
629
state._cutoff_time += 10.0
630
st = os.lstat('a-file')
631
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
632
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
634
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
635
state._dirblock_state)
637
# Now, before we try to save, grab another dirstate, and take out a
639
# TODO: jam 20070315 Ideally this would be locked by another
640
# process. To make sure the file is really OS locked.
641
state2 = dirstate.DirState.on_file('dirstate')
644
# This won't actually write anything, because it couldn't grab
645
# a write lock. But it shouldn't raise an error, either.
646
# TODO: jam 20070315 We should probably distinguish between
647
# being dirty because of 'update_entry'. And dirty
648
# because of real modification. So that save() *does*
649
# raise a real error if it fails when we have real
657
# The file on disk should not be modified.
658
state = dirstate.DirState.on_file('dirstate')
661
entry = state._get_entry(0, path_utf8='a-file')
662
self.assertEqual('', entry[1][0][1])
666
def test_save_refuses_if_changes_aborted(self):
667
self.build_tree(['a-file', 'a-dir/'])
668
state = dirstate.DirState.initialize('dirstate')
670
# No stat and no sha1 sum.
671
state.add('a-file', 'a-file-id', 'file', None, '')
676
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
678
('', [(('', '', 'TREE_ROOT'),
679
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
680
('', [(('', 'a-file', 'a-file-id'),
681
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
684
state = dirstate.DirState.on_file('dirstate')
687
state._read_dirblocks_if_needed()
688
self.assertEqual(expected_blocks, state._dirblocks)
690
# Now modify the state, but mark it as inconsistent
691
state.add('a-dir', 'a-dir-id', 'directory', None, '')
692
state._changes_aborted = True
697
state = dirstate.DirState.on_file('dirstate')
700
state._read_dirblocks_if_needed()
701
self.assertEqual(expected_blocks, state._dirblocks)
706
class TestDirStateInitialize(TestCaseWithDirState):
708
def test_initialize(self):
709
expected_result = ([], [
710
(('', '', 'TREE_ROOT'), # common details
711
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
714
state = dirstate.DirState.initialize('dirstate')
716
self.assertIsInstance(state, dirstate.DirState)
717
lines = state.get_lines()
720
# On win32 you can't read from a locked file, even within the same
721
# process. So we have to unlock and release before we check the file
723
self.assertFileEqual(''.join(lines), 'dirstate')
724
state.lock_read() # check_state_with_reopen will unlock
725
self.check_state_with_reopen(expected_result, state)
728
class TestDirStateManipulations(TestCaseWithDirState):
730
def make_minimal_tree(self):
731
tree1 = self.make_branch_and_memory_tree('tree1')
733
self.addCleanup(tree1.unlock)
735
revid1 = tree1.commit('foo')
738
def test_update_minimal_updates_id_index(self):
739
state = self.create_dirstate_with_root_and_subdir()
740
self.addCleanup(state.unlock)
741
id_index = state._get_id_index()
742
self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
743
state.add('file-name', 'file-id', 'file', None, '')
744
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
746
state.update_minimal(('', 'new-name', 'file-id'), 'f',
747
path_utf8='new-name')
748
self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
750
self.assertEqual([('', 'new-name', 'file-id')],
751
sorted(id_index['file-id']))
754
def test_set_state_from_inventory_no_content_no_parents(self):
755
# setting the current inventory is a slow but important api to support.
756
tree1, revid1 = self.make_minimal_tree()
757
inv = tree1.root_inventory
758
root_id = inv.path2id('')
759
expected_result = [], [
760
(('', '', root_id), [
761
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
762
state = dirstate.DirState.initialize('dirstate')
764
state.set_state_from_inventory(inv)
765
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
767
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
768
state._dirblock_state)
773
# This will unlock it
774
self.check_state_with_reopen(expected_result, state)
776
def test_set_state_from_scratch_no_parents(self):
777
tree1, revid1 = self.make_minimal_tree()
778
inv = tree1.root_inventory
779
root_id = inv.path2id('')
780
expected_result = [], [
781
(('', '', root_id), [
782
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
783
state = dirstate.DirState.initialize('dirstate')
785
state.set_state_from_scratch(inv, [], [])
786
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
788
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
789
state._dirblock_state)
794
# This will unlock it
795
self.check_state_with_reopen(expected_result, state)
797
def test_set_state_from_scratch_identical_parent(self):
798
tree1, revid1 = self.make_minimal_tree()
799
inv = tree1.root_inventory
800
root_id = inv.path2id('')
801
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
802
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
803
parent_entry = ('d', '', 0, False, revid1)
804
expected_result = [revid1], [
805
(('', '', root_id), [d_entry, parent_entry])]
806
state = dirstate.DirState.initialize('dirstate')
808
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
809
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
811
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
812
state._dirblock_state)
817
# This will unlock it
818
self.check_state_with_reopen(expected_result, state)
820
def test_set_state_from_inventory_preserves_hashcache(self):
821
# https://bugs.launchpad.net/bzr/+bug/146176
822
# set_state_from_inventory should preserve the stat and hash value for
823
# workingtree files that are not changed by the inventory.
825
tree = self.make_branch_and_tree('.')
826
# depends on the default format using dirstate...
829
# make a dirstate with some valid hashcache data
830
# file on disk, but that's not needed for this test
831
foo_contents = 'contents of foo'
832
self.build_tree_contents([('foo', foo_contents)])
833
tree.add('foo', 'foo-id')
835
foo_stat = os.stat('foo')
836
foo_packed = dirstate.pack_stat(foo_stat)
837
foo_sha = osutils.sha_string(foo_contents)
838
foo_size = len(foo_contents)
840
# should not be cached yet, because the file's too fresh
842
(('', 'foo', 'foo-id',),
843
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
844
tree._dirstate._get_entry(0, 'foo-id'))
845
# poke in some hashcache information - it wouldn't normally be
846
# stored because it's too fresh
847
tree._dirstate.update_minimal(
848
('', 'foo', 'foo-id'),
849
'f', False, foo_sha, foo_packed, foo_size, 'foo')
850
# now should be cached
852
(('', 'foo', 'foo-id',),
853
[('f', foo_sha, foo_size, False, foo_packed)]),
854
tree._dirstate._get_entry(0, 'foo-id'))
856
# extract the inventory, and add something to it
857
inv = tree._get_root_inventory()
858
# should see the file we poked in...
859
self.assertTrue(inv.has_id('foo-id'))
860
self.assertTrue(inv.has_filename('foo'))
861
inv.add_path('bar', 'file', 'bar-id')
862
tree._dirstate._validate()
863
# this used to cause it to lose its hashcache
864
tree._dirstate.set_state_from_inventory(inv)
865
tree._dirstate._validate()
871
# now check that the state still has the original hashcache value
872
state = tree._dirstate
874
foo_tuple = state._get_entry(0, path_utf8='foo')
876
(('', 'foo', 'foo-id',),
877
[('f', foo_sha, len(foo_contents), False,
878
dirstate.pack_stat(foo_stat))]),
883
def test_set_state_from_inventory_mixed_paths(self):
884
tree1 = self.make_branch_and_tree('tree1')
885
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
886
'tree1/a/b/foo', 'tree1/a-b/bar'])
889
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
890
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
891
tree1.commit('rev1', rev_id='rev1')
892
root_id = tree1.get_root_id()
893
inv = tree1.root_inventory
896
expected_result1 = [('', '', root_id, 'd'),
897
('', 'a', 'a-id', 'd'),
898
('', 'a-b', 'a-b-id', 'd'),
899
('a', 'b', 'b-id', 'd'),
900
('a/b', 'foo', 'foo-id', 'f'),
901
('a-b', 'bar', 'bar-id', 'f'),
903
expected_result2 = [('', '', root_id, 'd'),
904
('', 'a', 'a-id', 'd'),
905
('', 'a-b', 'a-b-id', 'd'),
906
('a-b', 'bar', 'bar-id', 'f'),
908
state = dirstate.DirState.initialize('dirstate')
910
state.set_state_from_inventory(inv)
912
for entry in state._iter_entries():
913
values.append(entry[0] + entry[1][0][:1])
914
self.assertEqual(expected_result1, values)
916
state.set_state_from_inventory(inv)
918
for entry in state._iter_entries():
919
values.append(entry[0] + entry[1][0][:1])
920
self.assertEqual(expected_result2, values)
924
def test_set_path_id_no_parents(self):
925
"""The id of a path can be changed trivally with no parents."""
926
state = dirstate.DirState.initialize('dirstate')
928
# check precondition to be sure the state does change appropriately.
929
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
930
self.assertEqual([root_entry], list(state._iter_entries()))
931
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
932
self.assertEqual(root_entry,
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual((None, None),
935
state._get_entry(0, fileid_utf8='second-root-id'))
936
state.set_path_id('', 'second-root-id')
937
new_root_entry = (('', '', 'second-root-id'),
938
[('d', '', 0, False, 'x'*32)])
939
expected_rows = [new_root_entry]
940
self.assertEqual(expected_rows, list(state._iter_entries()))
941
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
942
self.assertEqual(new_root_entry,
943
state._get_entry(0, fileid_utf8='second-root-id'))
944
self.assertEqual((None, None),
945
state._get_entry(0, fileid_utf8='TREE_ROOT'))
946
# should work across save too
950
state = dirstate.DirState.on_file('dirstate')
954
self.assertEqual(expected_rows, list(state._iter_entries()))
958
def test_set_path_id_with_parents(self):
959
"""Set the root file id in a dirstate with parents"""
960
mt = self.make_branch_and_tree('mt')
961
# in case the default tree format uses a different root id
962
mt.set_root_id('TREE_ROOT')
963
mt.commit('foo', rev_id='parent-revid')
964
rt = mt.branch.repository.revision_tree('parent-revid')
965
state = dirstate.DirState.initialize('dirstate')
968
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
969
root_entry = (('', '', 'TREE_ROOT'),
970
[('d', '', 0, False, 'x'*32),
971
('d', '', 0, False, 'parent-revid')])
972
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
973
self.assertEqual(root_entry,
974
state._get_entry(0, fileid_utf8='TREE_ROOT'))
975
self.assertEqual((None, None),
976
state._get_entry(0, fileid_utf8='Asecond-root-id'))
977
state.set_path_id('', 'Asecond-root-id')
979
# now see that it is what we expected
980
old_root_entry = (('', '', 'TREE_ROOT'),
981
[('a', '', 0, False, ''),
982
('d', '', 0, False, 'parent-revid')])
983
new_root_entry = (('', '', 'Asecond-root-id'),
984
[('d', '', 0, False, ''),
985
('a', '', 0, False, '')])
986
expected_rows = [new_root_entry, old_root_entry]
988
self.assertEqual(expected_rows, list(state._iter_entries()))
989
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
990
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
991
self.assertEqual((None, None),
992
state._get_entry(0, fileid_utf8='TREE_ROOT'))
993
self.assertEqual(old_root_entry,
994
state._get_entry(1, fileid_utf8='TREE_ROOT'))
995
self.assertEqual(new_root_entry,
996
state._get_entry(0, fileid_utf8='Asecond-root-id'))
997
self.assertEqual((None, None),
998
state._get_entry(1, fileid_utf8='Asecond-root-id'))
999
# should work across save too
1003
# now flush & check we get the same
1004
state = dirstate.DirState.on_file('dirstate')
1008
self.assertEqual(expected_rows, list(state._iter_entries()))
1011
# now change within an existing file-backed state
1015
state.set_path_id('', 'tree-root-2')
1020
def test_set_parent_trees_no_content(self):
1021
# set_parent_trees is a slow but important api to support.
1022
tree1 = self.make_branch_and_memory_tree('tree1')
1026
revid1 = tree1.commit('foo')
1029
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1030
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1033
revid2 = tree2.commit('foo')
1034
root_id = tree2.get_root_id()
1037
state = dirstate.DirState.initialize('dirstate')
1039
state.set_path_id('', root_id)
1040
state.set_parent_trees(
1041
((revid1, tree1.branch.repository.revision_tree(revid1)),
1042
(revid2, tree2.branch.repository.revision_tree(revid2)),
1043
('ghost-rev', None)),
1045
# check we can reopen and use the dirstate after setting parent
1052
state = dirstate.DirState.on_file('dirstate')
1055
self.assertEqual([revid1, revid2, 'ghost-rev'],
1056
state.get_parent_ids())
1057
# iterating the entire state ensures that the state is parsable.
1058
list(state._iter_entries())
1059
# be sure that it sets not appends - change it
1060
state.set_parent_trees(
1061
((revid1, tree1.branch.repository.revision_tree(revid1)),
1062
('ghost-rev', None)),
1064
# and now put it back.
1065
state.set_parent_trees(
1066
((revid1, tree1.branch.repository.revision_tree(revid1)),
1067
(revid2, tree2.branch.repository.revision_tree(revid2)),
1068
('ghost-rev', tree2.branch.repository.revision_tree(
1069
_mod_revision.NULL_REVISION))),
1071
self.assertEqual([revid1, revid2, 'ghost-rev'],
1072
state.get_parent_ids())
1073
# the ghost should be recorded as such by set_parent_trees.
1074
self.assertEqual(['ghost-rev'], state.get_ghosts())
1076
[(('', '', root_id), [
1077
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1078
('d', '', 0, False, revid1),
1079
('d', '', 0, False, revid1)
1081
list(state._iter_entries()))
1085
def test_set_parent_trees_file_missing_from_tree(self):
1086
# Adding a parent tree may reference files not in the current state.
1087
# they should get listed just once by id, even if they are in two
1089
# set_parent_trees is a slow but important api to support.
1090
tree1 = self.make_branch_and_memory_tree('tree1')
1094
tree1.add(['a file'], ['file-id'], ['file'])
1095
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1096
revid1 = tree1.commit('foo')
1099
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1100
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1103
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1104
revid2 = tree2.commit('foo')
1105
root_id = tree2.get_root_id()
1108
# check the layout in memory
1109
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1110
(('', '', root_id), [
1111
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1112
('d', '', 0, False, revid1.encode('utf8')),
1113
('d', '', 0, False, revid1.encode('utf8'))
1115
(('', 'a file', 'file-id'), [
1116
('a', '', 0, False, ''),
1117
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1118
revid1.encode('utf8')),
1119
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1120
revid2.encode('utf8'))
1123
state = dirstate.DirState.initialize('dirstate')
1125
state.set_path_id('', root_id)
1126
state.set_parent_trees(
1127
((revid1, tree1.branch.repository.revision_tree(revid1)),
1128
(revid2, tree2.branch.repository.revision_tree(revid2)),
1134
# check_state_with_reopen will unlock
1135
self.check_state_with_reopen(expected_result, state)
1137
### add a path via _set_data - so we dont need delta work, just
1138
# raw data in, and ensure that it comes out via get_lines happily.
1140
def test_add_path_to_root_no_parents_all_data(self):
1141
# The most trivial addition of a path is when there are no parents and
1142
# its in the root and all data about the file is supplied
1143
self.build_tree(['a file'])
1144
stat = os.lstat('a file')
1145
# the 1*20 is the sha1 pretend value.
1146
state = dirstate.DirState.initialize('dirstate')
1147
expected_entries = [
1148
(('', '', 'TREE_ROOT'), [
1149
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1151
(('', 'a file', 'a-file-id'), [
1152
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1156
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1157
# having added it, it should be in the output of iter_entries.
1158
self.assertEqual(expected_entries, list(state._iter_entries()))
1159
# saving and reloading should not affect this.
1163
state = dirstate.DirState.on_file('dirstate')
1165
self.addCleanup(state.unlock)
1166
self.assertEqual(expected_entries, list(state._iter_entries()))
1168
def test_add_path_to_unversioned_directory(self):
1169
"""Adding a path to an unversioned directory should error.
1171
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1172
once dirstate is stable and if it is merged with WorkingTree3, consider
1173
removing this copy of the test.
1175
self.build_tree(['unversioned/', 'unversioned/a file'])
1176
state = dirstate.DirState.initialize('dirstate')
1177
self.addCleanup(state.unlock)
1178
self.assertRaises(errors.NotVersionedError, state.add,
1179
'unversioned/a file', 'a-file-id', 'file', None, None)
1181
def test_add_directory_to_root_no_parents_all_data(self):
1182
# The most trivial addition of a dir is when there are no parents and
1183
# its in the root and all data about the file is supplied
1184
self.build_tree(['a dir/'])
1185
stat = os.lstat('a dir')
1186
expected_entries = [
1187
(('', '', 'TREE_ROOT'), [
1188
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1190
(('', 'a dir', 'a dir id'), [
1191
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1194
state = dirstate.DirState.initialize('dirstate')
1196
state.add('a dir', 'a dir id', 'directory', stat, None)
1197
# having added it, it should be in the output of iter_entries.
1198
self.assertEqual(expected_entries, list(state._iter_entries()))
1199
# saving and reloading should not affect this.
1203
state = dirstate.DirState.on_file('dirstate')
1205
self.addCleanup(state.unlock)
1207
self.assertEqual(expected_entries, list(state._iter_entries()))
1209
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1210
# The most trivial addition of a symlink when there are no parents and
1211
# its in the root and all data about the file is supplied
1212
# bzr doesn't support fake symlinks on windows, yet.
1213
self.requireFeature(features.SymlinkFeature)
1214
os.symlink(target, link_name)
1215
stat = os.lstat(link_name)
1216
expected_entries = [
1217
(('', '', 'TREE_ROOT'), [
1218
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1220
(('', link_name.encode('UTF-8'), 'a link id'), [
1221
('l', target.encode('UTF-8'), stat[6],
1222
False, dirstate.pack_stat(stat)), # current tree
1225
state = dirstate.DirState.initialize('dirstate')
1227
state.add(link_name, 'a link id', 'symlink', stat,
1228
target.encode('UTF-8'))
1229
# having added it, it should be in the output of iter_entries.
1230
self.assertEqual(expected_entries, list(state._iter_entries()))
1231
# saving and reloading should not affect this.
1235
state = dirstate.DirState.on_file('dirstate')
1237
self.addCleanup(state.unlock)
1238
self.assertEqual(expected_entries, list(state._iter_entries()))
1240
def test_add_symlink_to_root_no_parents_all_data(self):
1241
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1243
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1244
self.requireFeature(features.UnicodeFilenameFeature)
1245
self._test_add_symlink_to_root_no_parents_all_data(
1246
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1248
def test_add_directory_and_child_no_parents_all_data(self):
1249
# after adding a directory, we should be able to add children to it.
1250
self.build_tree(['a dir/', 'a dir/a file'])
1251
dirstat = os.lstat('a dir')
1252
filestat = os.lstat('a dir/a file')
1253
expected_entries = [
1254
(('', '', 'TREE_ROOT'), [
1255
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1257
(('', 'a dir', 'a dir id'), [
1258
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1260
(('a dir', 'a file', 'a-file-id'), [
1261
('f', '1'*20, 25, False,
1262
dirstate.pack_stat(filestat)), # current tree details
1265
state = dirstate.DirState.initialize('dirstate')
1267
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1268
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1269
# added it, it should be in the output of iter_entries.
1270
self.assertEqual(expected_entries, list(state._iter_entries()))
1271
# saving and reloading should not affect this.
1275
state = dirstate.DirState.on_file('dirstate')
1277
self.addCleanup(state.unlock)
1278
self.assertEqual(expected_entries, list(state._iter_entries()))
1280
def test_add_tree_reference(self):
1281
# make a dirstate and add a tree reference
1282
state = dirstate.DirState.initialize('dirstate')
1284
('', 'subdir', 'subdir-id'),
1285
[('t', 'subtree-123123', 0, False,
1286
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1289
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1290
entry = state._get_entry(0, 'subdir-id', 'subdir')
1291
self.assertEqual(entry, expected_entry)
1296
# now check we can read it back
1298
self.addCleanup(state.unlock)
1300
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1301
self.assertEqual(entry, entry2)
1302
self.assertEqual(entry, expected_entry)
1303
# and lookup by id should work too
1304
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1305
self.assertEqual(entry, expected_entry)
1307
def test_add_forbidden_names(self):
1308
state = dirstate.DirState.initialize('dirstate')
1309
self.addCleanup(state.unlock)
1310
self.assertRaises(errors.BzrError,
1311
state.add, '.', 'ass-id', 'directory', None, None)
1312
self.assertRaises(errors.BzrError,
1313
state.add, '..', 'ass-id', 'directory', None, None)
1315
def test_set_state_with_rename_b_a_bug_395556(self):
1316
# bug 395556 uncovered a bug where the dirstate ends up with a false
1317
# relocation record - in a tree with no parents there should be no
1318
# absent or relocated records. This then leads to further corruption
1319
# when a commit occurs, as the incorrect relocation gathers an
1320
# incorrect absent in tree 1, and future changes go to pot.
1321
tree1 = self.make_branch_and_tree('tree1')
1322
self.build_tree(['tree1/b'])
1325
tree1.add(['b'], ['b-id'])
1326
root_id = tree1.get_root_id()
1327
inv = tree1.root_inventory
1328
state = dirstate.DirState.initialize('dirstate')
1330
# Set the initial state with 'b'
1331
state.set_state_from_inventory(inv)
1332
inv.rename('b-id', root_id, 'a')
1333
# Set the new state with 'a', which currently corrupts.
1334
state.set_state_from_inventory(inv)
1335
expected_result1 = [('', '', root_id, 'd'),
1336
('', 'a', 'b-id', 'f'),
1339
for entry in state._iter_entries():
1340
values.append(entry[0] + entry[1][0][:1])
1341
self.assertEqual(expected_result1, values)
1348
class TestDirStateHashUpdates(TestCaseWithDirState):
1350
def do_update_entry(self, state, path):
1351
entry = state._get_entry(0, path_utf8=path)
1352
stat = os.lstat(path)
1353
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1355
def _read_state_content(self, state):
1356
"""Read the content of the dirstate file.
1358
On Windows when one process locks a file, you can't even open() the
1359
file in another process (to read it). So we go directly to
1360
state._state_file. This should always be the exact disk representation,
1361
so it is reasonable to do so.
1362
DirState also always seeks before reading, so it doesn't matter if we
1363
bump the file pointer.
1365
state._state_file.seek(0)
1366
return state._state_file.read()
1368
def test_worth_saving_limit_avoids_writing(self):
1369
tree = self.make_branch_and_tree('.')
1370
self.build_tree(['c', 'd'])
1372
tree.add(['c', 'd'], ['c-id', 'd-id'])
1373
tree.commit('add c and d')
1374
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1375
worth_saving_limit=2)
1378
self.addCleanup(state.unlock)
1379
state._read_dirblocks_if_needed()
1380
state.adjust_time(+20) # Allow things to be cached
1381
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1382
state._dirblock_state)
1383
content = self._read_state_content(state)
1384
self.do_update_entry(state, 'c')
1385
self.assertEqual(1, len(state._known_hash_changes))
1386
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1387
state._dirblock_state)
1389
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1390
# hash values haven't been written out.
1391
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1392
state._dirblock_state)
1393
self.assertEqual(content, self._read_state_content(state))
1394
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1395
state._dirblock_state)
1396
self.do_update_entry(state, 'd')
1397
self.assertEqual(2, len(state._known_hash_changes))
1399
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1400
state._dirblock_state)
1401
self.assertEqual(0, len(state._known_hash_changes))
1404
class TestGetLines(TestCaseWithDirState):
1406
def test_get_line_with_2_rows(self):
1407
state = self.create_dirstate_with_root_and_subdir()
1409
self.assertEqual(['#bazaar dirstate flat format 3\n',
1410
'crc32: 41262208\n',
1414
'\x00\x00a-root-value\x00'
1415
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1416
'\x00subdir\x00subdir-id\x00'
1417
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1418
], state.get_lines())
1422
def test_entry_to_line(self):
1423
state = self.create_dirstate_with_root()
1426
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1427
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1428
state._entry_to_line(state._dirblocks[0][1][0]))
1432
def test_entry_to_line_with_parent(self):
1433
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1434
root_entry = ('', '', 'a-root-value'), [
1435
('d', '', 0, False, packed_stat), # current tree details
1436
# first: a pointer to the current location
1437
('a', 'dirname/basename', 0, False, ''),
1439
state = dirstate.DirState.initialize('dirstate')
1442
'\x00\x00a-root-value\x00'
1443
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1444
'a\x00dirname/basename\x000\x00n\x00',
1445
state._entry_to_line(root_entry))
1449
def test_entry_to_line_with_two_parents_at_different_paths(self):
1450
# / in the tree, at / in one parent and /dirname/basename in the other.
1451
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1452
root_entry = ('', '', 'a-root-value'), [
1453
('d', '', 0, False, packed_stat), # current tree details
1454
('d', '', 0, False, 'rev_id'), # first parent details
1455
# second: a pointer to the current location
1456
('a', 'dirname/basename', 0, False, ''),
1458
state = dirstate.DirState.initialize('dirstate')
1461
'\x00\x00a-root-value\x00'
1462
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1463
'd\x00\x000\x00n\x00rev_id\x00'
1464
'a\x00dirname/basename\x000\x00n\x00',
1465
state._entry_to_line(root_entry))
1469
def test_iter_entries(self):
1470
# we should be able to iterate the dirstate entries from end to end
1471
# this is for get_lines to be easy to read.
1472
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1474
root_entries = [(('', '', 'a-root-value'), [
1475
('d', '', 0, False, packed_stat), # current tree details
1477
dirblocks.append(('', root_entries))
1478
# add two files in the root
1479
subdir_entry = ('', 'subdir', 'subdir-id'), [
1480
('d', '', 0, False, packed_stat), # current tree details
1482
afile_entry = ('', 'afile', 'afile-id'), [
1483
('f', 'sha1value', 34, False, packed_stat), # current tree details
1485
dirblocks.append(('', [subdir_entry, afile_entry]))
1487
file_entry2 = ('subdir', '2file', '2file-id'), [
1488
('f', 'sha1value', 23, False, packed_stat), # current tree details
1490
dirblocks.append(('subdir', [file_entry2]))
1491
state = dirstate.DirState.initialize('dirstate')
1493
state._set_data([], dirblocks)
1494
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1496
self.assertEqual(expected_entries, list(state._iter_entries()))
1501
class TestGetBlockRowIndex(TestCaseWithDirState):
1503
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1504
file_present, state, dirname, basename, tree_index):
1505
self.assertEqual((block_index, row_index, dir_present, file_present),
1506
state._get_block_entry_index(dirname, basename, tree_index))
1508
block = state._dirblocks[block_index]
1509
self.assertEqual(dirname, block[0])
1510
if dir_present and file_present:
1511
row = state._dirblocks[block_index][1][row_index]
1512
self.assertEqual(dirname, row[0][0])
1513
self.assertEqual(basename, row[0][1])
1515
def test_simple_structure(self):
1516
state = self.create_dirstate_with_root_and_subdir()
1517
self.addCleanup(state.unlock)
1518
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1519
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1520
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1521
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1522
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1525
def test_complex_structure_exists(self):
1526
state = self.create_complex_dirstate()
1527
self.addCleanup(state.unlock)
1528
# Make sure we can find everything that exists
1529
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1530
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1531
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1532
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1533
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1534
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1535
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1536
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1537
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1538
'b', 'h\xc3\xa5', 0)
1540
def test_complex_structure_missing(self):
1541
state = self.create_complex_dirstate()
1542
self.addCleanup(state.unlock)
1543
# Make sure things would be inserted in the right locations
1544
# '_' comes before 'a'
1545
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1546
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1547
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1548
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1550
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1551
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1552
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1553
# This would be inserted between a/ and b/
1554
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1556
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1559
class TestGetEntry(TestCaseWithDirState):
1561
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1562
"""Check that the right entry is returned for a request to getEntry."""
1563
entry = state._get_entry(index, path_utf8=path)
1565
self.assertEqual((None, None), entry)
1568
self.assertEqual((dirname, basename, file_id), cur[:3])
1570
def test_simple_structure(self):
1571
state = self.create_dirstate_with_root_and_subdir()
1572
self.addCleanup(state.unlock)
1573
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1574
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1575
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1576
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1577
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1579
def test_complex_structure_exists(self):
1580
state = self.create_complex_dirstate()
1581
self.addCleanup(state.unlock)
1582
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1583
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1584
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1585
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1586
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1587
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1588
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1589
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1590
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1593
def test_complex_structure_missing(self):
1594
state = self.create_complex_dirstate()
1595
self.addCleanup(state.unlock)
1596
self.assertEntryEqual(None, None, None, state, '_', 0)
1597
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1598
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1599
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1601
def test_get_entry_uninitialized(self):
1602
"""Calling get_entry will load data if it needs to"""
1603
state = self.create_dirstate_with_root()
1609
state = dirstate.DirState.on_file('dirstate')
1612
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1613
state._header_state)
1614
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1615
state._dirblock_state)
1616
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1621
class TestIterChildEntries(TestCaseWithDirState):
1623
def create_dirstate_with_two_trees(self):
1624
"""This dirstate contains multiple files and directories.
1634
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1636
Notice that a/e is an empty directory.
1638
There is one parent tree, which has the same shape with the following variations:
1639
b/g in the parent is gone.
1640
b/h in the parent has a different id
1641
b/i is new in the parent
1642
c is renamed to b/j in the parent
1644
:return: The dirstate, still write-locked.
1646
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1647
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1648
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1649
root_entry = ('', '', 'a-root-value'), [
1650
('d', '', 0, False, packed_stat),
1651
('d', '', 0, False, 'parent-revid'),
1653
a_entry = ('', 'a', 'a-dir'), [
1654
('d', '', 0, False, packed_stat),
1655
('d', '', 0, False, 'parent-revid'),
1657
b_entry = ('', 'b', 'b-dir'), [
1658
('d', '', 0, False, packed_stat),
1659
('d', '', 0, False, 'parent-revid'),
1661
c_entry = ('', 'c', 'c-file'), [
1662
('f', null_sha, 10, False, packed_stat),
1663
('r', 'b/j', 0, False, ''),
1665
d_entry = ('', 'd', 'd-file'), [
1666
('f', null_sha, 20, False, packed_stat),
1667
('f', 'd', 20, False, 'parent-revid'),
1669
e_entry = ('a', 'e', 'e-dir'), [
1670
('d', '', 0, False, packed_stat),
1671
('d', '', 0, False, 'parent-revid'),
1673
f_entry = ('a', 'f', 'f-file'), [
1674
('f', null_sha, 30, False, packed_stat),
1675
('f', 'f', 20, False, 'parent-revid'),
1677
g_entry = ('b', 'g', 'g-file'), [
1678
('f', null_sha, 30, False, packed_stat),
1679
NULL_PARENT_DETAILS,
1681
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1682
('f', null_sha, 40, False, packed_stat),
1683
NULL_PARENT_DETAILS,
1685
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1686
NULL_PARENT_DETAILS,
1687
('f', 'h', 20, False, 'parent-revid'),
1689
i_entry = ('b', 'i', 'i-file'), [
1690
NULL_PARENT_DETAILS,
1691
('f', 'h', 20, False, 'parent-revid'),
1693
j_entry = ('b', 'j', 'c-file'), [
1694
('r', 'c', 0, False, ''),
1695
('f', 'j', 20, False, 'parent-revid'),
1698
dirblocks.append(('', [root_entry]))
1699
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1700
dirblocks.append(('a', [e_entry, f_entry]))
1701
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1702
state = dirstate.DirState.initialize('dirstate')
1705
state._set_data(['parent'], dirblocks)
1709
return state, dirblocks
1711
def test_iter_children_b(self):
1712
state, dirblocks = self.create_dirstate_with_two_trees()
1713
self.addCleanup(state.unlock)
1714
expected_result = []
1715
expected_result.append(dirblocks[3][1][2]) # h2
1716
expected_result.append(dirblocks[3][1][3]) # i
1717
expected_result.append(dirblocks[3][1][4]) # j
1718
self.assertEqual(expected_result,
1719
list(state._iter_child_entries(1, 'b')))
1721
def test_iter_child_root(self):
1722
state, dirblocks = self.create_dirstate_with_two_trees()
1723
self.addCleanup(state.unlock)
1724
expected_result = []
1725
expected_result.append(dirblocks[1][1][0]) # a
1726
expected_result.append(dirblocks[1][1][1]) # b
1727
expected_result.append(dirblocks[1][1][3]) # d
1728
expected_result.append(dirblocks[2][1][0]) # e
1729
expected_result.append(dirblocks[2][1][1]) # f
1730
expected_result.append(dirblocks[3][1][2]) # h2
1731
expected_result.append(dirblocks[3][1][3]) # i
1732
expected_result.append(dirblocks[3][1][4]) # j
1733
self.assertEqual(expected_result,
1734
list(state._iter_child_entries(1, '')))
1737
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1738
"""Test that DirState adds entries in the right order."""
1740
def test_add_sorting(self):
1741
"""Add entries in lexicographical order, we get path sorted order.
1743
This tests it to a depth of 4, to make sure we don't just get it right
1744
at a single depth. 'a/a' should come before 'a-a', even though it
1745
doesn't lexicographically.
1747
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1748
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1751
state = dirstate.DirState.initialize('dirstate')
1752
self.addCleanup(state.unlock)
1754
fake_stat = os.stat('dirstate')
1756
d_id = d.replace('/', '_')+'-id'
1757
file_path = d + '/f'
1758
file_id = file_path.replace('/', '_')+'-id'
1759
state.add(d, d_id, 'directory', fake_stat, null_sha)
1760
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1762
expected = ['', '', 'a',
1763
'a/a', 'a/a/a', 'a/a/a/a',
1764
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1766
split = lambda p:p.split('/')
1767
self.assertEqual(sorted(expected, key=split), expected)
1768
dirblock_names = [d[0] for d in state._dirblocks]
1769
self.assertEqual(expected, dirblock_names)
1771
def test_set_parent_trees_correct_order(self):
1772
"""After calling set_parent_trees() we should maintain the order."""
1773
dirs = ['a', 'a-a', 'a/a']
1775
state = dirstate.DirState.initialize('dirstate')
1776
self.addCleanup(state.unlock)
1778
fake_stat = os.stat('dirstate')
1780
d_id = d.replace('/', '_')+'-id'
1781
file_path = d + '/f'
1782
file_id = file_path.replace('/', '_')+'-id'
1783
state.add(d, d_id, 'directory', fake_stat, null_sha)
1784
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1786
expected = ['', '', 'a', 'a/a', 'a-a']
1787
dirblock_names = [d[0] for d in state._dirblocks]
1788
self.assertEqual(expected, dirblock_names)
1790
# *really* cheesy way to just get an empty tree
1791
repo = self.make_repository('repo')
1792
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1793
state.set_parent_trees([('null:', empty_tree)], [])
1795
dirblock_names = [d[0] for d in state._dirblocks]
1796
self.assertEqual(expected, dirblock_names)
1799
class InstrumentedDirState(dirstate.DirState):
1800
"""An DirState with instrumented sha1 functionality."""
1802
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1803
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1804
worth_saving_limit=worth_saving_limit)
1805
self._time_offset = 0
1807
# member is dynamically set in DirState.__init__ to turn on trace
1808
self._sha1_provider = sha1_provider
1809
self._sha1_file = self._sha1_file_and_log
1811
def _sha_cutoff_time(self):
1812
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1813
self._cutoff_time = timestamp + self._time_offset
1815
def _sha1_file_and_log(self, abspath):
1816
self._log.append(('sha1', abspath))
1817
return self._sha1_provider.sha1(abspath)
1819
def _read_link(self, abspath, old_link):
1820
self._log.append(('read_link', abspath, old_link))
1821
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1823
def _lstat(self, abspath, entry):
1824
self._log.append(('lstat', abspath))
1825
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1827
def _is_executable(self, mode, old_executable):
1828
self._log.append(('is_exec', mode, old_executable))
1829
return super(InstrumentedDirState, self)._is_executable(mode,
1832
def adjust_time(self, secs):
1833
"""Move the clock forward or back.
1835
:param secs: The amount to adjust the clock by. Positive values make it
1836
seem as if we are in the future, negative values make it seem like we
1839
self._time_offset += secs
1840
self._cutoff_time = None
1843
class _FakeStat(object):
1844
"""A class with the same attributes as a real stat result."""
1846
def __init__(self, size, mtime, ctime, dev, ino, mode):
1848
self.st_mtime = mtime
1849
self.st_ctime = ctime
1856
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1857
st.st_ino, st.st_mode)
1860
class TestPackStat(tests.TestCaseWithTransport):
1862
def assertPackStat(self, expected, stat_value):
1863
"""Check the packed and serialized form of a stat value."""
1864
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1866
def test_pack_stat_int(self):
1867
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1868
# Make sure that all parameters have an impact on the packed stat.
1869
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1872
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1873
st.st_mtime = 1172758620
1875
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1876
st.st_ctime = 1172758630
1878
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1881
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1882
st.st_ino = 6499540L
1884
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1885
st.st_mode = 0100744
1887
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1889
def test_pack_stat_float(self):
1890
"""On some platforms mtime and ctime are floats.
1892
Make sure we don't get warnings or errors, and that we ignore changes <
1895
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1896
777L, 6499538L, 0100644)
1897
# These should all be the same as the integer counterparts
1898
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1899
st.st_mtime = 1172758620.0
1901
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1902
st.st_ctime = 1172758630.0
1904
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1905
# fractional seconds are discarded, so no change from above
1906
st.st_mtime = 1172758620.453
1907
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1908
st.st_ctime = 1172758630.228
1909
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1912
class TestBisect(TestCaseWithDirState):
1913
"""Test the ability to bisect into the disk format."""
1915
def assertBisect(self, expected_map, map_keys, state, paths):
1916
"""Assert that bisecting for paths returns the right result.
1918
:param expected_map: A map from key => entry value
1919
:param map_keys: The keys to expect for each path
1920
:param state: The DirState object.
1921
:param paths: A list of paths, these will automatically be split into
1922
(dir, name) tuples, and sorted according to how _bisect
1925
result = state._bisect(paths)
1926
# For now, results are just returned in whatever order we read them.
1927
# We could sort by (dir, name, file_id) or something like that, but in
1928
# the end it would still be fairly arbitrary, and we don't want the
1929
# extra overhead if we can avoid it. So sort everything to make sure
1931
self.assertEqual(len(map_keys), len(paths))
1933
for path, keys in zip(paths, map_keys):
1935
# This should not be present in the output
1937
expected[path] = sorted(expected_map[k] for k in keys)
1939
# The returned values are just arranged randomly based on when they
1940
# were read, for testing, make sure it is properly sorted.
1944
self.assertEqual(expected, result)
1946
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1947
"""Assert that bisecting for dirbblocks returns the right result.
1949
:param expected_map: A map from key => expected values
1950
:param map_keys: A nested list of paths we expect to be returned.
1951
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1952
:param state: The DirState object.
1953
:param paths: A list of directories
1955
result = state._bisect_dirblocks(paths)
1956
self.assertEqual(len(map_keys), len(paths))
1958
for path, keys in zip(paths, map_keys):
1960
# This should not be present in the output
1962
expected[path] = sorted(expected_map[k] for k in keys)
1966
self.assertEqual(expected, result)
1968
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1969
"""Assert the return value of a recursive bisection.
1971
:param expected_map: A map from key => entry value
1972
:param map_keys: A list of paths we expect to be returned.
1973
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1974
:param state: The DirState object.
1975
:param paths: A list of files and directories. It will be broken up
1976
into (dir, name) pairs and sorted before calling _bisect_recursive.
1979
for key in map_keys:
1980
entry = expected_map[key]
1981
dir_name_id, trees_info = entry
1982
expected[dir_name_id] = trees_info
1984
result = state._bisect_recursive(paths)
1986
self.assertEqual(expected, result)
1988
def test_bisect_each(self):
1989
"""Find a single record using bisect."""
1990
tree, state, expected = self.create_basic_dirstate()
1992
# Bisect should return the rows for the specified files.
1993
self.assertBisect(expected, [['']], state, [''])
1994
self.assertBisect(expected, [['a']], state, ['a'])
1995
self.assertBisect(expected, [['b']], state, ['b'])
1996
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1997
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1998
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1999
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2000
self.assertBisect(expected, [['f']], state, ['f'])
2002
def test_bisect_multi(self):
2003
"""Bisect can be used to find multiple records at the same time."""
2004
tree, state, expected = self.create_basic_dirstate()
2005
# Bisect should be capable of finding multiple entries at the same time
2006
self.assertBisect(expected, [['a'], ['b'], ['f']],
2007
state, ['a', 'b', 'f'])
2008
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2009
state, ['f', 'b/d', 'b/d/e'])
2010
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2011
state, ['b', 'b-c', 'b/c'])
2013
def test_bisect_one_page(self):
2014
"""Test bisect when there is only 1 page to read"""
2015
tree, state, expected = self.create_basic_dirstate()
2016
state._bisect_page_size = 5000
2017
self.assertBisect(expected,[['']], state, [''])
2018
self.assertBisect(expected,[['a']], state, ['a'])
2019
self.assertBisect(expected,[['b']], state, ['b'])
2020
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2021
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2022
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2023
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2024
self.assertBisect(expected,[['f']], state, ['f'])
2025
self.assertBisect(expected,[['a'], ['b'], ['f']],
2026
state, ['a', 'b', 'f'])
2027
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2028
state, ['b/d', 'b/d/e', 'f'])
2029
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2030
state, ['b', 'b/c', 'b-c'])
2032
def test_bisect_duplicate_paths(self):
2033
"""When bisecting for a path, handle multiple entries."""
2034
tree, state, expected = self.create_duplicated_dirstate()
2036
# Now make sure that both records are properly returned.
2037
self.assertBisect(expected, [['']], state, [''])
2038
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2039
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2040
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2041
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2042
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2044
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2045
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2047
def test_bisect_page_size_too_small(self):
2048
"""If the page size is too small, we will auto increase it."""
2049
tree, state, expected = self.create_basic_dirstate()
2050
state._bisect_page_size = 50
2051
self.assertBisect(expected, [None], state, ['b/e'])
2052
self.assertBisect(expected, [['a']], state, ['a'])
2053
self.assertBisect(expected, [['b']], state, ['b'])
2054
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2055
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2056
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2057
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2058
self.assertBisect(expected, [['f']], state, ['f'])
2060
def test_bisect_missing(self):
2061
"""Test that bisect return None if it cannot find a path."""
2062
tree, state, expected = self.create_basic_dirstate()
2063
self.assertBisect(expected, [None], state, ['foo'])
2064
self.assertBisect(expected, [None], state, ['b/foo'])
2065
self.assertBisect(expected, [None], state, ['bar/foo'])
2066
self.assertBisect(expected, [None], state, ['b-c/foo'])
2068
self.assertBisect(expected, [['a'], None, ['b/d']],
2069
state, ['a', 'foo', 'b/d'])
2071
def test_bisect_rename(self):
2072
"""Check that we find a renamed row."""
2073
tree, state, expected = self.create_renamed_dirstate()
2075
# Search for the pre and post renamed entries
2076
self.assertBisect(expected, [['a']], state, ['a'])
2077
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2078
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2079
self.assertBisect(expected, [['h']], state, ['h'])
2081
# What about b/d/e? shouldn't that also get 2 directory entries?
2082
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2083
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2085
def test_bisect_dirblocks(self):
2086
tree, state, expected = self.create_duplicated_dirstate()
2087
self.assertBisectDirBlocks(expected,
2088
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2090
self.assertBisectDirBlocks(expected,
2091
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2092
self.assertBisectDirBlocks(expected,
2093
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2094
self.assertBisectDirBlocks(expected,
2095
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2096
['b/c', 'b/c2', 'b/d', 'b/d2'],
2097
['b/d/e', 'b/d/e2'],
2098
], state, ['', 'b', 'b/d'])
2100
def test_bisect_dirblocks_missing(self):
2101
tree, state, expected = self.create_basic_dirstate()
2102
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2103
state, ['b/d', 'b/e'])
2104
# Files don't show up in this search
2105
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2106
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2107
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2108
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2109
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2111
def test_bisect_recursive_each(self):
2112
tree, state, expected = self.create_basic_dirstate()
2113
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2114
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2115
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2116
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2117
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2119
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2121
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2125
def test_bisect_recursive_multiple(self):
2126
tree, state, expected = self.create_basic_dirstate()
2127
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2128
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2129
state, ['b/d', 'b/d/e'])
2131
def test_bisect_recursive_missing(self):
2132
tree, state, expected = self.create_basic_dirstate()
2133
self.assertBisectRecursive(expected, [], state, ['d'])
2134
self.assertBisectRecursive(expected, [], state, ['b/e'])
2135
self.assertBisectRecursive(expected, [], state, ['g'])
2136
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2138
def test_bisect_recursive_renamed(self):
2139
tree, state, expected = self.create_renamed_dirstate()
2141
# Looking for either renamed item should find the other
2142
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2143
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2144
# Looking in the containing directory should find the rename target,
2145
# and anything in a subdir of the renamed target.
2146
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2147
'b/d/e', 'b/g', 'h', 'h/e'],
2151
class TestDirstateValidation(TestCaseWithDirState):
2153
def test_validate_correct_dirstate(self):
2154
state = self.create_complex_dirstate()
2157
# and make sure we can also validate with a read lock
2164
def test_dirblock_not_sorted(self):
2165
tree, state, expected = self.create_renamed_dirstate()
2166
state._read_dirblocks_if_needed()
2167
last_dirblock = state._dirblocks[-1]
2168
# we're appending to the dirblock, but this name comes before some of
2169
# the existing names; that's wrong
2170
last_dirblock[1].append(
2171
(('h', 'aaaa', 'a-id'),
2172
[('a', '', 0, False, ''),
2173
('a', '', 0, False, '')]))
2174
e = self.assertRaises(AssertionError,
2176
self.assertContainsRe(str(e), 'not sorted')
2178
def test_dirblock_name_mismatch(self):
2179
tree, state, expected = self.create_renamed_dirstate()
2180
state._read_dirblocks_if_needed()
2181
last_dirblock = state._dirblocks[-1]
2182
# add an entry with the wrong directory name
2183
last_dirblock[1].append(
2185
[('a', '', 0, False, ''),
2186
('a', '', 0, False, '')]))
2187
e = self.assertRaises(AssertionError,
2189
self.assertContainsRe(str(e),
2190
"doesn't match directory name")
2192
def test_dirblock_missing_rename(self):
2193
tree, state, expected = self.create_renamed_dirstate()
2194
state._read_dirblocks_if_needed()
2195
last_dirblock = state._dirblocks[-1]
2196
# make another entry for a-id, without a correct 'r' pointer to
2197
# the real occurrence in the working tree
2198
last_dirblock[1].append(
2199
(('h', 'z', 'a-id'),
2200
[('a', '', 0, False, ''),
2201
('a', '', 0, False, '')]))
2202
e = self.assertRaises(AssertionError,
2204
self.assertContainsRe(str(e),
2205
'file a-id is absent in row')
2208
class TestDirstateTreeReference(TestCaseWithDirState):
2210
def test_reference_revision_is_none(self):
2211
tree = self.make_branch_and_tree('tree', format='development-subtree')
2212
subtree = self.make_branch_and_tree('tree/subtree',
2213
format='development-subtree')
2214
subtree.set_root_id('subtree')
2215
tree.add_reference(subtree)
2217
state = dirstate.DirState.from_tree(tree, 'dirstate')
2218
key = ('', 'subtree', 'subtree')
2219
expected = ('', [(key,
2220
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2223
self.assertEqual(expected, state._find_block(key))
2228
class TestDiscardMergeParents(TestCaseWithDirState):
2230
def test_discard_no_parents(self):
2231
# This should be a no-op
2232
state = self.create_empty_dirstate()
2233
self.addCleanup(state.unlock)
2234
state._discard_merge_parents()
2237
def test_discard_one_parent(self):
2239
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2240
root_entry_direntry = ('', '', 'a-root-value'), [
2241
('d', '', 0, False, packed_stat),
2242
('d', '', 0, False, packed_stat),
2245
dirblocks.append(('', [root_entry_direntry]))
2246
dirblocks.append(('', []))
2248
state = self.create_empty_dirstate()
2249
self.addCleanup(state.unlock)
2250
state._set_data(['parent-id'], dirblocks[:])
2253
state._discard_merge_parents()
2255
self.assertEqual(dirblocks, state._dirblocks)
2257
def test_discard_simple(self):
2259
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2260
root_entry_direntry = ('', '', 'a-root-value'), [
2261
('d', '', 0, False, packed_stat),
2262
('d', '', 0, False, packed_stat),
2263
('d', '', 0, False, packed_stat),
2265
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2266
('d', '', 0, False, packed_stat),
2267
('d', '', 0, False, packed_stat),
2270
dirblocks.append(('', [root_entry_direntry]))
2271
dirblocks.append(('', []))
2273
state = self.create_empty_dirstate()
2274
self.addCleanup(state.unlock)
2275
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2278
# This should strip of the extra column
2279
state._discard_merge_parents()
2281
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2282
self.assertEqual(expected_dirblocks, state._dirblocks)
2284
def test_discard_absent(self):
2285
"""If entries are only in a merge, discard should remove the entries"""
2286
null_stat = dirstate.DirState.NULLSTAT
2287
present_dir = ('d', '', 0, False, null_stat)
2288
present_file = ('f', '', 0, False, null_stat)
2289
absent = dirstate.DirState.NULL_PARENT_DETAILS
2290
root_key = ('', '', 'a-root-value')
2291
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2292
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2293
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2294
('', [(file_in_merged_key,
2295
[absent, absent, present_file]),
2297
[present_file, present_file, present_file]),
2301
state = self.create_empty_dirstate()
2302
self.addCleanup(state.unlock)
2303
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2306
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2307
('', [(file_in_root_key,
2308
[present_file, present_file]),
2311
state._discard_merge_parents()
2313
self.assertEqual(exp_dirblocks, state._dirblocks)
2315
def test_discard_renamed(self):
2316
null_stat = dirstate.DirState.NULLSTAT
2317
present_dir = ('d', '', 0, False, null_stat)
2318
present_file = ('f', '', 0, False, null_stat)
2319
absent = dirstate.DirState.NULL_PARENT_DETAILS
2320
root_key = ('', '', 'a-root-value')
2321
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2322
# Renamed relative to parent
2323
file_rename_s_key = ('', 'file-s', 'b-file-id')
2324
file_rename_t_key = ('', 'file-t', 'b-file-id')
2325
# And one that is renamed between the parents, but absent in this
2326
key_in_1 = ('', 'file-in-1', 'c-file-id')
2327
key_in_2 = ('', 'file-in-2', 'c-file-id')
2330
('', [(root_key, [present_dir, present_dir, present_dir])]),
2332
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2334
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2336
[present_file, present_file, present_file]),
2338
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2340
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2344
('', [(root_key, [present_dir, present_dir])]),
2345
('', [(key_in_1, [absent, present_file]),
2346
(file_in_root_key, [present_file, present_file]),
2347
(file_rename_t_key, [present_file, absent]),
2350
state = self.create_empty_dirstate()
2351
self.addCleanup(state.unlock)
2352
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2355
state._discard_merge_parents()
2357
self.assertEqual(exp_dirblocks, state._dirblocks)
2359
def test_discard_all_subdir(self):
2360
null_stat = dirstate.DirState.NULLSTAT
2361
present_dir = ('d', '', 0, False, null_stat)
2362
present_file = ('f', '', 0, False, null_stat)
2363
absent = dirstate.DirState.NULL_PARENT_DETAILS
2364
root_key = ('', '', 'a-root-value')
2365
subdir_key = ('', 'sub', 'dir-id')
2366
child1_key = ('sub', 'child1', 'child1-id')
2367
child2_key = ('sub', 'child2', 'child2-id')
2368
child3_key = ('sub', 'child3', 'child3-id')
2371
('', [(root_key, [present_dir, present_dir, present_dir])]),
2372
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2373
('sub', [(child1_key, [absent, absent, present_file]),
2374
(child2_key, [absent, absent, present_file]),
2375
(child3_key, [absent, absent, present_file]),
2379
('', [(root_key, [present_dir, present_dir])]),
2380
('', [(subdir_key, [present_dir, present_dir])]),
2383
state = self.create_empty_dirstate()
2384
self.addCleanup(state.unlock)
2385
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2388
state._discard_merge_parents()
2390
self.assertEqual(exp_dirblocks, state._dirblocks)
2393
class Test_InvEntryToDetails(tests.TestCase):
2395
def assertDetails(self, expected, inv_entry):
2396
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2397
self.assertEqual(expected, details)
2398
# details should always allow join() and always be a plain str when
2400
(minikind, fingerprint, size, executable, tree_data) = details
2401
self.assertIsInstance(minikind, str)
2402
self.assertIsInstance(fingerprint, str)
2403
self.assertIsInstance(tree_data, str)
2405
def test_unicode_symlink(self):
2406
inv_entry = inventory.InventoryLink('link-file-id',
2407
u'nam\N{Euro Sign}e',
2409
inv_entry.revision = 'link-revision-id'
2410
target = u'link-targ\N{Euro Sign}t'
2411
inv_entry.symlink_target = target
2412
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2413
'link-revision-id'), inv_entry)
2416
class TestSHA1Provider(tests.TestCaseInTempDir):
2418
def test_sha1provider_is_an_interface(self):
2419
p = dirstate.SHA1Provider()
2420
self.assertRaises(NotImplementedError, p.sha1, "foo")
2421
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2423
def test_defaultsha1provider_sha1(self):
2424
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2425
self.build_tree_contents([('foo', text)])
2426
expected_sha = osutils.sha_string(text)
2427
p = dirstate.DefaultSHA1Provider()
2428
self.assertEqual(expected_sha, p.sha1('foo'))
2430
def test_defaultsha1provider_stat_and_sha1(self):
2431
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2432
self.build_tree_contents([('foo', text)])
2433
expected_sha = osutils.sha_string(text)
2434
p = dirstate.DefaultSHA1Provider()
2435
statvalue, sha1 = p.stat_and_sha1('foo')
2436
self.assertTrue(len(statvalue) >= 10)
2437
self.assertEqual(len(text), statvalue.st_size)
2438
self.assertEqual(expected_sha, sha1)
2441
class _Repo(object):
2442
"""A minimal api to get InventoryRevisionTree to work."""
2445
default_format = controldir.format_registry.make_bzrdir('default')
2446
self._format = default_format.repository_format
2448
def lock_read(self):
2455
class TestUpdateBasisByDelta(tests.TestCase):
2457
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2458
if path.endswith('/'):
2463
dirname, basename = osutils.split(path)
2465
dir_id = dir_ids[dirname]
2467
dir_id = osutils.basename(dirname) + '-id'
2469
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2470
dir_ids[path] = file_id
2472
ie = inventory.InventoryFile(file_id, basename, dir_id)
2475
ie.revision = rev_id
2478
def create_tree_from_shape(self, rev_id, shape):
2479
dir_ids = {'': 'root-id'}
2480
inv = inventory.Inventory('root-id', rev_id)
2483
path, file_id = info
2486
path, file_id, ie_rev_id = info
2488
# Replace the root entry
2489
del inv._byid[inv.root.file_id]
2490
inv.root.file_id = file_id
2491
inv._byid[file_id] = inv.root
2492
dir_ids[''] = file_id
2494
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2495
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2497
def create_empty_dirstate(self):
2498
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2499
self.addCleanup(os.remove, path)
2501
state = dirstate.DirState.initialize(path)
2502
self.addCleanup(state.unlock)
2505
def create_inv_delta(self, delta, rev_id):
2506
"""Translate a 'delta shape' into an actual InventoryDelta"""
2507
dir_ids = {'': 'root-id'}
2509
for old_path, new_path, file_id in delta:
2510
if old_path is not None and old_path.endswith('/'):
2511
# Don't have to actually do anything for this, because only
2512
# new_path creates InventoryEntries
2513
old_path = old_path[:-1]
2514
if new_path is None: # Delete
2515
inv_delta.append((old_path, None, file_id, None))
2517
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2518
inv_delta.append((old_path, new_path, file_id, ie))
2521
def assertUpdate(self, active, basis, target):
2522
"""Assert that update_basis_by_delta works how we want.
2524
Set up a DirState object with active_shape for tree 0, basis_shape for
2525
tree 1. Then apply the delta from basis_shape to target_shape,
2526
and assert that the DirState is still valid, and that its stored
2527
content matches the target_shape.
2529
active_tree = self.create_tree_from_shape('active', active)
2530
basis_tree = self.create_tree_from_shape('basis', basis)
2531
target_tree = self.create_tree_from_shape('target', target)
2532
state = self.create_empty_dirstate()
2533
state.set_state_from_scratch(active_tree.root_inventory,
2534
[('basis', basis_tree)], [])
2535
delta = target_tree.root_inventory._make_delta(
2536
basis_tree.root_inventory)
2537
state.update_basis_by_delta(delta, 'target')
2539
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2541
# The target now that delta has been applied should match the
2543
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2544
# And the dirblock state should be identical to the state if we created
2546
state2 = self.create_empty_dirstate()
2547
state2.set_state_from_scratch(active_tree.root_inventory,
2548
[('target', target_tree)], [])
2549
self.assertEqual(state2._dirblocks, state._dirblocks)
2552
def assertBadDelta(self, active, basis, delta):
2553
"""Test that we raise InconsistentDelta when appropriate.
2555
:param active: The active tree shape
2556
:param basis: The basis tree shape
2557
:param delta: A description of the delta to apply. Similar to the form
2558
for regular inventory deltas, but omitting the InventoryEntry.
2559
So adding a file is: (None, 'path', 'file-id')
2560
Adding a directory is: (None, 'path/', 'dir-id')
2561
Renaming a dir is: ('old/', 'new/', 'dir-id')
2564
active_tree = self.create_tree_from_shape('active', active)
2565
basis_tree = self.create_tree_from_shape('basis', basis)
2566
inv_delta = self.create_inv_delta(delta, 'target')
2567
state = self.create_empty_dirstate()
2568
state.set_state_from_scratch(active_tree.root_inventory,
2569
[('basis', basis_tree)], [])
2570
self.assertRaises(errors.InconsistentDelta,
2571
state.update_basis_by_delta, inv_delta, 'target')
2573
## state.update_basis_by_delta(inv_delta, 'target')
2574
## except errors.InconsistentDelta, e:
2575
## import pdb; pdb.set_trace()
2577
## import pdb; pdb.set_trace()
2578
self.assertTrue(state._changes_aborted)
2580
def test_remove_file_matching_active_state(self):
2581
state = self.assertUpdate(
2583
basis =[('file', 'file-id')],
2587
def test_remove_file_present_in_active_state(self):
2588
state = self.assertUpdate(
2589
active=[('file', 'file-id')],
2590
basis =[('file', 'file-id')],
2594
def test_remove_file_present_elsewhere_in_active_state(self):
2595
state = self.assertUpdate(
2596
active=[('other-file', 'file-id')],
2597
basis =[('file', 'file-id')],
2601
def test_remove_file_active_state_has_diff_file(self):
2602
state = self.assertUpdate(
2603
active=[('file', 'file-id-2')],
2604
basis =[('file', 'file-id')],
2608
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2609
state = self.assertUpdate(
2610
active=[('file', 'file-id-2'),
2611
('other-file', 'file-id')],
2612
basis =[('file', 'file-id')],
2616
def test_add_file_matching_active_state(self):
2617
state = self.assertUpdate(
2618
active=[('file', 'file-id')],
2620
target=[('file', 'file-id')],
2623
def test_add_file_in_empty_dir_not_matching_active_state(self):
2624
state = self.assertUpdate(
2626
basis=[('dir/', 'dir-id')],
2627
target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
2630
def test_add_file_missing_in_active_state(self):
2631
state = self.assertUpdate(
2634
target=[('file', 'file-id')],
2637
def test_add_file_elsewhere_in_active_state(self):
2638
state = self.assertUpdate(
2639
active=[('other-file', 'file-id')],
2641
target=[('file', 'file-id')],
2644
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2645
state = self.assertUpdate(
2646
active=[('other-file', 'file-id'),
2647
('file', 'file-id-2')],
2649
target=[('file', 'file-id')],
2652
def test_rename_file_matching_active_state(self):
2653
state = self.assertUpdate(
2654
active=[('other-file', 'file-id')],
2655
basis =[('file', 'file-id')],
2656
target=[('other-file', 'file-id')],
2659
def test_rename_file_missing_in_active_state(self):
2660
state = self.assertUpdate(
2662
basis =[('file', 'file-id')],
2663
target=[('other-file', 'file-id')],
2666
def test_rename_file_present_elsewhere_in_active_state(self):
2667
state = self.assertUpdate(
2668
active=[('third', 'file-id')],
2669
basis =[('file', 'file-id')],
2670
target=[('other-file', 'file-id')],
2673
def test_rename_file_active_state_has_diff_source_file(self):
2674
state = self.assertUpdate(
2675
active=[('file', 'file-id-2')],
2676
basis =[('file', 'file-id')],
2677
target=[('other-file', 'file-id')],
2680
def test_rename_file_active_state_has_diff_target_file(self):
2681
state = self.assertUpdate(
2682
active=[('other-file', 'file-id-2')],
2683
basis =[('file', 'file-id')],
2684
target=[('other-file', 'file-id')],
2687
def test_rename_file_active_has_swapped_files(self):
2688
state = self.assertUpdate(
2689
active=[('file', 'file-id'),
2690
('other-file', 'file-id-2')],
2691
basis= [('file', 'file-id'),
2692
('other-file', 'file-id-2')],
2693
target=[('file', 'file-id-2'),
2694
('other-file', 'file-id')])
2696
def test_rename_file_basis_has_swapped_files(self):
2697
state = self.assertUpdate(
2698
active=[('file', 'file-id'),
2699
('other-file', 'file-id-2')],
2700
basis= [('file', 'file-id-2'),
2701
('other-file', 'file-id')],
2702
target=[('file', 'file-id'),
2703
('other-file', 'file-id-2')])
2705
def test_rename_directory_with_contents(self):
2706
state = self.assertUpdate( # active matches basis
2707
active=[('dir1/', 'dir-id'),
2708
('dir1/file', 'file-id')],
2709
basis= [('dir1/', 'dir-id'),
2710
('dir1/file', 'file-id')],
2711
target=[('dir2/', 'dir-id'),
2712
('dir2/file', 'file-id')])
2713
state = self.assertUpdate( # active matches target
2714
active=[('dir2/', 'dir-id'),
2715
('dir2/file', 'file-id')],
2716
basis= [('dir1/', 'dir-id'),
2717
('dir1/file', 'file-id')],
2718
target=[('dir2/', 'dir-id'),
2719
('dir2/file', 'file-id')])
2720
state = self.assertUpdate( # active empty
2722
basis= [('dir1/', 'dir-id'),
2723
('dir1/file', 'file-id')],
2724
target=[('dir2/', 'dir-id'),
2725
('dir2/file', 'file-id')])
2726
state = self.assertUpdate( # active present at other location
2727
active=[('dir3/', 'dir-id'),
2728
('dir3/file', 'file-id')],
2729
basis= [('dir1/', 'dir-id'),
2730
('dir1/file', 'file-id')],
2731
target=[('dir2/', 'dir-id'),
2732
('dir2/file', 'file-id')])
2733
state = self.assertUpdate( # active has different ids
2734
active=[('dir1/', 'dir1-id'),
2735
('dir1/file', 'file1-id'),
2736
('dir2/', 'dir2-id'),
2737
('dir2/file', 'file2-id')],
2738
basis= [('dir1/', 'dir-id'),
2739
('dir1/file', 'file-id')],
2740
target=[('dir2/', 'dir-id'),
2741
('dir2/file', 'file-id')])
2743
def test_invalid_file_not_present(self):
2744
state = self.assertBadDelta(
2745
active=[('file', 'file-id')],
2746
basis= [('file', 'file-id')],
2747
delta=[('other-file', 'file', 'file-id')])
2749
def test_invalid_new_id_same_path(self):
2750
# The bad entry comes after
2751
state = self.assertBadDelta(
2752
active=[('file', 'file-id')],
2753
basis= [('file', 'file-id')],
2754
delta=[(None, 'file', 'file-id-2')])
2755
# The bad entry comes first
2756
state = self.assertBadDelta(
2757
active=[('file', 'file-id-2')],
2758
basis=[('file', 'file-id-2')],
2759
delta=[(None, 'file', 'file-id')])
2761
def test_invalid_existing_id(self):
2762
state = self.assertBadDelta(
2763
active=[('file', 'file-id')],
2764
basis= [('file', 'file-id')],
2765
delta=[(None, 'file', 'file-id')])
2767
def test_invalid_parent_missing(self):
2768
state = self.assertBadDelta(
2771
delta=[(None, 'path/path2', 'file-id')])
2772
# Note: we force the active tree to have the directory, by knowing how
2773
# path_to_ie handles entries with missing parents
2774
state = self.assertBadDelta(
2775
active=[('path/', 'path-id')],
2777
delta=[(None, 'path/path2', 'file-id')])
2778
state = self.assertBadDelta(
2779
active=[('path/', 'path-id'),
2780
('path/path2', 'file-id')],
2782
delta=[(None, 'path/path2', 'file-id')])
2784
def test_renamed_dir_same_path(self):
2785
# We replace the parent directory, with another parent dir. But the C
2786
# file doesn't look like it has been moved.
2787
state = self.assertUpdate(# Same as basis
2788
active=[('dir/', 'A-id'),
2790
basis= [('dir/', 'A-id'),
2792
target=[('dir/', 'C-id'),
2794
state = self.assertUpdate(# Same as target
2795
active=[('dir/', 'C-id'),
2797
basis= [('dir/', 'A-id'),
2799
target=[('dir/', 'C-id'),
2801
state = self.assertUpdate(# empty active
2803
basis= [('dir/', 'A-id'),
2805
target=[('dir/', 'C-id'),
2807
state = self.assertUpdate(# different active
2808
active=[('dir/', 'D-id'),
2810
basis= [('dir/', 'A-id'),
2812
target=[('dir/', 'C-id'),
2815
def test_parent_child_swap(self):
2816
state = self.assertUpdate(# Same as basis
2817
active=[('A/', 'A-id'),
2820
basis= [('A/', 'A-id'),
2823
target=[('A/', 'B-id'),
2826
state = self.assertUpdate(# Same as target
2827
active=[('A/', 'B-id'),
2830
basis= [('A/', 'A-id'),
2833
target=[('A/', 'B-id'),
2836
state = self.assertUpdate(# empty active
2838
basis= [('A/', 'A-id'),
2841
target=[('A/', 'B-id'),
2844
state = self.assertUpdate(# different active
2845
active=[('D/', 'A-id'),
2848
basis= [('A/', 'A-id'),
2851
target=[('A/', 'B-id'),
2855
def test_change_root_id(self):
2856
state = self.assertUpdate( # same as basis
2857
active=[('', 'root-id'),
2858
('file', 'file-id')],
2859
basis= [('', 'root-id'),
2860
('file', 'file-id')],
2861
target=[('', 'target-root-id'),
2862
('file', 'file-id')])
2863
state = self.assertUpdate( # same as target
2864
active=[('', 'target-root-id'),
2865
('file', 'file-id')],
2866
basis= [('', 'root-id'),
2867
('file', 'file-id')],
2868
target=[('', 'target-root-id'),
2869
('file', 'root-id')])
2870
state = self.assertUpdate( # all different
2871
active=[('', 'active-root-id'),
2872
('file', 'file-id')],
2873
basis= [('', 'root-id'),
2874
('file', 'file-id')],
2875
target=[('', 'target-root-id'),
2876
('file', 'root-id')])
2878
def test_change_file_absent_in_active(self):
2879
state = self.assertUpdate(
2881
basis= [('file', 'file-id')],
2882
target=[('file', 'file-id')])
2884
def test_invalid_changed_file(self):
2885
state = self.assertBadDelta( # Not present in basis
2886
active=[('file', 'file-id')],
2888
delta=[('file', 'file', 'file-id')])
2889
state = self.assertBadDelta( # present at another location in basis
2890
active=[('file', 'file-id')],
2891
basis= [('other-file', 'file-id')],
2892
delta=[('file', 'file', 'file-id')])