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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')), # 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.path2id('')
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.path2id('')
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.path2id('')
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'])
1340
with tree1.lock_write():
1341
tree1.add(['b'], [b'b-id'])
1342
root_id = tree1.path2id('')
1343
inv = tree1.root_inventory
1344
state = dirstate.DirState.initialize('dirstate')
1346
# Set the initial state with 'b'
1347
state.set_state_from_inventory(inv)
1348
inv.rename(b'b-id', root_id, 'a')
1349
# Set the new state with 'a', which currently corrupts.
1350
state.set_state_from_inventory(inv)
1351
expected_result1 = [(b'', b'', root_id, b'd'),
1352
(b'', b'a', b'b-id', b'f'),
1355
for entry in state._iter_entries():
1356
values.append(entry[0] + entry[1][0][:1])
1357
self.assertEqual(expected_result1, values)
1362
class TestDirStateHashUpdates(TestCaseWithDirState):
1364
def do_update_entry(self, state, path):
1365
entry = state._get_entry(0, path_utf8=path)
1366
stat = os.lstat(path)
1367
return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
1369
def _read_state_content(self, state):
1370
"""Read the content of the dirstate file.
1372
On Windows when one process locks a file, you can't even open() the
1373
file in another process (to read it). So we go directly to
1374
state._state_file. This should always be the exact disk representation,
1375
so it is reasonable to do so.
1376
DirState also always seeks before reading, so it doesn't matter if we
1377
bump the file pointer.
1379
state._state_file.seek(0)
1380
return state._state_file.read()
1382
def test_worth_saving_limit_avoids_writing(self):
1383
tree = self.make_branch_and_tree('.')
1384
self.build_tree(['c', 'd'])
1386
tree.add(['c', 'd'], [b'c-id', b'd-id'])
1387
tree.commit('add c and d')
1388
state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
1389
worth_saving_limit=2)
1392
self.addCleanup(state.unlock)
1393
state._read_dirblocks_if_needed()
1394
state.adjust_time(+20) # Allow things to be cached
1395
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1396
state._dirblock_state)
1397
content = self._read_state_content(state)
1398
self.do_update_entry(state, b'c')
1399
self.assertEqual(1, len(state._known_hash_changes))
1400
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1401
state._dirblock_state)
1403
# It should not have set the state to IN_MEMORY_UNMODIFIED because the
1404
# hash values haven't been written out.
1405
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1406
state._dirblock_state)
1407
self.assertEqual(content, self._read_state_content(state))
1408
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
1409
state._dirblock_state)
1410
self.do_update_entry(state, b'd')
1411
self.assertEqual(2, len(state._known_hash_changes))
1413
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1414
state._dirblock_state)
1415
self.assertEqual(0, len(state._known_hash_changes))
1418
class TestGetLines(TestCaseWithDirState):
1420
def test_get_line_with_2_rows(self):
1421
state = self.create_dirstate_with_root_and_subdir()
1423
self.assertEqual([b'#bazaar dirstate flat format 3\n',
1424
b'crc32: 41262208\n',
1425
b'num_entries: 2\n',
1428
b'\x00\x00a-root-value\x00'
1429
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1430
b'\x00subdir\x00subdir-id\x00'
1431
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1432
], state.get_lines())
1436
def test_entry_to_line(self):
1437
state = self.create_dirstate_with_root()
1440
b'\x00\x00a-root-value\x00d\x00\x000\x00n'
1441
b'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1442
state._entry_to_line(state._dirblocks[0][1][0]))
1446
def test_entry_to_line_with_parent(self):
1447
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1448
root_entry = (b'', b'', b'a-root-value'), [
1449
(b'd', b'', 0, False, packed_stat), # current tree details
1450
# first: a pointer to the current location
1451
(b'a', b'dirname/basename', 0, False, b''),
1453
state = dirstate.DirState.initialize('dirstate')
1456
b'\x00\x00a-root-value\x00'
1457
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1458
b'a\x00dirname/basename\x000\x00n\x00',
1459
state._entry_to_line(root_entry))
1463
def test_entry_to_line_with_two_parents_at_different_paths(self):
1464
# / in the tree, at / in one parent and /dirname/basename in the other.
1465
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1466
root_entry = (b'', b'', b'a-root-value'), [
1467
(b'd', b'', 0, False, packed_stat), # current tree details
1468
(b'd', b'', 0, False, b'rev_id'), # first parent details
1469
# second: a pointer to the current location
1470
(b'a', b'dirname/basename', 0, False, b''),
1472
state = dirstate.DirState.initialize('dirstate')
1475
b'\x00\x00a-root-value\x00'
1476
b'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1477
b'd\x00\x000\x00n\x00rev_id\x00'
1478
b'a\x00dirname/basename\x000\x00n\x00',
1479
state._entry_to_line(root_entry))
1483
def test_iter_entries(self):
1484
# we should be able to iterate the dirstate entries from end to end
1485
# this is for get_lines to be easy to read.
1486
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1488
root_entries = [((b'', b'', b'a-root-value'), [
1489
(b'd', b'', 0, False, packed_stat), # current tree details
1491
dirblocks.append(('', root_entries))
1492
# add two files in the root
1493
subdir_entry = (b'', b'subdir', b'subdir-id'), [
1494
(b'd', b'', 0, False, packed_stat), # current tree details
1496
afile_entry = (b'', b'afile', b'afile-id'), [
1497
(b'f', b'sha1value', 34, False, packed_stat), # current tree details
1499
dirblocks.append(('', [subdir_entry, afile_entry]))
1501
file_entry2 = (b'subdir', b'2file', b'2file-id'), [
1502
(b'f', b'sha1value', 23, False, packed_stat), # current tree details
1504
dirblocks.append(('subdir', [file_entry2]))
1505
state = dirstate.DirState.initialize('dirstate')
1507
state._set_data([], dirblocks)
1508
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1510
self.assertEqual(expected_entries, list(state._iter_entries()))
1515
class TestGetBlockRowIndex(TestCaseWithDirState):
1517
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1518
file_present, state, dirname, basename, tree_index):
1519
self.assertEqual((block_index, row_index, dir_present, file_present),
1520
state._get_block_entry_index(dirname, basename, tree_index))
1522
block = state._dirblocks[block_index]
1523
self.assertEqual(dirname, block[0])
1524
if dir_present and file_present:
1525
row = state._dirblocks[block_index][1][row_index]
1526
self.assertEqual(dirname, row[0][0])
1527
self.assertEqual(basename, row[0][1])
1529
def test_simple_structure(self):
1530
state = self.create_dirstate_with_root_and_subdir()
1531
self.addCleanup(state.unlock)
1532
self.assertBlockRowIndexEqual(
1533
1, 0, True, True, state, b'', b'subdir', 0)
1534
self.assertBlockRowIndexEqual(
1535
1, 0, True, False, state, b'', b'bdir', 0)
1536
self.assertBlockRowIndexEqual(
1537
1, 1, True, False, state, b'', b'zdir', 0)
1538
self.assertBlockRowIndexEqual(
1539
2, 0, False, False, state, b'a', b'foo', 0)
1540
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1541
b'subdir', b'foo', 0)
1543
def test_complex_structure_exists(self):
1544
state = self.create_complex_dirstate()
1545
self.addCleanup(state.unlock)
1546
# Make sure we can find everything that exists
1547
self.assertBlockRowIndexEqual(0, 0, True, True, state, b'', b'', 0)
1548
self.assertBlockRowIndexEqual(1, 0, True, True, state, b'', b'a', 0)
1549
self.assertBlockRowIndexEqual(1, 1, True, True, state, b'', b'b', 0)
1550
self.assertBlockRowIndexEqual(1, 2, True, True, state, b'', b'c', 0)
1551
self.assertBlockRowIndexEqual(1, 3, True, True, state, b'', b'd', 0)
1552
self.assertBlockRowIndexEqual(2, 0, True, True, state, b'a', b'e', 0)
1553
self.assertBlockRowIndexEqual(2, 1, True, True, state, b'a', b'f', 0)
1554
self.assertBlockRowIndexEqual(3, 0, True, True, state, b'b', b'g', 0)
1555
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1556
b'b', b'h\xc3\xa5', 0)
1558
def test_complex_structure_missing(self):
1559
state = self.create_complex_dirstate()
1560
self.addCleanup(state.unlock)
1561
# Make sure things would be inserted in the right locations
1562
# '_' comes before 'a'
1563
self.assertBlockRowIndexEqual(0, 0, True, True, state, b'', b'', 0)
1564
self.assertBlockRowIndexEqual(1, 0, True, False, state, b'', b'_', 0)
1565
self.assertBlockRowIndexEqual(1, 1, True, False, state, b'', b'aa', 0)
1566
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1567
b'', b'h\xc3\xa5', 0)
1568
self.assertBlockRowIndexEqual(2, 0, False, False, state, b'_', b'a', 0)
1569
self.assertBlockRowIndexEqual(
1570
3, 0, False, False, state, b'aa', b'a', 0)
1571
self.assertBlockRowIndexEqual(
1572
4, 0, False, False, state, b'bb', b'a', 0)
1573
# This would be inserted between a/ and b/
1574
self.assertBlockRowIndexEqual(
1575
3, 0, False, False, state, b'a/e', b'a', 0)
1577
self.assertBlockRowIndexEqual(4, 0, False, False, state, b'e', b'a', 0)
1580
class TestGetEntry(TestCaseWithDirState):
1582
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1583
"""Check that the right entry is returned for a request to getEntry."""
1584
entry = state._get_entry(index, path_utf8=path)
1586
self.assertEqual((None, None), entry)
1589
self.assertEqual((dirname, basename, file_id), cur[:3])
1591
def test_simple_structure(self):
1592
state = self.create_dirstate_with_root_and_subdir()
1593
self.addCleanup(state.unlock)
1594
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1595
self.assertEntryEqual(
1596
b'', b'subdir', b'subdir-id', state, b'subdir', 0)
1597
self.assertEntryEqual(None, None, None, state, b'missing', 0)
1598
self.assertEntryEqual(None, None, None, state, b'missing/foo', 0)
1599
self.assertEntryEqual(None, None, None, state, b'subdir/foo', 0)
1601
def test_complex_structure_exists(self):
1602
state = self.create_complex_dirstate()
1603
self.addCleanup(state.unlock)
1604
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1605
self.assertEntryEqual(b'', b'a', b'a-dir', state, b'a', 0)
1606
self.assertEntryEqual(b'', b'b', b'b-dir', state, b'b', 0)
1607
self.assertEntryEqual(b'', b'c', b'c-file', state, b'c', 0)
1608
self.assertEntryEqual(b'', b'd', b'd-file', state, b'd', 0)
1609
self.assertEntryEqual(b'a', b'e', b'e-dir', state, b'a/e', 0)
1610
self.assertEntryEqual(b'a', b'f', b'f-file', state, b'a/f', 0)
1611
self.assertEntryEqual(b'b', b'g', b'g-file', state, b'b/g', 0)
1612
self.assertEntryEqual(b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file', state,
1615
def test_complex_structure_missing(self):
1616
state = self.create_complex_dirstate()
1617
self.addCleanup(state.unlock)
1618
self.assertEntryEqual(None, None, None, state, b'_', 0)
1619
self.assertEntryEqual(None, None, None, state, b'_\xc3\xa5', 0)
1620
self.assertEntryEqual(None, None, None, state, b'a/b', 0)
1621
self.assertEntryEqual(None, None, None, state, b'c/d', 0)
1623
def test_get_entry_uninitialized(self):
1624
"""Calling get_entry will load data if it needs to"""
1625
state = self.create_dirstate_with_root()
1631
state = dirstate.DirState.on_file('dirstate')
1634
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1635
state._header_state)
1636
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1637
state._dirblock_state)
1638
self.assertEntryEqual(b'', b'', b'a-root-value', state, b'', 0)
1643
class TestIterChildEntries(TestCaseWithDirState):
1645
def create_dirstate_with_two_trees(self):
1646
"""This dirstate contains multiple files and directories.
1656
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1658
Notice that a/e is an empty directory.
1660
There is one parent tree, which has the same shape with the following variations:
1661
b/g in the parent is gone.
1662
b/h in the parent has a different id
1663
b/i is new in the parent
1664
c is renamed to b/j in the parent
1666
:return: The dirstate, still write-locked.
1668
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1669
null_sha = b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1670
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1671
root_entry = (b'', b'', b'a-root-value'), [
1672
(b'd', b'', 0, False, packed_stat),
1673
(b'd', b'', 0, False, b'parent-revid'),
1675
a_entry = (b'', b'a', b'a-dir'), [
1676
(b'd', b'', 0, False, packed_stat),
1677
(b'd', b'', 0, False, b'parent-revid'),
1679
b_entry = (b'', b'b', b'b-dir'), [
1680
(b'd', b'', 0, False, packed_stat),
1681
(b'd', b'', 0, False, b'parent-revid'),
1683
c_entry = (b'', b'c', b'c-file'), [
1684
(b'f', null_sha, 10, False, packed_stat),
1685
(b'r', b'b/j', 0, False, b''),
1687
d_entry = (b'', b'd', b'd-file'), [
1688
(b'f', null_sha, 20, False, packed_stat),
1689
(b'f', b'd', 20, False, b'parent-revid'),
1691
e_entry = (b'a', b'e', b'e-dir'), [
1692
(b'd', b'', 0, False, packed_stat),
1693
(b'd', b'', 0, False, b'parent-revid'),
1695
f_entry = (b'a', b'f', b'f-file'), [
1696
(b'f', null_sha, 30, False, packed_stat),
1697
(b'f', b'f', 20, False, b'parent-revid'),
1699
g_entry = (b'b', b'g', b'g-file'), [
1700
(b'f', null_sha, 30, False, packed_stat),
1701
NULL_PARENT_DETAILS,
1703
h_entry1 = (b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file1'), [
1704
(b'f', null_sha, 40, False, packed_stat),
1705
NULL_PARENT_DETAILS,
1707
h_entry2 = (b'b', b'h\xc3\xa5', b'h-\xc3\xa5-file2'), [
1708
NULL_PARENT_DETAILS,
1709
(b'f', b'h', 20, False, b'parent-revid'),
1711
i_entry = (b'b', b'i', b'i-file'), [
1712
NULL_PARENT_DETAILS,
1713
(b'f', b'h', 20, False, b'parent-revid'),
1715
j_entry = (b'b', b'j', b'c-file'), [
1716
(b'r', b'c', 0, False, b''),
1717
(b'f', b'j', 20, False, b'parent-revid'),
1720
dirblocks.append((b'', [root_entry]))
1721
dirblocks.append((b'', [a_entry, b_entry, c_entry, d_entry]))
1722
dirblocks.append((b'a', [e_entry, f_entry]))
1724
(b'b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1725
state = dirstate.DirState.initialize('dirstate')
1728
state._set_data([b'parent'], dirblocks)
1732
return state, dirblocks
1734
def test_iter_children_b(self):
1735
state, dirblocks = self.create_dirstate_with_two_trees()
1736
self.addCleanup(state.unlock)
1737
expected_result = []
1738
expected_result.append(dirblocks[3][1][2]) # h2
1739
expected_result.append(dirblocks[3][1][3]) # i
1740
expected_result.append(dirblocks[3][1][4]) # j
1741
self.assertEqual(expected_result,
1742
list(state._iter_child_entries(1, b'b')))
1744
def test_iter_child_root(self):
1745
state, dirblocks = self.create_dirstate_with_two_trees()
1746
self.addCleanup(state.unlock)
1747
expected_result = []
1748
expected_result.append(dirblocks[1][1][0]) # a
1749
expected_result.append(dirblocks[1][1][1]) # b
1750
expected_result.append(dirblocks[1][1][3]) # d
1751
expected_result.append(dirblocks[2][1][0]) # e
1752
expected_result.append(dirblocks[2][1][1]) # f
1753
expected_result.append(dirblocks[3][1][2]) # h2
1754
expected_result.append(dirblocks[3][1][3]) # i
1755
expected_result.append(dirblocks[3][1][4]) # j
1756
self.assertEqual(expected_result,
1757
list(state._iter_child_entries(1, b'')))
1760
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1761
"""Test that DirState adds entries in the right order."""
1763
def test_add_sorting(self):
1764
"""Add entries in lexicographical order, we get path sorted order.
1766
This tests it to a depth of 4, to make sure we don't just get it right
1767
at a single depth. 'a/a' should come before 'a-a', even though it
1768
doesn't lexicographically.
1770
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1771
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1774
state = dirstate.DirState.initialize('dirstate')
1775
self.addCleanup(state.unlock)
1777
fake_stat = os.stat('dirstate')
1779
d_id = d.encode('utf-8').replace(b'/', b'_') + b'-id'
1780
file_path = d + '/f'
1781
file_id = file_path.encode('utf-8').replace(b'/', b'_') + b'-id'
1782
state.add(d, d_id, 'directory', fake_stat, null_sha)
1783
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1785
expected = [b'', b'', b'a',
1786
b'a/a', b'a/a/a', b'a/a/a/a',
1787
b'a/a/a/a-a', b'a/a/a-a', b'a/a-a', b'a-a',
1790
def split(p): return p.split(b'/')
1791
self.assertEqual(sorted(expected, key=split), expected)
1792
dirblock_names = [d[0] for d in state._dirblocks]
1793
self.assertEqual(expected, dirblock_names)
1795
def test_set_parent_trees_correct_order(self):
1796
"""After calling set_parent_trees() we should maintain the order."""
1797
dirs = ['a', 'a-a', 'a/a']
1799
state = dirstate.DirState.initialize('dirstate')
1800
self.addCleanup(state.unlock)
1802
fake_stat = os.stat('dirstate')
1804
d_id = d.encode('utf-8').replace(b'/', b'_') + b'-id'
1805
file_path = d + '/f'
1806
file_id = file_path.encode('utf-8').replace(b'/', b'_') + b'-id'
1807
state.add(d, d_id, 'directory', fake_stat, null_sha)
1808
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1810
expected = [b'', b'', b'a', b'a/a', b'a-a']
1811
dirblock_names = [d[0] for d in state._dirblocks]
1812
self.assertEqual(expected, dirblock_names)
1814
# *really* cheesy way to just get an empty tree
1815
repo = self.make_repository('repo')
1816
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1817
state.set_parent_trees([('null:', empty_tree)], [])
1819
dirblock_names = [d[0] for d in state._dirblocks]
1820
self.assertEqual(expected, dirblock_names)
1823
class InstrumentedDirState(dirstate.DirState):
1824
"""An DirState with instrumented sha1 functionality."""
1826
def __init__(self, path, sha1_provider, worth_saving_limit=0,
1827
use_filesystem_for_exec=True):
1828
super(InstrumentedDirState, self).__init__(
1829
path, sha1_provider, worth_saving_limit=worth_saving_limit,
1830
use_filesystem_for_exec=use_filesystem_for_exec)
1831
self._time_offset = 0
1833
# member is dynamically set in DirState.__init__ to turn on trace
1834
self._sha1_provider = sha1_provider
1835
self._sha1_file = self._sha1_file_and_log
1837
def _sha_cutoff_time(self):
1838
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1839
self._cutoff_time = timestamp + self._time_offset
1841
def _sha1_file_and_log(self, abspath):
1842
self._log.append(('sha1', abspath))
1843
return self._sha1_provider.sha1(abspath)
1845
def _read_link(self, abspath, old_link):
1846
self._log.append(('read_link', abspath, old_link))
1847
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1849
def _lstat(self, abspath, entry):
1850
self._log.append(('lstat', abspath))
1851
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1853
def _is_executable(self, mode, old_executable):
1854
self._log.append(('is_exec', mode, old_executable))
1855
return super(InstrumentedDirState, self)._is_executable(mode,
1858
def adjust_time(self, secs):
1859
"""Move the clock forward or back.
1861
:param secs: The amount to adjust the clock by. Positive values make it
1862
seem as if we are in the future, negative values make it seem like we
1865
self._time_offset += secs
1866
self._cutoff_time = None
1869
class _FakeStat(object):
1870
"""A class with the same attributes as a real stat result."""
1872
def __init__(self, size, mtime, ctime, dev, ino, mode):
1874
self.st_mtime = mtime
1875
self.st_ctime = ctime
1882
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1883
st.st_ino, st.st_mode)
1886
class TestPackStat(tests.TestCaseWithTransport):
1888
def assertPackStat(self, expected, stat_value):
1889
"""Check the packed and serialized form of a stat value."""
1890
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1892
def test_pack_stat_int(self):
1893
st = _FakeStat(6859, 1172758614, 1172758617, 777, 6499538, 0o100644)
1894
# Make sure that all parameters have an impact on the packed stat.
1895
self.assertPackStat(b'AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1898
self.assertPackStat(b'AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1899
st.st_mtime = 1172758620
1901
self.assertPackStat(b'AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1902
st.st_ctime = 1172758630
1904
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1907
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1910
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1911
st.st_mode = 0o100744
1913
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1915
def test_pack_stat_float(self):
1916
"""On some platforms mtime and ctime are floats.
1918
Make sure we don't get warnings or errors, and that we ignore changes <
1921
st = _FakeStat(7000, 1172758614.0, 1172758617.0,
1922
777, 6499538, 0o100644)
1923
# These should all be the same as the integer counterparts
1924
self.assertPackStat(b'AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1925
st.st_mtime = 1172758620.0
1927
self.assertPackStat(b'AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1928
st.st_ctime = 1172758630.0
1930
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1931
# fractional seconds are discarded, so no change from above
1932
st.st_mtime = 1172758620.453
1933
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1934
st.st_ctime = 1172758630.228
1935
self.assertPackStat(b'AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1938
class TestBisect(TestCaseWithDirState):
1939
"""Test the ability to bisect into the disk format."""
1941
def assertBisect(self, expected_map, map_keys, state, paths):
1942
"""Assert that bisecting for paths returns the right result.
1944
:param expected_map: A map from key => entry value
1945
:param map_keys: The keys to expect for each path
1946
:param state: The DirState object.
1947
:param paths: A list of paths, these will automatically be split into
1948
(dir, name) tuples, and sorted according to how _bisect
1951
result = state._bisect(paths)
1952
# For now, results are just returned in whatever order we read them.
1953
# We could sort by (dir, name, file_id) or something like that, but in
1954
# the end it would still be fairly arbitrary, and we don't want the
1955
# extra overhead if we can avoid it. So sort everything to make sure
1957
self.assertEqual(len(map_keys), len(paths))
1959
for path, keys in zip(paths, map_keys):
1961
# This should not be present in the output
1963
expected[path] = sorted(expected_map[k] for k in keys)
1965
# The returned values are just arranged randomly based on when they
1966
# were read, for testing, make sure it is properly sorted.
1970
self.assertEqual(expected, result)
1972
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1973
"""Assert that bisecting for dirbblocks returns the right result.
1975
:param expected_map: A map from key => expected values
1976
:param map_keys: A nested list of paths we expect to be returned.
1977
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1978
:param state: The DirState object.
1979
:param paths: A list of directories
1981
result = state._bisect_dirblocks(paths)
1982
self.assertEqual(len(map_keys), len(paths))
1984
for path, keys in zip(paths, map_keys):
1986
# This should not be present in the output
1988
expected[path] = sorted(expected_map[k] for k in keys)
1992
self.assertEqual(expected, result)
1994
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1995
"""Assert the return value of a recursive bisection.
1997
:param expected_map: A map from key => entry value
1998
:param map_keys: A list of paths we expect to be returned.
1999
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2000
:param state: The DirState object.
2001
:param paths: A list of files and directories. It will be broken up
2002
into (dir, name) pairs and sorted before calling _bisect_recursive.
2005
for key in map_keys:
2006
entry = expected_map[key]
2007
dir_name_id, trees_info = entry
2008
expected[dir_name_id] = trees_info
2010
result = state._bisect_recursive(paths)
2012
self.assertEqual(expected, result)
2014
def test_bisect_each(self):
2015
"""Find a single record using bisect."""
2016
tree, state, expected = self.create_basic_dirstate()
2018
# Bisect should return the rows for the specified files.
2019
self.assertBisect(expected, [[b'']], state, [b''])
2020
self.assertBisect(expected, [[b'a']], state, [b'a'])
2021
self.assertBisect(expected, [[b'b']], state, [b'b'])
2022
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2023
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2024
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2025
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2026
self.assertBisect(expected, [[b'f']], state, [b'f'])
2028
def test_bisect_multi(self):
2029
"""Bisect can be used to find multiple records at the same time."""
2030
tree, state, expected = self.create_basic_dirstate()
2031
# Bisect should be capable of finding multiple entries at the same time
2032
self.assertBisect(expected, [[b'a'], [b'b'], [b'f']],
2033
state, [b'a', b'b', b'f'])
2034
self.assertBisect(expected, [[b'f'], [b'b/d'], [b'b/d/e']],
2035
state, [b'f', b'b/d', b'b/d/e'])
2036
self.assertBisect(expected, [[b'b'], [b'b-c'], [b'b/c']],
2037
state, [b'b', b'b-c', b'b/c'])
2039
def test_bisect_one_page(self):
2040
"""Test bisect when there is only 1 page to read"""
2041
tree, state, expected = self.create_basic_dirstate()
2042
state._bisect_page_size = 5000
2043
self.assertBisect(expected, [[b'']], state, [b''])
2044
self.assertBisect(expected, [[b'a']], state, [b'a'])
2045
self.assertBisect(expected, [[b'b']], state, [b'b'])
2046
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2047
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2048
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2049
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2050
self.assertBisect(expected, [[b'f']], state, [b'f'])
2051
self.assertBisect(expected, [[b'a'], [b'b'], [b'f']],
2052
state, [b'a', b'b', b'f'])
2053
self.assertBisect(expected, [[b'b/d'], [b'b/d/e'], [b'f']],
2054
state, [b'b/d', b'b/d/e', b'f'])
2055
self.assertBisect(expected, [[b'b'], [b'b/c'], [b'b-c']],
2056
state, [b'b', b'b/c', b'b-c'])
2058
def test_bisect_duplicate_paths(self):
2059
"""When bisecting for a path, handle multiple entries."""
2060
tree, state, expected = self.create_duplicated_dirstate()
2062
# Now make sure that both records are properly returned.
2063
self.assertBisect(expected, [[b'']], state, [b''])
2064
self.assertBisect(expected, [[b'a', b'a2']], state, [b'a'])
2065
self.assertBisect(expected, [[b'b', b'b2']], state, [b'b'])
2066
self.assertBisect(expected, [[b'b/c', b'b/c2']], state, [b'b/c'])
2067
self.assertBisect(expected, [[b'b/d', b'b/d2']], state, [b'b/d'])
2068
self.assertBisect(expected, [[b'b/d/e', b'b/d/e2']],
2070
self.assertBisect(expected, [[b'b-c', b'b-c2']], state, [b'b-c'])
2071
self.assertBisect(expected, [[b'f', b'f2']], state, [b'f'])
2073
def test_bisect_page_size_too_small(self):
2074
"""If the page size is too small, we will auto increase it."""
2075
tree, state, expected = self.create_basic_dirstate()
2076
state._bisect_page_size = 50
2077
self.assertBisect(expected, [None], state, [b'b/e'])
2078
self.assertBisect(expected, [[b'a']], state, [b'a'])
2079
self.assertBisect(expected, [[b'b']], state, [b'b'])
2080
self.assertBisect(expected, [[b'b/c']], state, [b'b/c'])
2081
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2082
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2083
self.assertBisect(expected, [[b'b-c']], state, [b'b-c'])
2084
self.assertBisect(expected, [[b'f']], state, [b'f'])
2086
def test_bisect_missing(self):
2087
"""Test that bisect return None if it cannot find a path."""
2088
tree, state, expected = self.create_basic_dirstate()
2089
self.assertBisect(expected, [None], state, [b'foo'])
2090
self.assertBisect(expected, [None], state, [b'b/foo'])
2091
self.assertBisect(expected, [None], state, [b'bar/foo'])
2092
self.assertBisect(expected, [None], state, [b'b-c/foo'])
2094
self.assertBisect(expected, [[b'a'], None, [b'b/d']],
2095
state, [b'a', b'foo', b'b/d'])
2097
def test_bisect_rename(self):
2098
"""Check that we find a renamed row."""
2099
tree, state, expected = self.create_renamed_dirstate()
2101
# Search for the pre and post renamed entries
2102
self.assertBisect(expected, [[b'a']], state, [b'a'])
2103
self.assertBisect(expected, [[b'b/g']], state, [b'b/g'])
2104
self.assertBisect(expected, [[b'b/d']], state, [b'b/d'])
2105
self.assertBisect(expected, [[b'h']], state, [b'h'])
2107
# What about b/d/e? shouldn't that also get 2 directory entries?
2108
self.assertBisect(expected, [[b'b/d/e']], state, [b'b/d/e'])
2109
self.assertBisect(expected, [[b'h/e']], state, [b'h/e'])
2111
def test_bisect_dirblocks(self):
2112
tree, state, expected = self.create_duplicated_dirstate()
2113
self.assertBisectDirBlocks(expected,
2114
[[b'', b'a', b'a2', b'b', b'b2',
2115
b'b-c', b'b-c2', b'f', b'f2']],
2117
self.assertBisectDirBlocks(expected,
2118
[[b'b/c', b'b/c2', b'b/d', b'b/d2']], state, [b'b'])
2119
self.assertBisectDirBlocks(expected,
2120
[[b'b/d/e', b'b/d/e2']], state, [b'b/d'])
2121
self.assertBisectDirBlocks(expected,
2122
[[b'', b'a', b'a2', b'b', b'b2', b'b-c', b'b-c2', b'f', b'f2'],
2123
[b'b/c', b'b/c2', b'b/d', b'b/d2'],
2124
[b'b/d/e', b'b/d/e2'],
2125
], state, [b'', b'b', b'b/d'])
2127
def test_bisect_dirblocks_missing(self):
2128
tree, state, expected = self.create_basic_dirstate()
2129
self.assertBisectDirBlocks(expected, [[b'b/d/e'], None],
2130
state, [b'b/d', b'b/e'])
2131
# Files don't show up in this search
2132
self.assertBisectDirBlocks(expected, [None], state, [b'a'])
2133
self.assertBisectDirBlocks(expected, [None], state, [b'b/c'])
2134
self.assertBisectDirBlocks(expected, [None], state, [b'c'])
2135
self.assertBisectDirBlocks(expected, [None], state, [b'b/d/e'])
2136
self.assertBisectDirBlocks(expected, [None], state, [b'f'])
2138
def test_bisect_recursive_each(self):
2139
tree, state, expected = self.create_basic_dirstate()
2140
self.assertBisectRecursive(expected, [b'a'], state, [b'a'])
2141
self.assertBisectRecursive(expected, [b'b/c'], state, [b'b/c'])
2142
self.assertBisectRecursive(expected, [b'b/d/e'], state, [b'b/d/e'])
2143
self.assertBisectRecursive(expected, [b'b-c'], state, [b'b-c'])
2144
self.assertBisectRecursive(expected, [b'b/d', b'b/d/e'],
2146
self.assertBisectRecursive(expected, [b'b', b'b/c', b'b/d', b'b/d/e'],
2148
self.assertBisectRecursive(expected, [b'', b'a', b'b', b'b-c', b'f', b'b/c',
2152
def test_bisect_recursive_multiple(self):
2153
tree, state, expected = self.create_basic_dirstate()
2154
self.assertBisectRecursive(
2155
expected, [b'a', b'b/c'], state, [b'a', b'b/c'])
2156
self.assertBisectRecursive(expected, [b'b/d', b'b/d/e'],
2157
state, [b'b/d', b'b/d/e'])
2159
def test_bisect_recursive_missing(self):
2160
tree, state, expected = self.create_basic_dirstate()
2161
self.assertBisectRecursive(expected, [], state, [b'd'])
2162
self.assertBisectRecursive(expected, [], state, [b'b/e'])
2163
self.assertBisectRecursive(expected, [], state, [b'g'])
2164
self.assertBisectRecursive(expected, [b'a'], state, [b'a', b'g'])
2166
def test_bisect_recursive_renamed(self):
2167
tree, state, expected = self.create_renamed_dirstate()
2169
# Looking for either renamed item should find the other
2170
self.assertBisectRecursive(expected, [b'a', b'b/g'], state, [b'a'])
2171
self.assertBisectRecursive(expected, [b'a', b'b/g'], state, [b'b/g'])
2172
# Looking in the containing directory should find the rename target,
2173
# and anything in a subdir of the renamed target.
2174
self.assertBisectRecursive(expected, [b'a', b'b', b'b/c', b'b/d',
2175
b'b/d/e', b'b/g', b'h', b'h/e'],
2179
class TestDirstateValidation(TestCaseWithDirState):
2181
def test_validate_correct_dirstate(self):
2182
state = self.create_complex_dirstate()
2185
# and make sure we can also validate with a read lock
2192
def test_dirblock_not_sorted(self):
2193
tree, state, expected = self.create_renamed_dirstate()
2194
state._read_dirblocks_if_needed()
2195
last_dirblock = state._dirblocks[-1]
2196
# we're appending to the dirblock, but this name comes before some of
2197
# the existing names; that's wrong
2198
last_dirblock[1].append(
2199
((b'h', b'aaaa', b'a-id'),
2200
[(b'a', b'', 0, False, b''),
2201
(b'a', b'', 0, False, b'')]))
2202
e = self.assertRaises(AssertionError,
2204
self.assertContainsRe(str(e), 'not sorted')
2206
def test_dirblock_name_mismatch(self):
2207
tree, state, expected = self.create_renamed_dirstate()
2208
state._read_dirblocks_if_needed()
2209
last_dirblock = state._dirblocks[-1]
2210
# add an entry with the wrong directory name
2211
last_dirblock[1].append(
2212
((b'', b'z', b'a-id'),
2213
[(b'a', b'', 0, False, b''),
2214
(b'a', b'', 0, False, b'')]))
2215
e = self.assertRaises(AssertionError,
2217
self.assertContainsRe(str(e),
2218
"doesn't match directory name")
2220
def test_dirblock_missing_rename(self):
2221
tree, state, expected = self.create_renamed_dirstate()
2222
state._read_dirblocks_if_needed()
2223
last_dirblock = state._dirblocks[-1]
2224
# make another entry for a-id, without a correct 'r' pointer to
2225
# the real occurrence in the working tree
2226
last_dirblock[1].append(
2227
((b'h', b'z', b'a-id'),
2228
[(b'a', b'', 0, False, b''),
2229
(b'a', b'', 0, False, b'')]))
2230
e = self.assertRaises(AssertionError,
2232
self.assertContainsRe(str(e),
2233
'file a-id is absent in row')
2236
class TestDirstateTreeReference(TestCaseWithDirState):
2238
def test_reference_revision_is_none(self):
2239
tree = self.make_branch_and_tree('tree', format='development-subtree')
2240
subtree = self.make_branch_and_tree('tree/subtree',
2241
format='development-subtree')
2242
subtree.set_root_id(b'subtree')
2243
tree.add_reference(subtree)
2245
state = dirstate.DirState.from_tree(tree, 'dirstate')
2246
key = (b'', b'subtree', b'subtree')
2247
expected = (b'', [(key,
2248
[(b't', b'', 0, False, b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2251
self.assertEqual(expected, state._find_block(key))
2256
class TestDiscardMergeParents(TestCaseWithDirState):
2258
def test_discard_no_parents(self):
2259
# This should be a no-op
2260
state = self.create_empty_dirstate()
2261
self.addCleanup(state.unlock)
2262
state._discard_merge_parents()
2265
def test_discard_one_parent(self):
2267
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2268
root_entry_direntry = (b'', b'', b'a-root-value'), [
2269
(b'd', b'', 0, False, packed_stat),
2270
(b'd', b'', 0, False, packed_stat),
2273
dirblocks.append((b'', [root_entry_direntry]))
2274
dirblocks.append((b'', []))
2276
state = self.create_empty_dirstate()
2277
self.addCleanup(state.unlock)
2278
state._set_data([b'parent-id'], dirblocks[:])
2281
state._discard_merge_parents()
2283
self.assertEqual(dirblocks, state._dirblocks)
2285
def test_discard_simple(self):
2287
packed_stat = b'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2288
root_entry_direntry = (b'', b'', b'a-root-value'), [
2289
(b'd', b'', 0, False, packed_stat),
2290
(b'd', b'', 0, False, packed_stat),
2291
(b'd', b'', 0, False, packed_stat),
2293
expected_root_entry_direntry = (b'', b'', b'a-root-value'), [
2294
(b'd', b'', 0, False, packed_stat),
2295
(b'd', b'', 0, False, packed_stat),
2298
dirblocks.append((b'', [root_entry_direntry]))
2299
dirblocks.append((b'', []))
2301
state = self.create_empty_dirstate()
2302
self.addCleanup(state.unlock)
2303
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2306
# This should strip of the extra column
2307
state._discard_merge_parents()
2309
expected_dirblocks = [(b'', [expected_root_entry_direntry]), (b'', [])]
2310
self.assertEqual(expected_dirblocks, state._dirblocks)
2312
def test_discard_absent(self):
2313
"""If entries are only in a merge, discard should remove the entries"""
2314
null_stat = dirstate.DirState.NULLSTAT
2315
present_dir = (b'd', b'', 0, False, null_stat)
2316
present_file = (b'f', b'', 0, False, null_stat)
2317
absent = dirstate.DirState.NULL_PARENT_DETAILS
2318
root_key = (b'', b'', b'a-root-value')
2319
file_in_root_key = (b'', b'file-in-root', b'a-file-id')
2320
file_in_merged_key = (b'', b'file-in-merged', b'b-file-id')
2321
dirblocks = [(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2322
(b'', [(file_in_merged_key,
2323
[absent, absent, present_file]),
2325
[present_file, present_file, present_file]),
2329
state = self.create_empty_dirstate()
2330
self.addCleanup(state.unlock)
2331
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2334
exp_dirblocks = [(b'', [(root_key, [present_dir, present_dir])]),
2335
(b'', [(file_in_root_key,
2336
[present_file, present_file]),
2339
state._discard_merge_parents()
2341
self.assertEqual(exp_dirblocks, state._dirblocks)
2343
def test_discard_renamed(self):
2344
null_stat = dirstate.DirState.NULLSTAT
2345
present_dir = (b'd', b'', 0, False, null_stat)
2346
present_file = (b'f', b'', 0, False, null_stat)
2347
absent = dirstate.DirState.NULL_PARENT_DETAILS
2348
root_key = (b'', b'', b'a-root-value')
2349
file_in_root_key = (b'', b'file-in-root', b'a-file-id')
2350
# Renamed relative to parent
2351
file_rename_s_key = (b'', b'file-s', b'b-file-id')
2352
file_rename_t_key = (b'', b'file-t', b'b-file-id')
2353
# And one that is renamed between the parents, but absent in this
2354
key_in_1 = (b'', b'file-in-1', b'c-file-id')
2355
key_in_2 = (b'', b'file-in-2', b'c-file-id')
2358
(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2360
[absent, present_file, (b'r', b'file-in-2', b'c-file-id')]),
2362
[absent, (b'r', b'file-in-1', b'c-file-id'), present_file]),
2364
[present_file, present_file, present_file]),
2366
[(b'r', b'file-t', b'b-file-id'), absent, present_file]),
2368
[present_file, absent, (b'r', b'file-s', b'b-file-id')]),
2372
(b'', [(root_key, [present_dir, present_dir])]),
2373
(b'', [(key_in_1, [absent, present_file]),
2374
(file_in_root_key, [present_file, present_file]),
2375
(file_rename_t_key, [present_file, absent]),
2378
state = self.create_empty_dirstate()
2379
self.addCleanup(state.unlock)
2380
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2383
state._discard_merge_parents()
2385
self.assertEqual(exp_dirblocks, state._dirblocks)
2387
def test_discard_all_subdir(self):
2388
null_stat = dirstate.DirState.NULLSTAT
2389
present_dir = (b'd', b'', 0, False, null_stat)
2390
present_file = (b'f', b'', 0, False, null_stat)
2391
absent = dirstate.DirState.NULL_PARENT_DETAILS
2392
root_key = (b'', b'', b'a-root-value')
2393
subdir_key = (b'', b'sub', b'dir-id')
2394
child1_key = (b'sub', b'child1', b'child1-id')
2395
child2_key = (b'sub', b'child2', b'child2-id')
2396
child3_key = (b'sub', b'child3', b'child3-id')
2399
(b'', [(root_key, [present_dir, present_dir, present_dir])]),
2400
(b'', [(subdir_key, [present_dir, present_dir, present_dir])]),
2401
(b'sub', [(child1_key, [absent, absent, present_file]),
2402
(child2_key, [absent, absent, present_file]),
2403
(child3_key, [absent, absent, present_file]),
2407
(b'', [(root_key, [present_dir, present_dir])]),
2408
(b'', [(subdir_key, [present_dir, present_dir])]),
2411
state = self.create_empty_dirstate()
2412
self.addCleanup(state.unlock)
2413
state._set_data([b'parent-id', b'merged-id'], dirblocks[:])
2416
state._discard_merge_parents()
2418
self.assertEqual(exp_dirblocks, state._dirblocks)
2421
class Test_InvEntryToDetails(tests.TestCase):
2423
def assertDetails(self, expected, inv_entry):
2424
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2425
self.assertEqual(expected, details)
2426
# details should always allow join() and always be a plain str when
2428
(minikind, fingerprint, size, executable, tree_data) = details
2429
self.assertIsInstance(minikind, bytes)
2430
self.assertIsInstance(fingerprint, bytes)
2431
self.assertIsInstance(tree_data, bytes)
2433
def test_unicode_symlink(self):
2434
inv_entry = inventory.InventoryLink(b'link-file-id',
2435
u'nam\N{Euro Sign}e',
2437
inv_entry.revision = b'link-revision-id'
2438
target = u'link-targ\N{Euro Sign}t'
2439
inv_entry.symlink_target = target
2440
self.assertDetails((b'l', target.encode('UTF-8'), 0, False,
2441
b'link-revision-id'), inv_entry)
2444
class TestSHA1Provider(tests.TestCaseInTempDir):
2446
def test_sha1provider_is_an_interface(self):
2447
p = dirstate.SHA1Provider()
2448
self.assertRaises(NotImplementedError, p.sha1, "foo")
2449
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2451
def test_defaultsha1provider_sha1(self):
2452
text = b'test\r\nwith\nall\rpossible line endings\r\n'
2453
self.build_tree_contents([('foo', text)])
2454
expected_sha = osutils.sha_string(text)
2455
p = dirstate.DefaultSHA1Provider()
2456
self.assertEqual(expected_sha, p.sha1('foo'))
2458
def test_defaultsha1provider_stat_and_sha1(self):
2459
text = b'test\r\nwith\nall\rpossible line endings\r\n'
2460
self.build_tree_contents([('foo', text)])
2461
expected_sha = osutils.sha_string(text)
2462
p = dirstate.DefaultSHA1Provider()
2463
statvalue, sha1 = p.stat_and_sha1('foo')
2464
self.assertTrue(len(statvalue) >= 10)
2465
self.assertEqual(len(text), statvalue.st_size)
2466
self.assertEqual(expected_sha, sha1)
2469
class _Repo(object):
2470
"""A minimal api to get InventoryRevisionTree to work."""
2473
default_format = controldir.format_registry.make_controldir('default')
2474
self._format = default_format.repository_format
2476
def lock_read(self):
2483
class TestUpdateBasisByDelta(tests.TestCase):
2485
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2486
if path.endswith('/'):
2491
dirname, basename = osutils.split(path)
2493
dir_id = dir_ids[dirname]
2495
dir_id = osutils.basename(dirname).encode('utf-8') + b'-id'
2497
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2498
dir_ids[path] = file_id
2500
ie = inventory.InventoryFile(file_id, basename, dir_id)
2503
ie.revision = rev_id
2506
def create_tree_from_shape(self, rev_id, shape):
2507
dir_ids = {'': b'root-id'}
2508
inv = inventory.Inventory(b'root-id', rev_id)
2511
path, file_id = info
2514
path, file_id, ie_rev_id = info
2516
# Replace the root entry
2517
del inv._byid[inv.root.file_id]
2518
inv.root.file_id = file_id
2519
inv._byid[file_id] = inv.root
2520
dir_ids[''] = file_id
2522
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2523
return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
2525
def create_empty_dirstate(self):
2526
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2527
self.addCleanup(os.remove, path)
2529
state = dirstate.DirState.initialize(path)
2530
self.addCleanup(state.unlock)
2533
def create_inv_delta(self, delta, rev_id):
2534
"""Translate a 'delta shape' into an actual InventoryDelta"""
2535
dir_ids = {'': b'root-id'}
2537
for old_path, new_path, file_id in delta:
2538
if old_path is not None and old_path.endswith('/'):
2539
# Don't have to actually do anything for this, because only
2540
# new_path creates InventoryEntries
2541
old_path = old_path[:-1]
2542
if new_path is None: # Delete
2543
inv_delta.append((old_path, None, file_id, None))
2545
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2546
inv_delta.append((old_path, new_path, file_id, ie))
2549
def assertUpdate(self, active, basis, target):
2550
"""Assert that update_basis_by_delta works how we want.
2552
Set up a DirState object with active_shape for tree 0, basis_shape for
2553
tree 1. Then apply the delta from basis_shape to target_shape,
2554
and assert that the DirState is still valid, and that its stored
2555
content matches the target_shape.
2557
active_tree = self.create_tree_from_shape(b'active', active)
2558
basis_tree = self.create_tree_from_shape(b'basis', basis)
2559
target_tree = self.create_tree_from_shape(b'target', target)
2560
state = self.create_empty_dirstate()
2561
state.set_state_from_scratch(active_tree.root_inventory,
2562
[(b'basis', basis_tree)], [])
2563
delta = target_tree.root_inventory._make_delta(
2564
basis_tree.root_inventory)
2565
state.update_basis_by_delta(delta, b'target')
2567
dirstate_tree = workingtree_4.DirStateRevisionTree(
2568
state, b'target', _Repo(), None)
2569
# The target now that delta has been applied should match the
2571
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2572
# And the dirblock state should be identical to the state if we created
2574
state2 = self.create_empty_dirstate()
2575
state2.set_state_from_scratch(active_tree.root_inventory,
2576
[(b'target', target_tree)], [])
2577
self.assertEqual(state2._dirblocks, state._dirblocks)
2580
def assertBadDelta(self, active, basis, delta):
2581
"""Test that we raise InconsistentDelta when appropriate.
2583
:param active: The active tree shape
2584
:param basis: The basis tree shape
2585
:param delta: A description of the delta to apply. Similar to the form
2586
for regular inventory deltas, but omitting the InventoryEntry.
2587
So adding a file is: (None, 'path', b'file-id')
2588
Adding a directory is: (None, 'path/', b'dir-id')
2589
Renaming a dir is: ('old/', 'new/', b'dir-id')
2592
active_tree = self.create_tree_from_shape(b'active', active)
2593
basis_tree = self.create_tree_from_shape(b'basis', basis)
2594
inv_delta = self.create_inv_delta(delta, b'target')
2595
state = self.create_empty_dirstate()
2596
state.set_state_from_scratch(active_tree.root_inventory,
2597
[(b'basis', basis_tree)], [])
2598
self.assertRaises(errors.InconsistentDelta,
2599
state.update_basis_by_delta, inv_delta, b'target')
2601
## state.update_basis_by_delta(inv_delta, b'target')
2602
# except errors.InconsistentDelta, e:
2603
## import pdb; pdb.set_trace()
2605
## import pdb; pdb.set_trace()
2606
self.assertTrue(state._changes_aborted)
2608
def test_remove_file_matching_active_state(self):
2609
state = self.assertUpdate(
2611
basis=[('file', b'file-id')],
2615
def test_remove_file_present_in_active_state(self):
2616
state = self.assertUpdate(
2617
active=[('file', b'file-id')],
2618
basis=[('file', b'file-id')],
2622
def test_remove_file_present_elsewhere_in_active_state(self):
2623
state = self.assertUpdate(
2624
active=[('other-file', b'file-id')],
2625
basis=[('file', b'file-id')],
2629
def test_remove_file_active_state_has_diff_file(self):
2630
state = self.assertUpdate(
2631
active=[('file', b'file-id-2')],
2632
basis=[('file', b'file-id')],
2636
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2637
state = self.assertUpdate(
2638
active=[('file', b'file-id-2'),
2639
('other-file', b'file-id')],
2640
basis=[('file', b'file-id')],
2644
def test_add_file_matching_active_state(self):
2645
state = self.assertUpdate(
2646
active=[('file', b'file-id')],
2648
target=[('file', b'file-id')],
2651
def test_add_file_in_empty_dir_not_matching_active_state(self):
2652
state = self.assertUpdate(
2654
basis=[('dir/', b'dir-id')],
2655
target=[('dir/', b'dir-id', b'basis'), ('dir/file', b'file-id')],
2658
def test_add_file_missing_in_active_state(self):
2659
state = self.assertUpdate(
2662
target=[('file', b'file-id')],
2665
def test_add_file_elsewhere_in_active_state(self):
2666
state = self.assertUpdate(
2667
active=[('other-file', b'file-id')],
2669
target=[('file', b'file-id')],
2672
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2673
state = self.assertUpdate(
2674
active=[('other-file', b'file-id'),
2675
('file', b'file-id-2')],
2677
target=[('file', b'file-id')],
2680
def test_rename_file_matching_active_state(self):
2681
state = self.assertUpdate(
2682
active=[('other-file', b'file-id')],
2683
basis=[('file', b'file-id')],
2684
target=[('other-file', b'file-id')],
2687
def test_rename_file_missing_in_active_state(self):
2688
state = self.assertUpdate(
2690
basis=[('file', b'file-id')],
2691
target=[('other-file', b'file-id')],
2694
def test_rename_file_present_elsewhere_in_active_state(self):
2695
state = self.assertUpdate(
2696
active=[('third', b'file-id')],
2697
basis=[('file', b'file-id')],
2698
target=[('other-file', b'file-id')],
2701
def test_rename_file_active_state_has_diff_source_file(self):
2702
state = self.assertUpdate(
2703
active=[('file', b'file-id-2')],
2704
basis=[('file', b'file-id')],
2705
target=[('other-file', b'file-id')],
2708
def test_rename_file_active_state_has_diff_target_file(self):
2709
state = self.assertUpdate(
2710
active=[('other-file', b'file-id-2')],
2711
basis=[('file', b'file-id')],
2712
target=[('other-file', b'file-id')],
2715
def test_rename_file_active_has_swapped_files(self):
2716
state = self.assertUpdate(
2717
active=[('file', b'file-id'),
2718
('other-file', b'file-id-2')],
2719
basis=[('file', b'file-id'),
2720
('other-file', b'file-id-2')],
2721
target=[('file', b'file-id-2'),
2722
('other-file', b'file-id')])
2724
def test_rename_file_basis_has_swapped_files(self):
2725
state = self.assertUpdate(
2726
active=[('file', b'file-id'),
2727
('other-file', b'file-id-2')],
2728
basis=[('file', b'file-id-2'),
2729
('other-file', b'file-id')],
2730
target=[('file', b'file-id'),
2731
('other-file', b'file-id-2')])
2733
def test_rename_directory_with_contents(self):
2734
state = self.assertUpdate( # active matches basis
2735
active=[('dir1/', b'dir-id'),
2736
('dir1/file', b'file-id')],
2737
basis=[('dir1/', b'dir-id'),
2738
('dir1/file', b'file-id')],
2739
target=[('dir2/', b'dir-id'),
2740
('dir2/file', b'file-id')])
2741
state = self.assertUpdate( # active matches target
2742
active=[('dir2/', b'dir-id'),
2743
('dir2/file', b'file-id')],
2744
basis=[('dir1/', b'dir-id'),
2745
('dir1/file', b'file-id')],
2746
target=[('dir2/', b'dir-id'),
2747
('dir2/file', b'file-id')])
2748
state = self.assertUpdate( # active empty
2750
basis=[('dir1/', b'dir-id'),
2751
('dir1/file', b'file-id')],
2752
target=[('dir2/', b'dir-id'),
2753
('dir2/file', b'file-id')])
2754
state = self.assertUpdate( # active present at other location
2755
active=[('dir3/', b'dir-id'),
2756
('dir3/file', b'file-id')],
2757
basis=[('dir1/', b'dir-id'),
2758
('dir1/file', b'file-id')],
2759
target=[('dir2/', b'dir-id'),
2760
('dir2/file', b'file-id')])
2761
state = self.assertUpdate( # active has different ids
2762
active=[('dir1/', b'dir1-id'),
2763
('dir1/file', b'file1-id'),
2764
('dir2/', b'dir2-id'),
2765
('dir2/file', b'file2-id')],
2766
basis=[('dir1/', b'dir-id'),
2767
('dir1/file', b'file-id')],
2768
target=[('dir2/', b'dir-id'),
2769
('dir2/file', b'file-id')])
2771
def test_invalid_file_not_present(self):
2772
state = self.assertBadDelta(
2773
active=[('file', b'file-id')],
2774
basis=[('file', b'file-id')],
2775
delta=[('other-file', 'file', b'file-id')])
2777
def test_invalid_new_id_same_path(self):
2778
# The bad entry comes after
2779
state = self.assertBadDelta(
2780
active=[('file', b'file-id')],
2781
basis=[('file', b'file-id')],
2782
delta=[(None, 'file', b'file-id-2')])
2783
# The bad entry comes first
2784
state = self.assertBadDelta(
2785
active=[('file', b'file-id-2')],
2786
basis=[('file', b'file-id-2')],
2787
delta=[(None, 'file', b'file-id')])
2789
def test_invalid_existing_id(self):
2790
state = self.assertBadDelta(
2791
active=[('file', b'file-id')],
2792
basis=[('file', b'file-id')],
2793
delta=[(None, 'file', b'file-id')])
2795
def test_invalid_parent_missing(self):
2796
state = self.assertBadDelta(
2799
delta=[(None, 'path/path2', b'file-id')])
2800
# Note: we force the active tree to have the directory, by knowing how
2801
# path_to_ie handles entries with missing parents
2802
state = self.assertBadDelta(
2803
active=[('path/', b'path-id')],
2805
delta=[(None, 'path/path2', b'file-id')])
2806
state = self.assertBadDelta(
2807
active=[('path/', b'path-id'),
2808
('path/path2', b'file-id')],
2810
delta=[(None, 'path/path2', b'file-id')])
2812
def test_renamed_dir_same_path(self):
2813
# We replace the parent directory, with another parent dir. But the C
2814
# file doesn't look like it has been moved.
2815
state = self.assertUpdate( # Same as basis
2816
active=[('dir/', b'A-id'),
2817
('dir/B', b'B-id')],
2818
basis=[('dir/', b'A-id'),
2819
('dir/B', b'B-id')],
2820
target=[('dir/', b'C-id'),
2821
('dir/B', b'B-id')])
2822
state = self.assertUpdate( # Same as target
2823
active=[('dir/', b'C-id'),
2824
('dir/B', b'B-id')],
2825
basis=[('dir/', b'A-id'),
2826
('dir/B', b'B-id')],
2827
target=[('dir/', b'C-id'),
2828
('dir/B', b'B-id')])
2829
state = self.assertUpdate( # empty active
2831
basis=[('dir/', b'A-id'),
2832
('dir/B', b'B-id')],
2833
target=[('dir/', b'C-id'),
2834
('dir/B', b'B-id')])
2835
state = self.assertUpdate( # different active
2836
active=[('dir/', b'D-id'),
2837
('dir/B', b'B-id')],
2838
basis=[('dir/', b'A-id'),
2839
('dir/B', b'B-id')],
2840
target=[('dir/', b'C-id'),
2841
('dir/B', b'B-id')])
2843
def test_parent_child_swap(self):
2844
state = self.assertUpdate( # Same as basis
2845
active=[('A/', b'A-id'),
2847
('A/B/C', b'C-id')],
2848
basis=[('A/', b'A-id'),
2850
('A/B/C', b'C-id')],
2851
target=[('A/', b'B-id'),
2853
('A/B/C', b'C-id')])
2854
state = self.assertUpdate( # Same as target
2855
active=[('A/', b'B-id'),
2857
('A/B/C', b'C-id')],
2858
basis=[('A/', b'A-id'),
2860
('A/B/C', b'C-id')],
2861
target=[('A/', b'B-id'),
2863
('A/B/C', b'C-id')])
2864
state = self.assertUpdate( # empty active
2866
basis=[('A/', b'A-id'),
2868
('A/B/C', b'C-id')],
2869
target=[('A/', b'B-id'),
2871
('A/B/C', b'C-id')])
2872
state = self.assertUpdate( # different active
2873
active=[('D/', b'A-id'),
2876
basis=[('A/', b'A-id'),
2878
('A/B/C', b'C-id')],
2879
target=[('A/', b'B-id'),
2881
('A/B/C', b'C-id')])
2883
def test_change_root_id(self):
2884
state = self.assertUpdate( # same as basis
2885
active=[('', b'root-id'),
2886
('file', b'file-id')],
2887
basis=[('', b'root-id'),
2888
('file', b'file-id')],
2889
target=[('', b'target-root-id'),
2890
('file', b'file-id')])
2891
state = self.assertUpdate( # same as target
2892
active=[('', b'target-root-id'),
2893
('file', b'file-id')],
2894
basis=[('', b'root-id'),
2895
('file', b'file-id')],
2896
target=[('', b'target-root-id'),
2897
('file', b'root-id')])
2898
state = self.assertUpdate( # all different
2899
active=[('', b'active-root-id'),
2900
('file', b'file-id')],
2901
basis=[('', b'root-id'),
2902
('file', b'file-id')],
2903
target=[('', b'target-root-id'),
2904
('file', b'root-id')])
2906
def test_change_file_absent_in_active(self):
2907
state = self.assertUpdate(
2909
basis=[('file', b'file-id')],
2910
target=[('file', b'file-id')])
2912
def test_invalid_changed_file(self):
2913
state = self.assertBadDelta( # Not present in basis
2914
active=[('file', b'file-id')],
2916
delta=[('file', 'file', b'file-id')])
2917
state = self.assertBadDelta( # present at another location in basis
2918
active=[('file', b'file-id')],
2919
basis=[('other-file', b'file-id')],
2920
delta=[('file', 'file', b'file-id')])