1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
27
revision as _mod_revision,
41
from .scenarios import load_tests_apply_scenarios
46
# general checks for NOT_IN_MEMORY error conditions.
47
# set_path_id on a NOT_IN_MEMORY dirstate
48
# set_path_id unicode support
49
# set_path_id setting id of a path not root
50
# set_path_id setting id when there are parents without the id in the parents
51
# set_path_id setting id when there are parents with the id in the parents
52
# set_path_id setting id when state is not in memory
53
# set_path_id setting id when state is in memory unmodified
54
# set_path_id setting id when state is in memory modified
57
class TestErrors(tests.TestCase):
59
def test_dirstate_corrupt(self):
60
error = dirstate.DirstateCorrupt('.bzr/checkout/dirstate',
61
'trailing garbage: "x"')
62
self.assertEqualDiff("The dirstate file (.bzr/checkout/dirstate)"
63
" appears to be corrupt: trailing garbage: \"x\"",
67
load_tests = load_tests_apply_scenarios
70
class TestCaseWithDirState(tests.TestCaseWithTransport):
71
"""Helper functions for creating DirState objects with various content."""
73
scenarios = test_osutils.dir_reader_scenarios()
76
_dir_reader_class = None
77
_native_to_unicode = None # Not used yet
80
super(TestCaseWithDirState, self).setUp()
81
self.overrideAttr(osutils,
82
'_selected_dir_reader', self._dir_reader_class())
84
def create_empty_dirstate(self):
85
"""Return a locked but empty dirstate"""
86
state = dirstate.DirState.initialize('dirstate')
89
def create_dirstate_with_root(self):
90
"""Return a write-locked state with a single root entry."""
91
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
92
root_entry_direntry = (b'', b'', b'a-root-value'), [
93
(b'd', b'', 0, False, packed_stat),
96
dirblocks.append((b'', [root_entry_direntry]))
97
dirblocks.append((b'', []))
98
state = self.create_empty_dirstate()
100
state._set_data([], dirblocks)
107
def create_dirstate_with_root_and_subdir(self):
108
"""Return a locked DirState with a root and a subdir"""
109
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
subdir_entry = (b'', b'subdir', b'subdir-id'), [
111
(b'd', b'', 0, False, packed_stat),
113
state = self.create_dirstate_with_root()
115
dirblocks = list(state._dirblocks)
116
dirblocks[1][1].append(subdir_entry)
117
state._set_data([], dirblocks)
123
def create_complex_dirstate(self):
124
"""This dirstate contains multiple files and directories.
134
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
136
Notice that a/e is an empty directory.
138
:return: The dirstate, still write-locked.
140
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
141
null_sha = b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
142
root_entry = (b'', b'', b'a-root-value'), [
143
(b'd', b'', 0, False, packed_stat),
145
a_entry = (b'', b'a', b'a-dir'), [
146
(b'd', b'', 0, False, packed_stat),
148
b_entry = (b'', b'b', b'b-dir'), [
149
(b'd', b'', 0, False, packed_stat),
151
c_entry = (b'', b'c', b'c-file'), [
152
(b'f', null_sha, 10, False, packed_stat),
154
d_entry = (b'', b'd', b'd-file'), [
155
(b'f', null_sha, 20, False, packed_stat),
157
e_entry = (b'a', b'e', b'e-dir'), [
158
(b'd', b'', 0, False, packed_stat),
160
f_entry = (b'a', b'f', b'f-file'), [
161
(b'f', null_sha, 30, False, packed_stat),
163
g_entry = (b'b', b'g', b'g-file'), [
164
(b'f', null_sha, 30, False, packed_stat),
166
h_entry = (b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file'), [
167
(b'f', null_sha, 40, False, packed_stat),
170
dirblocks.append((b'', [root_entry]))
171
dirblocks.append((b'', [a_entry, b_entry, c_entry, d_entry]))
172
dirblocks.append((b'a', [e_entry, f_entry]))
173
dirblocks.append((b'b', [g_entry, h_entry]))
174
state = dirstate.DirState.initialize('dirstate')
177
state._set_data([], dirblocks)
183
def check_state_with_reopen(self, expected_result, state):
184
"""Check that state has current state expected_result.
186
This will check the current state, open the file anew and check it
188
This function expects the current state to be locked for writing, and
189
will unlock it before re-opening.
190
This is required because we can't open a lock_read() while something
191
else has a lock_write().
192
write => mutually exclusive lock
195
# The state should already be write locked, since we just had to do
196
# some operation to get here.
197
self.assertTrue(state._lock_token is not None)
199
self.assertEqual(expected_result[0], state.get_parent_ids())
200
# there should be no ghosts in this tree.
201
self.assertEqual([], state.get_ghosts())
202
# there should be one fileid in this tree - the root of the tree.
203
self.assertEqual(expected_result[1], list(state._iter_entries()))
208
state = dirstate.DirState.on_file('dirstate')
211
self.assertEqual(expected_result[1], list(state._iter_entries()))
215
def create_basic_dirstate(self):
216
"""Create a dirstate with a few files and directories.
226
tree = self.make_branch_and_tree('tree')
227
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
228
file_ids = [b'a-id', b'b-id', b'c-id',
229
b'd-id', b'e-id', b'b-c-id', b'f-id']
230
self.build_tree(['tree/' + p for p in paths])
231
tree.set_root_id(b'TREE_ROOT')
232
tree.add([p.rstrip('/') for p in paths], file_ids)
233
tree.commit('initial', rev_id=b'rev-1')
234
revision_id = b'rev-1'
235
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
236
t = self.get_transport('tree')
237
a_text = t.get_bytes('a')
238
a_sha = osutils.sha_string(a_text)
240
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
241
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
242
c_text = t.get_bytes('b/c')
243
c_sha = osutils.sha_string(c_text)
245
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
246
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
247
e_text = t.get_bytes('b/d/e')
248
e_sha = osutils.sha_string(e_text)
250
b_c_text = t.get_bytes('b-c')
251
b_c_sha = osutils.sha_string(b_c_text)
252
b_c_len = len(b_c_text)
253
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
254
f_text = t.get_bytes('f')
255
f_sha = osutils.sha_string(f_text)
257
null_stat = dirstate.DirState.NULLSTAT
259
b'': ((b'', b'', b'TREE_ROOT'), [
260
(b'd', b'', 0, False, null_stat),
261
(b'd', b'', 0, False, revision_id),
263
b'a': ((b'', b'a', b'a-id'), [
264
(b'f', b'', 0, False, null_stat),
265
(b'f', a_sha, a_len, False, revision_id),
267
b'b': ((b'', b'b', b'b-id'), [
268
(b'd', b'', 0, False, null_stat),
269
(b'd', b'', 0, False, revision_id),
271
b'b/c': ((b'b', b'c', b'c-id'), [
272
(b'f', b'', 0, False, null_stat),
273
(b'f', c_sha, c_len, False, revision_id),
275
b'b/d': ((b'b', b'd', b'd-id'), [
276
(b'd', b'', 0, False, null_stat),
277
(b'd', b'', 0, False, revision_id),
279
b'b/d/e': ((b'b/d', b'e', b'e-id'), [
280
(b'f', b'', 0, False, null_stat),
281
(b'f', e_sha, e_len, False, revision_id),
283
b'b-c': ((b'', b'b-c', b'b-c-id'), [
284
(b'f', b'', 0, False, null_stat),
285
(b'f', b_c_sha, b_c_len, False, revision_id),
287
b'f': ((b'', b'f', b'f-id'), [
288
(b'f', b'', 0, False, null_stat),
289
(b'f', f_sha, f_len, False, revision_id),
292
state = dirstate.DirState.from_tree(tree, 'dirstate')
297
# Use a different object, to make sure nothing is pre-cached in memory.
298
state = dirstate.DirState.on_file('dirstate')
300
self.addCleanup(state.unlock)
301
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
302
state._dirblock_state)
303
# This is code is only really tested if we actually have to make more
304
# than one read, so set the page size to something smaller.
305
# We want it to contain about 2.2 records, so that we have a couple
306
# records that we can read per attempt
307
state._bisect_page_size = 200
308
return tree, state, expected
310
def create_duplicated_dirstate(self):
311
"""Create a dirstate with a deleted and added entries.
313
This grabs a basic_dirstate, and then removes and re adds every entry
316
tree, state, expected = self.create_basic_dirstate()
317
# Now we will just remove and add every file so we get an extra entry
318
# per entry. Unversion in reverse order so we handle subdirs
319
tree.unversion(['f', 'b-c', 'b/d/e', 'b/d', 'b/c', 'b', 'a'])
320
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
321
[b'a-id2', b'b-id2', b'c-id2', b'd-id2', b'e-id2', b'b-c-id2', b'f-id2'])
323
# Update the expected dictionary.
324
for path in [b'a', b'b', b'b/c', b'b/d', b'b/d/e', b'b-c', b'f']:
325
orig = expected[path]
327
# This record was deleted in the current tree
328
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
330
new_key = (orig[0][0], orig[0][1], orig[0][2] + b'2')
331
# And didn't exist in the basis tree
332
expected[path2] = (new_key, [orig[1][0],
333
dirstate.DirState.NULL_PARENT_DETAILS])
335
# We will replace the 'dirstate' file underneath 'state', but that is
336
# okay as lock as we unlock 'state' first.
339
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
345
# But we need to leave state in a read-lock because we already have
346
# a cleanup scheduled
348
return tree, state, expected
350
def create_renamed_dirstate(self):
351
"""Create a dirstate with a few internal renames.
353
This takes the basic dirstate, and moves the paths around.
355
tree, state, expected = self.create_basic_dirstate()
357
tree.rename_one('a', 'b/g')
359
tree.rename_one('b/d', 'h')
361
old_a = expected[b'a']
363
old_a[0], [(b'r', b'b/g', 0, False, b''), old_a[1][1]])
364
expected[b'b/g'] = ((b'b', b'g', b'a-id'), [old_a[1][0],
365
(b'r', b'a', 0, False, b'')])
366
old_d = expected[b'b/d']
367
expected[b'b/d'] = (old_d[0],
368
[(b'r', b'h', 0, False, b''), old_d[1][1]])
369
expected[b'h'] = ((b'', b'h', b'd-id'), [old_d[1][0],
370
(b'r', b'b/d', 0, False, b'')])
372
old_e = expected[b'b/d/e']
373
expected[b'b/d/e'] = (old_e[0], [(b'r', b'h/e', 0, False, b''),
375
expected[b'h/e'] = ((b'h', b'e', b'e-id'), [old_e[1][0],
376
(b'r', b'b/d/e', 0, False, b'')])
380
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
387
return tree, state, expected
390
class TestTreeToDirState(TestCaseWithDirState):
392
def test_empty_to_dirstate(self):
393
"""We should be able to create a dirstate for an empty tree."""
394
# There are no files on disk and no parents
395
tree = self.make_branch_and_tree('tree')
396
expected_result = ([], [
397
((b'', b'', tree.get_root_id()), # common details
398
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
400
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
404
def test_1_parents_empty_to_dirstate(self):
405
# create a parent by doing a commit
406
tree = self.make_branch_and_tree('tree')
407
rev_id = tree.commit('first post')
408
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
409
expected_result = ([rev_id], [
410
((b'', b'', tree.get_root_id()), # common details
411
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
412
(b'd', b'', 0, False, rev_id), # first parent details
414
state = dirstate.DirState.from_tree(tree, 'dirstate')
415
self.check_state_with_reopen(expected_result, state)
422
def test_2_parents_empty_to_dirstate(self):
423
# create a parent by doing a commit
424
tree = self.make_branch_and_tree('tree')
425
rev_id = tree.commit('first post')
426
tree2 = tree.controldir.sprout('tree2').open_workingtree()
427
rev_id2 = tree2.commit('second post', allow_pointless=True)
428
tree.merge_from_branch(tree2.branch)
429
expected_result = ([rev_id, rev_id2], [
430
((b'', b'', tree.get_root_id()), # common details
431
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
432
(b'd', b'', 0, False, rev_id), # first parent details
433
(b'd', b'', 0, False, rev_id), # second parent details
435
state = dirstate.DirState.from_tree(tree, 'dirstate')
436
self.check_state_with_reopen(expected_result, state)
443
def test_empty_unknowns_are_ignored_to_dirstate(self):
444
"""We should be able to create a dirstate for an empty tree."""
445
# There are no files on disk and no parents
446
tree = self.make_branch_and_tree('tree')
447
self.build_tree(['tree/unknown'])
448
expected_result = ([], [
449
((b'', b'', tree.get_root_id()), # common details
450
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
state = dirstate.DirState.from_tree(tree, 'dirstate')
453
self.check_state_with_reopen(expected_result, state)
455
def get_tree_with_a_file(self):
456
tree = self.make_branch_and_tree('tree')
457
self.build_tree(['tree/a file'])
458
tree.add('a file', b'a-file-id')
461
def test_non_empty_no_parents_to_dirstate(self):
462
"""We should be able to create a dirstate for an empty tree."""
463
# There are files on disk and no parents
464
tree = self.get_tree_with_a_file()
465
expected_result = ([], [
466
((b'', b'', tree.get_root_id()), # common details
467
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
469
((b'', b'a file', b'a-file-id'), # common
470
[(b'f', b'', 0, False, dirstate.DirState.NULLSTAT), # current
473
state = dirstate.DirState.from_tree(tree, 'dirstate')
474
self.check_state_with_reopen(expected_result, state)
476
def test_1_parents_not_empty_to_dirstate(self):
477
# create a parent by doing a commit
478
tree = self.get_tree_with_a_file()
479
rev_id = tree.commit('first post')
480
# change the current content to be different this will alter stat, sha
482
self.build_tree_contents([('tree/a file', b'new content\n')])
483
expected_result = ([rev_id], [
484
((b'', b'', tree.get_root_id()), # common details
485
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
486
(b'd', b'', 0, False, rev_id), # first parent details
488
((b'', b'a file', b'a-file-id'), # common
489
[(b'f', b'', 0, False, dirstate.DirState.NULLSTAT), # current
490
(b'f', b'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
491
rev_id), # first parent
494
state = dirstate.DirState.from_tree(tree, 'dirstate')
495
self.check_state_with_reopen(expected_result, state)
497
def test_2_parents_not_empty_to_dirstate(self):
498
# create a parent by doing a commit
499
tree = self.get_tree_with_a_file()
500
rev_id = tree.commit('first post')
501
tree2 = tree.controldir.sprout('tree2').open_workingtree()
502
# change the current content to be different this will alter stat, sha
504
self.build_tree_contents([('tree2/a file', b'merge content\n')])
505
rev_id2 = tree2.commit('second post')
506
tree.merge_from_branch(tree2.branch)
507
# change the current content to be different this will alter stat, sha
508
# and length again, giving us three distinct values:
509
self.build_tree_contents([('tree/a file', b'new content\n')])
510
expected_result = ([rev_id, rev_id2], [
511
((b'', b'', tree.get_root_id()), # common details
512
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
513
(b'd', b'', 0, False, rev_id), # first parent details
514
(b'd', b'', 0, False, rev_id), # second parent details
516
((b'', b'a file', b'a-file-id'), # common
517
[(b'f', b'', 0, False, dirstate.DirState.NULLSTAT), # current
518
(b'f', b'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
519
rev_id), # first parent
520
(b'f', b'314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
521
rev_id2), # second parent
524
state = dirstate.DirState.from_tree(tree, 'dirstate')
525
self.check_state_with_reopen(expected_result, state)
527
def test_colliding_fileids(self):
528
# test insertion of parents creating several entries at the same path.
529
# we used to have a bug where they could cause the dirstate to break
530
# its ordering invariants.
531
# create some trees to test from
534
tree = self.make_branch_and_tree('tree%d' % i)
535
self.build_tree(['tree%d/name' % i, ])
536
tree.add(['name'], [b'file-id%d' % i])
537
revision_id = b'revid-%d' % i
538
tree.commit('message', rev_id=revision_id)
539
parents.append((revision_id,
540
tree.branch.repository.revision_tree(revision_id)))
541
# now fold these trees into a dirstate
542
state = dirstate.DirState.initialize('dirstate')
544
state.set_parent_trees(parents, [])
550
class TestDirStateOnFile(TestCaseWithDirState):
552
def create_updated_dirstate(self):
553
self.build_tree(['a-file'])
554
tree = self.make_branch_and_tree('.')
555
tree.add(['a-file'], [b'a-id'])
556
tree.commit('add a-file')
557
# Save and unlock the state, re-open it in readonly mode
558
state = dirstate.DirState.from_tree(tree, 'dirstate')
561
state = dirstate.DirState.on_file('dirstate')
565
def test_construct_with_path(self):
566
tree = self.make_branch_and_tree('tree')
567
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
568
# we want to be able to get the lines of the dirstate that we will
570
lines = state.get_lines()
572
self.build_tree_contents([('dirstate', b''.join(lines))])
574
# no parents, default tree content
575
expected_result = ([], [
576
((b'', b'', tree.get_root_id()), # common details
577
# current tree details, but new from_tree skips statting, it
578
# uses set_state_from_inventory, and thus depends on the
580
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT),
583
state = dirstate.DirState.on_file('dirstate')
584
state.lock_write() # check_state_with_reopen will save() and unlock it
585
self.check_state_with_reopen(expected_result, state)
587
def test_can_save_clean_on_file(self):
588
tree = self.make_branch_and_tree('tree')
589
state = dirstate.DirState.from_tree(tree, 'dirstate')
591
# doing a save should work here as there have been no changes.
593
# TODO: stat it and check it hasn't changed; may require waiting
594
# for the state accuracy window.
598
def test_can_save_in_read_lock(self):
599
state = self.create_updated_dirstate()
601
entry = state._get_entry(0, path_utf8=b'a-file')
602
# The current size should be 0 (default)
603
self.assertEqual(0, entry[1][0][2])
604
# We should have a real entry.
605
self.assertNotEqual((None, None), entry)
606
# Set the cutoff-time into the future, so things look cacheable
607
state._sha_cutoff_time()
608
state._cutoff_time += 10.0
609
st = os.lstat('a-file')
610
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
611
# We updated the current sha1sum because the file is cacheable
612
self.assertEqual(b'ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
615
# The dirblock has been updated
616
self.assertEqual(st.st_size, entry[1][0][2])
617
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
618
state._dirblock_state)
621
# Now, since we are the only one holding a lock, we should be able
622
# to save and have it written to disk
627
# Re-open the file, and ensure that the state has been updated.
628
state = dirstate.DirState.on_file('dirstate')
631
entry = state._get_entry(0, path_utf8=b'a-file')
632
self.assertEqual(st.st_size, entry[1][0][2])
636
def test_save_fails_quietly_if_locked(self):
637
"""If dirstate is locked, save will fail without complaining."""
638
state = self.create_updated_dirstate()
640
entry = state._get_entry(0, path_utf8=b'a-file')
641
# No cached sha1 yet.
642
self.assertEqual(b'', entry[1][0][1])
643
# Set the cutoff-time into the future, so things look cacheable
644
state._sha_cutoff_time()
645
state._cutoff_time += 10.0
646
st = os.lstat('a-file')
647
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
648
self.assertEqual(b'ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
650
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
651
state._dirblock_state)
653
# Now, before we try to save, grab another dirstate, and take out a
655
# TODO: jam 20070315 Ideally this would be locked by another
656
# process. To make sure the file is really OS locked.
657
state2 = dirstate.DirState.on_file('dirstate')
660
# This won't actually write anything, because it couldn't grab
661
# a write lock. But it shouldn't raise an error, either.
662
# TODO: jam 20070315 We should probably distinguish between
663
# being dirty because of 'update_entry'. And dirty
664
# because of real modification. So that save() *does*
665
# raise a real error if it fails when we have real
673
# The file on disk should not be modified.
674
state = dirstate.DirState.on_file('dirstate')
677
entry = state._get_entry(0, path_utf8=b'a-file')
678
self.assertEqual(b'', entry[1][0][1])
682
def test_save_refuses_if_changes_aborted(self):
683
self.build_tree(['a-file', 'a-dir/'])
684
state = dirstate.DirState.initialize('dirstate')
686
# No stat and no sha1 sum.
687
state.add('a-file', b'a-file-id', 'file', None, b'')
692
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
694
(b'', [((b'', b'', b'TREE_ROOT'),
695
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT)])]),
696
(b'', [((b'', b'a-file', b'a-file-id'),
697
[(b'f', b'', 0, False, dirstate.DirState.NULLSTAT)])]),
700
state = dirstate.DirState.on_file('dirstate')
703
state._read_dirblocks_if_needed()
704
self.assertEqual(expected_blocks, state._dirblocks)
706
# Now modify the state, but mark it as inconsistent
707
state.add('a-dir', b'a-dir-id', 'directory', None, b'')
708
state._changes_aborted = True
713
state = dirstate.DirState.on_file('dirstate')
716
state._read_dirblocks_if_needed()
717
self.assertEqual(expected_blocks, state._dirblocks)
722
class TestDirStateInitialize(TestCaseWithDirState):
724
def test_initialize(self):
725
expected_result = ([], [
726
((b'', b'', b'TREE_ROOT'), # common details
727
[(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
730
state = dirstate.DirState.initialize('dirstate')
732
self.assertIsInstance(state, dirstate.DirState)
733
lines = state.get_lines()
736
# On win32 you can't read from a locked file, even within the same
737
# process. So we have to unlock and release before we check the file
739
self.assertFileEqual(b''.join(lines), 'dirstate')
740
state.lock_read() # check_state_with_reopen will unlock
741
self.check_state_with_reopen(expected_result, state)
744
class TestDirStateManipulations(TestCaseWithDirState):
746
def make_minimal_tree(self):
747
tree1 = self.make_branch_and_memory_tree('tree1')
749
self.addCleanup(tree1.unlock)
751
revid1 = tree1.commit('foo')
754
def test_update_minimal_updates_id_index(self):
755
state = self.create_dirstate_with_root_and_subdir()
756
self.addCleanup(state.unlock)
757
id_index = state._get_id_index()
758
self.assertEqual([b'a-root-value', b'subdir-id'], sorted(id_index))
759
state.add('file-name', b'file-id', 'file', None, '')
760
self.assertEqual([b'a-root-value', b'file-id', b'subdir-id'],
762
state.update_minimal((b'', b'new-name', b'file-id'), b'f',
763
path_utf8=b'new-name')
764
self.assertEqual([b'a-root-value', b'file-id', b'subdir-id'],
766
self.assertEqual([(b'', b'new-name', b'file-id')],
767
sorted(id_index[b'file-id']))
770
def test_set_state_from_inventory_no_content_no_parents(self):
771
# setting the current inventory is a slow but important api to support.
772
tree1, revid1 = self.make_minimal_tree()
773
inv = tree1.root_inventory
774
root_id = inv.path2id('')
775
expected_result = [], [
776
((b'', b'', root_id), [
777
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT)])]
778
state = dirstate.DirState.initialize('dirstate')
780
state.set_state_from_inventory(inv)
781
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
783
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
784
state._dirblock_state)
789
# This will unlock it
790
self.check_state_with_reopen(expected_result, state)
792
def test_set_state_from_scratch_no_parents(self):
793
tree1, revid1 = self.make_minimal_tree()
794
inv = tree1.root_inventory
795
root_id = inv.path2id('')
796
expected_result = [], [
797
((b'', b'', root_id), [
798
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT)])]
799
state = dirstate.DirState.initialize('dirstate')
801
state.set_state_from_scratch(inv, [], [])
802
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
804
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
805
state._dirblock_state)
810
# This will unlock it
811
self.check_state_with_reopen(expected_result, state)
813
def test_set_state_from_scratch_identical_parent(self):
814
tree1, revid1 = self.make_minimal_tree()
815
inv = tree1.root_inventory
816
root_id = inv.path2id('')
817
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
818
d_entry = (b'd', b'', 0, False, dirstate.DirState.NULLSTAT)
819
parent_entry = (b'd', b'', 0, False, revid1)
820
expected_result = [revid1], [
821
((b'', b'', root_id), [d_entry, parent_entry])]
822
state = dirstate.DirState.initialize('dirstate')
824
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
825
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
827
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
828
state._dirblock_state)
833
# This will unlock it
834
self.check_state_with_reopen(expected_result, state)
836
def test_set_state_from_inventory_preserves_hashcache(self):
837
# https://bugs.launchpad.net/bzr/+bug/146176
838
# set_state_from_inventory should preserve the stat and hash value for
839
# workingtree files that are not changed by the inventory.
841
tree = self.make_branch_and_tree('.')
842
# depends on the default format using dirstate...
843
with tree.lock_write():
844
# make a dirstate with some valid hashcache data
845
# file on disk, but that's not needed for this test
846
foo_contents = b'contents of foo'
847
self.build_tree_contents([('foo', foo_contents)])
848
tree.add('foo', b'foo-id')
850
foo_stat = os.stat('foo')
851
foo_packed = dirstate.pack_stat(foo_stat)
852
foo_sha = osutils.sha_string(foo_contents)
853
foo_size = len(foo_contents)
855
# should not be cached yet, because the file's too fresh
857
((b'', b'foo', b'foo-id',),
858
[(b'f', b'', 0, False, dirstate.DirState.NULLSTAT)]),
859
tree._dirstate._get_entry(0, b'foo-id'))
860
# poke in some hashcache information - it wouldn't normally be
861
# stored because it's too fresh
862
tree._dirstate.update_minimal(
863
(b'', b'foo', b'foo-id'),
864
b'f', False, foo_sha, foo_packed, foo_size, b'foo')
865
# now should be cached
867
((b'', b'foo', b'foo-id',),
868
[(b'f', foo_sha, foo_size, False, foo_packed)]),
869
tree._dirstate._get_entry(0, b'foo-id'))
871
# extract the inventory, and add something to it
872
inv = tree._get_root_inventory()
873
# should see the file we poked in...
874
self.assertTrue(inv.has_id(b'foo-id'))
875
self.assertTrue(inv.has_filename('foo'))
876
inv.add_path('bar', 'file', b'bar-id')
877
tree._dirstate._validate()
878
# this used to cause it to lose its hashcache
879
tree._dirstate.set_state_from_inventory(inv)
880
tree._dirstate._validate()
882
with tree.lock_read():
883
# now check that the state still has the original hashcache value
884
state = tree._dirstate
886
foo_tuple = state._get_entry(0, path_utf8=b'foo')
888
((b'', b'foo', b'foo-id',),
889
[(b'f', foo_sha, len(foo_contents), False,
890
dirstate.pack_stat(foo_stat))]),
893
def test_set_state_from_inventory_mixed_paths(self):
894
tree1 = self.make_branch_and_tree('tree1')
895
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
896
'tree1/a/b/foo', 'tree1/a-b/bar'])
899
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
900
[b'a-id', b'b-id', b'a-b-id', b'foo-id', b'bar-id'])
901
tree1.commit('rev1', rev_id=b'rev1')
902
root_id = tree1.get_root_id()
903
inv = tree1.root_inventory
906
expected_result1 = [(b'', b'', root_id, b'd'),
907
(b'', b'a', b'a-id', b'd'),
908
(b'', b'a-b', b'a-b-id', b'd'),
909
(b'a', b'b', b'b-id', b'd'),
910
(b'a/b', b'foo', b'foo-id', b'f'),
911
(b'a-b', b'bar', b'bar-id', b'f'),
913
expected_result2 = [(b'', b'', root_id, b'd'),
914
(b'', b'a', b'a-id', b'd'),
915
(b'', b'a-b', b'a-b-id', b'd'),
916
(b'a-b', b'bar', b'bar-id', b'f'),
918
state = dirstate.DirState.initialize('dirstate')
920
state.set_state_from_inventory(inv)
922
for entry in state._iter_entries():
923
values.append(entry[0] + entry[1][0][:1])
924
self.assertEqual(expected_result1, values)
926
state.set_state_from_inventory(inv)
928
for entry in state._iter_entries():
929
values.append(entry[0] + entry[1][0][:1])
930
self.assertEqual(expected_result2, values)
934
def test_set_path_id_no_parents(self):
935
"""The id of a path can be changed trivally with no parents."""
936
state = dirstate.DirState.initialize('dirstate')
938
# check precondition to be sure the state does change appropriately.
939
root_entry = ((b'', b'', b'TREE_ROOT'), [
940
(b'd', b'', 0, False, b'x' * 32)])
941
self.assertEqual([root_entry], list(state._iter_entries()))
942
self.assertEqual(root_entry, state._get_entry(0, path_utf8=b''))
943
self.assertEqual(root_entry,
944
state._get_entry(0, fileid_utf8=b'TREE_ROOT'))
945
self.assertEqual((None, None),
946
state._get_entry(0, fileid_utf8=b'second-root-id'))
947
state.set_path_id(b'', b'second-root-id')
948
new_root_entry = ((b'', b'', b'second-root-id'),
949
[(b'd', b'', 0, False, b'x' * 32)])
950
expected_rows = [new_root_entry]
951
self.assertEqual(expected_rows, list(state._iter_entries()))
953
new_root_entry, state._get_entry(0, path_utf8=b''))
954
self.assertEqual(new_root_entry,
955
state._get_entry(0, fileid_utf8=b'second-root-id'))
956
self.assertEqual((None, None),
957
state._get_entry(0, fileid_utf8=b'TREE_ROOT'))
958
# should work across save too
962
state = dirstate.DirState.on_file('dirstate')
966
self.assertEqual(expected_rows, list(state._iter_entries()))
970
def test_set_path_id_with_parents(self):
971
"""Set the root file id in a dirstate with parents"""
972
mt = self.make_branch_and_tree('mt')
973
# in case the default tree format uses a different root id
974
mt.set_root_id(b'TREE_ROOT')
975
mt.commit('foo', rev_id=b'parent-revid')
976
rt = mt.branch.repository.revision_tree(b'parent-revid')
977
state = dirstate.DirState.initialize('dirstate')
980
state.set_parent_trees([(b'parent-revid', rt)], ghosts=[])
981
root_entry = ((b'', b'', b'TREE_ROOT'),
982
[(b'd', b'', 0, False, b'x' * 32),
983
(b'd', b'', 0, False, b'parent-revid')])
984
self.assertEqual(root_entry, state._get_entry(0, path_utf8=b''))
985
self.assertEqual(root_entry,
986
state._get_entry(0, fileid_utf8=b'TREE_ROOT'))
987
self.assertEqual((None, None),
988
state._get_entry(0, fileid_utf8=b'Asecond-root-id'))
989
state.set_path_id(b'', b'Asecond-root-id')
991
# now see that it is what we expected
992
old_root_entry = ((b'', b'', b'TREE_ROOT'),
993
[(b'a', b'', 0, False, b''),
994
(b'd', b'', 0, False, b'parent-revid')])
995
new_root_entry = ((b'', b'', b'Asecond-root-id'),
996
[(b'd', b'', 0, False, b''),
997
(b'a', b'', 0, False, b'')])
998
expected_rows = [new_root_entry, old_root_entry]
1000
self.assertEqual(expected_rows, list(state._iter_entries()))
1002
new_root_entry, state._get_entry(0, path_utf8=b''))
1004
old_root_entry, state._get_entry(1, path_utf8=b''))
1005
self.assertEqual((None, None),
1006
state._get_entry(0, fileid_utf8=b'TREE_ROOT'))
1007
self.assertEqual(old_root_entry,
1008
state._get_entry(1, fileid_utf8=b'TREE_ROOT'))
1009
self.assertEqual(new_root_entry,
1010
state._get_entry(0, fileid_utf8=b'Asecond-root-id'))
1011
self.assertEqual((None, None),
1012
state._get_entry(1, fileid_utf8=b'Asecond-root-id'))
1013
# should work across save too
1017
# now flush & check we get the same
1018
state = dirstate.DirState.on_file('dirstate')
1022
self.assertEqual(expected_rows, list(state._iter_entries()))
1025
# now change within an existing file-backed state
1029
state.set_path_id(b'', b'tree-root-2')
1034
def test_set_parent_trees_no_content(self):
1035
# set_parent_trees is a slow but important api to support.
1036
tree1 = self.make_branch_and_memory_tree('tree1')
1040
revid1 = tree1.commit('foo')
1043
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1044
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1047
revid2 = tree2.commit('foo')
1048
root_id = tree2.get_root_id()
1051
state = dirstate.DirState.initialize('dirstate')
1053
state.set_path_id(b'', root_id)
1054
state.set_parent_trees(
1055
((revid1, tree1.branch.repository.revision_tree(revid1)),
1056
(revid2, tree2.branch.repository.revision_tree(revid2)),
1057
(b'ghost-rev', None)),
1059
# check we can reopen and use the dirstate after setting parent
1066
state = dirstate.DirState.on_file('dirstate')
1069
self.assertEqual([revid1, revid2, b'ghost-rev'],
1070
state.get_parent_ids())
1071
# iterating the entire state ensures that the state is parsable.
1072
list(state._iter_entries())
1073
# be sure that it sets not appends - change it
1074
state.set_parent_trees(
1075
((revid1, tree1.branch.repository.revision_tree(revid1)),
1076
(b'ghost-rev', None)),
1078
# and now put it back.
1079
state.set_parent_trees(
1080
((revid1, tree1.branch.repository.revision_tree(revid1)),
1081
(revid2, tree2.branch.repository.revision_tree(revid2)),
1082
(b'ghost-rev', tree2.branch.repository.revision_tree(
1083
_mod_revision.NULL_REVISION))),
1085
self.assertEqual([revid1, revid2, b'ghost-rev'],
1086
state.get_parent_ids())
1087
# the ghost should be recorded as such by set_parent_trees.
1088
self.assertEqual([b'ghost-rev'], state.get_ghosts())
1090
[((b'', b'', root_id), [
1091
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT),
1092
(b'd', b'', 0, False, revid1),
1093
(b'd', b'', 0, False, revid1)
1095
list(state._iter_entries()))
1099
def test_set_parent_trees_file_missing_from_tree(self):
1100
# Adding a parent tree may reference files not in the current state.
1101
# they should get listed just once by id, even if they are in two
1103
# set_parent_trees is a slow but important api to support.
1104
tree1 = self.make_branch_and_memory_tree('tree1')
1108
tree1.add(['a file'], [b'file-id'], ['file'])
1109
tree1.put_file_bytes_non_atomic('a file', b'file-content')
1110
revid1 = tree1.commit('foo')
1113
branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1114
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1117
tree2.put_file_bytes_non_atomic('a file', b'new file-content')
1118
revid2 = tree2.commit('foo')
1119
root_id = tree2.get_root_id()
1122
# check the layout in memory
1123
expected_result = [revid1, revid2], [
1124
((b'', b'', root_id), [
1125
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT),
1126
(b'd', b'', 0, False, revid1),
1127
(b'd', b'', 0, False, revid1)
1129
((b'', b'a file', b'file-id'), [
1130
(b'a', b'', 0, False, b''),
1131
(b'f', b'2439573625385400f2a669657a7db6ae7515d371', 12, False,
1133
(b'f', b'542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1137
state = dirstate.DirState.initialize('dirstate')
1139
state.set_path_id(b'', root_id)
1140
state.set_parent_trees(
1141
((revid1, tree1.branch.repository.revision_tree(revid1)),
1142
(revid2, tree2.branch.repository.revision_tree(revid2)),
1148
# check_state_with_reopen will unlock
1149
self.check_state_with_reopen(expected_result, state)
1151
# add a path via _set_data - so we dont need delta work, just
1152
# raw data in, and ensure that it comes out via get_lines happily.
1154
def test_add_path_to_root_no_parents_all_data(self):
1155
# The most trivial addition of a path is when there are no parents and
1156
# its in the root and all data about the file is supplied
1157
self.build_tree(['a file'])
1158
stat = os.lstat('a file')
1159
# the 1*20 is the sha1 pretend value.
1160
state = dirstate.DirState.initialize('dirstate')
1161
expected_entries = [
1162
((b'', b'', b'TREE_ROOT'), [
1163
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
1165
((b'', b'a file', b'a-file-id'), [
1166
(b'f', b'1' * 20, 19, False, dirstate.pack_stat(stat)), # current tree
1170
state.add('a file', b'a-file-id', 'file', stat, b'1' * 20)
1171
# having added it, it should be in the output of iter_entries.
1172
self.assertEqual(expected_entries, list(state._iter_entries()))
1173
# saving and reloading should not affect this.
1177
state = dirstate.DirState.on_file('dirstate')
1179
self.addCleanup(state.unlock)
1180
self.assertEqual(expected_entries, list(state._iter_entries()))
1182
def test_add_path_to_unversioned_directory(self):
1183
"""Adding a path to an unversioned directory should error.
1185
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1186
once dirstate is stable and if it is merged with WorkingTree3, consider
1187
removing this copy of the test.
1189
self.build_tree(['unversioned/', 'unversioned/a file'])
1190
state = dirstate.DirState.initialize('dirstate')
1191
self.addCleanup(state.unlock)
1192
self.assertRaises(errors.NotVersionedError, state.add,
1193
'unversioned/a file', b'a-file-id', 'file', None, None)
1195
def test_add_directory_to_root_no_parents_all_data(self):
1196
# The most trivial addition of a dir is when there are no parents and
1197
# its in the root and all data about the file is supplied
1198
self.build_tree(['a dir/'])
1199
stat = os.lstat('a dir')
1200
expected_entries = [
1201
((b'', b'', b'TREE_ROOT'), [
1202
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
1204
((b'', b'a dir', b'a dir id'), [
1205
(b'd', b'', 0, False, dirstate.pack_stat(stat)), # current tree
1208
state = dirstate.DirState.initialize('dirstate')
1210
state.add('a dir', b'a dir id', 'directory', stat, None)
1211
# having added it, it should be in the output of iter_entries.
1212
self.assertEqual(expected_entries, list(state._iter_entries()))
1213
# saving and reloading should not affect this.
1217
state = dirstate.DirState.on_file('dirstate')
1219
self.addCleanup(state.unlock)
1221
self.assertEqual(expected_entries, list(state._iter_entries()))
1223
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1224
# The most trivial addition of a symlink when there are no parents and
1225
# its in the root and all data about the file is supplied
1226
# bzr doesn't support fake symlinks on windows, yet.
1227
self.requireFeature(features.SymlinkFeature)
1228
os.symlink(target, link_name)
1229
stat = os.lstat(link_name)
1230
expected_entries = [
1231
((b'', b'', b'TREE_ROOT'), [
1232
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
1234
((b'', link_name.encode('UTF-8'), b'a link id'), [
1235
(b'l', target.encode('UTF-8'), stat[6],
1236
False, dirstate.pack_stat(stat)), # current tree
1239
state = dirstate.DirState.initialize('dirstate')
1241
state.add(link_name, b'a link id', 'symlink', stat,
1242
target.encode('UTF-8'))
1243
# having added it, it should be in the output of iter_entries.
1244
self.assertEqual(expected_entries, list(state._iter_entries()))
1245
# saving and reloading should not affect this.
1249
state = dirstate.DirState.on_file('dirstate')
1251
self.addCleanup(state.unlock)
1252
self.assertEqual(expected_entries, list(state._iter_entries()))
1254
def test_add_symlink_to_root_no_parents_all_data(self):
1255
self._test_add_symlink_to_root_no_parents_all_data(
1256
u'a link', u'target')
1258
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1259
self.requireFeature(features.UnicodeFilenameFeature)
1260
self._test_add_symlink_to_root_no_parents_all_data(
1261
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1263
def test_add_directory_and_child_no_parents_all_data(self):
1264
# after adding a directory, we should be able to add children to it.
1265
self.build_tree(['a dir/', 'a dir/a file'])
1266
dirstat = os.lstat('a dir')
1267
filestat = os.lstat('a dir/a file')
1268
expected_entries = [
1269
((b'', b'', b'TREE_ROOT'), [
1270
(b'd', b'', 0, False, dirstate.DirState.NULLSTAT), # current tree
1272
((b'', b'a dir', b'a dir id'), [
1273
(b'd', b'', 0, False, dirstate.pack_stat(dirstat)), # current tree
1275
((b'a dir', b'a file', b'a-file-id'), [
1276
(b'f', b'1' * 20, 25, False,
1277
dirstate.pack_stat(filestat)), # current tree details
1280
state = dirstate.DirState.initialize('dirstate')
1282
state.add('a dir', b'a dir id', 'directory', dirstat, None)
1283
state.add('a dir/a file', b'a-file-id',
1284
'file', filestat, b'1' * 20)
1285
# added it, it should be in the output of iter_entries.
1286
self.assertEqual(expected_entries, list(state._iter_entries()))
1287
# saving and reloading should not affect this.
1291
state = dirstate.DirState.on_file('dirstate')
1293
self.addCleanup(state.unlock)
1294
self.assertEqual(expected_entries, list(state._iter_entries()))
1296
def test_add_tree_reference(self):
1297
# make a dirstate and add a tree reference
1298
state = dirstate.DirState.initialize('dirstate')
1300
(b'', b'subdir', b'subdir-id'),
1301
[(b't', b'subtree-123123', 0, False,
1302
b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1305
state.add('subdir', b'subdir-id', 'tree-reference',
1306
None, b'subtree-123123')
1307
entry = state._get_entry(0, b'subdir-id', b'subdir')
1308
self.assertEqual(entry, expected_entry)
1313
# now check we can read it back
1315
self.addCleanup(state.unlock)
1317
entry2 = state._get_entry(0, b'subdir-id', b'subdir')
1318
self.assertEqual(entry, entry2)
1319
self.assertEqual(entry, expected_entry)
1320
# and lookup by id should work too
1321
entry2 = state._get_entry(0, fileid_utf8=b'subdir-id')
1322
self.assertEqual(entry, expected_entry)
1324
def test_add_forbidden_names(self):
1325
state = dirstate.DirState.initialize('dirstate')
1326
self.addCleanup(state.unlock)
1327
self.assertRaises(errors.BzrError,
1328
state.add, '.', b'ass-id', 'directory', None, None)
1329
self.assertRaises(errors.BzrError,
1330
state.add, '..', b'ass-id', 'directory', None, None)
1332
def test_set_state_with_rename_b_a_bug_395556(self):
1333
# bug 395556 uncovered a bug where the dirstate ends up with a false
1334
# relocation record - in a tree with no parents there should be no
1335
# absent or relocated records. This then leads to further corruption
1336
# when a commit occurs, as the incorrect relocation gathers an
1337
# incorrect absent in tree 1, and future changes go to pot.
1338
tree1 = self.make_branch_and_tree('tree1')
1339
self.build_tree(['tree1/b'])
1342
tree1.add(['b'], [b'b-id'])
1343
root_id = tree1.get_root_id()
1344
inv = tree1.root_inventory
1345
state = dirstate.DirState.initialize('dirstate')
1347
# Set the initial state with 'b'
1348
state.set_state_from_inventory(inv)
1349
inv.rename(b'b-id', root_id, 'a')
1350
# Set the new state with 'a', which currently corrupts.
1351
state.set_state_from_inventory(inv)
1352
expected_result1 = [(b'', b'', root_id, b'd'),
1353
(b'', b'a', b'b-id', b'f'),
1356
for entry in state._iter_entries():
1357
values.append(entry[0] + entry[1][0][:1])
1358
self.assertEqual(expected_result1, values)
1365
class TestDirStateHashUpdates(TestCaseWithDirState):
1367
def do_update_entry(self, state, path):
1368
entry = state._get_entry(0, path_utf8=path)
1369
stat = os.lstat(path)
1370
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1372
def _read_state_content(self, state):
1373
"""Read the content of the dirstate file.
1375
On Windows when one process locks a file, you can't even open() the
1376
file in another process (to read it). So we go directly to
1377
state._state_file. This should always be the exact disk representation,
1378
so it is reasonable to do so.
1379
DirState also always seeks before reading, so it doesn't matter if we
1380
bump the file pointer.
1382
state._state_file.seek(0)
1383
return state._state_file.read()
1385
def test_worth_saving_limit_avoids_writing(self):
1386
tree = self.make_branch_and_tree('.')
1387
self.build_tree(['c', 'd'])
1389
tree.add(['c', 'd'], [b'c-id', b'd-id'])
1390
tree.commit('add c and d')
1391
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1392
worth_saving_limit=2)
1395
self.addCleanup(state.unlock)
1396
state._read_dirblocks_if_needed()
1397
state.adjust_time(+20) # Allow things to be cached
1398
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1399
state._dirblock_state)
1400
content = self._read_state_content(state)
1401
self.do_update_entry(state, b'c')
1402
self.assertEqual(1, len(state._known_hash_changes))
1403
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1404
state._dirblock_state)
1406
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1407
# hash values haven't been written out.
1408
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1409
state._dirblock_state)
1410
self.assertEqual(content, self._read_state_content(state))
1411
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1412
state._dirblock_state)
1413
self.do_update_entry(state, b'd')
1414
self.assertEqual(2, len(state._known_hash_changes))
1416
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1417
state._dirblock_state)
1418
self.assertEqual(0, len(state._known_hash_changes))
1421
class TestGetLines(TestCaseWithDirState):
1423
def test_get_line_with_2_rows(self):
1424
state = self.create_dirstate_with_root_and_subdir()
1426
self.assertEqual([b'#bazaar dirstate flat format 3\n',
1427
b'crc32: 41262208\n',
1428
b'num_entries: 2\n',
1431
b'\x00\x00a-root-value\x00'
1432
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1433
b'\x00subdir\x00subdir-id\x00'
1434
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1435
], state.get_lines())
1439
def test_entry_to_line(self):
1440
state = self.create_dirstate_with_root()
1443
b'\x00\x00a-root-value\x00d\x00\x000\x00n'
1444
b'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1445
state._entry_to_line(state._dirblocks[0][1][0]))
1449
def test_entry_to_line_with_parent(self):
1450
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1451
root_entry = (b'', b'', b'a-root-value'), [
1452
(b'd', b'', 0, False, packed_stat), # current tree details
1453
# first: a pointer to the current location
1454
(b'a', b'dirname/basename', 0, False, b''),
1456
state = dirstate.DirState.initialize('dirstate')
1459
b'\x00\x00a-root-value\x00'
1460
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1461
b'a\x00dirname/basename\x000\x00n\x00',
1462
state._entry_to_line(root_entry))
1466
def test_entry_to_line_with_two_parents_at_different_paths(self):
1467
# / in the tree, at / in one parent and /dirname/basename in the other.
1468
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1469
root_entry = (b'', b'', b'a-root-value'), [
1470
(b'd', b'', 0, False, packed_stat), # current tree details
1471
(b'd', b'', 0, False, b'rev_id'), # first parent details
1472
# second: a pointer to the current location
1473
(b'a', b'dirname/basename', 0, False, b''),
1475
state = dirstate.DirState.initialize('dirstate')
1478
b'\x00\x00a-root-value\x00'
1479
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1480
b'd\x00\x000\x00n\x00rev_id\x00'
1481
b'a\x00dirname/basename\x000\x00n\x00',
1482
state._entry_to_line(root_entry))
1486
def test_iter_entries(self):
1487
# we should be able to iterate the dirstate entries from end to end
1488
# this is for get_lines to be easy to read.
1489
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1491
root_entries = [((b'', b'', b'a-root-value'), [
1492
(b'd', b'', 0, False, packed_stat), # current tree details
1494
dirblocks.append(('', root_entries))
1495
# add two files in the root
1496
subdir_entry = (b'', b'subdir', b'subdir-id'), [
1497
(b'd', b'', 0, False, packed_stat), # current tree details
1499
afile_entry = (b'', b'afile', b'afile-id'), [
1500
(b'f', b'sha1value', 34, False, packed_stat), # current tree details
1502
dirblocks.append(('', [subdir_entry, afile_entry]))
1504
file_entry2 = (b'subdir', b'2file', b'2file-id'), [
1505
(b'f', b'sha1value', 23, False, packed_stat), # current tree details
1507
dirblocks.append(('subdir', [file_entry2]))
1508
state = dirstate.DirState.initialize('dirstate')
1510
state._set_data([], dirblocks)
1511
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1513
self.assertEqual(expected_entries, list(state._iter_entries()))
1518
class TestGetBlockRowIndex(TestCaseWithDirState):
1520
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1521
file_present, state, dirname, basename, tree_index):
1522
self.assertEqual((block_index, row_index, dir_present, file_present),
1523
state._get_block_entry_index(dirname, basename, tree_index))
1525
block = state._dirblocks[block_index]
1526
self.assertEqual(dirname, block[0])
1527
if dir_present and file_present:
1528
row = state._dirblocks[block_index][1][row_index]
1529
self.assertEqual(dirname, row[0][0])
1530
self.assertEqual(basename, row[0][1])
1532
def test_simple_structure(self):
1533
state = self.create_dirstate_with_root_and_subdir()
1534
self.addCleanup(state.unlock)
1535
self.assertBlockRowIndexEqual(
1536
1, 0, True, True, state, b'', b'subdir', 0)
1537
self.assertBlockRowIndexEqual(
1538
1, 0, True, False, state, b'', b'bdir', 0)
1539
self.assertBlockRowIndexEqual(
1540
1, 1, True, False, state, b'', b'zdir', 0)
1541
self.assertBlockRowIndexEqual(
1542
2, 0, False, False, state, b'a', b'foo', 0)
1543
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1544
b'subdir', b'foo', 0)
1546
def test_complex_structure_exists(self):
1547
state = self.create_complex_dirstate()
1548
self.addCleanup(state.unlock)
1549
# Make sure we can find everything that exists
1550
self.assertBlockRowIndexEqual(0, 0, True, True, state, b'', b'', 0)
1551
self.assertBlockRowIndexEqual(1, 0, True, True, state, b'', b'a', 0)
1552
self.assertBlockRowIndexEqual(1, 1, True, True, state, b'', b'b', 0)
1553
self.assertBlockRowIndexEqual(1, 2, True, True, state, b'', b'c', 0)
1554
self.assertBlockRowIndexEqual(1, 3, True, True, state, b'', b'd', 0)
1555
self.assertBlockRowIndexEqual(2, 0, True, True, state, b'a', b'e', 0)
1556
self.assertBlockRowIndexEqual(2, 1, True, True, state, b'a', b'f', 0)
1557
self.assertBlockRowIndexEqual(3, 0, True, True, state, b'b', b'g', 0)
1558
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1559
b'b', b'h\xc3\xa5', 0)
1561
def test_complex_structure_missing(self):
1562
state = self.create_complex_dirstate()
1563
self.addCleanup(state.unlock)
1564
# Make sure things would be inserted in the right locations
1565
# '_' comes before 'a'
1566
self.assertBlockRowIndexEqual(0, 0, True, True, state, b'', b'', 0)
1567
self.assertBlockRowIndexEqual(1, 0, True, False, state, b'', b'_', 0)
1568
self.assertBlockRowIndexEqual(1, 1, True, False, state, b'', b'aa', 0)
1569
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1570
b'', b'h\xc3\xa5', 0)
1571
self.assertBlockRowIndexEqual(2, 0, False, False, state, b'_', b'a', 0)
1572
self.assertBlockRowIndexEqual(
1573
3, 0, False, False, state, b'aa', b'a', 0)
1574
self.assertBlockRowIndexEqual(
1575
4, 0, False, False, state, b'bb', b'a', 0)
1576
# This would be inserted between a/ and b/
1577
self.assertBlockRowIndexEqual(
1578
3, 0, False, False, state, b'a/e', b'a', 0)
1580
self.assertBlockRowIndexEqual(4, 0, False, False, state, b'e', b'a', 0)
1583
class TestGetEntry(TestCaseWithDirState):
1585
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1586
"""Check that the right entry is returned for a request to getEntry."""
1587
entry = state._get_entry(index, path_utf8=path)
1589
self.assertEqual((None, None), entry)
1592
self.assertEqual((dirname, basename, file_id), cur[:3])
1594
def test_simple_structure(self):
1595
state = self.create_dirstate_with_root_and_subdir()
1596
self.addCleanup(state.unlock)
1597
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1598
self.assertEntryEqual(
1599
b'', b'subdir', b'subdir-id', state, b'subdir', 0)
1600
self.assertEntryEqual(None, None, None, state, b'missing', 0)
1601
self.assertEntryEqual(None, None, None, state, b'missing/foo', 0)
1602
self.assertEntryEqual(None, None, None, state, b'subdir/foo', 0)
1604
def test_complex_structure_exists(self):
1605
state = self.create_complex_dirstate()
1606
self.addCleanup(state.unlock)
1607
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1608
self.assertEntryEqual(b'', b'a', b'a-dir', state, b'a', 0)
1609
self.assertEntryEqual(b'', b'b', b'b-dir', state, b'b', 0)
1610
self.assertEntryEqual(b'', b'c', b'c-file', state, b'c', 0)
1611
self.assertEntryEqual(b'', b'd', b'd-file', state, b'd', 0)
1612
self.assertEntryEqual(b'a', b'e', b'e-dir', state, b'a/e', 0)
1613
self.assertEntryEqual(b'a', b'f', b'f-file', state, b'a/f', 0)
1614
self.assertEntryEqual(b'b', b'g', b'g-file', state, b'b/g', 0)
1615
self.assertEntryEqual(b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file', state,
1618
def test_complex_structure_missing(self):
1619
state = self.create_complex_dirstate()
1620
self.addCleanup(state.unlock)
1621
self.assertEntryEqual(None, None, None, state, b'_', 0)
1622
self.assertEntryEqual(None, None, None, state, b'_\xc3\xa5', 0)
1623
self.assertEntryEqual(None, None, None, state, b'a/b', 0)
1624
self.assertEntryEqual(None, None, None, state, b'c/d', 0)
1626
def test_get_entry_uninitialized(self):
1627
"""Calling get_entry will load data if it needs to"""
1628
state = self.create_dirstate_with_root()
1634
state = dirstate.DirState.on_file('dirstate')
1637
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1638
state._header_state)
1639
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1640
state._dirblock_state)
1641
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1646
class TestIterChildEntries(TestCaseWithDirState):
1648
def create_dirstate_with_two_trees(self):
1649
"""This dirstate contains multiple files and directories.
1659
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1661
Notice that a/e is an empty directory.
1663
There is one parent tree, which has the same shape with the following variations:
1664
b/g in the parent is gone.
1665
b/h in the parent has a different id
1666
b/i is new in the parent
1667
c is renamed to b/j in the parent
1669
:return: The dirstate, still write-locked.
1671
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1672
null_sha = b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1673
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1674
root_entry = (b'', b'', b'a-root-value'), [
1675
(b'd', b'', 0, False, packed_stat),
1676
(b'd', b'', 0, False, b'parent-revid'),
1678
a_entry = (b'', b'a', b'a-dir'), [
1679
(b'd', b'', 0, False, packed_stat),
1680
(b'd', b'', 0, False, b'parent-revid'),
1682
b_entry = (b'', b'b', b'b-dir'), [
1683
(b'd', b'', 0, False, packed_stat),
1684
(b'd', b'', 0, False, b'parent-revid'),
1686
c_entry = (b'', b'c', b'c-file'), [
1687
(b'f', null_sha, 10, False, packed_stat),
1688
(b'r', b'b/j', 0, False, b''),
1690
d_entry = (b'', b'd', b'd-file'), [
1691
(b'f', null_sha, 20, False, packed_stat),
1692
(b'f', b'd', 20, False, b'parent-revid'),
1694
e_entry = (b'a', b'e', b'e-dir'), [
1695
(b'd', b'', 0, False, packed_stat),
1696
(b'd', b'', 0, False, b'parent-revid'),
1698
f_entry = (b'a', b'f', b'f-file'), [
1699
(b'f', null_sha, 30, False, packed_stat),
1700
(b'f', b'f', 20, False, b'parent-revid'),
1702
g_entry = (b'b', b'g', b'g-file'), [
1703
(b'f', null_sha, 30, False, packed_stat),
1704
NULL_PARENT_DETAILS,
1706
h_entry1 = (b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file1'), [
1707
(b'f', null_sha, 40, False, packed_stat),
1708
NULL_PARENT_DETAILS,
1710
h_entry2 = (b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file2'), [
1711
NULL_PARENT_DETAILS,
1712
(b'f', b'h', 20, False, b'parent-revid'),
1714
i_entry = (b'b', b'i', b'i-file'), [
1715
NULL_PARENT_DETAILS,
1716
(b'f', b'h', 20, False, b'parent-revid'),
1718
j_entry = (b'b', b'j', b'c-file'), [
1719
(b'r', b'c', 0, False, b''),
1720
(b'f', b'j', 20, False, b'parent-revid'),
1723
dirblocks.append((b'', [root_entry]))
1724
dirblocks.append((b'', [a_entry, b_entry, c_entry, d_entry]))
1725
dirblocks.append((b'a', [e_entry, f_entry]))
1727
(b'b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1728
state = dirstate.DirState.initialize('dirstate')
1731
state._set_data([b'parent'], dirblocks)
1735
return state, dirblocks
1737
def test_iter_children_b(self):
1738
state, dirblocks = self.create_dirstate_with_two_trees()
1739
self.addCleanup(state.unlock)
1740
expected_result = []
1741
expected_result.append(dirblocks[3][1][2]) # h2
1742
expected_result.append(dirblocks[3][1][3]) # i
1743
expected_result.append(dirblocks[3][1][4]) # j
1744
self.assertEqual(expected_result,
1745
list(state._iter_child_entries(1, b'b')))
1747
def test_iter_child_root(self):
1748
state, dirblocks = self.create_dirstate_with_two_trees()
1749
self.addCleanup(state.unlock)
1750
expected_result = []
1751
expected_result.append(dirblocks[1][1][0]) # a
1752
expected_result.append(dirblocks[1][1][1]) # b
1753
expected_result.append(dirblocks[1][1][3]) # d
1754
expected_result.append(dirblocks[2][1][0]) # e
1755
expected_result.append(dirblocks[2][1][1]) # f
1756
expected_result.append(dirblocks[3][1][2]) # h2
1757
expected_result.append(dirblocks[3][1][3]) # i
1758
expected_result.append(dirblocks[3][1][4]) # j
1759
self.assertEqual(expected_result,
1760
list(state._iter_child_entries(1, b'')))
1763
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1764
"""Test that DirState adds entries in the right order."""
1766
def test_add_sorting(self):
1767
"""Add entries in lexicographical order, we get path sorted order.
1769
This tests it to a depth of 4, to make sure we don't just get it right
1770
at a single depth. 'a/a' should come before 'a-a', even though it
1771
doesn't lexicographically.
1773
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1774
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1777
state = dirstate.DirState.initialize('dirstate')
1778
self.addCleanup(state.unlock)
1780
fake_stat = os.stat('dirstate')
1782
d_id = d.encode('utf-8').replace(b'/', b'_') + b'-id'
1783
file_path = d + '/f'
1784
file_id = file_path.encode('utf-8').replace(b'/', b'_') + b'-id'
1785
state.add(d, d_id, 'directory', fake_stat, null_sha)
1786
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1788
expected = [b'', b'', b'a',
1789
b'a/a', b'a/a/a', b'a/a/a/a',
1790
b'a/a/a/a-a', b'a/a/a-a', b'a/a-a', b'a-a',
1793
def split(p): return p.split(b'/')
1794
self.assertEqual(sorted(expected, key=split), expected)
1795
dirblock_names = [d[0] for d in state._dirblocks]
1796
self.assertEqual(expected, dirblock_names)
1798
def test_set_parent_trees_correct_order(self):
1799
"""After calling set_parent_trees() we should maintain the order."""
1800
dirs = ['a', 'a-a', 'a/a']
1802
state = dirstate.DirState.initialize('dirstate')
1803
self.addCleanup(state.unlock)
1805
fake_stat = os.stat('dirstate')
1807
d_id = d.encode('utf-8').replace(b'/', b'_') + b'-id'
1808
file_path = d + '/f'
1809
file_id = file_path.encode('utf-8').replace(b'/', b'_') + b'-id'
1810
state.add(d, d_id, 'directory', fake_stat, null_sha)
1811
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1813
expected = [b'', b'', b'a', b'a/a', b'a-a']
1814
dirblock_names = [d[0] for d in state._dirblocks]
1815
self.assertEqual(expected, dirblock_names)
1817
# *really* cheesy way to just get an empty tree
1818
repo = self.make_repository('repo')
1819
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1820
state.set_parent_trees([('null:', empty_tree)], [])
1822
dirblock_names = [d[0] for d in state._dirblocks]
1823
self.assertEqual(expected, dirblock_names)
1826
class InstrumentedDirState(dirstate.DirState):
1827
"""An DirState with instrumented sha1 functionality."""
1829
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1830
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1831
worth_saving_limit=worth_saving_limit)
1832
self._time_offset = 0
1834
# member is dynamically set in DirState.__init__ to turn on trace
1835
self._sha1_provider = sha1_provider
1836
self._sha1_file = self._sha1_file_and_log
1838
def _sha_cutoff_time(self):
1839
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1840
self._cutoff_time = timestamp + self._time_offset
1842
def _sha1_file_and_log(self, abspath):
1843
self._log.append(('sha1', abspath))
1844
return self._sha1_provider.sha1(abspath)
1846
def _read_link(self, abspath, old_link):
1847
self._log.append(('read_link', abspath, old_link))
1848
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1850
def _lstat(self, abspath, entry):
1851
self._log.append(('lstat', abspath))
1852
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1854
def _is_executable(self, mode, old_executable):
1855
self._log.append(('is_exec', mode, old_executable))
1856
return super(InstrumentedDirState, self)._is_executable(mode,
1859
def adjust_time(self, secs):
1860
"""Move the clock forward or back.
1862
:param secs: The amount to adjust the clock by. Positive values make it
1863
seem as if we are in the future, negative values make it seem like we
1866
self._time_offset += secs
1867
self._cutoff_time = None
1870
class _FakeStat(object):
1871
"""A class with the same attributes as a real stat result."""
1873
def __init__(self, size, mtime, ctime, dev, ino, mode):
1875
self.st_mtime = mtime
1876
self.st_ctime = ctime
1883
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1884
st.st_ino, st.st_mode)
1887
class TestPackStat(tests.TestCaseWithTransport):
1889
def assertPackStat(self, expected, stat_value):
1890
"""Check the packed and serialized form of a stat value."""
1891
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1893
def test_pack_stat_int(self):
1894
st = _FakeStat(6859, 1172758614, 1172758617, 777, 6499538, 0o100644)
1895
# Make sure that all parameters have an impact on the packed stat.
1896
self.assertPackStat(b'AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1899
self.assertPackStat(b'AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1900
st.st_mtime = 1172758620
1902
self.assertPackStat(b'AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1903
st.st_ctime = 1172758630
1905
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1908
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1911
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1912
st.st_mode = 0o100744
1914
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1916
def test_pack_stat_float(self):
1917
"""On some platforms mtime and ctime are floats.
1919
Make sure we don't get warnings or errors, and that we ignore changes <
1922
st = _FakeStat(7000, 1172758614.0, 1172758617.0,
1923
777, 6499538, 0o100644)
1924
# These should all be the same as the integer counterparts
1925
self.assertPackStat(b'AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1926
st.st_mtime = 1172758620.0
1928
self.assertPackStat(b'AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1929
st.st_ctime = 1172758630.0
1931
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1932
# fractional seconds are discarded, so no change from above
1933
st.st_mtime = 1172758620.453
1934
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1935
st.st_ctime = 1172758630.228
1936
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1939
class TestBisect(TestCaseWithDirState):
1940
"""Test the ability to bisect into the disk format."""
1942
def assertBisect(self, expected_map, map_keys, state, paths):
1943
"""Assert that bisecting for paths returns the right result.
1945
:param expected_map: A map from key => entry value
1946
:param map_keys: The keys to expect for each path
1947
:param state: The DirState object.
1948
:param paths: A list of paths, these will automatically be split into
1949
(dir, name) tuples, and sorted according to how _bisect
1952
result = state._bisect(paths)
1953
# For now, results are just returned in whatever order we read them.
1954
# We could sort by (dir, name, file_id) or something like that, but in
1955
# the end it would still be fairly arbitrary, and we don't want the
1956
# extra overhead if we can avoid it. So sort everything to make sure
1958
self.assertEqual(len(map_keys), len(paths))
1960
for path, keys in zip(paths, map_keys):
1962
# This should not be present in the output
1964
expected[path] = sorted(expected_map[k] for k in keys)
1966
# The returned values are just arranged randomly based on when they
1967
# were read, for testing, make sure it is properly sorted.
1971
self.assertEqual(expected, result)
1973
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1974
"""Assert that bisecting for dirbblocks returns the right result.
1976
:param expected_map: A map from key => expected values
1977
:param map_keys: A nested list of paths we expect to be returned.
1978
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1979
:param state: The DirState object.
1980
:param paths: A list of directories
1982
result = state._bisect_dirblocks(paths)
1983
self.assertEqual(len(map_keys), len(paths))
1985
for path, keys in zip(paths, map_keys):
1987
# This should not be present in the output
1989
expected[path] = sorted(expected_map[k] for k in keys)
1993
self.assertEqual(expected, result)
1995
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1996
"""Assert the return value of a recursive bisection.
1998
:param expected_map: A map from key => entry value
1999
:param map_keys: A list of paths we expect to be returned.
2000
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2001
:param state: The DirState object.
2002
:param paths: A list of files and directories. It will be broken up
2003
into (dir, name) pairs and sorted before calling _bisect_recursive.
2006
for key in map_keys:
2007
entry = expected_map[key]
2008
dir_name_id, trees_info = entry
2009
expected[dir_name_id] = trees_info
2011
result = state._bisect_recursive(paths)
2013
self.assertEqual(expected, result)
2015
def test_bisect_each(self):
2016
"""Find a single record using bisect."""
2017
tree, state, expected = self.create_basic_dirstate()
2019
# Bisect should return the rows for the specified files.
2020
self.assertBisect(expected, [[b'']], state, [b''])
2021
self.assertBisect(expected, [[b'a']], state, [b'a'])
2022
self.assertBisect(expected, [[b'b']], state, [b'b'])
2023
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2024
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2025
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2026
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2027
self.assertBisect(expected, [[b'f']], state, [b'f'])
2029
def test_bisect_multi(self):
2030
"""Bisect can be used to find multiple records at the same time."""
2031
tree, state, expected = self.create_basic_dirstate()
2032
# Bisect should be capable of finding multiple entries at the same time
2033
self.assertBisect(expected, [[b'a'], [b'b'], [b'f']],
2034
state, [b'a', b'b', b'f'])
2035
self.assertBisect(expected, [[b'f'], [b'b/d'], [b'b/d/e']],
2036
state, [b'f', b'b/d', b'b/d/e'])
2037
self.assertBisect(expected, [[b'b'], [b'b-c'], [b'b/c']],
2038
state, [b'b', b'b-c', b'b/c'])
2040
def test_bisect_one_page(self):
2041
"""Test bisect when there is only 1 page to read"""
2042
tree, state, expected = self.create_basic_dirstate()
2043
state._bisect_page_size = 5000
2044
self.assertBisect(expected, [[b'']], state, [b''])
2045
self.assertBisect(expected, [[b'a']], state, [b'a'])
2046
self.assertBisect(expected, [[b'b']], state, [b'b'])
2047
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2048
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2049
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2050
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2051
self.assertBisect(expected, [[b'f']], state, [b'f'])
2052
self.assertBisect(expected, [[b'a'], [b'b'], [b'f']],
2053
state, [b'a', b'b', b'f'])
2054
self.assertBisect(expected, [[b'b/d'], [b'b/d/e'], [b'f']],
2055
state, [b'b/d', b'b/d/e', b'f'])
2056
self.assertBisect(expected, [[b'b'], [b'b/c'], [b'b-c']],
2057
state, [b'b', b'b/c', b'b-c'])
2059
def test_bisect_duplicate_paths(self):
2060
"""When bisecting for a path, handle multiple entries."""
2061
tree, state, expected = self.create_duplicated_dirstate()
2063
# Now make sure that both records are properly returned.
2064
self.assertBisect(expected, [[b'']], state, [b''])
2065
self.assertBisect(expected, [[b'a', b'a2']], state, [b'a'])
2066
self.assertBisect(expected, [[b'b', b'b2']], state, [b'b'])
2067
self.assertBisect(expected, [[b'b/c', b'b/c2']], state, [b'b/c'])
2068
self.assertBisect(expected, [[b'b/d', b'b/d2']], state, [b'b/d'])
2069
self.assertBisect(expected, [[b'b/d/e', b'b/d/e2']],
2071
self.assertBisect(expected, [[b'b-c', b'b-c2']], state, [b'b-c'])
2072
self.assertBisect(expected, [[b'f', b'f2']], state, [b'f'])
2074
def test_bisect_page_size_too_small(self):
2075
"""If the page size is too small, we will auto increase it."""
2076
tree, state, expected = self.create_basic_dirstate()
2077
state._bisect_page_size = 50
2078
self.assertBisect(expected, [None], state, [b'b/e'])
2079
self.assertBisect(expected, [[b'a']], state, [b'a'])
2080
self.assertBisect(expected, [[b'b']], state, [b'b'])
2081
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2082
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2083
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2084
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2085
self.assertBisect(expected, [[b'f']], state, [b'f'])
2087
def test_bisect_missing(self):
2088
"""Test that bisect return None if it cannot find a path."""
2089
tree, state, expected = self.create_basic_dirstate()
2090
self.assertBisect(expected, [None], state, [b'foo'])
2091
self.assertBisect(expected, [None], state, [b'b/foo'])
2092
self.assertBisect(expected, [None], state, [b'bar/foo'])
2093
self.assertBisect(expected, [None], state, [b'b-c/foo'])
2095
self.assertBisect(expected, [[b'a'], None, [b'b/d']],
2096
state, [b'a', b'foo', b'b/d'])
2098
def test_bisect_rename(self):
2099
"""Check that we find a renamed row."""
2100
tree, state, expected = self.create_renamed_dirstate()
2102
# Search for the pre and post renamed entries
2103
self.assertBisect(expected, [[b'a']], state, [b'a'])
2104
self.assertBisect(expected, [[b'b/g']], state, [b'b/g'])
2105
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2106
self.assertBisect(expected, [[b'h']], state, [b'h'])
2108
# What about b/d/e? shouldn't that also get 2 directory entries?
2109
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2110
self.assertBisect(expected, [[b'h/e']], state, [b'h/e'])
2112
def test_bisect_dirblocks(self):
2113
tree, state, expected = self.create_duplicated_dirstate()
2114
self.assertBisectDirBlocks(expected,
2115
[[b'', b'a', b'a2', b'b', b'b2',
2116
b'b-c', b'b-c2', b'f', b'f2']],
2118
self.assertBisectDirBlocks(expected,
2119
[[b'b/c', b'b/c2', b'b/d', b'b/d2']], state, [b'b'])
2120
self.assertBisectDirBlocks(expected,
2121
[[b'b/d/e', b'b/d/e2']], state, [b'b/d'])
2122
self.assertBisectDirBlocks(expected,
2123
[[b'', b'a', b'a2', b'b', b'b2', b'b-c', b'b-c2', b'f', b'f2'],
2124
[b'b/c', b'b/c2', b'b/d', b'b/d2'],
2125
[b'b/d/e', b'b/d/e2'],
2126
], state, [b'', b'b', b'b/d'])
2128
def test_bisect_dirblocks_missing(self):
2129
tree, state, expected = self.create_basic_dirstate()
2130
self.assertBisectDirBlocks(expected, [[b'b/d/e'], None],
2131
state, [b'b/d', b'b/e'])
2132
# Files don't show up in this search
2133
self.assertBisectDirBlocks(expected, [None], state, [b'a'])
2134
self.assertBisectDirBlocks(expected, [None], state, [b'b/c'])
2135
self.assertBisectDirBlocks(expected, [None], state, [b'c'])
2136
self.assertBisectDirBlocks(expected, [None], state, [b'b/d/e'])
2137
self.assertBisectDirBlocks(expected, [None], state, [b'f'])
2139
def test_bisect_recursive_each(self):
2140
tree, state, expected = self.create_basic_dirstate()
2141
self.assertBisectRecursive(expected, [b'a'], state, [b'a'])
2142
self.assertBisectRecursive(expected, [b'b/c'], state, [b'b/c'])
2143
self.assertBisectRecursive(expected, [b'b/d/e'], state, [b'b/d/e'])
2144
self.assertBisectRecursive(expected, [b'b-c'], state, [b'b-c'])
2145
self.assertBisectRecursive(expected, [b'b/d', b'b/d/e'],
2147
self.assertBisectRecursive(expected, [b'b', b'b/c', b'b/d', b'b/d/e'],
2149
self.assertBisectRecursive(expected, [b'', b'a', b'b', b'b-c', b'f', b'b/c',
2153
def test_bisect_recursive_multiple(self):
2154
tree, state, expected = self.create_basic_dirstate()
2155
self.assertBisectRecursive(
2156
expected, [b'a', b'b/c'], state, [b'a', b'b/c'])
2157
self.assertBisectRecursive(expected, [b'b/d', b'b/d/e'],
2158
state, [b'b/d', b'b/d/e'])
2160
def test_bisect_recursive_missing(self):
2161
tree, state, expected = self.create_basic_dirstate()
2162
self.assertBisectRecursive(expected, [], state, [b'd'])
2163
self.assertBisectRecursive(expected, [], state, [b'b/e'])
2164
self.assertBisectRecursive(expected, [], state, [b'g'])
2165
self.assertBisectRecursive(expected, [b'a'], state, [b'a', b'g'])
2167
def test_bisect_recursive_renamed(self):
2168
tree, state, expected = self.create_renamed_dirstate()
2170
# Looking for either renamed item should find the other
2171
self.assertBisectRecursive(expected, [b'a', b'b/g'], state, [b'a'])
2172
self.assertBisectRecursive(expected, [b'a', b'b/g'], state, [b'b/g'])
2173
# Looking in the containing directory should find the rename target,
2174
# and anything in a subdir of the renamed target.
2175
self.assertBisectRecursive(expected, [b'a', b'b', b'b/c', b'b/d',
2176
b'b/d/e', b'b/g', b'h', b'h/e'],
2180
class TestDirstateValidation(TestCaseWithDirState):
2182
def test_validate_correct_dirstate(self):
2183
state = self.create_complex_dirstate()
2186
# and make sure we can also validate with a read lock
2193
def test_dirblock_not_sorted(self):
2194
tree, state, expected = self.create_renamed_dirstate()
2195
state._read_dirblocks_if_needed()
2196
last_dirblock = state._dirblocks[-1]
2197
# we're appending to the dirblock, but this name comes before some of
2198
# the existing names; that's wrong
2199
last_dirblock[1].append(
2200
((b'h', b'aaaa', b'a-id'),
2201
[(b'a', b'', 0, False, b''),
2202
(b'a', b'', 0, False, b'')]))
2203
e = self.assertRaises(AssertionError,
2205
self.assertContainsRe(str(e), 'not sorted')
2207
def test_dirblock_name_mismatch(self):
2208
tree, state, expected = self.create_renamed_dirstate()
2209
state._read_dirblocks_if_needed()
2210
last_dirblock = state._dirblocks[-1]
2211
# add an entry with the wrong directory name
2212
last_dirblock[1].append(
2213
((b'', b'z', b'a-id'),
2214
[(b'a', b'', 0, False, b''),
2215
(b'a', b'', 0, False, b'')]))
2216
e = self.assertRaises(AssertionError,
2218
self.assertContainsRe(str(e),
2219
"doesn't match directory name")
2221
def test_dirblock_missing_rename(self):
2222
tree, state, expected = self.create_renamed_dirstate()
2223
state._read_dirblocks_if_needed()
2224
last_dirblock = state._dirblocks[-1]
2225
# make another entry for a-id, without a correct 'r' pointer to
2226
# the real occurrence in the working tree
2227
last_dirblock[1].append(
2228
((b'h', b'z', b'a-id'),
2229
[(b'a', b'', 0, False, b''),
2230
(b'a', b'', 0, False, b'')]))
2231
e = self.assertRaises(AssertionError,
2233
self.assertContainsRe(str(e),
2234
'file a-id is absent in row')
2237
class TestDirstateTreeReference(TestCaseWithDirState):
2239
def test_reference_revision_is_none(self):
2240
tree = self.make_branch_and_tree('tree', format='development-subtree')
2241
subtree = self.make_branch_and_tree('tree/subtree',
2242
format='development-subtree')
2243
subtree.set_root_id(b'subtree')
2244
tree.add_reference(subtree)
2246
state = dirstate.DirState.from_tree(tree, 'dirstate')
2247
key = (b'', b'subtree', b'subtree')
2248
expected = (b'', [(key,
2249
[(b't', b'', 0, False, b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2252
self.assertEqual(expected, state._find_block(key))
2257
class TestDiscardMergeParents(TestCaseWithDirState):
2259
def test_discard_no_parents(self):
2260
# This should be a no-op
2261
state = self.create_empty_dirstate()
2262
self.addCleanup(state.unlock)
2263
state._discard_merge_parents()
2266
def test_discard_one_parent(self):
2268
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2269
root_entry_direntry = (b'', b'', b'a-root-value'), [
2270
(b'd', b'', 0, False, packed_stat),
2271
(b'd', b'', 0, False, packed_stat),
2274
dirblocks.append((b'', [root_entry_direntry]))
2275
dirblocks.append((b'', []))
2277
state = self.create_empty_dirstate()
2278
self.addCleanup(state.unlock)
2279
state._set_data([b'parent-id'], dirblocks[:])
2282
state._discard_merge_parents()
2284
self.assertEqual(dirblocks, state._dirblocks)
2286
def test_discard_simple(self):
2288
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2289
root_entry_direntry = (b'', b'', b'a-root-value'), [
2290
(b'd', b'', 0, False, packed_stat),
2291
(b'd', b'', 0, False, packed_stat),
2292
(b'd', b'', 0, False, packed_stat),
2294
expected_root_entry_direntry = (b'', b'', b'a-root-value'), [
2295
(b'd', b'', 0, False, packed_stat),
2296
(b'd', b'', 0, False, packed_stat),
2299
dirblocks.append((b'', [root_entry_direntry]))
2300
dirblocks.append((b'', []))
2302
state = self.create_empty_dirstate()
2303
self.addCleanup(state.unlock)
2304
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2307
# This should strip of the extra column
2308
state._discard_merge_parents()
2310
expected_dirblocks = [(b'', [expected_root_entry_direntry]), (b'', [])]
2311
self.assertEqual(expected_dirblocks, state._dirblocks)
2313
def test_discard_absent(self):
2314
"""If entries are only in a merge, discard should remove the entries"""
2315
null_stat = dirstate.DirState.NULLSTAT
2316
present_dir = (b'd', b'', 0, False, null_stat)
2317
present_file = (b'f', b'', 0, False, null_stat)
2318
absent = dirstate.DirState.NULL_PARENT_DETAILS
2319
root_key = (b'', b'', b'a-root-value')
2320
file_in_root_key = (b'', b'file-in-root', b'a-file-id')
2321
file_in_merged_key = (b'', b'file-in-merged', b'b-file-id')
2322
dirblocks = [(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2323
(b'', [(file_in_merged_key,
2324
[absent, absent, present_file]),
2326
[present_file, present_file, present_file]),
2330
state = self.create_empty_dirstate()
2331
self.addCleanup(state.unlock)
2332
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2335
exp_dirblocks = [(b'', [(root_key, [present_dir, present_dir])]),
2336
(b'', [(file_in_root_key,
2337
[present_file, present_file]),
2340
state._discard_merge_parents()
2342
self.assertEqual(exp_dirblocks, state._dirblocks)
2344
def test_discard_renamed(self):
2345
null_stat = dirstate.DirState.NULLSTAT
2346
present_dir = (b'd', b'', 0, False, null_stat)
2347
present_file = (b'f', b'', 0, False, null_stat)
2348
absent = dirstate.DirState.NULL_PARENT_DETAILS
2349
root_key = (b'', b'', b'a-root-value')
2350
file_in_root_key = (b'', b'file-in-root', b'a-file-id')
2351
# Renamed relative to parent
2352
file_rename_s_key = (b'', b'file-s', b'b-file-id')
2353
file_rename_t_key = (b'', b'file-t', b'b-file-id')
2354
# And one that is renamed between the parents, but absent in this
2355
key_in_1 = (b'', b'file-in-1', b'c-file-id')
2356
key_in_2 = (b'', b'file-in-2', b'c-file-id')
2359
(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2361
[absent, present_file, (b'r', b'file-in-2', b'c-file-id')]),
2363
[absent, (b'r', b'file-in-1', b'c-file-id'), present_file]),
2365
[present_file, present_file, present_file]),
2367
[(b'r', b'file-t', b'b-file-id'), absent, present_file]),
2369
[present_file, absent, (b'r', b'file-s', b'b-file-id')]),
2373
(b'', [(root_key, [present_dir, present_dir])]),
2374
(b'', [(key_in_1, [absent, present_file]),
2375
(file_in_root_key, [present_file, present_file]),
2376
(file_rename_t_key, [present_file, absent]),
2379
state = self.create_empty_dirstate()
2380
self.addCleanup(state.unlock)
2381
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2384
state._discard_merge_parents()
2386
self.assertEqual(exp_dirblocks, state._dirblocks)
2388
def test_discard_all_subdir(self):
2389
null_stat = dirstate.DirState.NULLSTAT
2390
present_dir = (b'd', b'', 0, False, null_stat)
2391
present_file = (b'f', b'', 0, False, null_stat)
2392
absent = dirstate.DirState.NULL_PARENT_DETAILS
2393
root_key = (b'', b'', b'a-root-value')
2394
subdir_key = (b'', b'sub', b'dir-id')
2395
child1_key = (b'sub', b'child1', b'child1-id')
2396
child2_key = (b'sub', b'child2', b'child2-id')
2397
child3_key = (b'sub', b'child3', b'child3-id')
2400
(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2401
(b'', [(subdir_key, [present_dir, present_dir, present_dir])]),
2402
(b'sub', [(child1_key, [absent, absent, present_file]),
2403
(child2_key, [absent, absent, present_file]),
2404
(child3_key, [absent, absent, present_file]),
2408
(b'', [(root_key, [present_dir, present_dir])]),
2409
(b'', [(subdir_key, [present_dir, present_dir])]),
2412
state = self.create_empty_dirstate()
2413
self.addCleanup(state.unlock)
2414
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2417
state._discard_merge_parents()
2419
self.assertEqual(exp_dirblocks, state._dirblocks)
2422
class Test_InvEntryToDetails(tests.TestCase):
2424
def assertDetails(self, expected, inv_entry):
2425
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2426
self.assertEqual(expected, details)
2427
# details should always allow join() and always be a plain str when
2429
(minikind, fingerprint, size, executable, tree_data) = details
2430
self.assertIsInstance(minikind, bytes)
2431
self.assertIsInstance(fingerprint, bytes)
2432
self.assertIsInstance(tree_data, bytes)
2434
def test_unicode_symlink(self):
2435
inv_entry = inventory.InventoryLink(b'link-file-id',
2436
u'nam\N{Euro Sign}e',
2438
inv_entry.revision = b'link-revision-id'
2439
target = u'link-targ\N{Euro Sign}t'
2440
inv_entry.symlink_target = target
2441
self.assertDetails((b'l', target.encode('UTF-8'), 0, False,
2442
b'link-revision-id'), inv_entry)
2445
class TestSHA1Provider(tests.TestCaseInTempDir):
2447
def test_sha1provider_is_an_interface(self):
2448
p = dirstate.SHA1Provider()
2449
self.assertRaises(NotImplementedError, p.sha1, "foo")
2450
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2452
def test_defaultsha1provider_sha1(self):
2453
text = b'test\r\nwith\nall\rpossible line endings\r\n'
2454
self.build_tree_contents([('foo', text)])
2455
expected_sha = osutils.sha_string(text)
2456
p = dirstate.DefaultSHA1Provider()
2457
self.assertEqual(expected_sha, p.sha1('foo'))
2459
def test_defaultsha1provider_stat_and_sha1(self):
2460
text = b'test\r\nwith\nall\rpossible line endings\r\n'
2461
self.build_tree_contents([('foo', text)])
2462
expected_sha = osutils.sha_string(text)
2463
p = dirstate.DefaultSHA1Provider()
2464
statvalue, sha1 = p.stat_and_sha1('foo')
2465
self.assertTrue(len(statvalue) >= 10)
2466
self.assertEqual(len(text), statvalue.st_size)
2467
self.assertEqual(expected_sha, sha1)
2470
class _Repo(object):
2471
"""A minimal api to get InventoryRevisionTree to work."""
2474
default_format = controldir.format_registry.make_controldir('default')
2475
self._format = default_format.repository_format
2477
def lock_read(self):
2484
class TestUpdateBasisByDelta(tests.TestCase):
2486
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2487
if path.endswith('/'):
2492
dirname, basename = osutils.split(path)
2494
dir_id = dir_ids[dirname]
2496
dir_id = osutils.basename(dirname).encode('utf-8') + b'-id'
2498
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2499
dir_ids[path] = file_id
2501
ie = inventory.InventoryFile(file_id, basename, dir_id)
2504
ie.revision = rev_id
2507
def create_tree_from_shape(self, rev_id, shape):
2508
dir_ids = {'': b'root-id'}
2509
inv = inventory.Inventory(b'root-id', rev_id)
2512
path, file_id = info
2515
path, file_id, ie_rev_id = info
2517
# Replace the root entry
2518
del inv._byid[inv.root.file_id]
2519
inv.root.file_id = file_id
2520
inv._byid[file_id] = inv.root
2521
dir_ids[''] = file_id
2523
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2524
return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
2526
def create_empty_dirstate(self):
2527
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2528
self.addCleanup(os.remove, path)
2530
state = dirstate.DirState.initialize(path)
2531
self.addCleanup(state.unlock)
2534
def create_inv_delta(self, delta, rev_id):
2535
"""Translate a 'delta shape' into an actual InventoryDelta"""
2536
dir_ids = {'': b'root-id'}
2538
for old_path, new_path, file_id in delta:
2539
if old_path is not None and old_path.endswith('/'):
2540
# Don't have to actually do anything for this, because only
2541
# new_path creates InventoryEntries
2542
old_path = old_path[:-1]
2543
if new_path is None: # Delete
2544
inv_delta.append((old_path, None, file_id, None))
2546
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2547
inv_delta.append((old_path, new_path, file_id, ie))
2550
def assertUpdate(self, active, basis, target):
2551
"""Assert that update_basis_by_delta works how we want.
2553
Set up a DirState object with active_shape for tree 0, basis_shape for
2554
tree 1. Then apply the delta from basis_shape to target_shape,
2555
and assert that the DirState is still valid, and that its stored
2556
content matches the target_shape.
2558
active_tree = self.create_tree_from_shape(b'active', active)
2559
basis_tree = self.create_tree_from_shape(b'basis', basis)
2560
target_tree = self.create_tree_from_shape(b'target', target)
2561
state = self.create_empty_dirstate()
2562
state.set_state_from_scratch(active_tree.root_inventory,
2563
[(b'basis', basis_tree)], [])
2564
delta = target_tree.root_inventory._make_delta(
2565
basis_tree.root_inventory)
2566
state.update_basis_by_delta(delta, b'target')
2568
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2570
# The target now that delta has been applied should match the
2572
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2573
# And the dirblock state should be identical to the state if we created
2575
state2 = self.create_empty_dirstate()
2576
state2.set_state_from_scratch(active_tree.root_inventory,
2577
[(b'target', target_tree)], [])
2578
self.assertEqual(state2._dirblocks, state._dirblocks)
2581
def assertBadDelta(self, active, basis, delta):
2582
"""Test that we raise InconsistentDelta when appropriate.
2584
:param active: The active tree shape
2585
:param basis: The basis tree shape
2586
:param delta: A description of the delta to apply. Similar to the form
2587
for regular inventory deltas, but omitting the InventoryEntry.
2588
So adding a file is: (None, 'path', b'file-id')
2589
Adding a directory is: (None, 'path/', b'dir-id')
2590
Renaming a dir is: ('old/', 'new/', b'dir-id')
2593
active_tree = self.create_tree_from_shape(b'active', active)
2594
basis_tree = self.create_tree_from_shape(b'basis', basis)
2595
inv_delta = self.create_inv_delta(delta, b'target')
2596
state = self.create_empty_dirstate()
2597
state.set_state_from_scratch(active_tree.root_inventory,
2598
[(b'basis', basis_tree)], [])
2599
self.assertRaises(errors.InconsistentDelta,
2600
state.update_basis_by_delta, inv_delta, b'target')
2602
## state.update_basis_by_delta(inv_delta, b'target')
2603
# except errors.InconsistentDelta, e:
2604
## import pdb; pdb.set_trace()
2606
## import pdb; pdb.set_trace()
2607
self.assertTrue(state._changes_aborted)
2609
def test_remove_file_matching_active_state(self):
2610
state = self.assertUpdate(
2612
basis=[('file', b'file-id')],
2616
def test_remove_file_present_in_active_state(self):
2617
state = self.assertUpdate(
2618
active=[('file', b'file-id')],
2619
basis=[('file', b'file-id')],
2623
def test_remove_file_present_elsewhere_in_active_state(self):
2624
state = self.assertUpdate(
2625
active=[('other-file', b'file-id')],
2626
basis=[('file', b'file-id')],
2630
def test_remove_file_active_state_has_diff_file(self):
2631
state = self.assertUpdate(
2632
active=[('file', b'file-id-2')],
2633
basis=[('file', b'file-id')],
2637
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2638
state = self.assertUpdate(
2639
active=[('file', b'file-id-2'),
2640
('other-file', b'file-id')],
2641
basis=[('file', b'file-id')],
2645
def test_add_file_matching_active_state(self):
2646
state = self.assertUpdate(
2647
active=[('file', b'file-id')],
2649
target=[('file', b'file-id')],
2652
def test_add_file_in_empty_dir_not_matching_active_state(self):
2653
state = self.assertUpdate(
2655
basis=[('dir/', b'dir-id')],
2656
target=[('dir/', b'dir-id', b'basis'), ('dir/file', b'file-id')],
2659
def test_add_file_missing_in_active_state(self):
2660
state = self.assertUpdate(
2663
target=[('file', b'file-id')],
2666
def test_add_file_elsewhere_in_active_state(self):
2667
state = self.assertUpdate(
2668
active=[('other-file', b'file-id')],
2670
target=[('file', b'file-id')],
2673
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2674
state = self.assertUpdate(
2675
active=[('other-file', b'file-id'),
2676
('file', b'file-id-2')],
2678
target=[('file', b'file-id')],
2681
def test_rename_file_matching_active_state(self):
2682
state = self.assertUpdate(
2683
active=[('other-file', b'file-id')],
2684
basis=[('file', b'file-id')],
2685
target=[('other-file', b'file-id')],
2688
def test_rename_file_missing_in_active_state(self):
2689
state = self.assertUpdate(
2691
basis=[('file', b'file-id')],
2692
target=[('other-file', b'file-id')],
2695
def test_rename_file_present_elsewhere_in_active_state(self):
2696
state = self.assertUpdate(
2697
active=[('third', b'file-id')],
2698
basis=[('file', b'file-id')],
2699
target=[('other-file', b'file-id')],
2702
def test_rename_file_active_state_has_diff_source_file(self):
2703
state = self.assertUpdate(
2704
active=[('file', b'file-id-2')],
2705
basis=[('file', b'file-id')],
2706
target=[('other-file', b'file-id')],
2709
def test_rename_file_active_state_has_diff_target_file(self):
2710
state = self.assertUpdate(
2711
active=[('other-file', b'file-id-2')],
2712
basis=[('file', b'file-id')],
2713
target=[('other-file', b'file-id')],
2716
def test_rename_file_active_has_swapped_files(self):
2717
state = self.assertUpdate(
2718
active=[('file', b'file-id'),
2719
('other-file', b'file-id-2')],
2720
basis=[('file', b'file-id'),
2721
('other-file', b'file-id-2')],
2722
target=[('file', b'file-id-2'),
2723
('other-file', b'file-id')])
2725
def test_rename_file_basis_has_swapped_files(self):
2726
state = self.assertUpdate(
2727
active=[('file', b'file-id'),
2728
('other-file', b'file-id-2')],
2729
basis=[('file', b'file-id-2'),
2730
('other-file', b'file-id')],
2731
target=[('file', b'file-id'),
2732
('other-file', b'file-id-2')])
2734
def test_rename_directory_with_contents(self):
2735
state = self.assertUpdate( # active matches basis
2736
active=[('dir1/', b'dir-id'),
2737
('dir1/file', b'file-id')],
2738
basis=[('dir1/', b'dir-id'),
2739
('dir1/file', b'file-id')],
2740
target=[('dir2/', b'dir-id'),
2741
('dir2/file', b'file-id')])
2742
state = self.assertUpdate( # active matches target
2743
active=[('dir2/', b'dir-id'),
2744
('dir2/file', b'file-id')],
2745
basis=[('dir1/', b'dir-id'),
2746
('dir1/file', b'file-id')],
2747
target=[('dir2/', b'dir-id'),
2748
('dir2/file', b'file-id')])
2749
state = self.assertUpdate( # active empty
2751
basis=[('dir1/', b'dir-id'),
2752
('dir1/file', b'file-id')],
2753
target=[('dir2/', b'dir-id'),
2754
('dir2/file', b'file-id')])
2755
state = self.assertUpdate( # active present at other location
2756
active=[('dir3/', b'dir-id'),
2757
('dir3/file', b'file-id')],
2758
basis=[('dir1/', b'dir-id'),
2759
('dir1/file', b'file-id')],
2760
target=[('dir2/', b'dir-id'),
2761
('dir2/file', b'file-id')])
2762
state = self.assertUpdate( # active has different ids
2763
active=[('dir1/', b'dir1-id'),
2764
('dir1/file', b'file1-id'),
2765
('dir2/', b'dir2-id'),
2766
('dir2/file', b'file2-id')],
2767
basis=[('dir1/', b'dir-id'),
2768
('dir1/file', b'file-id')],
2769
target=[('dir2/', b'dir-id'),
2770
('dir2/file', b'file-id')])
2772
def test_invalid_file_not_present(self):
2773
state = self.assertBadDelta(
2774
active=[('file', b'file-id')],
2775
basis=[('file', b'file-id')],
2776
delta=[('other-file', 'file', b'file-id')])
2778
def test_invalid_new_id_same_path(self):
2779
# The bad entry comes after
2780
state = self.assertBadDelta(
2781
active=[('file', b'file-id')],
2782
basis=[('file', b'file-id')],
2783
delta=[(None, 'file', b'file-id-2')])
2784
# The bad entry comes first
2785
state = self.assertBadDelta(
2786
active=[('file', b'file-id-2')],
2787
basis=[('file', b'file-id-2')],
2788
delta=[(None, 'file', b'file-id')])
2790
def test_invalid_existing_id(self):
2791
state = self.assertBadDelta(
2792
active=[('file', b'file-id')],
2793
basis=[('file', b'file-id')],
2794
delta=[(None, 'file', b'file-id')])
2796
def test_invalid_parent_missing(self):
2797
state = self.assertBadDelta(
2800
delta=[(None, 'path/path2', b'file-id')])
2801
# Note: we force the active tree to have the directory, by knowing how
2802
# path_to_ie handles entries with missing parents
2803
state = self.assertBadDelta(
2804
active=[('path/', b'path-id')],
2806
delta=[(None, 'path/path2', b'file-id')])
2807
state = self.assertBadDelta(
2808
active=[('path/', b'path-id'),
2809
('path/path2', b'file-id')],
2811
delta=[(None, 'path/path2', b'file-id')])
2813
def test_renamed_dir_same_path(self):
2814
# We replace the parent directory, with another parent dir. But the C
2815
# file doesn't look like it has been moved.
2816
state = self.assertUpdate( # Same as basis
2817
active=[('dir/', b'A-id'),
2818
('dir/B', b'B-id')],
2819
basis=[('dir/', b'A-id'),
2820
('dir/B', b'B-id')],
2821
target=[('dir/', b'C-id'),
2822
('dir/B', b'B-id')])
2823
state = self.assertUpdate( # Same as target
2824
active=[('dir/', b'C-id'),
2825
('dir/B', b'B-id')],
2826
basis=[('dir/', b'A-id'),
2827
('dir/B', b'B-id')],
2828
target=[('dir/', b'C-id'),
2829
('dir/B', b'B-id')])
2830
state = self.assertUpdate( # empty active
2832
basis=[('dir/', b'A-id'),
2833
('dir/B', b'B-id')],
2834
target=[('dir/', b'C-id'),
2835
('dir/B', b'B-id')])
2836
state = self.assertUpdate( # different active
2837
active=[('dir/', b'D-id'),
2838
('dir/B', b'B-id')],
2839
basis=[('dir/', b'A-id'),
2840
('dir/B', b'B-id')],
2841
target=[('dir/', b'C-id'),
2842
('dir/B', b'B-id')])
2844
def test_parent_child_swap(self):
2845
state = self.assertUpdate( # Same as basis
2846
active=[('A/', b'A-id'),
2848
('A/B/C', b'C-id')],
2849
basis=[('A/', b'A-id'),
2851
('A/B/C', b'C-id')],
2852
target=[('A/', b'B-id'),
2854
('A/B/C', b'C-id')])
2855
state = self.assertUpdate( # Same as target
2856
active=[('A/', b'B-id'),
2858
('A/B/C', b'C-id')],
2859
basis=[('A/', b'A-id'),
2861
('A/B/C', b'C-id')],
2862
target=[('A/', b'B-id'),
2864
('A/B/C', b'C-id')])
2865
state = self.assertUpdate( # empty active
2867
basis=[('A/', b'A-id'),
2869
('A/B/C', b'C-id')],
2870
target=[('A/', b'B-id'),
2872
('A/B/C', b'C-id')])
2873
state = self.assertUpdate( # different active
2874
active=[('D/', b'A-id'),
2877
basis=[('A/', b'A-id'),
2879
('A/B/C', b'C-id')],
2880
target=[('A/', b'B-id'),
2882
('A/B/C', b'C-id')])
2884
def test_change_root_id(self):
2885
state = self.assertUpdate( # same as basis
2886
active=[('', b'root-id'),
2887
('file', b'file-id')],
2888
basis=[('', b'root-id'),
2889
('file', b'file-id')],
2890
target=[('', b'target-root-id'),
2891
('file', b'file-id')])
2892
state = self.assertUpdate( # same as target
2893
active=[('', b'target-root-id'),
2894
('file', b'file-id')],
2895
basis=[('', b'root-id'),
2896
('file', b'file-id')],
2897
target=[('', b'target-root-id'),
2898
('file', b'root-id')])
2899
state = self.assertUpdate( # all different
2900
active=[('', b'active-root-id'),
2901
('file', b'file-id')],
2902
basis=[('', b'root-id'),
2903
('file', b'file-id')],
2904
target=[('', b'target-root-id'),
2905
('file', b'root-id')])
2907
def test_change_file_absent_in_active(self):
2908
state = self.assertUpdate(
2910
basis=[('file', b'file-id')],
2911
target=[('file', b'file-id')])
2913
def test_invalid_changed_file(self):
2914
state = self.assertBadDelta( # Not present in basis
2915
active=[('file', b'file-id')],
2917
delta=[('file', 'file', b'file-id')])
2918
state = self.assertBadDelta( # present at another location in basis
2919
active=[('file', b'file-id')],
2920
basis=[('other-file', b'file-id')],
2921
delta=[('file', 'file', b'file-id')])