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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
revision as _mod_revision,
30
from bzrlib.memorytree import MemoryTree
31
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):
1621
super(InstrumentedDirState, self).__init__(path)
1622
self._time_offset = 0
1624
# member is dynamically set in DirState.__init__ to turn on trace
1625
self._size_sha1_file = self._size_sha1_file_and_log
1627
def _sha_cutoff_time(self):
1628
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1629
self._cutoff_time = timestamp + self._time_offset
1631
def _size_sha1_file_and_log(self, abspath, filter_list):
1632
self._log.append(('sha1', abspath))
1633
return filters.internal_size_sha_file_byname(abspath, filter_list)
1635
def _read_link(self, abspath, old_link):
1636
self._log.append(('read_link', abspath, old_link))
1637
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1639
def _lstat(self, abspath, entry):
1640
self._log.append(('lstat', abspath))
1641
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1643
def _is_executable(self, mode, old_executable):
1644
self._log.append(('is_exec', mode, old_executable))
1645
return super(InstrumentedDirState, self)._is_executable(mode,
1648
def adjust_time(self, secs):
1649
"""Move the clock forward or back.
1651
:param secs: The amount to adjust the clock by. Positive values make it
1652
seem as if we are in the future, negative values make it seem like we
1655
self._time_offset += secs
1656
self._cutoff_time = None
1659
class _FakeStat(object):
1660
"""A class with the same attributes as a real stat result."""
1662
def __init__(self, size, mtime, ctime, dev, ino, mode):
1664
self.st_mtime = mtime
1665
self.st_ctime = ctime
1671
class TestPackStat(TestCaseWithTransport):
1673
def assertPackStat(self, expected, stat_value):
1674
"""Check the packed and serialized form of a stat value."""
1675
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1677
def test_pack_stat_int(self):
1678
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1679
# Make sure that all parameters have an impact on the packed stat.
1680
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1683
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1684
st.st_mtime = 1172758620
1686
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1687
st.st_ctime = 1172758630
1689
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1692
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1693
st.st_ino = 6499540L
1695
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1696
st.st_mode = 0100744
1698
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1700
def test_pack_stat_float(self):
1701
"""On some platforms mtime and ctime are floats.
1703
Make sure we don't get warnings or errors, and that we ignore changes <
1706
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1707
777L, 6499538L, 0100644)
1708
# These should all be the same as the integer counterparts
1709
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1710
st.st_mtime = 1172758620.0
1712
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1713
st.st_ctime = 1172758630.0
1715
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1716
# fractional seconds are discarded, so no change from above
1717
st.st_mtime = 1172758620.453
1718
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1719
st.st_ctime = 1172758630.228
1720
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1723
class TestBisect(TestCaseWithDirState):
1724
"""Test the ability to bisect into the disk format."""
1726
def assertBisect(self, expected_map, map_keys, state, paths):
1727
"""Assert that bisecting for paths returns the right result.
1729
:param expected_map: A map from key => entry value
1730
:param map_keys: The keys to expect for each path
1731
:param state: The DirState object.
1732
:param paths: A list of paths, these will automatically be split into
1733
(dir, name) tuples, and sorted according to how _bisect
1736
result = state._bisect(paths)
1737
# For now, results are just returned in whatever order we read them.
1738
# We could sort by (dir, name, file_id) or something like that, but in
1739
# the end it would still be fairly arbitrary, and we don't want the
1740
# extra overhead if we can avoid it. So sort everything to make sure
1742
self.assertEqual(len(map_keys), len(paths))
1744
for path, keys in zip(paths, map_keys):
1746
# This should not be present in the output
1748
expected[path] = sorted(expected_map[k] for k in keys)
1750
# The returned values are just arranged randomly based on when they
1751
# were read, for testing, make sure it is properly sorted.
1755
self.assertEqual(expected, result)
1757
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1758
"""Assert that bisecting for dirbblocks returns the right result.
1760
:param expected_map: A map from key => expected values
1761
:param map_keys: A nested list of paths we expect to be returned.
1762
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1763
:param state: The DirState object.
1764
:param paths: A list of directories
1766
result = state._bisect_dirblocks(paths)
1767
self.assertEqual(len(map_keys), len(paths))
1769
for path, keys in zip(paths, map_keys):
1771
# This should not be present in the output
1773
expected[path] = sorted(expected_map[k] for k in keys)
1777
self.assertEqual(expected, result)
1779
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1780
"""Assert the return value of a recursive bisection.
1782
:param expected_map: A map from key => entry value
1783
:param map_keys: A list of paths we expect to be returned.
1784
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1785
:param state: The DirState object.
1786
:param paths: A list of files and directories. It will be broken up
1787
into (dir, name) pairs and sorted before calling _bisect_recursive.
1790
for key in map_keys:
1791
entry = expected_map[key]
1792
dir_name_id, trees_info = entry
1793
expected[dir_name_id] = trees_info
1795
result = state._bisect_recursive(paths)
1797
self.assertEqual(expected, result)
1799
def test_bisect_each(self):
1800
"""Find a single record using bisect."""
1801
tree, state, expected = self.create_basic_dirstate()
1803
# Bisect should return the rows for the specified files.
1804
self.assertBisect(expected, [['']], state, [''])
1805
self.assertBisect(expected, [['a']], state, ['a'])
1806
self.assertBisect(expected, [['b']], state, ['b'])
1807
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1808
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1809
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1810
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1811
self.assertBisect(expected, [['f']], state, ['f'])
1813
def test_bisect_multi(self):
1814
"""Bisect can be used to find multiple records at the same time."""
1815
tree, state, expected = self.create_basic_dirstate()
1816
# Bisect should be capable of finding multiple entries at the same time
1817
self.assertBisect(expected, [['a'], ['b'], ['f']],
1818
state, ['a', 'b', 'f'])
1819
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1820
state, ['f', 'b/d', 'b/d/e'])
1821
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1822
state, ['b', 'b-c', 'b/c'])
1824
def test_bisect_one_page(self):
1825
"""Test bisect when there is only 1 page to read"""
1826
tree, state, expected = self.create_basic_dirstate()
1827
state._bisect_page_size = 5000
1828
self.assertBisect(expected,[['']], state, [''])
1829
self.assertBisect(expected,[['a']], state, ['a'])
1830
self.assertBisect(expected,[['b']], state, ['b'])
1831
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1832
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1833
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1834
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1835
self.assertBisect(expected,[['f']], state, ['f'])
1836
self.assertBisect(expected,[['a'], ['b'], ['f']],
1837
state, ['a', 'b', 'f'])
1838
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1839
state, ['b/d', 'b/d/e', 'f'])
1840
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1841
state, ['b', 'b/c', 'b-c'])
1843
def test_bisect_duplicate_paths(self):
1844
"""When bisecting for a path, handle multiple entries."""
1845
tree, state, expected = self.create_duplicated_dirstate()
1847
# Now make sure that both records are properly returned.
1848
self.assertBisect(expected, [['']], state, [''])
1849
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1850
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1851
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1852
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1853
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1855
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1856
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1858
def test_bisect_page_size_too_small(self):
1859
"""If the page size is too small, we will auto increase it."""
1860
tree, state, expected = self.create_basic_dirstate()
1861
state._bisect_page_size = 50
1862
self.assertBisect(expected, [None], state, ['b/e'])
1863
self.assertBisect(expected, [['a']], state, ['a'])
1864
self.assertBisect(expected, [['b']], state, ['b'])
1865
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1866
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1867
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1868
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1869
self.assertBisect(expected, [['f']], state, ['f'])
1871
def test_bisect_missing(self):
1872
"""Test that bisect return None if it cannot find a path."""
1873
tree, state, expected = self.create_basic_dirstate()
1874
self.assertBisect(expected, [None], state, ['foo'])
1875
self.assertBisect(expected, [None], state, ['b/foo'])
1876
self.assertBisect(expected, [None], state, ['bar/foo'])
1877
self.assertBisect(expected, [None], state, ['b-c/foo'])
1879
self.assertBisect(expected, [['a'], None, ['b/d']],
1880
state, ['a', 'foo', 'b/d'])
1882
def test_bisect_rename(self):
1883
"""Check that we find a renamed row."""
1884
tree, state, expected = self.create_renamed_dirstate()
1886
# Search for the pre and post renamed entries
1887
self.assertBisect(expected, [['a']], state, ['a'])
1888
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1889
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1890
self.assertBisect(expected, [['h']], state, ['h'])
1892
# What about b/d/e? shouldn't that also get 2 directory entries?
1893
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1894
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1896
def test_bisect_dirblocks(self):
1897
tree, state, expected = self.create_duplicated_dirstate()
1898
self.assertBisectDirBlocks(expected,
1899
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1901
self.assertBisectDirBlocks(expected,
1902
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1903
self.assertBisectDirBlocks(expected,
1904
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1905
self.assertBisectDirBlocks(expected,
1906
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1907
['b/c', 'b/c2', 'b/d', 'b/d2'],
1908
['b/d/e', 'b/d/e2'],
1909
], state, ['', 'b', 'b/d'])
1911
def test_bisect_dirblocks_missing(self):
1912
tree, state, expected = self.create_basic_dirstate()
1913
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1914
state, ['b/d', 'b/e'])
1915
# Files don't show up in this search
1916
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1917
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1918
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1919
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1920
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1922
def test_bisect_recursive_each(self):
1923
tree, state, expected = self.create_basic_dirstate()
1924
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1925
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1926
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1927
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1928
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1930
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1932
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1936
def test_bisect_recursive_multiple(self):
1937
tree, state, expected = self.create_basic_dirstate()
1938
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1939
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1940
state, ['b/d', 'b/d/e'])
1942
def test_bisect_recursive_missing(self):
1943
tree, state, expected = self.create_basic_dirstate()
1944
self.assertBisectRecursive(expected, [], state, ['d'])
1945
self.assertBisectRecursive(expected, [], state, ['b/e'])
1946
self.assertBisectRecursive(expected, [], state, ['g'])
1947
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1949
def test_bisect_recursive_renamed(self):
1950
tree, state, expected = self.create_renamed_dirstate()
1952
# Looking for either renamed item should find the other
1953
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1954
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1955
# Looking in the containing directory should find the rename target,
1956
# and anything in a subdir of the renamed target.
1957
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1958
'b/d/e', 'b/g', 'h', 'h/e'],
1962
class TestDirstateValidation(TestCaseWithDirState):
1964
def test_validate_correct_dirstate(self):
1965
state = self.create_complex_dirstate()
1968
# and make sure we can also validate with a read lock
1975
def test_dirblock_not_sorted(self):
1976
tree, state, expected = self.create_renamed_dirstate()
1977
state._read_dirblocks_if_needed()
1978
last_dirblock = state._dirblocks[-1]
1979
# we're appending to the dirblock, but this name comes before some of
1980
# the existing names; that's wrong
1981
last_dirblock[1].append(
1982
(('h', 'aaaa', 'a-id'),
1983
[('a', '', 0, False, ''),
1984
('a', '', 0, False, '')]))
1985
e = self.assertRaises(AssertionError,
1987
self.assertContainsRe(str(e), 'not sorted')
1989
def test_dirblock_name_mismatch(self):
1990
tree, state, expected = self.create_renamed_dirstate()
1991
state._read_dirblocks_if_needed()
1992
last_dirblock = state._dirblocks[-1]
1993
# add an entry with the wrong directory name
1994
last_dirblock[1].append(
1996
[('a', '', 0, False, ''),
1997
('a', '', 0, False, '')]))
1998
e = self.assertRaises(AssertionError,
2000
self.assertContainsRe(str(e),
2001
"doesn't match directory name")
2003
def test_dirblock_missing_rename(self):
2004
tree, state, expected = self.create_renamed_dirstate()
2005
state._read_dirblocks_if_needed()
2006
last_dirblock = state._dirblocks[-1]
2007
# make another entry for a-id, without a correct 'r' pointer to
2008
# the real occurrence in the working tree
2009
last_dirblock[1].append(
2010
(('h', 'z', 'a-id'),
2011
[('a', '', 0, False, ''),
2012
('a', '', 0, False, '')]))
2013
e = self.assertRaises(AssertionError,
2015
self.assertContainsRe(str(e),
2016
'file a-id is absent in row')
2019
class TestDirstateTreeReference(TestCaseWithDirState):
2021
def test_reference_revision_is_none(self):
2022
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2023
subtree = self.make_branch_and_tree('tree/subtree',
2024
format='dirstate-with-subtree')
2025
subtree.set_root_id('subtree')
2026
tree.add_reference(subtree)
2028
state = dirstate.DirState.from_tree(tree, 'dirstate')
2029
key = ('', 'subtree', 'subtree')
2030
expected = ('', [(key,
2031
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2034
self.assertEqual(expected, state._find_block(key))
2039
class TestDiscardMergeParents(TestCaseWithDirState):
2041
def test_discard_no_parents(self):
2042
# This should be a no-op
2043
state = self.create_empty_dirstate()
2044
self.addCleanup(state.unlock)
2045
state._discard_merge_parents()
2048
def test_discard_one_parent(self):
2050
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2051
root_entry_direntry = ('', '', 'a-root-value'), [
2052
('d', '', 0, False, packed_stat),
2053
('d', '', 0, False, packed_stat),
2056
dirblocks.append(('', [root_entry_direntry]))
2057
dirblocks.append(('', []))
2059
state = self.create_empty_dirstate()
2060
self.addCleanup(state.unlock)
2061
state._set_data(['parent-id'], dirblocks[:])
2064
state._discard_merge_parents()
2066
self.assertEqual(dirblocks, state._dirblocks)
2068
def test_discard_simple(self):
2070
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2071
root_entry_direntry = ('', '', 'a-root-value'), [
2072
('d', '', 0, False, packed_stat),
2073
('d', '', 0, False, packed_stat),
2074
('d', '', 0, False, packed_stat),
2076
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2077
('d', '', 0, False, packed_stat),
2078
('d', '', 0, False, packed_stat),
2081
dirblocks.append(('', [root_entry_direntry]))
2082
dirblocks.append(('', []))
2084
state = self.create_empty_dirstate()
2085
self.addCleanup(state.unlock)
2086
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2089
# This should strip of the extra column
2090
state._discard_merge_parents()
2092
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2093
self.assertEqual(expected_dirblocks, state._dirblocks)
2095
def test_discard_absent(self):
2096
"""If entries are only in a merge, discard should remove the entries"""
2097
null_stat = dirstate.DirState.NULLSTAT
2098
present_dir = ('d', '', 0, False, null_stat)
2099
present_file = ('f', '', 0, False, null_stat)
2100
absent = dirstate.DirState.NULL_PARENT_DETAILS
2101
root_key = ('', '', 'a-root-value')
2102
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2103
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2104
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2105
('', [(file_in_merged_key,
2106
[absent, absent, present_file]),
2108
[present_file, present_file, present_file]),
2112
state = self.create_empty_dirstate()
2113
self.addCleanup(state.unlock)
2114
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2117
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2118
('', [(file_in_root_key,
2119
[present_file, present_file]),
2122
state._discard_merge_parents()
2124
self.assertEqual(exp_dirblocks, state._dirblocks)
2126
def test_discard_renamed(self):
2127
null_stat = dirstate.DirState.NULLSTAT
2128
present_dir = ('d', '', 0, False, null_stat)
2129
present_file = ('f', '', 0, False, null_stat)
2130
absent = dirstate.DirState.NULL_PARENT_DETAILS
2131
root_key = ('', '', 'a-root-value')
2132
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2133
# Renamed relative to parent
2134
file_rename_s_key = ('', 'file-s', 'b-file-id')
2135
file_rename_t_key = ('', 'file-t', 'b-file-id')
2136
# And one that is renamed between the parents, but absent in this
2137
key_in_1 = ('', 'file-in-1', 'c-file-id')
2138
key_in_2 = ('', 'file-in-2', 'c-file-id')
2141
('', [(root_key, [present_dir, present_dir, present_dir])]),
2143
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2145
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2147
[present_file, present_file, present_file]),
2149
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2151
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2155
('', [(root_key, [present_dir, present_dir])]),
2156
('', [(key_in_1, [absent, present_file]),
2157
(file_in_root_key, [present_file, present_file]),
2158
(file_rename_t_key, [present_file, absent]),
2161
state = self.create_empty_dirstate()
2162
self.addCleanup(state.unlock)
2163
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2166
state._discard_merge_parents()
2168
self.assertEqual(exp_dirblocks, state._dirblocks)
2170
def test_discard_all_subdir(self):
2171
null_stat = dirstate.DirState.NULLSTAT
2172
present_dir = ('d', '', 0, False, null_stat)
2173
present_file = ('f', '', 0, False, null_stat)
2174
absent = dirstate.DirState.NULL_PARENT_DETAILS
2175
root_key = ('', '', 'a-root-value')
2176
subdir_key = ('', 'sub', 'dir-id')
2177
child1_key = ('sub', 'child1', 'child1-id')
2178
child2_key = ('sub', 'child2', 'child2-id')
2179
child3_key = ('sub', 'child3', 'child3-id')
2182
('', [(root_key, [present_dir, present_dir, present_dir])]),
2183
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2184
('sub', [(child1_key, [absent, absent, present_file]),
2185
(child2_key, [absent, absent, present_file]),
2186
(child3_key, [absent, absent, present_file]),
2190
('', [(root_key, [present_dir, present_dir])]),
2191
('', [(subdir_key, [present_dir, present_dir])]),
2194
state = self.create_empty_dirstate()
2195
self.addCleanup(state.unlock)
2196
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2199
state._discard_merge_parents()
2201
self.assertEqual(exp_dirblocks, state._dirblocks)
2204
class Test_InvEntryToDetails(TestCaseWithDirState):
2206
def assertDetails(self, expected, inv_entry):
2207
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2208
self.assertEqual(expected, details)
2209
# details should always allow join() and always be a plain str when
2211
(minikind, fingerprint, size, executable, tree_data) = details
2212
self.assertIsInstance(minikind, str)
2213
self.assertIsInstance(fingerprint, str)
2214
self.assertIsInstance(tree_data, str)
2216
def test_unicode_symlink(self):
2217
# In general, the code base doesn't support a target that contains
2218
# non-ascii characters. So we just assert tha
2219
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2221
inv_entry.revision = 'link-revision-id'
2222
inv_entry.symlink_target = u'link-target'
2223
details = self.assertDetails(('l', 'link-target', 0, False,
2224
'link-revision-id'), inv_entry)