1
# Copyright (C) 2006, 2007 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."""
28
revision as _mod_revision,
31
from bzrlib.tests import test_osutils
36
# general checks for NOT_IN_MEMORY error conditions.
37
# set_path_id on a NOT_IN_MEMORY dirstate
38
# set_path_id unicode support
39
# set_path_id setting id of a path not root
40
# set_path_id setting id when there are parents without the id in the parents
41
# set_path_id setting id when there are parents with the id in the parents
42
# set_path_id setting id when state is not in memory
43
# set_path_id setting id when state is in memory unmodified
44
# set_path_id setting id when state is in memory modified
47
class TestCaseWithDirState(tests.TestCaseWithTransport):
48
"""Helper functions for creating DirState objects with various content."""
50
def create_empty_dirstate(self):
51
"""Return a locked but empty dirstate"""
52
state = dirstate.DirState.initialize('dirstate')
55
def create_dirstate_with_root(self):
56
"""Return a write-locked state with a single root entry."""
57
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
58
root_entry_direntry = ('', '', 'a-root-value'), [
59
('d', '', 0, False, packed_stat),
62
dirblocks.append(('', [root_entry_direntry]))
63
dirblocks.append(('', []))
64
state = self.create_empty_dirstate()
66
state._set_data([], dirblocks)
73
def create_dirstate_with_root_and_subdir(self):
74
"""Return a locked DirState with a root and a subdir"""
75
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
76
subdir_entry = ('', 'subdir', 'subdir-id'), [
77
('d', '', 0, False, packed_stat),
79
state = self.create_dirstate_with_root()
81
dirblocks = list(state._dirblocks)
82
dirblocks[1][1].append(subdir_entry)
83
state._set_data([], dirblocks)
89
def create_complex_dirstate(self):
90
"""This dirstate contains multiple files and directories.
100
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
102
Notice that a/e is an empty directory.
104
:return: The dirstate, still write-locked.
106
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
107
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
108
root_entry = ('', '', 'a-root-value'), [
109
('d', '', 0, False, packed_stat),
111
a_entry = ('', 'a', 'a-dir'), [
112
('d', '', 0, False, packed_stat),
114
b_entry = ('', 'b', 'b-dir'), [
115
('d', '', 0, False, packed_stat),
117
c_entry = ('', 'c', 'c-file'), [
118
('f', null_sha, 10, False, packed_stat),
120
d_entry = ('', 'd', 'd-file'), [
121
('f', null_sha, 20, False, packed_stat),
123
e_entry = ('a', 'e', 'e-dir'), [
124
('d', '', 0, False, packed_stat),
126
f_entry = ('a', 'f', 'f-file'), [
127
('f', null_sha, 30, False, packed_stat),
129
g_entry = ('b', 'g', 'g-file'), [
130
('f', null_sha, 30, False, packed_stat),
132
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
133
('f', null_sha, 40, False, packed_stat),
136
dirblocks.append(('', [root_entry]))
137
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
138
dirblocks.append(('a', [e_entry, f_entry]))
139
dirblocks.append(('b', [g_entry, h_entry]))
140
state = dirstate.DirState.initialize('dirstate')
143
state._set_data([], dirblocks)
149
def check_state_with_reopen(self, expected_result, state):
150
"""Check that state has current state expected_result.
152
This will check the current state, open the file anew and check it
154
This function expects the current state to be locked for writing, and
155
will unlock it before re-opening.
156
This is required because we can't open a lock_read() while something
157
else has a lock_write().
158
write => mutually exclusive lock
161
# The state should already be write locked, since we just had to do
162
# some operation to get here.
163
self.assertTrue(state._lock_token is not None)
165
self.assertEqual(expected_result[0], state.get_parent_ids())
166
# there should be no ghosts in this tree.
167
self.assertEqual([], state.get_ghosts())
168
# there should be one fileid in this tree - the root of the tree.
169
self.assertEqual(expected_result[1], list(state._iter_entries()))
174
state = dirstate.DirState.on_file('dirstate')
177
self.assertEqual(expected_result[1], list(state._iter_entries()))
181
def create_basic_dirstate(self):
182
"""Create a dirstate with a few files and directories.
192
tree = self.make_branch_and_tree('tree')
193
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
194
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
195
self.build_tree(['tree/' + p for p in paths])
196
tree.set_root_id('TREE_ROOT')
197
tree.add([p.rstrip('/') for p in paths], file_ids)
198
tree.commit('initial', rev_id='rev-1')
199
revision_id = 'rev-1'
200
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
201
t = self.get_transport('tree')
202
a_text = t.get_bytes('a')
203
a_sha = osutils.sha_string(a_text)
205
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
206
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
207
c_text = t.get_bytes('b/c')
208
c_sha = osutils.sha_string(c_text)
210
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
211
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
212
e_text = t.get_bytes('b/d/e')
213
e_sha = osutils.sha_string(e_text)
215
b_c_text = t.get_bytes('b-c')
216
b_c_sha = osutils.sha_string(b_c_text)
217
b_c_len = len(b_c_text)
218
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
219
f_text = t.get_bytes('f')
220
f_sha = osutils.sha_string(f_text)
222
null_stat = dirstate.DirState.NULLSTAT
224
'':(('', '', 'TREE_ROOT'), [
225
('d', '', 0, False, null_stat),
226
('d', '', 0, False, revision_id),
228
'a':(('', 'a', 'a-id'), [
229
('f', '', 0, False, null_stat),
230
('f', a_sha, a_len, False, revision_id),
232
'b':(('', 'b', 'b-id'), [
233
('d', '', 0, False, null_stat),
234
('d', '', 0, False, revision_id),
236
'b/c':(('b', 'c', 'c-id'), [
237
('f', '', 0, False, null_stat),
238
('f', c_sha, c_len, False, revision_id),
240
'b/d':(('b', 'd', 'd-id'), [
241
('d', '', 0, False, null_stat),
242
('d', '', 0, False, revision_id),
244
'b/d/e':(('b/d', 'e', 'e-id'), [
245
('f', '', 0, False, null_stat),
246
('f', e_sha, e_len, False, revision_id),
248
'b-c':(('', 'b-c', 'b-c-id'), [
249
('f', '', 0, False, null_stat),
250
('f', b_c_sha, b_c_len, False, revision_id),
252
'f':(('', 'f', 'f-id'), [
253
('f', '', 0, False, null_stat),
254
('f', f_sha, f_len, False, revision_id),
257
state = dirstate.DirState.from_tree(tree, 'dirstate')
262
# Use a different object, to make sure nothing is pre-cached in memory.
263
state = dirstate.DirState.on_file('dirstate')
265
self.addCleanup(state.unlock)
266
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
267
state._dirblock_state)
268
# This is code is only really tested if we actually have to make more
269
# than one read, so set the page size to something smaller.
270
# We want it to contain about 2.2 records, so that we have a couple
271
# records that we can read per attempt
272
state._bisect_page_size = 200
273
return tree, state, expected
275
def create_duplicated_dirstate(self):
276
"""Create a dirstate with a deleted and added entries.
278
This grabs a basic_dirstate, and then removes and re adds every entry
281
tree, state, expected = self.create_basic_dirstate()
282
# Now we will just remove and add every file so we get an extra entry
283
# per entry. Unversion in reverse order so we handle subdirs
284
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
285
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
286
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
288
# Update the expected dictionary.
289
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
290
orig = expected[path]
292
# This record was deleted in the current tree
293
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
295
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
296
# And didn't exist in the basis tree
297
expected[path2] = (new_key, [orig[1][0],
298
dirstate.DirState.NULL_PARENT_DETAILS])
300
# We will replace the 'dirstate' file underneath 'state', but that is
301
# okay as lock as we unlock 'state' first.
304
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
310
# But we need to leave state in a read-lock because we already have
311
# a cleanup scheduled
313
return tree, state, expected
315
def create_renamed_dirstate(self):
316
"""Create a dirstate with a few internal renames.
318
This takes the basic dirstate, and moves the paths around.
320
tree, state, expected = self.create_basic_dirstate()
322
tree.rename_one('a', 'b/g')
324
tree.rename_one('b/d', 'h')
326
old_a = expected['a']
327
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
328
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
329
('r', 'a', 0, False, '')])
330
old_d = expected['b/d']
331
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
332
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
333
('r', 'b/d', 0, False, '')])
335
old_e = expected['b/d/e']
336
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
338
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
339
('r', 'b/d/e', 0, False, '')])
343
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
350
return tree, state, expected
353
class TestTreeToDirState(TestCaseWithDirState):
355
def test_empty_to_dirstate(self):
356
"""We should be able to create a dirstate for an empty tree."""
357
# There are no files on disk and no parents
358
tree = self.make_branch_and_tree('tree')
359
expected_result = ([], [
360
(('', '', tree.get_root_id()), # common details
361
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
363
state = dirstate.DirState.from_tree(tree, 'dirstate')
365
self.check_state_with_reopen(expected_result, state)
367
def test_1_parents_empty_to_dirstate(self):
368
# create a parent by doing a commit
369
tree = self.make_branch_and_tree('tree')
370
rev_id = tree.commit('first post').encode('utf8')
371
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
372
expected_result = ([rev_id], [
373
(('', '', tree.get_root_id()), # common details
374
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
375
('d', '', 0, False, rev_id), # first parent details
377
state = dirstate.DirState.from_tree(tree, 'dirstate')
378
self.check_state_with_reopen(expected_result, state)
385
def test_2_parents_empty_to_dirstate(self):
386
# create a parent by doing a commit
387
tree = self.make_branch_and_tree('tree')
388
rev_id = tree.commit('first post')
389
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
390
rev_id2 = tree2.commit('second post', allow_pointless=True)
391
tree.merge_from_branch(tree2.branch)
392
expected_result = ([rev_id, rev_id2], [
393
(('', '', tree.get_root_id()), # common details
394
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
395
('d', '', 0, False, rev_id), # first parent details
396
('d', '', 0, False, rev_id2), # second parent details
398
state = dirstate.DirState.from_tree(tree, 'dirstate')
399
self.check_state_with_reopen(expected_result, state)
406
def test_empty_unknowns_are_ignored_to_dirstate(self):
407
"""We should be able to create a dirstate for an empty tree."""
408
# There are no files on disk and no parents
409
tree = self.make_branch_and_tree('tree')
410
self.build_tree(['tree/unknown'])
411
expected_result = ([], [
412
(('', '', tree.get_root_id()), # common details
413
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
415
state = dirstate.DirState.from_tree(tree, 'dirstate')
416
self.check_state_with_reopen(expected_result, state)
418
def get_tree_with_a_file(self):
419
tree = self.make_branch_and_tree('tree')
420
self.build_tree(['tree/a file'])
421
tree.add('a file', 'a-file-id')
424
def test_non_empty_no_parents_to_dirstate(self):
425
"""We should be able to create a dirstate for an empty tree."""
426
# There are files on disk and no parents
427
tree = self.get_tree_with_a_file()
428
expected_result = ([], [
429
(('', '', tree.get_root_id()), # common details
430
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
432
(('', 'a file', 'a-file-id'), # common
433
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
436
state = dirstate.DirState.from_tree(tree, 'dirstate')
437
self.check_state_with_reopen(expected_result, state)
439
def test_1_parents_not_empty_to_dirstate(self):
440
# create a parent by doing a commit
441
tree = self.get_tree_with_a_file()
442
rev_id = tree.commit('first post').encode('utf8')
443
# change the current content to be different this will alter stat, sha
445
self.build_tree_contents([('tree/a file', 'new content\n')])
446
expected_result = ([rev_id], [
447
(('', '', tree.get_root_id()), # common details
448
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
449
('d', '', 0, False, rev_id), # first parent details
451
(('', 'a file', 'a-file-id'), # common
452
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
453
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
454
rev_id), # first parent
457
state = dirstate.DirState.from_tree(tree, 'dirstate')
458
self.check_state_with_reopen(expected_result, state)
460
def test_2_parents_not_empty_to_dirstate(self):
461
# create a parent by doing a commit
462
tree = self.get_tree_with_a_file()
463
rev_id = tree.commit('first post').encode('utf8')
464
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
465
# change the current content to be different this will alter stat, sha
467
self.build_tree_contents([('tree2/a file', 'merge content\n')])
468
rev_id2 = tree2.commit('second post').encode('utf8')
469
tree.merge_from_branch(tree2.branch)
470
# change the current content to be different this will alter stat, sha
471
# and length again, giving us three distinct values:
472
self.build_tree_contents([('tree/a file', 'new content\n')])
473
expected_result = ([rev_id, rev_id2], [
474
(('', '', tree.get_root_id()), # common details
475
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
476
('d', '', 0, False, rev_id), # first parent details
477
('d', '', 0, False, rev_id2), # second parent details
479
(('', 'a file', 'a-file-id'), # common
480
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
481
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
482
rev_id), # first parent
483
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
484
rev_id2), # second parent
487
state = dirstate.DirState.from_tree(tree, 'dirstate')
488
self.check_state_with_reopen(expected_result, state)
490
def test_colliding_fileids(self):
491
# test insertion of parents creating several entries at the same path.
492
# we used to have a bug where they could cause the dirstate to break
493
# its ordering invariants.
494
# create some trees to test from
497
tree = self.make_branch_and_tree('tree%d' % i)
498
self.build_tree(['tree%d/name' % i,])
499
tree.add(['name'], ['file-id%d' % i])
500
revision_id = 'revid-%d' % i
501
tree.commit('message', rev_id=revision_id)
502
parents.append((revision_id,
503
tree.branch.repository.revision_tree(revision_id)))
504
# now fold these trees into a dirstate
505
state = dirstate.DirState.initialize('dirstate')
507
state.set_parent_trees(parents, [])
513
class TestDirStateOnFile(TestCaseWithDirState):
515
def test_construct_with_path(self):
516
tree = self.make_branch_and_tree('tree')
517
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
518
# we want to be able to get the lines of the dirstate that we will
520
lines = state.get_lines()
522
self.build_tree_contents([('dirstate', ''.join(lines))])
524
# no parents, default tree content
525
expected_result = ([], [
526
(('', '', tree.get_root_id()), # common details
527
# current tree details, but new from_tree skips statting, it
528
# uses set_state_from_inventory, and thus depends on the
530
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
533
state = dirstate.DirState.on_file('dirstate')
534
state.lock_write() # check_state_with_reopen will save() and unlock it
535
self.check_state_with_reopen(expected_result, state)
537
def test_can_save_clean_on_file(self):
538
tree = self.make_branch_and_tree('tree')
539
state = dirstate.DirState.from_tree(tree, 'dirstate')
541
# doing a save should work here as there have been no changes.
543
# TODO: stat it and check it hasn't changed; may require waiting
544
# for the state accuracy window.
548
def test_can_save_in_read_lock(self):
549
self.build_tree(['a-file'])
550
state = dirstate.DirState.initialize('dirstate')
552
# No stat and no sha1 sum.
553
state.add('a-file', 'a-file-id', 'file', None, '')
558
# Now open in readonly mode
559
state = dirstate.DirState.on_file('dirstate')
562
entry = state._get_entry(0, path_utf8='a-file')
563
# The current size should be 0 (default)
564
self.assertEqual(0, entry[1][0][2])
565
# We should have a real entry.
566
self.assertNotEqual((None, None), entry)
567
# Make sure everything is old enough
568
state._sha_cutoff_time()
569
state._cutoff_time += 10
570
# Change the file length
571
self.build_tree_contents([('a-file', 'shorter')])
572
sha1sum = dirstate.update_entry(state, entry, 'a-file',
574
# new file, no cached sha:
575
self.assertEqual(None, sha1sum)
577
# The dirblock has been updated
578
self.assertEqual(7, entry[1][0][2])
579
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
580
state._dirblock_state)
583
# Now, since we are the only one holding a lock, we should be able
584
# to save and have it written to disk
589
# Re-open the file, and ensure that the state has been updated.
590
state = dirstate.DirState.on_file('dirstate')
593
entry = state._get_entry(0, path_utf8='a-file')
594
self.assertEqual(7, entry[1][0][2])
598
def test_save_fails_quietly_if_locked(self):
599
"""If dirstate is locked, save will fail without complaining."""
600
self.build_tree(['a-file'])
601
state = dirstate.DirState.initialize('dirstate')
603
# No stat and no sha1 sum.
604
state.add('a-file', 'a-file-id', 'file', None, '')
609
state = dirstate.DirState.on_file('dirstate')
612
entry = state._get_entry(0, path_utf8='a-file')
613
sha1sum = dirstate.update_entry(state, entry, 'a-file',
616
self.assertEqual(None, sha1sum)
617
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
618
state._dirblock_state)
620
# Now, before we try to save, grab another dirstate, and take out a
622
# TODO: jam 20070315 Ideally this would be locked by another
623
# process. To make sure the file is really OS locked.
624
state2 = dirstate.DirState.on_file('dirstate')
627
# This won't actually write anything, because it couldn't grab
628
# a write lock. But it shouldn't raise an error, either.
629
# TODO: jam 20070315 We should probably distinguish between
630
# being dirty because of 'update_entry'. And dirty
631
# because of real modification. So that save() *does*
632
# raise a real error if it fails when we have real
640
# The file on disk should not be modified.
641
state = dirstate.DirState.on_file('dirstate')
644
entry = state._get_entry(0, path_utf8='a-file')
645
self.assertEqual('', entry[1][0][1])
649
def test_save_refuses_if_changes_aborted(self):
650
self.build_tree(['a-file', 'a-dir/'])
651
state = dirstate.DirState.initialize('dirstate')
653
# No stat and no sha1 sum.
654
state.add('a-file', 'a-file-id', 'file', None, '')
659
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
661
('', [(('', '', 'TREE_ROOT'),
662
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
663
('', [(('', 'a-file', 'a-file-id'),
664
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
667
state = dirstate.DirState.on_file('dirstate')
670
state._read_dirblocks_if_needed()
671
self.assertEqual(expected_blocks, state._dirblocks)
673
# Now modify the state, but mark it as inconsistent
674
state.add('a-dir', 'a-dir-id', 'directory', None, '')
675
state._changes_aborted = True
680
state = dirstate.DirState.on_file('dirstate')
683
state._read_dirblocks_if_needed()
684
self.assertEqual(expected_blocks, state._dirblocks)
689
class TestDirStateInitialize(TestCaseWithDirState):
691
def test_initialize(self):
692
expected_result = ([], [
693
(('', '', 'TREE_ROOT'), # common details
694
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
697
state = dirstate.DirState.initialize('dirstate')
699
self.assertIsInstance(state, dirstate.DirState)
700
lines = state.get_lines()
703
# On win32 you can't read from a locked file, even within the same
704
# process. So we have to unlock and release before we check the file
706
self.assertFileEqual(''.join(lines), 'dirstate')
707
state.lock_read() # check_state_with_reopen will unlock
708
self.check_state_with_reopen(expected_result, state)
711
class TestDirStateManipulations(TestCaseWithDirState):
713
def test_set_state_from_inventory_no_content_no_parents(self):
714
# setting the current inventory is a slow but important api to support.
715
tree1 = self.make_branch_and_memory_tree('tree1')
719
revid1 = tree1.commit('foo').encode('utf8')
720
root_id = tree1.get_root_id()
721
inv = tree1.inventory
724
expected_result = [], [
725
(('', '', root_id), [
726
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
727
state = dirstate.DirState.initialize('dirstate')
729
state.set_state_from_inventory(inv)
730
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
732
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
733
state._dirblock_state)
738
# This will unlock it
739
self.check_state_with_reopen(expected_result, state)
741
def test_set_state_from_inventory_preserves_hashcache(self):
742
# https://bugs.launchpad.net/bzr/+bug/146176
743
# set_state_from_inventory should preserve the stat and hash value for
744
# workingtree files that are not changed by the inventory.
746
tree = self.make_branch_and_tree('.')
747
# depends on the default format using dirstate...
750
# make a dirstate with some valid hashcache data
751
# file on disk, but that's not needed for this test
752
foo_contents = 'contents of foo'
753
self.build_tree_contents([('foo', foo_contents)])
754
tree.add('foo', 'foo-id')
756
foo_stat = os.stat('foo')
757
foo_packed = dirstate.pack_stat(foo_stat)
758
foo_sha = osutils.sha_string(foo_contents)
759
foo_size = len(foo_contents)
761
# should not be cached yet, because the file's too fresh
763
(('', 'foo', 'foo-id',),
764
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
765
tree._dirstate._get_entry(0, 'foo-id'))
766
# poke in some hashcache information - it wouldn't normally be
767
# stored because it's too fresh
768
tree._dirstate.update_minimal(
769
('', 'foo', 'foo-id'),
770
'f', False, foo_sha, foo_packed, foo_size, 'foo')
771
# now should be cached
773
(('', 'foo', 'foo-id',),
774
[('f', foo_sha, foo_size, False, foo_packed)]),
775
tree._dirstate._get_entry(0, 'foo-id'))
777
# extract the inventory, and add something to it
778
inv = tree._get_inventory()
779
# should see the file we poked in...
780
self.assertTrue(inv.has_id('foo-id'))
781
self.assertTrue(inv.has_filename('foo'))
782
inv.add_path('bar', 'file', 'bar-id')
783
tree._dirstate._validate()
784
# this used to cause it to lose its hashcache
785
tree._dirstate.set_state_from_inventory(inv)
786
tree._dirstate._validate()
792
# now check that the state still has the original hashcache value
793
state = tree._dirstate
795
foo_tuple = state._get_entry(0, path_utf8='foo')
797
(('', 'foo', 'foo-id',),
798
[('f', foo_sha, len(foo_contents), False,
799
dirstate.pack_stat(foo_stat))]),
805
def test_set_state_from_inventory_mixed_paths(self):
806
tree1 = self.make_branch_and_tree('tree1')
807
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
808
'tree1/a/b/foo', 'tree1/a-b/bar'])
811
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
812
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
813
tree1.commit('rev1', rev_id='rev1')
814
root_id = tree1.get_root_id()
815
inv = tree1.inventory
818
expected_result1 = [('', '', root_id, 'd'),
819
('', 'a', 'a-id', 'd'),
820
('', 'a-b', 'a-b-id', 'd'),
821
('a', 'b', 'b-id', 'd'),
822
('a/b', 'foo', 'foo-id', 'f'),
823
('a-b', 'bar', 'bar-id', 'f'),
825
expected_result2 = [('', '', root_id, 'd'),
826
('', 'a', 'a-id', 'd'),
827
('', 'a-b', 'a-b-id', 'd'),
828
('a-b', 'bar', 'bar-id', 'f'),
830
state = dirstate.DirState.initialize('dirstate')
832
state.set_state_from_inventory(inv)
834
for entry in state._iter_entries():
835
values.append(entry[0] + entry[1][0][:1])
836
self.assertEqual(expected_result1, values)
838
state.set_state_from_inventory(inv)
840
for entry in state._iter_entries():
841
values.append(entry[0] + entry[1][0][:1])
842
self.assertEqual(expected_result2, values)
846
def test_set_path_id_no_parents(self):
847
"""The id of a path can be changed trivally with no parents."""
848
state = dirstate.DirState.initialize('dirstate')
850
# check precondition to be sure the state does change appropriately.
852
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
853
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
854
list(state._iter_entries()))
855
state.set_path_id('', 'foobarbaz')
857
(('', '', 'foobarbaz'), [('d', '', 0, False,
858
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
859
self.assertEqual(expected_rows, list(state._iter_entries()))
860
# should work across save too
864
state = dirstate.DirState.on_file('dirstate')
868
self.assertEqual(expected_rows, list(state._iter_entries()))
872
def test_set_path_id_with_parents(self):
873
"""Set the root file id in a dirstate with parents"""
874
mt = self.make_branch_and_tree('mt')
875
# in case the default tree format uses a different root id
876
mt.set_root_id('TREE_ROOT')
877
mt.commit('foo', rev_id='parent-revid')
878
rt = mt.branch.repository.revision_tree('parent-revid')
879
state = dirstate.DirState.initialize('dirstate')
882
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
883
state.set_path_id('', 'foobarbaz')
885
# now see that it is what we expected
887
(('', '', 'TREE_ROOT'),
888
[('a', '', 0, False, ''),
889
('d', '', 0, False, 'parent-revid'),
891
(('', '', 'foobarbaz'),
892
[('d', '', 0, False, ''),
893
('a', '', 0, False, ''),
897
self.assertEqual(expected_rows, list(state._iter_entries()))
898
# should work across save too
902
# now flush & check we get the same
903
state = dirstate.DirState.on_file('dirstate')
907
self.assertEqual(expected_rows, list(state._iter_entries()))
910
# now change within an existing file-backed state
914
state.set_path_id('', 'tree-root-2')
920
def test_set_parent_trees_no_content(self):
921
# set_parent_trees is a slow but important api to support.
922
tree1 = self.make_branch_and_memory_tree('tree1')
926
revid1 = tree1.commit('foo')
929
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
930
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
933
revid2 = tree2.commit('foo')
934
root_id = tree2.get_root_id()
937
state = dirstate.DirState.initialize('dirstate')
939
state.set_path_id('', root_id)
940
state.set_parent_trees(
941
((revid1, tree1.branch.repository.revision_tree(revid1)),
942
(revid2, tree2.branch.repository.revision_tree(revid2)),
943
('ghost-rev', None)),
945
# check we can reopen and use the dirstate after setting parent
952
state = dirstate.DirState.on_file('dirstate')
955
self.assertEqual([revid1, revid2, 'ghost-rev'],
956
state.get_parent_ids())
957
# iterating the entire state ensures that the state is parsable.
958
list(state._iter_entries())
959
# be sure that it sets not appends - change it
960
state.set_parent_trees(
961
((revid1, tree1.branch.repository.revision_tree(revid1)),
962
('ghost-rev', None)),
964
# and now put it back.
965
state.set_parent_trees(
966
((revid1, tree1.branch.repository.revision_tree(revid1)),
967
(revid2, tree2.branch.repository.revision_tree(revid2)),
968
('ghost-rev', tree2.branch.repository.revision_tree(
969
_mod_revision.NULL_REVISION))),
971
self.assertEqual([revid1, revid2, 'ghost-rev'],
972
state.get_parent_ids())
973
# the ghost should be recorded as such by set_parent_trees.
974
self.assertEqual(['ghost-rev'], state.get_ghosts())
976
[(('', '', root_id), [
977
('d', '', 0, False, dirstate.DirState.NULLSTAT),
978
('d', '', 0, False, revid1),
979
('d', '', 0, False, revid2)
981
list(state._iter_entries()))
985
def test_set_parent_trees_file_missing_from_tree(self):
986
# Adding a parent tree may reference files not in the current state.
987
# they should get listed just once by id, even if they are in two
989
# set_parent_trees is a slow but important api to support.
990
tree1 = self.make_branch_and_memory_tree('tree1')
994
tree1.add(['a file'], ['file-id'], ['file'])
995
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
996
revid1 = tree1.commit('foo')
999
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1000
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1003
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1004
revid2 = tree2.commit('foo')
1005
root_id = tree2.get_root_id()
1008
# check the layout in memory
1009
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1010
(('', '', root_id), [
1011
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1012
('d', '', 0, False, revid1.encode('utf8')),
1013
('d', '', 0, False, revid2.encode('utf8'))
1015
(('', 'a file', 'file-id'), [
1016
('a', '', 0, False, ''),
1017
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1018
revid1.encode('utf8')),
1019
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1020
revid2.encode('utf8'))
1023
state = dirstate.DirState.initialize('dirstate')
1025
state.set_path_id('', root_id)
1026
state.set_parent_trees(
1027
((revid1, tree1.branch.repository.revision_tree(revid1)),
1028
(revid2, tree2.branch.repository.revision_tree(revid2)),
1034
# check_state_with_reopen will unlock
1035
self.check_state_with_reopen(expected_result, state)
1037
### add a path via _set_data - so we dont need delta work, just
1038
# raw data in, and ensure that it comes out via get_lines happily.
1040
def test_add_path_to_root_no_parents_all_data(self):
1041
# The most trivial addition of a path is when there are no parents and
1042
# its in the root and all data about the file is supplied
1043
self.build_tree(['a file'])
1044
stat = os.lstat('a file')
1045
# the 1*20 is the sha1 pretend value.
1046
state = dirstate.DirState.initialize('dirstate')
1047
expected_entries = [
1048
(('', '', 'TREE_ROOT'), [
1049
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1051
(('', 'a file', 'a-file-id'), [
1052
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1056
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1057
# having added it, it should be in the output of iter_entries.
1058
self.assertEqual(expected_entries, list(state._iter_entries()))
1059
# saving and reloading should not affect this.
1063
state = dirstate.DirState.on_file('dirstate')
1065
self.addCleanup(state.unlock)
1066
self.assertEqual(expected_entries, list(state._iter_entries()))
1068
def test_add_path_to_unversioned_directory(self):
1069
"""Adding a path to an unversioned directory should error.
1071
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1072
once dirstate is stable and if it is merged with WorkingTree3, consider
1073
removing this copy of the test.
1075
self.build_tree(['unversioned/', 'unversioned/a file'])
1076
state = dirstate.DirState.initialize('dirstate')
1077
self.addCleanup(state.unlock)
1078
self.assertRaises(errors.NotVersionedError, state.add,
1079
'unversioned/a file', 'a-file-id', 'file', None, None)
1081
def test_add_directory_to_root_no_parents_all_data(self):
1082
# The most trivial addition of a dir is when there are no parents and
1083
# its in the root and all data about the file is supplied
1084
self.build_tree(['a dir/'])
1085
stat = os.lstat('a dir')
1086
expected_entries = [
1087
(('', '', 'TREE_ROOT'), [
1088
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1090
(('', 'a dir', 'a dir id'), [
1091
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1094
state = dirstate.DirState.initialize('dirstate')
1096
state.add('a dir', 'a dir id', 'directory', stat, None)
1097
# having added it, it should be in the output of iter_entries.
1098
self.assertEqual(expected_entries, list(state._iter_entries()))
1099
# saving and reloading should not affect this.
1103
state = dirstate.DirState.on_file('dirstate')
1105
self.addCleanup(state.unlock)
1107
self.assertEqual(expected_entries, list(state._iter_entries()))
1109
def test_add_symlink_to_root_no_parents_all_data(self):
1110
# The most trivial addition of a symlink when there are no parents and
1111
# its in the root and all data about the file is supplied
1112
# bzr doesn't support fake symlinks on windows, yet.
1113
self.requireFeature(tests.SymlinkFeature)
1114
os.symlink('target', 'a link')
1115
stat = os.lstat('a link')
1116
expected_entries = [
1117
(('', '', 'TREE_ROOT'), [
1118
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1120
(('', 'a link', 'a link id'), [
1121
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1124
state = dirstate.DirState.initialize('dirstate')
1126
state.add('a link', 'a link id', 'symlink', stat, 'target')
1127
# having added it, it should be in the output of iter_entries.
1128
self.assertEqual(expected_entries, list(state._iter_entries()))
1129
# saving and reloading should not affect this.
1133
state = dirstate.DirState.on_file('dirstate')
1135
self.addCleanup(state.unlock)
1136
self.assertEqual(expected_entries, list(state._iter_entries()))
1138
def test_add_directory_and_child_no_parents_all_data(self):
1139
# after adding a directory, we should be able to add children to it.
1140
self.build_tree(['a dir/', 'a dir/a file'])
1141
dirstat = os.lstat('a dir')
1142
filestat = os.lstat('a dir/a file')
1143
expected_entries = [
1144
(('', '', 'TREE_ROOT'), [
1145
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1147
(('', 'a dir', 'a dir id'), [
1148
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1150
(('a dir', 'a file', 'a-file-id'), [
1151
('f', '1'*20, 25, False,
1152
dirstate.pack_stat(filestat)), # current tree details
1155
state = dirstate.DirState.initialize('dirstate')
1157
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1158
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1159
# added it, it should be in the output of iter_entries.
1160
self.assertEqual(expected_entries, list(state._iter_entries()))
1161
# saving and reloading should not affect this.
1165
state = dirstate.DirState.on_file('dirstate')
1167
self.addCleanup(state.unlock)
1168
self.assertEqual(expected_entries, list(state._iter_entries()))
1170
def test_add_tree_reference(self):
1171
# make a dirstate and add a tree reference
1172
state = dirstate.DirState.initialize('dirstate')
1174
('', 'subdir', 'subdir-id'),
1175
[('t', 'subtree-123123', 0, False,
1176
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1179
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1180
entry = state._get_entry(0, 'subdir-id', 'subdir')
1181
self.assertEqual(entry, expected_entry)
1186
# now check we can read it back
1188
self.addCleanup(state.unlock)
1190
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1191
self.assertEqual(entry, entry2)
1192
self.assertEqual(entry, expected_entry)
1193
# and lookup by id should work too
1194
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1195
self.assertEqual(entry, expected_entry)
1197
def test_add_forbidden_names(self):
1198
state = dirstate.DirState.initialize('dirstate')
1199
self.addCleanup(state.unlock)
1200
self.assertRaises(errors.BzrError,
1201
state.add, '.', 'ass-id', 'directory', None, None)
1202
self.assertRaises(errors.BzrError,
1203
state.add, '..', 'ass-id', 'directory', None, None)
1206
class TestGetLines(TestCaseWithDirState):
1208
def test_get_line_with_2_rows(self):
1209
state = self.create_dirstate_with_root_and_subdir()
1211
self.assertEqual(['#bazaar dirstate flat format 3\n',
1212
'crc32: 41262208\n',
1216
'\x00\x00a-root-value\x00'
1217
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1218
'\x00subdir\x00subdir-id\x00'
1219
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1220
], state.get_lines())
1224
def test_entry_to_line(self):
1225
state = self.create_dirstate_with_root()
1228
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1229
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1230
state._entry_to_line(state._dirblocks[0][1][0]))
1234
def test_entry_to_line_with_parent(self):
1235
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1236
root_entry = ('', '', 'a-root-value'), [
1237
('d', '', 0, False, packed_stat), # current tree details
1238
# first: a pointer to the current location
1239
('a', 'dirname/basename', 0, False, ''),
1241
state = dirstate.DirState.initialize('dirstate')
1244
'\x00\x00a-root-value\x00'
1245
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1246
'a\x00dirname/basename\x000\x00n\x00',
1247
state._entry_to_line(root_entry))
1251
def test_entry_to_line_with_two_parents_at_different_paths(self):
1252
# / in the tree, at / in one parent and /dirname/basename in the other.
1253
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1254
root_entry = ('', '', 'a-root-value'), [
1255
('d', '', 0, False, packed_stat), # current tree details
1256
('d', '', 0, False, 'rev_id'), # first parent details
1257
# second: a pointer to the current location
1258
('a', 'dirname/basename', 0, False, ''),
1260
state = dirstate.DirState.initialize('dirstate')
1263
'\x00\x00a-root-value\x00'
1264
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1265
'd\x00\x000\x00n\x00rev_id\x00'
1266
'a\x00dirname/basename\x000\x00n\x00',
1267
state._entry_to_line(root_entry))
1271
def test_iter_entries(self):
1272
# we should be able to iterate the dirstate entries from end to end
1273
# this is for get_lines to be easy to read.
1274
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1276
root_entries = [(('', '', 'a-root-value'), [
1277
('d', '', 0, False, packed_stat), # current tree details
1279
dirblocks.append(('', root_entries))
1280
# add two files in the root
1281
subdir_entry = ('', 'subdir', 'subdir-id'), [
1282
('d', '', 0, False, packed_stat), # current tree details
1284
afile_entry = ('', 'afile', 'afile-id'), [
1285
('f', 'sha1value', 34, False, packed_stat), # current tree details
1287
dirblocks.append(('', [subdir_entry, afile_entry]))
1289
file_entry2 = ('subdir', '2file', '2file-id'), [
1290
('f', 'sha1value', 23, False, packed_stat), # current tree details
1292
dirblocks.append(('subdir', [file_entry2]))
1293
state = dirstate.DirState.initialize('dirstate')
1295
state._set_data([], dirblocks)
1296
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1298
self.assertEqual(expected_entries, list(state._iter_entries()))
1303
class TestGetBlockRowIndex(TestCaseWithDirState):
1305
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1306
file_present, state, dirname, basename, tree_index):
1307
self.assertEqual((block_index, row_index, dir_present, file_present),
1308
state._get_block_entry_index(dirname, basename, tree_index))
1310
block = state._dirblocks[block_index]
1311
self.assertEqual(dirname, block[0])
1312
if dir_present and file_present:
1313
row = state._dirblocks[block_index][1][row_index]
1314
self.assertEqual(dirname, row[0][0])
1315
self.assertEqual(basename, row[0][1])
1317
def test_simple_structure(self):
1318
state = self.create_dirstate_with_root_and_subdir()
1319
self.addCleanup(state.unlock)
1320
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1321
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1322
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1323
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1324
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1327
def test_complex_structure_exists(self):
1328
state = self.create_complex_dirstate()
1329
self.addCleanup(state.unlock)
1330
# Make sure we can find everything that exists
1331
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1332
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1333
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1334
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1335
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1336
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1337
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1338
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1339
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1340
'b', 'h\xc3\xa5', 0)
1342
def test_complex_structure_missing(self):
1343
state = self.create_complex_dirstate()
1344
self.addCleanup(state.unlock)
1345
# Make sure things would be inserted in the right locations
1346
# '_' comes before 'a'
1347
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1348
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1349
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1350
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1352
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1353
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1354
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1355
# This would be inserted between a/ and b/
1356
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1358
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1361
class TestGetEntry(TestCaseWithDirState):
1363
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1364
"""Check that the right entry is returned for a request to getEntry."""
1365
entry = state._get_entry(index, path_utf8=path)
1367
self.assertEqual((None, None), entry)
1370
self.assertEqual((dirname, basename, file_id), cur[:3])
1372
def test_simple_structure(self):
1373
state = self.create_dirstate_with_root_and_subdir()
1374
self.addCleanup(state.unlock)
1375
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1376
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1377
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1378
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1379
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1381
def test_complex_structure_exists(self):
1382
state = self.create_complex_dirstate()
1383
self.addCleanup(state.unlock)
1384
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1385
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1386
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1387
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1388
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1389
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1390
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1391
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1392
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1395
def test_complex_structure_missing(self):
1396
state = self.create_complex_dirstate()
1397
self.addCleanup(state.unlock)
1398
self.assertEntryEqual(None, None, None, state, '_', 0)
1399
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1400
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1401
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1403
def test_get_entry_uninitialized(self):
1404
"""Calling get_entry will load data if it needs to"""
1405
state = self.create_dirstate_with_root()
1411
state = dirstate.DirState.on_file('dirstate')
1414
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1415
state._header_state)
1416
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1417
state._dirblock_state)
1418
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1423
class TestIterChildEntries(TestCaseWithDirState):
1425
def create_dirstate_with_two_trees(self):
1426
"""This dirstate contains multiple files and directories.
1436
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1438
Notice that a/e is an empty directory.
1440
There is one parent tree, which has the same shape with the following variations:
1441
b/g in the parent is gone.
1442
b/h in the parent has a different id
1443
b/i is new in the parent
1444
c is renamed to b/j in the parent
1446
:return: The dirstate, still write-locked.
1448
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1449
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1450
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1451
root_entry = ('', '', 'a-root-value'), [
1452
('d', '', 0, False, packed_stat),
1453
('d', '', 0, False, 'parent-revid'),
1455
a_entry = ('', 'a', 'a-dir'), [
1456
('d', '', 0, False, packed_stat),
1457
('d', '', 0, False, 'parent-revid'),
1459
b_entry = ('', 'b', 'b-dir'), [
1460
('d', '', 0, False, packed_stat),
1461
('d', '', 0, False, 'parent-revid'),
1463
c_entry = ('', 'c', 'c-file'), [
1464
('f', null_sha, 10, False, packed_stat),
1465
('r', 'b/j', 0, False, ''),
1467
d_entry = ('', 'd', 'd-file'), [
1468
('f', null_sha, 20, False, packed_stat),
1469
('f', 'd', 20, False, 'parent-revid'),
1471
e_entry = ('a', 'e', 'e-dir'), [
1472
('d', '', 0, False, packed_stat),
1473
('d', '', 0, False, 'parent-revid'),
1475
f_entry = ('a', 'f', 'f-file'), [
1476
('f', null_sha, 30, False, packed_stat),
1477
('f', 'f', 20, False, 'parent-revid'),
1479
g_entry = ('b', 'g', 'g-file'), [
1480
('f', null_sha, 30, False, packed_stat),
1481
NULL_PARENT_DETAILS,
1483
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1484
('f', null_sha, 40, False, packed_stat),
1485
NULL_PARENT_DETAILS,
1487
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1488
NULL_PARENT_DETAILS,
1489
('f', 'h', 20, False, 'parent-revid'),
1491
i_entry = ('b', 'i', 'i-file'), [
1492
NULL_PARENT_DETAILS,
1493
('f', 'h', 20, False, 'parent-revid'),
1495
j_entry = ('b', 'j', 'c-file'), [
1496
('r', 'c', 0, False, ''),
1497
('f', 'j', 20, False, 'parent-revid'),
1500
dirblocks.append(('', [root_entry]))
1501
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1502
dirblocks.append(('a', [e_entry, f_entry]))
1503
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1504
state = dirstate.DirState.initialize('dirstate')
1507
state._set_data(['parent'], dirblocks)
1511
return state, dirblocks
1513
def test_iter_children_b(self):
1514
state, dirblocks = self.create_dirstate_with_two_trees()
1515
self.addCleanup(state.unlock)
1516
expected_result = []
1517
expected_result.append(dirblocks[3][1][2]) # h2
1518
expected_result.append(dirblocks[3][1][3]) # i
1519
expected_result.append(dirblocks[3][1][4]) # j
1520
self.assertEqual(expected_result,
1521
list(state._iter_child_entries(1, 'b')))
1523
def test_iter_child_root(self):
1524
state, dirblocks = self.create_dirstate_with_two_trees()
1525
self.addCleanup(state.unlock)
1526
expected_result = []
1527
expected_result.append(dirblocks[1][1][0]) # a
1528
expected_result.append(dirblocks[1][1][1]) # b
1529
expected_result.append(dirblocks[1][1][3]) # d
1530
expected_result.append(dirblocks[2][1][0]) # e
1531
expected_result.append(dirblocks[2][1][1]) # f
1532
expected_result.append(dirblocks[3][1][2]) # h2
1533
expected_result.append(dirblocks[3][1][3]) # i
1534
expected_result.append(dirblocks[3][1][4]) # j
1535
self.assertEqual(expected_result,
1536
list(state._iter_child_entries(1, '')))
1539
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1540
"""Test that DirState adds entries in the right order."""
1542
def test_add_sorting(self):
1543
"""Add entries in lexicographical order, we get path sorted order.
1545
This tests it to a depth of 4, to make sure we don't just get it right
1546
at a single depth. 'a/a' should come before 'a-a', even though it
1547
doesn't lexicographically.
1549
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1550
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1553
state = dirstate.DirState.initialize('dirstate')
1554
self.addCleanup(state.unlock)
1556
fake_stat = os.stat('dirstate')
1558
d_id = d.replace('/', '_')+'-id'
1559
file_path = d + '/f'
1560
file_id = file_path.replace('/', '_')+'-id'
1561
state.add(d, d_id, 'directory', fake_stat, null_sha)
1562
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1564
expected = ['', '', 'a',
1565
'a/a', 'a/a/a', 'a/a/a/a',
1566
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1568
split = lambda p:p.split('/')
1569
self.assertEqual(sorted(expected, key=split), expected)
1570
dirblock_names = [d[0] for d in state._dirblocks]
1571
self.assertEqual(expected, dirblock_names)
1573
def test_set_parent_trees_correct_order(self):
1574
"""After calling set_parent_trees() we should maintain the order."""
1575
dirs = ['a', 'a-a', 'a/a']
1577
state = dirstate.DirState.initialize('dirstate')
1578
self.addCleanup(state.unlock)
1580
fake_stat = os.stat('dirstate')
1582
d_id = d.replace('/', '_')+'-id'
1583
file_path = d + '/f'
1584
file_id = file_path.replace('/', '_')+'-id'
1585
state.add(d, d_id, 'directory', fake_stat, null_sha)
1586
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1588
expected = ['', '', 'a', 'a/a', 'a-a']
1589
dirblock_names = [d[0] for d in state._dirblocks]
1590
self.assertEqual(expected, dirblock_names)
1592
# *really* cheesy way to just get an empty tree
1593
repo = self.make_repository('repo')
1594
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1595
state.set_parent_trees([('null:', empty_tree)], [])
1597
dirblock_names = [d[0] for d in state._dirblocks]
1598
self.assertEqual(expected, dirblock_names)
1601
class InstrumentedDirState(dirstate.DirState):
1602
"""An DirState with instrumented sha1 functionality."""
1604
def __init__(self, path, sha1_provider):
1605
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1606
self._time_offset = 0
1608
# member is dynamically set in DirState.__init__ to turn on trace
1609
self._sha1_provider = sha1_provider
1610
self._sha1_file = self._sha1_file_and_log
1612
def _sha_cutoff_time(self):
1613
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1614
self._cutoff_time = timestamp + self._time_offset
1616
def _sha1_file_and_log(self, abspath):
1617
self._log.append(('sha1', abspath))
1618
return self._sha1_provider.sha1(abspath)
1620
def _read_link(self, abspath, old_link):
1621
self._log.append(('read_link', abspath, old_link))
1622
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1624
def _lstat(self, abspath, entry):
1625
self._log.append(('lstat', abspath))
1626
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1628
def _is_executable(self, mode, old_executable):
1629
self._log.append(('is_exec', mode, old_executable))
1630
return super(InstrumentedDirState, self)._is_executable(mode,
1633
def adjust_time(self, secs):
1634
"""Move the clock forward or back.
1636
:param secs: The amount to adjust the clock by. Positive values make it
1637
seem as if we are in the future, negative values make it seem like we
1640
self._time_offset += secs
1641
self._cutoff_time = None
1644
class _FakeStat(object):
1645
"""A class with the same attributes as a real stat result."""
1647
def __init__(self, size, mtime, ctime, dev, ino, mode):
1649
self.st_mtime = mtime
1650
self.st_ctime = ctime
1657
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1658
st.st_ino, st.st_mode)
1661
class TestPackStat(tests.TestCaseWithTransport):
1663
def assertPackStat(self, expected, stat_value):
1664
"""Check the packed and serialized form of a stat value."""
1665
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1667
def test_pack_stat_int(self):
1668
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1669
# Make sure that all parameters have an impact on the packed stat.
1670
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1673
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1674
st.st_mtime = 1172758620
1676
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1677
st.st_ctime = 1172758630
1679
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1682
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1683
st.st_ino = 6499540L
1685
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1686
st.st_mode = 0100744
1688
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1690
def test_pack_stat_float(self):
1691
"""On some platforms mtime and ctime are floats.
1693
Make sure we don't get warnings or errors, and that we ignore changes <
1696
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1697
777L, 6499538L, 0100644)
1698
# These should all be the same as the integer counterparts
1699
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1700
st.st_mtime = 1172758620.0
1702
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1703
st.st_ctime = 1172758630.0
1705
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1706
# fractional seconds are discarded, so no change from above
1707
st.st_mtime = 1172758620.453
1708
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1709
st.st_ctime = 1172758630.228
1710
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1713
class TestBisect(TestCaseWithDirState):
1714
"""Test the ability to bisect into the disk format."""
1716
def assertBisect(self, expected_map, map_keys, state, paths):
1717
"""Assert that bisecting for paths returns the right result.
1719
:param expected_map: A map from key => entry value
1720
:param map_keys: The keys to expect for each path
1721
:param state: The DirState object.
1722
:param paths: A list of paths, these will automatically be split into
1723
(dir, name) tuples, and sorted according to how _bisect
1726
result = state._bisect(paths)
1727
# For now, results are just returned in whatever order we read them.
1728
# We could sort by (dir, name, file_id) or something like that, but in
1729
# the end it would still be fairly arbitrary, and we don't want the
1730
# extra overhead if we can avoid it. So sort everything to make sure
1732
self.assertEqual(len(map_keys), len(paths))
1734
for path, keys in zip(paths, map_keys):
1736
# This should not be present in the output
1738
expected[path] = sorted(expected_map[k] for k in keys)
1740
# The returned values are just arranged randomly based on when they
1741
# were read, for testing, make sure it is properly sorted.
1745
self.assertEqual(expected, result)
1747
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1748
"""Assert that bisecting for dirbblocks returns the right result.
1750
:param expected_map: A map from key => expected values
1751
:param map_keys: A nested list of paths we expect to be returned.
1752
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1753
:param state: The DirState object.
1754
:param paths: A list of directories
1756
result = state._bisect_dirblocks(paths)
1757
self.assertEqual(len(map_keys), len(paths))
1759
for path, keys in zip(paths, map_keys):
1761
# This should not be present in the output
1763
expected[path] = sorted(expected_map[k] for k in keys)
1767
self.assertEqual(expected, result)
1769
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1770
"""Assert the return value of a recursive bisection.
1772
:param expected_map: A map from key => entry value
1773
:param map_keys: A list of paths we expect to be returned.
1774
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1775
:param state: The DirState object.
1776
:param paths: A list of files and directories. It will be broken up
1777
into (dir, name) pairs and sorted before calling _bisect_recursive.
1780
for key in map_keys:
1781
entry = expected_map[key]
1782
dir_name_id, trees_info = entry
1783
expected[dir_name_id] = trees_info
1785
result = state._bisect_recursive(paths)
1787
self.assertEqual(expected, result)
1789
def test_bisect_each(self):
1790
"""Find a single record using bisect."""
1791
tree, state, expected = self.create_basic_dirstate()
1793
# Bisect should return the rows for the specified files.
1794
self.assertBisect(expected, [['']], state, [''])
1795
self.assertBisect(expected, [['a']], state, ['a'])
1796
self.assertBisect(expected, [['b']], state, ['b'])
1797
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1798
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1799
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1800
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1801
self.assertBisect(expected, [['f']], state, ['f'])
1803
def test_bisect_multi(self):
1804
"""Bisect can be used to find multiple records at the same time."""
1805
tree, state, expected = self.create_basic_dirstate()
1806
# Bisect should be capable of finding multiple entries at the same time
1807
self.assertBisect(expected, [['a'], ['b'], ['f']],
1808
state, ['a', 'b', 'f'])
1809
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1810
state, ['f', 'b/d', 'b/d/e'])
1811
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1812
state, ['b', 'b-c', 'b/c'])
1814
def test_bisect_one_page(self):
1815
"""Test bisect when there is only 1 page to read"""
1816
tree, state, expected = self.create_basic_dirstate()
1817
state._bisect_page_size = 5000
1818
self.assertBisect(expected,[['']], state, [''])
1819
self.assertBisect(expected,[['a']], state, ['a'])
1820
self.assertBisect(expected,[['b']], state, ['b'])
1821
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1822
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1823
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1824
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1825
self.assertBisect(expected,[['f']], state, ['f'])
1826
self.assertBisect(expected,[['a'], ['b'], ['f']],
1827
state, ['a', 'b', 'f'])
1828
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1829
state, ['b/d', 'b/d/e', 'f'])
1830
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1831
state, ['b', 'b/c', 'b-c'])
1833
def test_bisect_duplicate_paths(self):
1834
"""When bisecting for a path, handle multiple entries."""
1835
tree, state, expected = self.create_duplicated_dirstate()
1837
# Now make sure that both records are properly returned.
1838
self.assertBisect(expected, [['']], state, [''])
1839
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1840
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1841
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1842
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1843
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1845
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1846
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1848
def test_bisect_page_size_too_small(self):
1849
"""If the page size is too small, we will auto increase it."""
1850
tree, state, expected = self.create_basic_dirstate()
1851
state._bisect_page_size = 50
1852
self.assertBisect(expected, [None], state, ['b/e'])
1853
self.assertBisect(expected, [['a']], state, ['a'])
1854
self.assertBisect(expected, [['b']], state, ['b'])
1855
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1856
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1857
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1858
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1859
self.assertBisect(expected, [['f']], state, ['f'])
1861
def test_bisect_missing(self):
1862
"""Test that bisect return None if it cannot find a path."""
1863
tree, state, expected = self.create_basic_dirstate()
1864
self.assertBisect(expected, [None], state, ['foo'])
1865
self.assertBisect(expected, [None], state, ['b/foo'])
1866
self.assertBisect(expected, [None], state, ['bar/foo'])
1867
self.assertBisect(expected, [None], state, ['b-c/foo'])
1869
self.assertBisect(expected, [['a'], None, ['b/d']],
1870
state, ['a', 'foo', 'b/d'])
1872
def test_bisect_rename(self):
1873
"""Check that we find a renamed row."""
1874
tree, state, expected = self.create_renamed_dirstate()
1876
# Search for the pre and post renamed entries
1877
self.assertBisect(expected, [['a']], state, ['a'])
1878
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1879
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1880
self.assertBisect(expected, [['h']], state, ['h'])
1882
# What about b/d/e? shouldn't that also get 2 directory entries?
1883
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1884
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1886
def test_bisect_dirblocks(self):
1887
tree, state, expected = self.create_duplicated_dirstate()
1888
self.assertBisectDirBlocks(expected,
1889
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1891
self.assertBisectDirBlocks(expected,
1892
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1893
self.assertBisectDirBlocks(expected,
1894
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1895
self.assertBisectDirBlocks(expected,
1896
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1897
['b/c', 'b/c2', 'b/d', 'b/d2'],
1898
['b/d/e', 'b/d/e2'],
1899
], state, ['', 'b', 'b/d'])
1901
def test_bisect_dirblocks_missing(self):
1902
tree, state, expected = self.create_basic_dirstate()
1903
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1904
state, ['b/d', 'b/e'])
1905
# Files don't show up in this search
1906
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1907
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1908
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1909
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1910
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1912
def test_bisect_recursive_each(self):
1913
tree, state, expected = self.create_basic_dirstate()
1914
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1915
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1916
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1917
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1918
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1920
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1922
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1926
def test_bisect_recursive_multiple(self):
1927
tree, state, expected = self.create_basic_dirstate()
1928
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1929
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1930
state, ['b/d', 'b/d/e'])
1932
def test_bisect_recursive_missing(self):
1933
tree, state, expected = self.create_basic_dirstate()
1934
self.assertBisectRecursive(expected, [], state, ['d'])
1935
self.assertBisectRecursive(expected, [], state, ['b/e'])
1936
self.assertBisectRecursive(expected, [], state, ['g'])
1937
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1939
def test_bisect_recursive_renamed(self):
1940
tree, state, expected = self.create_renamed_dirstate()
1942
# Looking for either renamed item should find the other
1943
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1944
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1945
# Looking in the containing directory should find the rename target,
1946
# and anything in a subdir of the renamed target.
1947
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1948
'b/d/e', 'b/g', 'h', 'h/e'],
1952
class TestDirstateValidation(TestCaseWithDirState):
1954
def test_validate_correct_dirstate(self):
1955
state = self.create_complex_dirstate()
1958
# and make sure we can also validate with a read lock
1965
def test_dirblock_not_sorted(self):
1966
tree, state, expected = self.create_renamed_dirstate()
1967
state._read_dirblocks_if_needed()
1968
last_dirblock = state._dirblocks[-1]
1969
# we're appending to the dirblock, but this name comes before some of
1970
# the existing names; that's wrong
1971
last_dirblock[1].append(
1972
(('h', 'aaaa', 'a-id'),
1973
[('a', '', 0, False, ''),
1974
('a', '', 0, False, '')]))
1975
e = self.assertRaises(AssertionError,
1977
self.assertContainsRe(str(e), 'not sorted')
1979
def test_dirblock_name_mismatch(self):
1980
tree, state, expected = self.create_renamed_dirstate()
1981
state._read_dirblocks_if_needed()
1982
last_dirblock = state._dirblocks[-1]
1983
# add an entry with the wrong directory name
1984
last_dirblock[1].append(
1986
[('a', '', 0, False, ''),
1987
('a', '', 0, False, '')]))
1988
e = self.assertRaises(AssertionError,
1990
self.assertContainsRe(str(e),
1991
"doesn't match directory name")
1993
def test_dirblock_missing_rename(self):
1994
tree, state, expected = self.create_renamed_dirstate()
1995
state._read_dirblocks_if_needed()
1996
last_dirblock = state._dirblocks[-1]
1997
# make another entry for a-id, without a correct 'r' pointer to
1998
# the real occurrence in the working tree
1999
last_dirblock[1].append(
2000
(('h', 'z', 'a-id'),
2001
[('a', '', 0, False, ''),
2002
('a', '', 0, False, '')]))
2003
e = self.assertRaises(AssertionError,
2005
self.assertContainsRe(str(e),
2006
'file a-id is absent in row')
2009
class TestDirstateTreeReference(TestCaseWithDirState):
2011
def test_reference_revision_is_none(self):
2012
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2013
subtree = self.make_branch_and_tree('tree/subtree',
2014
format='dirstate-with-subtree')
2015
subtree.set_root_id('subtree')
2016
tree.add_reference(subtree)
2018
state = dirstate.DirState.from_tree(tree, 'dirstate')
2019
key = ('', 'subtree', 'subtree')
2020
expected = ('', [(key,
2021
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2024
self.assertEqual(expected, state._find_block(key))
2029
class TestDiscardMergeParents(TestCaseWithDirState):
2031
def test_discard_no_parents(self):
2032
# This should be a no-op
2033
state = self.create_empty_dirstate()
2034
self.addCleanup(state.unlock)
2035
state._discard_merge_parents()
2038
def test_discard_one_parent(self):
2040
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2041
root_entry_direntry = ('', '', 'a-root-value'), [
2042
('d', '', 0, False, packed_stat),
2043
('d', '', 0, False, packed_stat),
2046
dirblocks.append(('', [root_entry_direntry]))
2047
dirblocks.append(('', []))
2049
state = self.create_empty_dirstate()
2050
self.addCleanup(state.unlock)
2051
state._set_data(['parent-id'], dirblocks[:])
2054
state._discard_merge_parents()
2056
self.assertEqual(dirblocks, state._dirblocks)
2058
def test_discard_simple(self):
2060
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2061
root_entry_direntry = ('', '', 'a-root-value'), [
2062
('d', '', 0, False, packed_stat),
2063
('d', '', 0, False, packed_stat),
2064
('d', '', 0, False, packed_stat),
2066
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2067
('d', '', 0, False, packed_stat),
2068
('d', '', 0, False, packed_stat),
2071
dirblocks.append(('', [root_entry_direntry]))
2072
dirblocks.append(('', []))
2074
state = self.create_empty_dirstate()
2075
self.addCleanup(state.unlock)
2076
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2079
# This should strip of the extra column
2080
state._discard_merge_parents()
2082
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2083
self.assertEqual(expected_dirblocks, state._dirblocks)
2085
def test_discard_absent(self):
2086
"""If entries are only in a merge, discard should remove the entries"""
2087
null_stat = dirstate.DirState.NULLSTAT
2088
present_dir = ('d', '', 0, False, null_stat)
2089
present_file = ('f', '', 0, False, null_stat)
2090
absent = dirstate.DirState.NULL_PARENT_DETAILS
2091
root_key = ('', '', 'a-root-value')
2092
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2093
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2094
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2095
('', [(file_in_merged_key,
2096
[absent, absent, present_file]),
2098
[present_file, present_file, present_file]),
2102
state = self.create_empty_dirstate()
2103
self.addCleanup(state.unlock)
2104
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2107
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2108
('', [(file_in_root_key,
2109
[present_file, present_file]),
2112
state._discard_merge_parents()
2114
self.assertEqual(exp_dirblocks, state._dirblocks)
2116
def test_discard_renamed(self):
2117
null_stat = dirstate.DirState.NULLSTAT
2118
present_dir = ('d', '', 0, False, null_stat)
2119
present_file = ('f', '', 0, False, null_stat)
2120
absent = dirstate.DirState.NULL_PARENT_DETAILS
2121
root_key = ('', '', 'a-root-value')
2122
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2123
# Renamed relative to parent
2124
file_rename_s_key = ('', 'file-s', 'b-file-id')
2125
file_rename_t_key = ('', 'file-t', 'b-file-id')
2126
# And one that is renamed between the parents, but absent in this
2127
key_in_1 = ('', 'file-in-1', 'c-file-id')
2128
key_in_2 = ('', 'file-in-2', 'c-file-id')
2131
('', [(root_key, [present_dir, present_dir, present_dir])]),
2133
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2135
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2137
[present_file, present_file, present_file]),
2139
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2141
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2145
('', [(root_key, [present_dir, present_dir])]),
2146
('', [(key_in_1, [absent, present_file]),
2147
(file_in_root_key, [present_file, present_file]),
2148
(file_rename_t_key, [present_file, absent]),
2151
state = self.create_empty_dirstate()
2152
self.addCleanup(state.unlock)
2153
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2156
state._discard_merge_parents()
2158
self.assertEqual(exp_dirblocks, state._dirblocks)
2160
def test_discard_all_subdir(self):
2161
null_stat = dirstate.DirState.NULLSTAT
2162
present_dir = ('d', '', 0, False, null_stat)
2163
present_file = ('f', '', 0, False, null_stat)
2164
absent = dirstate.DirState.NULL_PARENT_DETAILS
2165
root_key = ('', '', 'a-root-value')
2166
subdir_key = ('', 'sub', 'dir-id')
2167
child1_key = ('sub', 'child1', 'child1-id')
2168
child2_key = ('sub', 'child2', 'child2-id')
2169
child3_key = ('sub', 'child3', 'child3-id')
2172
('', [(root_key, [present_dir, present_dir, present_dir])]),
2173
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2174
('sub', [(child1_key, [absent, absent, present_file]),
2175
(child2_key, [absent, absent, present_file]),
2176
(child3_key, [absent, absent, present_file]),
2180
('', [(root_key, [present_dir, present_dir])]),
2181
('', [(subdir_key, [present_dir, present_dir])]),
2184
state = self.create_empty_dirstate()
2185
self.addCleanup(state.unlock)
2186
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2189
state._discard_merge_parents()
2191
self.assertEqual(exp_dirblocks, state._dirblocks)
2194
class Test_InvEntryToDetails(tests.TestCase):
2196
def assertDetails(self, expected, inv_entry):
2197
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2198
self.assertEqual(expected, details)
2199
# details should always allow join() and always be a plain str when
2201
(minikind, fingerprint, size, executable, tree_data) = details
2202
self.assertIsInstance(minikind, str)
2203
self.assertIsInstance(fingerprint, str)
2204
self.assertIsInstance(tree_data, str)
2206
def test_unicode_symlink(self):
2207
# In general, the code base doesn't support a target that contains
2208
# non-ascii characters. So we just assert tha
2209
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2211
inv_entry.revision = 'link-revision-id'
2212
inv_entry.symlink_target = u'link-target'
2213
details = self.assertDetails(('l', 'link-target', 0, False,
2214
'link-revision-id'), inv_entry)
2217
class TestSHA1Provider(tests.TestCaseInTempDir):
2219
def test_sha1provider_is_an_interface(self):
2220
p = dirstate.SHA1Provider()
2221
self.assertRaises(NotImplementedError, p.sha1, "foo")
2222
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2224
def test_defaultsha1provider_sha1(self):
2225
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2226
self.build_tree_contents([('foo', text)])
2227
expected_sha = osutils.sha_string(text)
2228
p = dirstate.DefaultSHA1Provider()
2229
self.assertEqual(expected_sha, p.sha1('foo'))
2231
def test_defaultsha1provider_stat_and_sha1(self):
2232
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2233
self.build_tree_contents([('foo', text)])
2234
expected_sha = osutils.sha_string(text)
2235
p = dirstate.DefaultSHA1Provider()
2236
statvalue, sha1 = p.stat_and_sha1('foo')
2237
self.assertTrue(len(statvalue) >= 10)
2238
self.assertEqual(len(text), statvalue.st_size)
2239
self.assertEqual(expected_sha, sha1)