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."""
27
revision as _mod_revision,
29
from bzrlib.memorytree import MemoryTree
30
from bzrlib.tests import (
34
TestCaseWithTransport,
40
# general checks for NOT_IN_MEMORY error conditions.
41
# set_path_id on a NOT_IN_MEMORY dirstate
42
# set_path_id unicode support
43
# set_path_id setting id of a path not root
44
# set_path_id setting id when there are parents without the id in the parents
45
# set_path_id setting id when there are parents with the id in the parents
46
# set_path_id setting id when state is not in memory
47
# set_path_id setting id when state is in memory unmodified
48
# set_path_id setting id when state is in memory modified
51
class TestCaseWithDirState(TestCaseWithTransport):
52
"""Helper functions for creating DirState objects with various content."""
54
def create_empty_dirstate(self):
55
"""Return a locked but empty dirstate"""
56
state = dirstate.DirState.initialize('dirstate')
59
def create_dirstate_with_root(self):
60
"""Return a write-locked state with a single root entry."""
61
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
62
root_entry_direntry = ('', '', 'a-root-value'), [
63
('d', '', 0, False, packed_stat),
66
dirblocks.append(('', [root_entry_direntry]))
67
dirblocks.append(('', []))
68
state = self.create_empty_dirstate()
70
state._set_data([], dirblocks)
77
def create_dirstate_with_root_and_subdir(self):
78
"""Return a locked DirState with a root and a subdir"""
79
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
80
subdir_entry = ('', 'subdir', 'subdir-id'), [
81
('d', '', 0, False, packed_stat),
83
state = self.create_dirstate_with_root()
85
dirblocks = list(state._dirblocks)
86
dirblocks[1][1].append(subdir_entry)
87
state._set_data([], dirblocks)
93
def create_complex_dirstate(self):
94
"""This dirstate contains multiple files and directories.
104
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
106
Notice that a/e is an empty directory.
108
:return: The dirstate, still write-locked.
110
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
111
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
112
root_entry = ('', '', 'a-root-value'), [
113
('d', '', 0, False, packed_stat),
115
a_entry = ('', 'a', 'a-dir'), [
116
('d', '', 0, False, packed_stat),
118
b_entry = ('', 'b', 'b-dir'), [
119
('d', '', 0, False, packed_stat),
121
c_entry = ('', 'c', 'c-file'), [
122
('f', null_sha, 10, False, packed_stat),
124
d_entry = ('', 'd', 'd-file'), [
125
('f', null_sha, 20, False, packed_stat),
127
e_entry = ('a', 'e', 'e-dir'), [
128
('d', '', 0, False, packed_stat),
130
f_entry = ('a', 'f', 'f-file'), [
131
('f', null_sha, 30, False, packed_stat),
133
g_entry = ('b', 'g', 'g-file'), [
134
('f', null_sha, 30, False, packed_stat),
136
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
137
('f', null_sha, 40, False, packed_stat),
140
dirblocks.append(('', [root_entry]))
141
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
142
dirblocks.append(('a', [e_entry, f_entry]))
143
dirblocks.append(('b', [g_entry, h_entry]))
144
state = dirstate.DirState.initialize('dirstate')
147
state._set_data([], dirblocks)
153
def check_state_with_reopen(self, expected_result, state):
154
"""Check that state has current state expected_result.
156
This will check the current state, open the file anew and check it
158
This function expects the current state to be locked for writing, and
159
will unlock it before re-opening.
160
This is required because we can't open a lock_read() while something
161
else has a lock_write().
162
write => mutually exclusive lock
165
# The state should already be write locked, since we just had to do
166
# some operation to get here.
167
self.assertTrue(state._lock_token is not None)
169
self.assertEqual(expected_result[0], state.get_parent_ids())
170
# there should be no ghosts in this tree.
171
self.assertEqual([], state.get_ghosts())
172
# there should be one fileid in this tree - the root of the tree.
173
self.assertEqual(expected_result[1], list(state._iter_entries()))
178
state = dirstate.DirState.on_file('dirstate')
181
self.assertEqual(expected_result[1], list(state._iter_entries()))
185
def create_basic_dirstate(self):
186
"""Create a dirstate with a few files and directories.
196
tree = self.make_branch_and_tree('tree')
197
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
198
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
199
self.build_tree(['tree/' + p for p in paths])
200
tree.set_root_id('TREE_ROOT')
201
tree.add([p.rstrip('/') for p in paths], file_ids)
202
tree.commit('initial', rev_id='rev-1')
203
revision_id = 'rev-1'
204
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
205
t = self.get_transport('tree')
206
a_text = t.get_bytes('a')
207
a_sha = osutils.sha_string(a_text)
209
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
210
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
211
c_text = t.get_bytes('b/c')
212
c_sha = osutils.sha_string(c_text)
214
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
215
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
216
e_text = t.get_bytes('b/d/e')
217
e_sha = osutils.sha_string(e_text)
219
b_c_text = t.get_bytes('b-c')
220
b_c_sha = osutils.sha_string(b_c_text)
221
b_c_len = len(b_c_text)
222
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
223
f_text = t.get_bytes('f')
224
f_sha = osutils.sha_string(f_text)
226
null_stat = dirstate.DirState.NULLSTAT
228
'':(('', '', 'TREE_ROOT'), [
229
('d', '', 0, False, null_stat),
230
('d', '', 0, False, revision_id),
232
'a':(('', 'a', 'a-id'), [
233
('f', '', 0, False, null_stat),
234
('f', a_sha, a_len, False, revision_id),
236
'b':(('', 'b', 'b-id'), [
237
('d', '', 0, False, null_stat),
238
('d', '', 0, False, revision_id),
240
'b/c':(('b', 'c', 'c-id'), [
241
('f', '', 0, False, null_stat),
242
('f', c_sha, c_len, False, revision_id),
244
'b/d':(('b', 'd', 'd-id'), [
245
('d', '', 0, False, null_stat),
246
('d', '', 0, False, revision_id),
248
'b/d/e':(('b/d', 'e', 'e-id'), [
249
('f', '', 0, False, null_stat),
250
('f', e_sha, e_len, False, revision_id),
252
'b-c':(('', 'b-c', 'b-c-id'), [
253
('f', '', 0, False, null_stat),
254
('f', b_c_sha, b_c_len, False, revision_id),
256
'f':(('', 'f', 'f-id'), [
257
('f', '', 0, False, null_stat),
258
('f', f_sha, f_len, False, revision_id),
261
state = dirstate.DirState.from_tree(tree, 'dirstate')
266
# Use a different object, to make sure nothing is pre-cached in memory.
267
state = dirstate.DirState.on_file('dirstate')
269
self.addCleanup(state.unlock)
270
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
271
state._dirblock_state)
272
# This is code is only really tested if we actually have to make more
273
# than one read, so set the page size to something smaller.
274
# We want it to contain about 2.2 records, so that we have a couple
275
# records that we can read per attempt
276
state._bisect_page_size = 200
277
return tree, state, expected
279
def create_duplicated_dirstate(self):
280
"""Create a dirstate with a deleted and added entries.
282
This grabs a basic_dirstate, and then removes and re adds every entry
285
tree, state, expected = self.create_basic_dirstate()
286
# Now we will just remove and add every file so we get an extra entry
287
# per entry. Unversion in reverse order so we handle subdirs
288
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
289
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
290
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
292
# Update the expected dictionary.
293
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
294
orig = expected[path]
296
# This record was deleted in the current tree
297
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
299
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
300
# And didn't exist in the basis tree
301
expected[path2] = (new_key, [orig[1][0],
302
dirstate.DirState.NULL_PARENT_DETAILS])
304
# We will replace the 'dirstate' file underneath 'state', but that is
305
# okay as lock as we unlock 'state' first.
308
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
314
# But we need to leave state in a read-lock because we already have
315
# a cleanup scheduled
317
return tree, state, expected
319
def create_renamed_dirstate(self):
320
"""Create a dirstate with a few internal renames.
322
This takes the basic dirstate, and moves the paths around.
324
tree, state, expected = self.create_basic_dirstate()
326
tree.rename_one('a', 'b/g')
328
tree.rename_one('b/d', 'h')
330
old_a = expected['a']
331
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
332
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
333
('r', 'a', 0, False, '')])
334
old_d = expected['b/d']
335
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
336
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
337
('r', 'b/d', 0, False, '')])
339
old_e = expected['b/d/e']
340
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
342
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
343
('r', 'b/d/e', 0, False, '')])
347
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
354
return tree, state, expected
357
class TestTreeToDirState(TestCaseWithDirState):
359
def test_empty_to_dirstate(self):
360
"""We should be able to create a dirstate for an empty tree."""
361
# There are no files on disk and no parents
362
tree = self.make_branch_and_tree('tree')
363
expected_result = ([], [
364
(('', '', tree.get_root_id()), # common details
365
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
367
state = dirstate.DirState.from_tree(tree, 'dirstate')
369
self.check_state_with_reopen(expected_result, state)
371
def test_1_parents_empty_to_dirstate(self):
372
# create a parent by doing a commit
373
tree = self.make_branch_and_tree('tree')
374
rev_id = tree.commit('first post').encode('utf8')
375
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
376
expected_result = ([rev_id], [
377
(('', '', tree.get_root_id()), # common details
378
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
379
('d', '', 0, False, rev_id), # first parent details
381
state = dirstate.DirState.from_tree(tree, 'dirstate')
382
self.check_state_with_reopen(expected_result, state)
389
def test_2_parents_empty_to_dirstate(self):
390
# create a parent by doing a commit
391
tree = self.make_branch_and_tree('tree')
392
rev_id = tree.commit('first post')
393
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
394
rev_id2 = tree2.commit('second post', allow_pointless=True)
395
tree.merge_from_branch(tree2.branch)
396
expected_result = ([rev_id, rev_id2], [
397
(('', '', tree.get_root_id()), # common details
398
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
399
('d', '', 0, False, rev_id), # first parent details
400
('d', '', 0, False, rev_id2), # second parent details
402
state = dirstate.DirState.from_tree(tree, 'dirstate')
403
self.check_state_with_reopen(expected_result, state)
410
def test_empty_unknowns_are_ignored_to_dirstate(self):
411
"""We should be able to create a dirstate for an empty tree."""
412
# There are no files on disk and no parents
413
tree = self.make_branch_and_tree('tree')
414
self.build_tree(['tree/unknown'])
415
expected_result = ([], [
416
(('', '', tree.get_root_id()), # common details
417
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
419
state = dirstate.DirState.from_tree(tree, 'dirstate')
420
self.check_state_with_reopen(expected_result, state)
422
def get_tree_with_a_file(self):
423
tree = self.make_branch_and_tree('tree')
424
self.build_tree(['tree/a file'])
425
tree.add('a file', 'a-file-id')
428
def test_non_empty_no_parents_to_dirstate(self):
429
"""We should be able to create a dirstate for an empty tree."""
430
# There are files on disk and no parents
431
tree = self.get_tree_with_a_file()
432
expected_result = ([], [
433
(('', '', tree.get_root_id()), # common details
434
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
436
(('', 'a file', 'a-file-id'), # common
437
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
440
state = dirstate.DirState.from_tree(tree, 'dirstate')
441
self.check_state_with_reopen(expected_result, state)
443
def test_1_parents_not_empty_to_dirstate(self):
444
# create a parent by doing a commit
445
tree = self.get_tree_with_a_file()
446
rev_id = tree.commit('first post').encode('utf8')
447
# change the current content to be different this will alter stat, sha
449
self.build_tree_contents([('tree/a file', 'new content\n')])
450
expected_result = ([rev_id], [
451
(('', '', tree.get_root_id()), # common details
452
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
453
('d', '', 0, False, rev_id), # first parent details
455
(('', 'a file', 'a-file-id'), # common
456
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
457
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
458
rev_id), # first parent
461
state = dirstate.DirState.from_tree(tree, 'dirstate')
462
self.check_state_with_reopen(expected_result, state)
464
def test_2_parents_not_empty_to_dirstate(self):
465
# create a parent by doing a commit
466
tree = self.get_tree_with_a_file()
467
rev_id = tree.commit('first post').encode('utf8')
468
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
469
# change the current content to be different this will alter stat, sha
471
self.build_tree_contents([('tree2/a file', 'merge content\n')])
472
rev_id2 = tree2.commit('second post').encode('utf8')
473
tree.merge_from_branch(tree2.branch)
474
# change the current content to be different this will alter stat, sha
475
# and length again, giving us three distinct values:
476
self.build_tree_contents([('tree/a file', 'new content\n')])
477
expected_result = ([rev_id, rev_id2], [
478
(('', '', tree.get_root_id()), # common details
479
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
480
('d', '', 0, False, rev_id), # first parent details
481
('d', '', 0, False, rev_id2), # second parent details
483
(('', 'a file', 'a-file-id'), # common
484
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
485
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
486
rev_id), # first parent
487
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
488
rev_id2), # second parent
491
state = dirstate.DirState.from_tree(tree, 'dirstate')
492
self.check_state_with_reopen(expected_result, state)
494
def test_colliding_fileids(self):
495
# test insertion of parents creating several entries at the same path.
496
# we used to have a bug where they could cause the dirstate to break
497
# its ordering invariants.
498
# create some trees to test from
501
tree = self.make_branch_and_tree('tree%d' % i)
502
self.build_tree(['tree%d/name' % i,])
503
tree.add(['name'], ['file-id%d' % i])
504
revision_id = 'revid-%d' % i
505
tree.commit('message', rev_id=revision_id)
506
parents.append((revision_id,
507
tree.branch.repository.revision_tree(revision_id)))
508
# now fold these trees into a dirstate
509
state = dirstate.DirState.initialize('dirstate')
511
state.set_parent_trees(parents, [])
517
class TestDirStateOnFile(TestCaseWithDirState):
519
def test_construct_with_path(self):
520
tree = self.make_branch_and_tree('tree')
521
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
522
# we want to be able to get the lines of the dirstate that we will
524
lines = state.get_lines()
526
self.build_tree_contents([('dirstate', ''.join(lines))])
528
# no parents, default tree content
529
expected_result = ([], [
530
(('', '', tree.get_root_id()), # common details
531
# current tree details, but new from_tree skips statting, it
532
# uses set_state_from_inventory, and thus depends on the
534
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
537
state = dirstate.DirState.on_file('dirstate')
538
state.lock_write() # check_state_with_reopen will save() and unlock it
539
self.check_state_with_reopen(expected_result, state)
541
def test_can_save_clean_on_file(self):
542
tree = self.make_branch_and_tree('tree')
543
state = dirstate.DirState.from_tree(tree, 'dirstate')
545
# doing a save should work here as there have been no changes.
547
# TODO: stat it and check it hasn't changed; may require waiting
548
# for the state accuracy window.
552
def test_can_save_in_read_lock(self):
553
self.build_tree(['a-file'])
554
state = dirstate.DirState.initialize('dirstate')
556
# No stat and no sha1 sum.
557
state.add('a-file', 'a-file-id', 'file', None, '')
562
# Now open in readonly mode
563
state = dirstate.DirState.on_file('dirstate')
566
entry = state._get_entry(0, path_utf8='a-file')
567
# The current size should be 0 (default)
568
self.assertEqual(0, entry[1][0][2])
569
# We should have a real entry.
570
self.assertNotEqual((None, None), entry)
571
# Make sure everything is old enough
572
state._sha_cutoff_time()
573
state._cutoff_time += 10
574
# Change the file length
575
self.build_tree_contents([('a-file', 'shorter')])
576
sha1sum = dirstate.update_entry(state, entry, 'a-file',
578
# new file, no cached sha:
579
self.assertEqual(None, sha1sum)
581
# The dirblock has been updated
582
self.assertEqual(7, entry[1][0][2])
583
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
584
state._dirblock_state)
587
# Now, since we are the only one holding a lock, we should be able
588
# to save and have it written to disk
593
# Re-open the file, and ensure that the state has been updated.
594
state = dirstate.DirState.on_file('dirstate')
597
entry = state._get_entry(0, path_utf8='a-file')
598
self.assertEqual(7, entry[1][0][2])
602
def test_save_fails_quietly_if_locked(self):
603
"""If dirstate is locked, save will fail without complaining."""
604
self.build_tree(['a-file'])
605
state = dirstate.DirState.initialize('dirstate')
607
# No stat and no sha1 sum.
608
state.add('a-file', 'a-file-id', 'file', None, '')
613
state = dirstate.DirState.on_file('dirstate')
616
entry = state._get_entry(0, path_utf8='a-file')
617
sha1sum = dirstate.update_entry(state, entry, 'a-file',
620
self.assertEqual(None, sha1sum)
621
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
622
state._dirblock_state)
624
# Now, before we try to save, grab another dirstate, and take out a
626
# TODO: jam 20070315 Ideally this would be locked by another
627
# process. To make sure the file is really OS locked.
628
state2 = dirstate.DirState.on_file('dirstate')
631
# This won't actually write anything, because it couldn't grab
632
# a write lock. But it shouldn't raise an error, either.
633
# TODO: jam 20070315 We should probably distinguish between
634
# being dirty because of 'update_entry'. And dirty
635
# because of real modification. So that save() *does*
636
# raise a real error if it fails when we have real
644
# The file on disk should not be modified.
645
state = dirstate.DirState.on_file('dirstate')
648
entry = state._get_entry(0, path_utf8='a-file')
649
self.assertEqual('', entry[1][0][1])
653
def test_save_refuses_if_changes_aborted(self):
654
self.build_tree(['a-file', 'a-dir/'])
655
state = dirstate.DirState.initialize('dirstate')
657
# No stat and no sha1 sum.
658
state.add('a-file', 'a-file-id', 'file', None, '')
663
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
665
('', [(('', '', 'TREE_ROOT'),
666
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
667
('', [(('', 'a-file', 'a-file-id'),
668
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
671
state = dirstate.DirState.on_file('dirstate')
674
state._read_dirblocks_if_needed()
675
self.assertEqual(expected_blocks, state._dirblocks)
677
# Now modify the state, but mark it as inconsistent
678
state.add('a-dir', 'a-dir-id', 'directory', None, '')
679
state._changes_aborted = True
684
state = dirstate.DirState.on_file('dirstate')
687
state._read_dirblocks_if_needed()
688
self.assertEqual(expected_blocks, state._dirblocks)
693
class TestDirStateInitialize(TestCaseWithDirState):
695
def test_initialize(self):
696
expected_result = ([], [
697
(('', '', 'TREE_ROOT'), # common details
698
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
701
state = dirstate.DirState.initialize('dirstate')
703
self.assertIsInstance(state, dirstate.DirState)
704
lines = state.get_lines()
707
# On win32 you can't read from a locked file, even within the same
708
# process. So we have to unlock and release before we check the file
710
self.assertFileEqual(''.join(lines), 'dirstate')
711
state.lock_read() # check_state_with_reopen will unlock
712
self.check_state_with_reopen(expected_result, state)
715
class TestDirStateManipulations(TestCaseWithDirState):
717
def test_set_state_from_inventory_no_content_no_parents(self):
718
# setting the current inventory is a slow but important api to support.
719
tree1 = self.make_branch_and_memory_tree('tree1')
723
revid1 = tree1.commit('foo').encode('utf8')
724
root_id = tree1.get_root_id()
725
inv = tree1.inventory
728
expected_result = [], [
729
(('', '', root_id), [
730
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
731
state = dirstate.DirState.initialize('dirstate')
733
state.set_state_from_inventory(inv)
734
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
736
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
737
state._dirblock_state)
742
# This will unlock it
743
self.check_state_with_reopen(expected_result, state)
745
def test_set_state_from_inventory_preserves_hashcache(self):
746
# https://bugs.launchpad.net/bzr/+bug/146176
747
# set_state_from_inventory should preserve the stat and hash value for
748
# workingtree files that are not changed by the inventory.
750
tree = self.make_branch_and_tree('.')
751
# depends on the default format using dirstate...
754
# make a dirstate with some valid hashcache data
755
# file on disk, but that's not needed for this test
756
foo_contents = 'contents of foo'
757
self.build_tree_contents([('foo', foo_contents)])
758
tree.add('foo', 'foo-id')
760
foo_stat = os.stat('foo')
761
foo_packed = dirstate.pack_stat(foo_stat)
762
foo_sha = osutils.sha_string(foo_contents)
763
foo_size = len(foo_contents)
765
# should not be cached yet, because the file's too fresh
767
(('', 'foo', 'foo-id',),
768
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
769
tree._dirstate._get_entry(0, 'foo-id'))
770
# poke in some hashcache information - it wouldn't normally be
771
# stored because it's too fresh
772
tree._dirstate.update_minimal(
773
('', 'foo', 'foo-id'),
774
'f', False, foo_sha, foo_packed, foo_size, 'foo')
775
# now should be cached
777
(('', 'foo', 'foo-id',),
778
[('f', foo_sha, foo_size, False, foo_packed)]),
779
tree._dirstate._get_entry(0, 'foo-id'))
781
# extract the inventory, and add something to it
782
inv = tree._get_inventory()
783
# should see the file we poked in...
784
self.assertTrue(inv.has_id('foo-id'))
785
self.assertTrue(inv.has_filename('foo'))
786
inv.add_path('bar', 'file', 'bar-id')
787
tree._dirstate._validate()
788
# this used to cause it to lose its hashcache
789
tree._dirstate.set_state_from_inventory(inv)
790
tree._dirstate._validate()
796
# now check that the state still has the original hashcache value
797
state = tree._dirstate
799
foo_tuple = state._get_entry(0, path_utf8='foo')
801
(('', 'foo', 'foo-id',),
802
[('f', foo_sha, len(foo_contents), False,
803
dirstate.pack_stat(foo_stat))]),
809
def test_set_state_from_inventory_mixed_paths(self):
810
tree1 = self.make_branch_and_tree('tree1')
811
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
812
'tree1/a/b/foo', 'tree1/a-b/bar'])
815
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
816
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
817
tree1.commit('rev1', rev_id='rev1')
818
root_id = tree1.get_root_id()
819
inv = tree1.inventory
822
expected_result1 = [('', '', root_id, 'd'),
823
('', 'a', 'a-id', 'd'),
824
('', 'a-b', 'a-b-id', 'd'),
825
('a', 'b', 'b-id', 'd'),
826
('a/b', 'foo', 'foo-id', 'f'),
827
('a-b', 'bar', 'bar-id', 'f'),
829
expected_result2 = [('', '', root_id, 'd'),
830
('', 'a', 'a-id', 'd'),
831
('', 'a-b', 'a-b-id', 'd'),
832
('a-b', 'bar', 'bar-id', 'f'),
834
state = dirstate.DirState.initialize('dirstate')
836
state.set_state_from_inventory(inv)
838
for entry in state._iter_entries():
839
values.append(entry[0] + entry[1][0][:1])
840
self.assertEqual(expected_result1, values)
842
state.set_state_from_inventory(inv)
844
for entry in state._iter_entries():
845
values.append(entry[0] + entry[1][0][:1])
846
self.assertEqual(expected_result2, values)
850
def test_set_path_id_no_parents(self):
851
"""The id of a path can be changed trivally with no parents."""
852
state = dirstate.DirState.initialize('dirstate')
854
# check precondition to be sure the state does change appropriately.
856
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
857
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
858
list(state._iter_entries()))
859
state.set_path_id('', 'foobarbaz')
861
(('', '', 'foobarbaz'), [('d', '', 0, False,
862
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
863
self.assertEqual(expected_rows, list(state._iter_entries()))
864
# should work across save too
868
state = dirstate.DirState.on_file('dirstate')
872
self.assertEqual(expected_rows, list(state._iter_entries()))
876
def test_set_path_id_with_parents(self):
877
"""Set the root file id in a dirstate with parents"""
878
mt = self.make_branch_and_tree('mt')
879
# in case the default tree format uses a different root id
880
mt.set_root_id('TREE_ROOT')
881
mt.commit('foo', rev_id='parent-revid')
882
rt = mt.branch.repository.revision_tree('parent-revid')
883
state = dirstate.DirState.initialize('dirstate')
886
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
887
state.set_path_id('', 'foobarbaz')
889
# now see that it is what we expected
891
(('', '', 'TREE_ROOT'),
892
[('a', '', 0, False, ''),
893
('d', '', 0, False, 'parent-revid'),
895
(('', '', 'foobarbaz'),
896
[('d', '', 0, False, ''),
897
('a', '', 0, False, ''),
901
self.assertEqual(expected_rows, list(state._iter_entries()))
902
# should work across save too
906
# now flush & check we get the same
907
state = dirstate.DirState.on_file('dirstate')
911
self.assertEqual(expected_rows, list(state._iter_entries()))
914
# now change within an existing file-backed state
918
state.set_path_id('', 'tree-root-2')
924
def test_set_parent_trees_no_content(self):
925
# set_parent_trees is a slow but important api to support.
926
tree1 = self.make_branch_and_memory_tree('tree1')
930
revid1 = tree1.commit('foo')
933
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
934
tree2 = MemoryTree.create_on_branch(branch2)
937
revid2 = tree2.commit('foo')
938
root_id = tree2.get_root_id()
941
state = dirstate.DirState.initialize('dirstate')
943
state.set_path_id('', root_id)
944
state.set_parent_trees(
945
((revid1, tree1.branch.repository.revision_tree(revid1)),
946
(revid2, tree2.branch.repository.revision_tree(revid2)),
947
('ghost-rev', None)),
949
# check we can reopen and use the dirstate after setting parent
956
state = dirstate.DirState.on_file('dirstate')
959
self.assertEqual([revid1, revid2, 'ghost-rev'],
960
state.get_parent_ids())
961
# iterating the entire state ensures that the state is parsable.
962
list(state._iter_entries())
963
# be sure that it sets not appends - change it
964
state.set_parent_trees(
965
((revid1, tree1.branch.repository.revision_tree(revid1)),
966
('ghost-rev', None)),
968
# and now put it back.
969
state.set_parent_trees(
970
((revid1, tree1.branch.repository.revision_tree(revid1)),
971
(revid2, tree2.branch.repository.revision_tree(revid2)),
972
('ghost-rev', tree2.branch.repository.revision_tree(
973
_mod_revision.NULL_REVISION))),
975
self.assertEqual([revid1, revid2, 'ghost-rev'],
976
state.get_parent_ids())
977
# the ghost should be recorded as such by set_parent_trees.
978
self.assertEqual(['ghost-rev'], state.get_ghosts())
980
[(('', '', root_id), [
981
('d', '', 0, False, dirstate.DirState.NULLSTAT),
982
('d', '', 0, False, revid1),
983
('d', '', 0, False, revid2)
985
list(state._iter_entries()))
989
def test_set_parent_trees_file_missing_from_tree(self):
990
# Adding a parent tree may reference files not in the current state.
991
# they should get listed just once by id, even if they are in two
993
# set_parent_trees is a slow but important api to support.
994
tree1 = self.make_branch_and_memory_tree('tree1')
998
tree1.add(['a file'], ['file-id'], ['file'])
999
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1000
revid1 = tree1.commit('foo')
1003
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1004
tree2 = MemoryTree.create_on_branch(branch2)
1007
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1008
revid2 = tree2.commit('foo')
1009
root_id = tree2.get_root_id()
1012
# check the layout in memory
1013
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1014
(('', '', root_id), [
1015
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1016
('d', '', 0, False, revid1.encode('utf8')),
1017
('d', '', 0, False, revid2.encode('utf8'))
1019
(('', 'a file', 'file-id'), [
1020
('a', '', 0, False, ''),
1021
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1022
revid1.encode('utf8')),
1023
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1024
revid2.encode('utf8'))
1027
state = dirstate.DirState.initialize('dirstate')
1029
state.set_path_id('', root_id)
1030
state.set_parent_trees(
1031
((revid1, tree1.branch.repository.revision_tree(revid1)),
1032
(revid2, tree2.branch.repository.revision_tree(revid2)),
1038
# check_state_with_reopen will unlock
1039
self.check_state_with_reopen(expected_result, state)
1041
### add a path via _set_data - so we dont need delta work, just
1042
# raw data in, and ensure that it comes out via get_lines happily.
1044
def test_add_path_to_root_no_parents_all_data(self):
1045
# The most trivial addition of a path is when there are no parents and
1046
# its in the root and all data about the file is supplied
1047
self.build_tree(['a file'])
1048
stat = os.lstat('a file')
1049
# the 1*20 is the sha1 pretend value.
1050
state = dirstate.DirState.initialize('dirstate')
1051
expected_entries = [
1052
(('', '', 'TREE_ROOT'), [
1053
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1055
(('', 'a file', 'a-file-id'), [
1056
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1060
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1061
# having added it, it should be in the output of iter_entries.
1062
self.assertEqual(expected_entries, list(state._iter_entries()))
1063
# saving and reloading should not affect this.
1067
state = dirstate.DirState.on_file('dirstate')
1070
self.assertEqual(expected_entries, list(state._iter_entries()))
1074
def test_add_path_to_unversioned_directory(self):
1075
"""Adding a path to an unversioned directory should error.
1077
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1078
once dirstate is stable and if it is merged with WorkingTree3, consider
1079
removing this copy of the test.
1081
self.build_tree(['unversioned/', 'unversioned/a file'])
1082
state = dirstate.DirState.initialize('dirstate')
1084
self.assertRaises(errors.NotVersionedError, state.add,
1085
'unversioned/a file', 'a-file-id', 'file', None, None)
1089
def test_add_directory_to_root_no_parents_all_data(self):
1090
# The most trivial addition of a dir is when there are no parents and
1091
# its in the root and all data about the file is supplied
1092
self.build_tree(['a dir/'])
1093
stat = os.lstat('a dir')
1094
expected_entries = [
1095
(('', '', 'TREE_ROOT'), [
1096
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1098
(('', 'a dir', 'a dir id'), [
1099
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1102
state = dirstate.DirState.initialize('dirstate')
1104
state.add('a dir', 'a dir id', 'directory', stat, None)
1105
# having added it, it should be in the output of iter_entries.
1106
self.assertEqual(expected_entries, list(state._iter_entries()))
1107
# saving and reloading should not affect this.
1111
state = dirstate.DirState.on_file('dirstate')
1115
self.assertEqual(expected_entries, list(state._iter_entries()))
1119
def test_add_symlink_to_root_no_parents_all_data(self):
1120
# The most trivial addition of a symlink when there are no parents and
1121
# its in the root and all data about the file is supplied
1122
# bzr doesn't support fake symlinks on windows, yet.
1123
self.requireFeature(SymlinkFeature)
1124
os.symlink('target', 'a link')
1125
stat = os.lstat('a link')
1126
expected_entries = [
1127
(('', '', 'TREE_ROOT'), [
1128
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1130
(('', 'a link', 'a link id'), [
1131
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1134
state = dirstate.DirState.initialize('dirstate')
1136
state.add('a link', 'a link id', 'symlink', stat, 'target')
1137
# having added it, it should be in the output of iter_entries.
1138
self.assertEqual(expected_entries, list(state._iter_entries()))
1139
# saving and reloading should not affect this.
1143
state = dirstate.DirState.on_file('dirstate')
1146
self.assertEqual(expected_entries, list(state._iter_entries()))
1150
def test_add_directory_and_child_no_parents_all_data(self):
1151
# after adding a directory, we should be able to add children to it.
1152
self.build_tree(['a dir/', 'a dir/a file'])
1153
dirstat = os.lstat('a dir')
1154
filestat = os.lstat('a dir/a file')
1155
expected_entries = [
1156
(('', '', 'TREE_ROOT'), [
1157
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1159
(('', 'a dir', 'a dir id'), [
1160
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1162
(('a dir', 'a file', 'a-file-id'), [
1163
('f', '1'*20, 25, False,
1164
dirstate.pack_stat(filestat)), # current tree details
1167
state = dirstate.DirState.initialize('dirstate')
1169
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1170
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1171
# 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')
1180
self.assertEqual(expected_entries, list(state._iter_entries()))
1184
def test_add_tree_reference(self):
1185
# make a dirstate and add a tree reference
1186
state = dirstate.DirState.initialize('dirstate')
1188
('', 'subdir', 'subdir-id'),
1189
[('t', 'subtree-123123', 0, False,
1190
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1193
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1194
entry = state._get_entry(0, 'subdir-id', 'subdir')
1195
self.assertEqual(entry, expected_entry)
1200
# now check we can read it back
1204
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1205
self.assertEqual(entry, entry2)
1206
self.assertEqual(entry, expected_entry)
1207
# and lookup by id should work too
1208
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1209
self.assertEqual(entry, expected_entry)
1213
def test_add_forbidden_names(self):
1214
state = dirstate.DirState.initialize('dirstate')
1215
self.addCleanup(state.unlock)
1216
self.assertRaises(errors.BzrError,
1217
state.add, '.', 'ass-id', 'directory', None, None)
1218
self.assertRaises(errors.BzrError,
1219
state.add, '..', 'ass-id', 'directory', None, None)
1222
class TestGetLines(TestCaseWithDirState):
1224
def test_get_line_with_2_rows(self):
1225
state = self.create_dirstate_with_root_and_subdir()
1227
self.assertEqual(['#bazaar dirstate flat format 3\n',
1228
'crc32: 41262208\n',
1232
'\x00\x00a-root-value\x00'
1233
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1234
'\x00subdir\x00subdir-id\x00'
1235
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1236
], state.get_lines())
1240
def test_entry_to_line(self):
1241
state = self.create_dirstate_with_root()
1244
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1245
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1246
state._entry_to_line(state._dirblocks[0][1][0]))
1250
def test_entry_to_line_with_parent(self):
1251
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1252
root_entry = ('', '', 'a-root-value'), [
1253
('d', '', 0, False, packed_stat), # current tree details
1254
# first: a pointer to the current location
1255
('a', 'dirname/basename', 0, False, ''),
1257
state = dirstate.DirState.initialize('dirstate')
1260
'\x00\x00a-root-value\x00'
1261
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1262
'a\x00dirname/basename\x000\x00n\x00',
1263
state._entry_to_line(root_entry))
1267
def test_entry_to_line_with_two_parents_at_different_paths(self):
1268
# / in the tree, at / in one parent and /dirname/basename in the other.
1269
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1270
root_entry = ('', '', 'a-root-value'), [
1271
('d', '', 0, False, packed_stat), # current tree details
1272
('d', '', 0, False, 'rev_id'), # first parent details
1273
# second: a pointer to the current location
1274
('a', 'dirname/basename', 0, False, ''),
1276
state = dirstate.DirState.initialize('dirstate')
1279
'\x00\x00a-root-value\x00'
1280
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1281
'd\x00\x000\x00n\x00rev_id\x00'
1282
'a\x00dirname/basename\x000\x00n\x00',
1283
state._entry_to_line(root_entry))
1287
def test_iter_entries(self):
1288
# we should be able to iterate the dirstate entries from end to end
1289
# this is for get_lines to be easy to read.
1290
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1292
root_entries = [(('', '', 'a-root-value'), [
1293
('d', '', 0, False, packed_stat), # current tree details
1295
dirblocks.append(('', root_entries))
1296
# add two files in the root
1297
subdir_entry = ('', 'subdir', 'subdir-id'), [
1298
('d', '', 0, False, packed_stat), # current tree details
1300
afile_entry = ('', 'afile', 'afile-id'), [
1301
('f', 'sha1value', 34, False, packed_stat), # current tree details
1303
dirblocks.append(('', [subdir_entry, afile_entry]))
1305
file_entry2 = ('subdir', '2file', '2file-id'), [
1306
('f', 'sha1value', 23, False, packed_stat), # current tree details
1308
dirblocks.append(('subdir', [file_entry2]))
1309
state = dirstate.DirState.initialize('dirstate')
1311
state._set_data([], dirblocks)
1312
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1314
self.assertEqual(expected_entries, list(state._iter_entries()))
1319
class TestGetBlockRowIndex(TestCaseWithDirState):
1321
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1322
file_present, state, dirname, basename, tree_index):
1323
self.assertEqual((block_index, row_index, dir_present, file_present),
1324
state._get_block_entry_index(dirname, basename, tree_index))
1326
block = state._dirblocks[block_index]
1327
self.assertEqual(dirname, block[0])
1328
if dir_present and file_present:
1329
row = state._dirblocks[block_index][1][row_index]
1330
self.assertEqual(dirname, row[0][0])
1331
self.assertEqual(basename, row[0][1])
1333
def test_simple_structure(self):
1334
state = self.create_dirstate_with_root_and_subdir()
1335
self.addCleanup(state.unlock)
1336
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1337
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1338
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1339
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1340
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1343
def test_complex_structure_exists(self):
1344
state = self.create_complex_dirstate()
1345
self.addCleanup(state.unlock)
1346
# Make sure we can find everything that exists
1347
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1348
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1349
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1350
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1351
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1352
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1353
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1354
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1355
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1356
'b', 'h\xc3\xa5', 0)
1358
def test_complex_structure_missing(self):
1359
state = self.create_complex_dirstate()
1360
self.addCleanup(state.unlock)
1361
# Make sure things would be inserted in the right locations
1362
# '_' comes before 'a'
1363
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1364
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1365
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1366
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1368
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1369
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1370
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1371
# This would be inserted between a/ and b/
1372
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1374
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1377
class TestGetEntry(TestCaseWithDirState):
1379
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1380
"""Check that the right entry is returned for a request to getEntry."""
1381
entry = state._get_entry(index, path_utf8=path)
1383
self.assertEqual((None, None), entry)
1386
self.assertEqual((dirname, basename, file_id), cur[:3])
1388
def test_simple_structure(self):
1389
state = self.create_dirstate_with_root_and_subdir()
1390
self.addCleanup(state.unlock)
1391
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1392
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1393
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1394
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1395
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1397
def test_complex_structure_exists(self):
1398
state = self.create_complex_dirstate()
1399
self.addCleanup(state.unlock)
1400
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1401
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1402
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1403
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1404
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1405
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1406
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1407
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1408
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1411
def test_complex_structure_missing(self):
1412
state = self.create_complex_dirstate()
1413
self.addCleanup(state.unlock)
1414
self.assertEntryEqual(None, None, None, state, '_', 0)
1415
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1416
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1417
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1419
def test_get_entry_uninitialized(self):
1420
"""Calling get_entry will load data if it needs to"""
1421
state = self.create_dirstate_with_root()
1427
state = dirstate.DirState.on_file('dirstate')
1430
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1431
state._header_state)
1432
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1433
state._dirblock_state)
1434
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1439
class TestIterChildEntries(TestCaseWithDirState):
1441
def create_dirstate_with_two_trees(self):
1442
"""This dirstate contains multiple files and directories.
1452
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1454
Notice that a/e is an empty directory.
1456
There is one parent tree, which has the same shape with the following variations:
1457
b/g in the parent is gone.
1458
b/h in the parent has a different id
1459
b/i is new in the parent
1460
c is renamed to b/j in the parent
1462
:return: The dirstate, still write-locked.
1464
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1465
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1466
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1467
root_entry = ('', '', 'a-root-value'), [
1468
('d', '', 0, False, packed_stat),
1469
('d', '', 0, False, 'parent-revid'),
1471
a_entry = ('', 'a', 'a-dir'), [
1472
('d', '', 0, False, packed_stat),
1473
('d', '', 0, False, 'parent-revid'),
1475
b_entry = ('', 'b', 'b-dir'), [
1476
('d', '', 0, False, packed_stat),
1477
('d', '', 0, False, 'parent-revid'),
1479
c_entry = ('', 'c', 'c-file'), [
1480
('f', null_sha, 10, False, packed_stat),
1481
('r', 'b/j', 0, False, ''),
1483
d_entry = ('', 'd', 'd-file'), [
1484
('f', null_sha, 20, False, packed_stat),
1485
('f', 'd', 20, False, 'parent-revid'),
1487
e_entry = ('a', 'e', 'e-dir'), [
1488
('d', '', 0, False, packed_stat),
1489
('d', '', 0, False, 'parent-revid'),
1491
f_entry = ('a', 'f', 'f-file'), [
1492
('f', null_sha, 30, False, packed_stat),
1493
('f', 'f', 20, False, 'parent-revid'),
1495
g_entry = ('b', 'g', 'g-file'), [
1496
('f', null_sha, 30, False, packed_stat),
1497
NULL_PARENT_DETAILS,
1499
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1500
('f', null_sha, 40, False, packed_stat),
1501
NULL_PARENT_DETAILS,
1503
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1504
NULL_PARENT_DETAILS,
1505
('f', 'h', 20, False, 'parent-revid'),
1507
i_entry = ('b', 'i', 'i-file'), [
1508
NULL_PARENT_DETAILS,
1509
('f', 'h', 20, False, 'parent-revid'),
1511
j_entry = ('b', 'j', 'c-file'), [
1512
('r', 'c', 0, False, ''),
1513
('f', 'j', 20, False, 'parent-revid'),
1516
dirblocks.append(('', [root_entry]))
1517
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1518
dirblocks.append(('a', [e_entry, f_entry]))
1519
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1520
state = dirstate.DirState.initialize('dirstate')
1523
state._set_data(['parent'], dirblocks)
1527
return state, dirblocks
1529
def test_iter_children_b(self):
1530
state, dirblocks = self.create_dirstate_with_two_trees()
1531
self.addCleanup(state.unlock)
1532
expected_result = []
1533
expected_result.append(dirblocks[3][1][2]) # h2
1534
expected_result.append(dirblocks[3][1][3]) # i
1535
expected_result.append(dirblocks[3][1][4]) # j
1536
self.assertEqual(expected_result,
1537
list(state._iter_child_entries(1, 'b')))
1539
def test_iter_child_root(self):
1540
state, dirblocks = self.create_dirstate_with_two_trees()
1541
self.addCleanup(state.unlock)
1542
expected_result = []
1543
expected_result.append(dirblocks[1][1][0]) # a
1544
expected_result.append(dirblocks[1][1][1]) # b
1545
expected_result.append(dirblocks[1][1][3]) # d
1546
expected_result.append(dirblocks[2][1][0]) # e
1547
expected_result.append(dirblocks[2][1][1]) # f
1548
expected_result.append(dirblocks[3][1][2]) # h2
1549
expected_result.append(dirblocks[3][1][3]) # i
1550
expected_result.append(dirblocks[3][1][4]) # j
1551
self.assertEqual(expected_result,
1552
list(state._iter_child_entries(1, '')))
1555
class TestDirstateSortOrder(TestCaseWithTransport):
1556
"""Test that DirState adds entries in the right order."""
1558
def test_add_sorting(self):
1559
"""Add entries in lexicographical order, we get path sorted order.
1561
This tests it to a depth of 4, to make sure we don't just get it right
1562
at a single depth. 'a/a' should come before 'a-a', even though it
1563
doesn't lexicographically.
1565
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1566
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1569
state = dirstate.DirState.initialize('dirstate')
1570
self.addCleanup(state.unlock)
1572
fake_stat = os.stat('dirstate')
1574
d_id = d.replace('/', '_')+'-id'
1575
file_path = d + '/f'
1576
file_id = file_path.replace('/', '_')+'-id'
1577
state.add(d, d_id, 'directory', fake_stat, null_sha)
1578
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1580
expected = ['', '', 'a',
1581
'a/a', 'a/a/a', 'a/a/a/a',
1582
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1584
split = lambda p:p.split('/')
1585
self.assertEqual(sorted(expected, key=split), expected)
1586
dirblock_names = [d[0] for d in state._dirblocks]
1587
self.assertEqual(expected, dirblock_names)
1589
def test_set_parent_trees_correct_order(self):
1590
"""After calling set_parent_trees() we should maintain the order."""
1591
dirs = ['a', 'a-a', 'a/a']
1593
state = dirstate.DirState.initialize('dirstate')
1594
self.addCleanup(state.unlock)
1596
fake_stat = os.stat('dirstate')
1598
d_id = d.replace('/', '_')+'-id'
1599
file_path = d + '/f'
1600
file_id = file_path.replace('/', '_')+'-id'
1601
state.add(d, d_id, 'directory', fake_stat, null_sha)
1602
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1604
expected = ['', '', 'a', 'a/a', 'a-a']
1605
dirblock_names = [d[0] for d in state._dirblocks]
1606
self.assertEqual(expected, dirblock_names)
1608
# *really* cheesy way to just get an empty tree
1609
repo = self.make_repository('repo')
1610
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1611
state.set_parent_trees([('null:', empty_tree)], [])
1613
dirblock_names = [d[0] for d in state._dirblocks]
1614
self.assertEqual(expected, dirblock_names)
1617
class InstrumentedDirState(dirstate.DirState):
1618
"""An DirState with instrumented sha1 functionality."""
1620
def __init__(self, path, sha1_provider):
1621
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1622
self._time_offset = 0
1624
# member is dynamically set in DirState.__init__ to turn on trace
1625
self._sha1_provider = sha1_provider
1626
self._sha1_file = self._sha1_file_and_log
1628
def _sha_cutoff_time(self):
1629
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1630
self._cutoff_time = timestamp + self._time_offset
1632
def _sha1_file_and_log(self, abspath):
1633
self._log.append(('sha1', abspath))
1634
return self._sha1_provider.sha1(abspath)
1636
def _read_link(self, abspath, old_link):
1637
self._log.append(('read_link', abspath, old_link))
1638
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1640
def _lstat(self, abspath, entry):
1641
self._log.append(('lstat', abspath))
1642
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1644
def _is_executable(self, mode, old_executable):
1645
self._log.append(('is_exec', mode, old_executable))
1646
return super(InstrumentedDirState, self)._is_executable(mode,
1649
def adjust_time(self, secs):
1650
"""Move the clock forward or back.
1652
:param secs: The amount to adjust the clock by. Positive values make it
1653
seem as if we are in the future, negative values make it seem like we
1656
self._time_offset += secs
1657
self._cutoff_time = None
1660
class _FakeStat(object):
1661
"""A class with the same attributes as a real stat result."""
1663
def __init__(self, size, mtime, ctime, dev, ino, mode):
1665
self.st_mtime = mtime
1666
self.st_ctime = ctime
1673
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1674
st.st_ino, st.st_mode)
1677
class TestPackStat(TestCaseWithTransport):
1679
def assertPackStat(self, expected, stat_value):
1680
"""Check the packed and serialized form of a stat value."""
1681
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1683
def test_pack_stat_int(self):
1684
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1685
# Make sure that all parameters have an impact on the packed stat.
1686
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1689
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1690
st.st_mtime = 1172758620
1692
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1693
st.st_ctime = 1172758630
1695
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1698
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1699
st.st_ino = 6499540L
1701
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1702
st.st_mode = 0100744
1704
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1706
def test_pack_stat_float(self):
1707
"""On some platforms mtime and ctime are floats.
1709
Make sure we don't get warnings or errors, and that we ignore changes <
1712
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1713
777L, 6499538L, 0100644)
1714
# These should all be the same as the integer counterparts
1715
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1716
st.st_mtime = 1172758620.0
1718
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1719
st.st_ctime = 1172758630.0
1721
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1722
# fractional seconds are discarded, so no change from above
1723
st.st_mtime = 1172758620.453
1724
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1725
st.st_ctime = 1172758630.228
1726
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1729
class TestBisect(TestCaseWithDirState):
1730
"""Test the ability to bisect into the disk format."""
1732
def assertBisect(self, expected_map, map_keys, state, paths):
1733
"""Assert that bisecting for paths returns the right result.
1735
:param expected_map: A map from key => entry value
1736
:param map_keys: The keys to expect for each path
1737
:param state: The DirState object.
1738
:param paths: A list of paths, these will automatically be split into
1739
(dir, name) tuples, and sorted according to how _bisect
1742
result = state._bisect(paths)
1743
# For now, results are just returned in whatever order we read them.
1744
# We could sort by (dir, name, file_id) or something like that, but in
1745
# the end it would still be fairly arbitrary, and we don't want the
1746
# extra overhead if we can avoid it. So sort everything to make sure
1748
self.assertEqual(len(map_keys), len(paths))
1750
for path, keys in zip(paths, map_keys):
1752
# This should not be present in the output
1754
expected[path] = sorted(expected_map[k] for k in keys)
1756
# The returned values are just arranged randomly based on when they
1757
# were read, for testing, make sure it is properly sorted.
1761
self.assertEqual(expected, result)
1763
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1764
"""Assert that bisecting for dirbblocks returns the right result.
1766
:param expected_map: A map from key => expected values
1767
:param map_keys: A nested list of paths we expect to be returned.
1768
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1769
:param state: The DirState object.
1770
:param paths: A list of directories
1772
result = state._bisect_dirblocks(paths)
1773
self.assertEqual(len(map_keys), len(paths))
1775
for path, keys in zip(paths, map_keys):
1777
# This should not be present in the output
1779
expected[path] = sorted(expected_map[k] for k in keys)
1783
self.assertEqual(expected, result)
1785
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1786
"""Assert the return value of a recursive bisection.
1788
:param expected_map: A map from key => entry value
1789
:param map_keys: A list of paths we expect to be returned.
1790
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1791
:param state: The DirState object.
1792
:param paths: A list of files and directories. It will be broken up
1793
into (dir, name) pairs and sorted before calling _bisect_recursive.
1796
for key in map_keys:
1797
entry = expected_map[key]
1798
dir_name_id, trees_info = entry
1799
expected[dir_name_id] = trees_info
1801
result = state._bisect_recursive(paths)
1803
self.assertEqual(expected, result)
1805
def test_bisect_each(self):
1806
"""Find a single record using bisect."""
1807
tree, state, expected = self.create_basic_dirstate()
1809
# Bisect should return the rows for the specified files.
1810
self.assertBisect(expected, [['']], state, [''])
1811
self.assertBisect(expected, [['a']], state, ['a'])
1812
self.assertBisect(expected, [['b']], state, ['b'])
1813
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1814
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1815
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1816
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1817
self.assertBisect(expected, [['f']], state, ['f'])
1819
def test_bisect_multi(self):
1820
"""Bisect can be used to find multiple records at the same time."""
1821
tree, state, expected = self.create_basic_dirstate()
1822
# Bisect should be capable of finding multiple entries at the same time
1823
self.assertBisect(expected, [['a'], ['b'], ['f']],
1824
state, ['a', 'b', 'f'])
1825
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1826
state, ['f', 'b/d', 'b/d/e'])
1827
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1828
state, ['b', 'b-c', 'b/c'])
1830
def test_bisect_one_page(self):
1831
"""Test bisect when there is only 1 page to read"""
1832
tree, state, expected = self.create_basic_dirstate()
1833
state._bisect_page_size = 5000
1834
self.assertBisect(expected,[['']], state, [''])
1835
self.assertBisect(expected,[['a']], state, ['a'])
1836
self.assertBisect(expected,[['b']], state, ['b'])
1837
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1838
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1839
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1840
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1841
self.assertBisect(expected,[['f']], state, ['f'])
1842
self.assertBisect(expected,[['a'], ['b'], ['f']],
1843
state, ['a', 'b', 'f'])
1844
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1845
state, ['b/d', 'b/d/e', 'f'])
1846
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1847
state, ['b', 'b/c', 'b-c'])
1849
def test_bisect_duplicate_paths(self):
1850
"""When bisecting for a path, handle multiple entries."""
1851
tree, state, expected = self.create_duplicated_dirstate()
1853
# Now make sure that both records are properly returned.
1854
self.assertBisect(expected, [['']], state, [''])
1855
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1856
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1857
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1858
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1859
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1861
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1862
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1864
def test_bisect_page_size_too_small(self):
1865
"""If the page size is too small, we will auto increase it."""
1866
tree, state, expected = self.create_basic_dirstate()
1867
state._bisect_page_size = 50
1868
self.assertBisect(expected, [None], state, ['b/e'])
1869
self.assertBisect(expected, [['a']], state, ['a'])
1870
self.assertBisect(expected, [['b']], state, ['b'])
1871
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1872
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1873
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1874
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1875
self.assertBisect(expected, [['f']], state, ['f'])
1877
def test_bisect_missing(self):
1878
"""Test that bisect return None if it cannot find a path."""
1879
tree, state, expected = self.create_basic_dirstate()
1880
self.assertBisect(expected, [None], state, ['foo'])
1881
self.assertBisect(expected, [None], state, ['b/foo'])
1882
self.assertBisect(expected, [None], state, ['bar/foo'])
1883
self.assertBisect(expected, [None], state, ['b-c/foo'])
1885
self.assertBisect(expected, [['a'], None, ['b/d']],
1886
state, ['a', 'foo', 'b/d'])
1888
def test_bisect_rename(self):
1889
"""Check that we find a renamed row."""
1890
tree, state, expected = self.create_renamed_dirstate()
1892
# Search for the pre and post renamed entries
1893
self.assertBisect(expected, [['a']], state, ['a'])
1894
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1895
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1896
self.assertBisect(expected, [['h']], state, ['h'])
1898
# What about b/d/e? shouldn't that also get 2 directory entries?
1899
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1900
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1902
def test_bisect_dirblocks(self):
1903
tree, state, expected = self.create_duplicated_dirstate()
1904
self.assertBisectDirBlocks(expected,
1905
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1907
self.assertBisectDirBlocks(expected,
1908
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1909
self.assertBisectDirBlocks(expected,
1910
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1911
self.assertBisectDirBlocks(expected,
1912
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1913
['b/c', 'b/c2', 'b/d', 'b/d2'],
1914
['b/d/e', 'b/d/e2'],
1915
], state, ['', 'b', 'b/d'])
1917
def test_bisect_dirblocks_missing(self):
1918
tree, state, expected = self.create_basic_dirstate()
1919
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1920
state, ['b/d', 'b/e'])
1921
# Files don't show up in this search
1922
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1923
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1924
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1925
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1926
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1928
def test_bisect_recursive_each(self):
1929
tree, state, expected = self.create_basic_dirstate()
1930
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1931
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1932
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1933
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1934
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1936
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1938
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1942
def test_bisect_recursive_multiple(self):
1943
tree, state, expected = self.create_basic_dirstate()
1944
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1945
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1946
state, ['b/d', 'b/d/e'])
1948
def test_bisect_recursive_missing(self):
1949
tree, state, expected = self.create_basic_dirstate()
1950
self.assertBisectRecursive(expected, [], state, ['d'])
1951
self.assertBisectRecursive(expected, [], state, ['b/e'])
1952
self.assertBisectRecursive(expected, [], state, ['g'])
1953
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1955
def test_bisect_recursive_renamed(self):
1956
tree, state, expected = self.create_renamed_dirstate()
1958
# Looking for either renamed item should find the other
1959
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1960
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1961
# Looking in the containing directory should find the rename target,
1962
# and anything in a subdir of the renamed target.
1963
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1964
'b/d/e', 'b/g', 'h', 'h/e'],
1968
class TestDirstateValidation(TestCaseWithDirState):
1970
def test_validate_correct_dirstate(self):
1971
state = self.create_complex_dirstate()
1974
# and make sure we can also validate with a read lock
1981
def test_dirblock_not_sorted(self):
1982
tree, state, expected = self.create_renamed_dirstate()
1983
state._read_dirblocks_if_needed()
1984
last_dirblock = state._dirblocks[-1]
1985
# we're appending to the dirblock, but this name comes before some of
1986
# the existing names; that's wrong
1987
last_dirblock[1].append(
1988
(('h', 'aaaa', 'a-id'),
1989
[('a', '', 0, False, ''),
1990
('a', '', 0, False, '')]))
1991
e = self.assertRaises(AssertionError,
1993
self.assertContainsRe(str(e), 'not sorted')
1995
def test_dirblock_name_mismatch(self):
1996
tree, state, expected = self.create_renamed_dirstate()
1997
state._read_dirblocks_if_needed()
1998
last_dirblock = state._dirblocks[-1]
1999
# add an entry with the wrong directory name
2000
last_dirblock[1].append(
2002
[('a', '', 0, False, ''),
2003
('a', '', 0, False, '')]))
2004
e = self.assertRaises(AssertionError,
2006
self.assertContainsRe(str(e),
2007
"doesn't match directory name")
2009
def test_dirblock_missing_rename(self):
2010
tree, state, expected = self.create_renamed_dirstate()
2011
state._read_dirblocks_if_needed()
2012
last_dirblock = state._dirblocks[-1]
2013
# make another entry for a-id, without a correct 'r' pointer to
2014
# the real occurrence in the working tree
2015
last_dirblock[1].append(
2016
(('h', 'z', 'a-id'),
2017
[('a', '', 0, False, ''),
2018
('a', '', 0, False, '')]))
2019
e = self.assertRaises(AssertionError,
2021
self.assertContainsRe(str(e),
2022
'file a-id is absent in row')
2025
class TestDirstateTreeReference(TestCaseWithDirState):
2027
def test_reference_revision_is_none(self):
2028
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2029
subtree = self.make_branch_and_tree('tree/subtree',
2030
format='dirstate-with-subtree')
2031
subtree.set_root_id('subtree')
2032
tree.add_reference(subtree)
2034
state = dirstate.DirState.from_tree(tree, 'dirstate')
2035
key = ('', 'subtree', 'subtree')
2036
expected = ('', [(key,
2037
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2040
self.assertEqual(expected, state._find_block(key))
2045
class TestDiscardMergeParents(TestCaseWithDirState):
2047
def test_discard_no_parents(self):
2048
# This should be a no-op
2049
state = self.create_empty_dirstate()
2050
self.addCleanup(state.unlock)
2051
state._discard_merge_parents()
2054
def test_discard_one_parent(self):
2056
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2057
root_entry_direntry = ('', '', 'a-root-value'), [
2058
('d', '', 0, False, packed_stat),
2059
('d', '', 0, False, packed_stat),
2062
dirblocks.append(('', [root_entry_direntry]))
2063
dirblocks.append(('', []))
2065
state = self.create_empty_dirstate()
2066
self.addCleanup(state.unlock)
2067
state._set_data(['parent-id'], dirblocks[:])
2070
state._discard_merge_parents()
2072
self.assertEqual(dirblocks, state._dirblocks)
2074
def test_discard_simple(self):
2076
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2077
root_entry_direntry = ('', '', 'a-root-value'), [
2078
('d', '', 0, False, packed_stat),
2079
('d', '', 0, False, packed_stat),
2080
('d', '', 0, False, packed_stat),
2082
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2083
('d', '', 0, False, packed_stat),
2084
('d', '', 0, False, packed_stat),
2087
dirblocks.append(('', [root_entry_direntry]))
2088
dirblocks.append(('', []))
2090
state = self.create_empty_dirstate()
2091
self.addCleanup(state.unlock)
2092
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2095
# This should strip of the extra column
2096
state._discard_merge_parents()
2098
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2099
self.assertEqual(expected_dirblocks, state._dirblocks)
2101
def test_discard_absent(self):
2102
"""If entries are only in a merge, discard should remove the entries"""
2103
null_stat = dirstate.DirState.NULLSTAT
2104
present_dir = ('d', '', 0, False, null_stat)
2105
present_file = ('f', '', 0, False, null_stat)
2106
absent = dirstate.DirState.NULL_PARENT_DETAILS
2107
root_key = ('', '', 'a-root-value')
2108
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2109
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2110
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2111
('', [(file_in_merged_key,
2112
[absent, absent, present_file]),
2114
[present_file, present_file, present_file]),
2118
state = self.create_empty_dirstate()
2119
self.addCleanup(state.unlock)
2120
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2123
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2124
('', [(file_in_root_key,
2125
[present_file, present_file]),
2128
state._discard_merge_parents()
2130
self.assertEqual(exp_dirblocks, state._dirblocks)
2132
def test_discard_renamed(self):
2133
null_stat = dirstate.DirState.NULLSTAT
2134
present_dir = ('d', '', 0, False, null_stat)
2135
present_file = ('f', '', 0, False, null_stat)
2136
absent = dirstate.DirState.NULL_PARENT_DETAILS
2137
root_key = ('', '', 'a-root-value')
2138
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2139
# Renamed relative to parent
2140
file_rename_s_key = ('', 'file-s', 'b-file-id')
2141
file_rename_t_key = ('', 'file-t', 'b-file-id')
2142
# And one that is renamed between the parents, but absent in this
2143
key_in_1 = ('', 'file-in-1', 'c-file-id')
2144
key_in_2 = ('', 'file-in-2', 'c-file-id')
2147
('', [(root_key, [present_dir, present_dir, present_dir])]),
2149
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2151
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2153
[present_file, present_file, present_file]),
2155
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2157
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2161
('', [(root_key, [present_dir, present_dir])]),
2162
('', [(key_in_1, [absent, present_file]),
2163
(file_in_root_key, [present_file, present_file]),
2164
(file_rename_t_key, [present_file, absent]),
2167
state = self.create_empty_dirstate()
2168
self.addCleanup(state.unlock)
2169
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2172
state._discard_merge_parents()
2174
self.assertEqual(exp_dirblocks, state._dirblocks)
2176
def test_discard_all_subdir(self):
2177
null_stat = dirstate.DirState.NULLSTAT
2178
present_dir = ('d', '', 0, False, null_stat)
2179
present_file = ('f', '', 0, False, null_stat)
2180
absent = dirstate.DirState.NULL_PARENT_DETAILS
2181
root_key = ('', '', 'a-root-value')
2182
subdir_key = ('', 'sub', 'dir-id')
2183
child1_key = ('sub', 'child1', 'child1-id')
2184
child2_key = ('sub', 'child2', 'child2-id')
2185
child3_key = ('sub', 'child3', 'child3-id')
2188
('', [(root_key, [present_dir, present_dir, present_dir])]),
2189
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2190
('sub', [(child1_key, [absent, absent, present_file]),
2191
(child2_key, [absent, absent, present_file]),
2192
(child3_key, [absent, absent, present_file]),
2196
('', [(root_key, [present_dir, present_dir])]),
2197
('', [(subdir_key, [present_dir, present_dir])]),
2200
state = self.create_empty_dirstate()
2201
self.addCleanup(state.unlock)
2202
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2205
state._discard_merge_parents()
2207
self.assertEqual(exp_dirblocks, state._dirblocks)
2210
class Test_InvEntryToDetails(TestCaseWithDirState):
2212
def assertDetails(self, expected, inv_entry):
2213
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2214
self.assertEqual(expected, details)
2215
# details should always allow join() and always be a plain str when
2217
(minikind, fingerprint, size, executable, tree_data) = details
2218
self.assertIsInstance(minikind, str)
2219
self.assertIsInstance(fingerprint, str)
2220
self.assertIsInstance(tree_data, str)
2222
def test_unicode_symlink(self):
2223
# In general, the code base doesn't support a target that contains
2224
# non-ascii characters. So we just assert tha
2225
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2227
inv_entry.revision = 'link-revision-id'
2228
inv_entry.symlink_target = u'link-target'
2229
details = self.assertDetails(('l', 'link-target', 0, False,
2230
'link-revision-id'), inv_entry)
2233
class TestSHA1Provider(TestCaseInTempDir):
2235
def test_sha1provider_is_an_interface(self):
2236
p = dirstate.SHA1Provider()
2237
self.assertRaises(NotImplementedError, p.sha1, "foo")
2238
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2240
def test_defaultsha1provider_sha1(self):
2241
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2242
self.build_tree_contents([('foo', text)])
2243
expected_sha = osutils.sha_string(text)
2244
p = dirstate.DefaultSHA1Provider()
2245
self.assertEqual(expected_sha, p.sha1('foo'))
2247
def test_defaultsha1provider_stat_and_sha1(self):
2248
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2249
self.build_tree_contents([('foo', text)])
2250
expected_sha = osutils.sha_string(text)
2251
p = dirstate.DefaultSHA1Provider()
2252
statvalue, sha1 = p.stat_and_sha1('foo')
2253
self.assertTrue(len(statvalue) >= 10)
2254
self.assertEqual(len(text), statvalue.st_size)
2255
self.assertEqual(expected_sha, sha1)